Skip to content

Commit 324845c

Browse files
author
Dan Costello
committed
Improve popover
1 parent 9842d16 commit 324845c

3 files changed

Lines changed: 60 additions & 50 deletions

File tree

app/components/Popover.tsx

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,53 @@
11
'use client';
22

33
import React, { useState, useRef, useEffect } from 'react';
4+
import { createPortal } from 'react-dom';
45

56
const Popover = ({ children, content }: { children: React.ReactNode, content: React.ReactNode }) => {
67
const [isVisible, setIsVisible] = useState(false);
8+
const [popoverStyle, setPopoverStyle] = useState<React.CSSProperties>({});
79
const popoverRef = useRef<HTMLDivElement>(null);
810
const triggerRef = useRef<HTMLButtonElement>(null);
911

10-
const toggleVisibility = () => setIsVisible(v => !v);
12+
const toggleVisibility = () => {
13+
setIsVisible(v => {
14+
const next = !v;
15+
if (next && triggerRef.current) {
16+
const rect = triggerRef.current.getBoundingClientRect();
17+
setPopoverStyle({
18+
position: 'absolute',
19+
top: rect.bottom + window.scrollY + 4, // 4px gap
20+
left: rect.left + window.scrollX,
21+
zIndex: 1000
22+
});
23+
}
24+
return next;
25+
});
26+
};
27+
28+
// Focus management and Escape key
29+
useEffect(() => {
30+
if (isVisible && popoverRef.current) {
31+
popoverRef.current.focus();
32+
}
33+
}, [isVisible]);
34+
35+
useEffect(() => {
36+
if (!isVisible && triggerRef.current) {
37+
triggerRef.current.focus();
38+
}
39+
}, [isVisible]);
40+
41+
useEffect(() => {
42+
if (!isVisible) return;
43+
const handleKeyDown = (event: KeyboardEvent) => {
44+
if (event.key === 'Escape') {
45+
setIsVisible(false);
46+
}
47+
};
48+
document.addEventListener('keydown', handleKeyDown);
49+
return () => document.removeEventListener('keydown', handleKeyDown);
50+
}, [isVisible]);
1151

1252
useEffect(() => {
1353
const handleClickOutside = (event: MouseEvent) => {
@@ -25,31 +65,34 @@ const Popover = ({ children, content }: { children: React.ReactNode, content: Re
2565
}, []);
2666

2767
return (
28-
<span className="popover-container">
68+
<span className="relative inline-block">
2969
<button
3070
ref={triggerRef}
3171
onClick={toggleVisibility}
32-
className="popover-trigger"
72+
className="p-0 underline decoration-dotted text-inherit border-none background-none z-10"
3373
aria-haspopup="true"
3474
aria-expanded={isVisible}
3575
aria-controls="popover-content"
3676
type="button"
3777
>
3878
{children}
3979
</button>
40-
{isVisible && (
80+
{isVisible && typeof window !== 'undefined' && createPortal(
4181
<div
4282
id="popover-content"
4383
ref={popoverRef}
44-
className="popover-content"
84+
className={`absolute top-0 left-1/2 transform -translate-x-1/2 bg-fd-popover text-fd-popover-foreground border border-fd-border shadow-lg rounded-md p-4 z-10 whitespace-normal min-w-[250px] max-w-[320px] text-[13px]`}
4585
role="dialog"
4686
aria-modal="true"
47-
>
48-
{content}
49-
</div>
87+
aria-label="Popover dialog"
88+
tabIndex={-1}
89+
style={popoverStyle}
90+
dangerouslySetInnerHTML={{ __html: content as string }} // Use dangerouslySetInnerHTML to set HTML content
91+
/>,
92+
document.body
5093
)}
5194
</span>
5295
);
5396
};
5497

55-
export default Popover;
98+
export default Popover;

app/global.css

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -56,36 +56,3 @@
5656
background-repeat: no-repeat;
5757
}
5858

59-
/* Popover styles */
60-
.popover-container {
61-
position: relative;
62-
display: inline-block;
63-
}
64-
65-
.popover-trigger {
66-
background: none;
67-
color: inherit;
68-
border: none;
69-
padding: 0;
70-
font: inherit;
71-
cursor: pointer;
72-
text-decoration: underline dotted;
73-
}
74-
75-
.popover-content {
76-
position: absolute;
77-
top: 100%;
78-
left: 50%;
79-
transform: translateX(-50%);
80-
margin-top: 10px;
81-
background-color: var(--color-fd-popover, white);
82-
color: var(--color-fd-popover-foreground, black);
83-
border: 1px solid var(--color-fd-border, #ccc);
84-
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
85-
border-radius: 4px;
86-
padding: 15px;
87-
z-index: 1000;
88-
white-space: normal;
89-
min-width: 200px;
90-
max-width: 320px;
91-
}

content/glossary-definitions.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
export const glossaryDefinitions: Record<string, string> = {
2-
"selector" : "Selectors are SQL-like clauses that specify which records an API should act on. They are analogous to WHERE clauses in SQL. Each API (accessor/mutator) is associated with exactly one selector. The selector is specified at accessor/mutator creation time, either as a free text input in the UI, or as a string through the API. An example of a selector is: `{DateCreated} < ? AND {DateCreated} >= ?`. Each ? represents a parameter that is passed in an array, called SelectorValues, at API invocation time.",
3-
"accessor": "Accessors are configurable APIs that allow a client to retrieve data from the user store. Accessors are intended to be use-case specific. For example, you might configure two separate accessors GetEmailForMarketing and GetEmailForAuthentication. They enforce data usage policies and minimize outbound data from the store for their given use case.",
4-
"mutator": "Mutators are configurable APIs that allow a client to write data to the User Store. Mutators (setters) can be thought of as the complement to accessors (getters). Mutators are intended to capture and store purpose alongside the sensitive data. The mutator will save a configurable set of purposes alongside the data, such as operations, personalization or marketing.",
5-
"access policy": "Access Policies control the circumstances in which data can be retrieved or edited. Practically, access policies are functions that receive contextual data and return true or false according to whether access is allowed or denied. Access policies can be composed from other access policies or access policy templates.",
6-
"access policy template": "Access Policy Templates are parametrizable functions that can be parametrized to create multiple access policies with parallel logic. For example, you might create a template `User is over X years old`. You may use this template to create several access policy instances, allowing you to create conditional logic on a user's age group.",
7-
"data transformer": "Data transformers are re-usable functions that manipulate data in UserClouds. They allow you to minimize the data that you pass or store for each use case. This is key for complying with the data minimization principles in regulations like GDPR. For example, you may use a transformer to pass the last 4 digits of an Social Security Number, rather than the raw SSN, from the store.",
2+
"selector": "<b>Selectors</b> are SQL-like clauses that specify which records an API should act on. They are analogous to WHERE clauses in SQL. Each API (accessor/mutator) is associated with exactly one selector. The selector is specified at accessor/mutator creation time, either as a free text input in the UI, or as a string through the API. An example of a selector is: `{DateCreated} < ? AND {DateCreated} >= ?`. Each ? represents a parameter that is passed in an array, called SelectorValues, at API invocation time.",
3+
"accessor": "<b>Accessors</b> are configurable APIs that allow a client to retrieve data from the user store. Accessors are intended to be use-case specific. For example, you might configure two separate accessors GetEmailForMarketing and GetEmailForAuthentication. They enforce data usage policies and minimize outbound data from the store for their given use case.",
4+
"mutator": "<b>Mutators</b> are configurable APIs that allow a client to write data to the User Store. Mutators (setters) can be thought of as the complement to accessors (getters). Mutators are intended to capture and store purpose alongside the sensitive data. The mutator will save a configurable set of purposes alongside the data, such as operations, personalization or marketing.",
5+
"access policy": "<b>Access Policies</b> control the circumstances in which data can be retrieved or edited. Practically, access policies are functions that receive contextual data and return true or false according to whether access is allowed or denied. Access policies can be composed from other access policies or access policy templates.",
6+
"access policy template": "<b>Access Policy Templates</b> are parametrizable functions that can be parametrized to create multiple access policies with parallel logic. For example, you might create a template `User is over X years old`. You may use this template to create several access policy instances, allowing you to create conditional logic on a user's age group.",
7+
"data transformer": "<b>Data transformers</b> are re-usable functions that manipulate data in UserClouds. They allow you to minimize the data that you pass or store for each use case. This is key for complying with the data minimization principles in regulations like GDPR. For example, you may use a transformer to pass the last 4 digits of an Social Security Number, rather than the raw SSN, from the store.",
88
"column": "The user data table is built from columns and populated with records. Each column has a primitive type (describing what the column stores, like string or boolean) and logical type (describing what the column represents, like address or phone number). Columns can store a single data value or multiple values, in which case they are called array columns.",
99
"tokenize": "When you tokenize a piece of sensitive data, you replace it with a secure (but usable) reference token. The token is then used in place of the data throughout systems. The token can be configured to retain the structure of the underlying data to prevent validation errors. The token is associated with an access policy which controls the circumstances in which the token can be exchanged for the original raw data.",
1010
"resolve": "Exchange a token for the raw data it represents. Token resolution is controlled by the token's access policy.",
11-
"context": "Context is evaluated by access policies to determine whether data access is allowed. Context is automatically generated by the server and can be augmented with additional data, generated and passed by the client.",
11+
"context": "<b>Context</b> is evaluated by access policies to determine whether data access is allowed. Context is automatically generated by the server and can be augmented with additional data, generated and passed by the client.",
1212
"token resolution policy": "An access policy applied to token resolution. This controls the circumstances in which the token can be resolved.",
1313
"tenant": "A single, isolated instance of UserClouds's tech (APIs, user store etc). Typically, customers set up one tenant per environment (e.g.. dev / testing / production).",
1414
"company": "A collection of team mates and tenants, used for billing and role management. Companies represent UserClouds's customers - e.g. the company that you work at.",
15-
"organization": "Organizations are primarily used by B2B customers of UserClouds. They represent UserClouds's grand-customers (i.e. your customers). Youll configure one organization for each client you serve, plus one organization for your employees (the Company Organization).",
15+
"organization": "Organizations are primarily used by B2B customers of UserClouds. They represent UserClouds's grand-customers (i.e. your customers). You'll configure one organization for each client you serve, plus one organization for your employees (the Company Organization).",
1616
"application": "A single OAuth2 client that can call the APIs of any IDPs configured in your tenant (e.g. primary - Auth0, back-up - Plex, third party - social). It is where you will configure how authentication works for your project. For example, you might configure the application to require two factor authentication via SMS or offer passwordless login.",
1717
"permission": "A permission gives a user a right to take a particular action on an object in your system. Examples of permissions are view, edit, and manage-members.",
1818
"object": "Objects are the nodes of your authorization graph. Each object represents a single user, group or asset (like a file or folder) in your system.",

0 commit comments

Comments
 (0)