@reltio/design 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/AppSelector/AppSelector.d.ts +5 -0
- package/components/AppSelector/AppSelector.js +20 -0
- package/components/AppSelector/AppSelector.module.css.d.ts +2 -0
- package/components/AppSelector/AppSelector.module.css.js +61 -0
- package/components/AppSelector/AppSelector.module.css.json +1 -0
- package/components/AppSelector/AppSelector.types.d.ts +29 -0
- package/components/AppSelector/AppSelector.types.js +1 -0
- package/components/AppSelector/index.d.ts +2 -0
- package/components/AppSelector/index.js +1 -0
- package/components/Button/Button.d.ts +3 -0
- package/components/Button/Button.js +9 -3
- package/components/Button/Button.module.css.js +9 -3
- package/components/Button/Button.module.css.json +1 -1
- package/components/Divider/Divider.d.ts +2 -0
- package/components/Divider/Divider.js +6 -0
- package/components/Divider/Divider.module.css.d.ts +2 -0
- package/components/Divider/Divider.module.css.js +40 -0
- package/components/Divider/Divider.module.css.json +1 -0
- package/components/Divider/Divider.types.d.ts +11 -0
- package/components/Divider/Divider.types.js +1 -0
- package/components/Divider/index.d.ts +2 -0
- package/components/Divider/index.js +1 -0
- package/components/Popover/Popover.d.ts +36 -0
- package/components/Popover/Popover.js +62 -0
- package/components/Popover/Popover.module.css.d.ts +2 -0
- package/components/Popover/Popover.module.css.js +62 -0
- package/components/Popover/Popover.module.css.json +1 -0
- package/components/Popover/Popover.types.d.ts +18 -0
- package/components/Popover/Popover.types.js +1 -0
- package/components/Popover/index.d.ts +2 -0
- package/components/Popover/index.js +1 -0
- package/components/index.d.ts +1 -0
- package/components/index.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AppSelectorProps } from "./AppSelector.types";
|
|
2
|
+
/** Navigation popover for switching between Reltio platform applications.
|
|
3
|
+
* Displays apps grouped by category in a grid layout.
|
|
4
|
+
*/
|
|
5
|
+
export declare const AppSelector: ({ apps, env, tenant, className, positionArea, ...rest }: AppSelectorProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from "../../components/Button";
|
|
3
|
+
import { Divider } from "../../components/Divider";
|
|
4
|
+
import { Popover } from "../../components/Popover";
|
|
5
|
+
import { Applications } from "../../icons/Applications";
|
|
6
|
+
import { Link } from "../../icons/Link";
|
|
7
|
+
import { classNames } from "../../utils/classNames";
|
|
8
|
+
import styles from "./AppSelector.module.css";
|
|
9
|
+
const DEFAULT_CATEGORY = "Applications";
|
|
10
|
+
/** Navigation popover for switching between Reltio platform applications.
|
|
11
|
+
* Displays apps grouped by category in a grid layout.
|
|
12
|
+
*/
|
|
13
|
+
export const AppSelector = ({ apps, env, tenant, className, positionArea = "right span-top", ...rest }) => {
|
|
14
|
+
const validApps = apps.filter((app) => app.name && app.uri);
|
|
15
|
+
const groups = Object.groupBy(validApps, ({ category }) => category || DEFAULT_CATEGORY);
|
|
16
|
+
return (_jsx("nav", { className: classNames(className), "aria-label": "Applications", ...rest, children: _jsx(Popover, { trigger: _jsx(Button, { variant: "text", "aria-label": "Applications", children: _jsx(Applications, {}) }), positionArea: positionArea, children: _jsx("div", { className: classNames(styles.content), children: Object.entries(groups).map(([category, apps]) => (_jsxs("div", { className: classNames(styles.group), children: [_jsx(Divider, { className: classNames(styles.divider), children: category }), _jsx("div", { className: classNames(styles.grid), children: apps?.map((app) => (_jsxs("a", { href: resolveUri(app.uri, env, tenant), target: "_blank", rel: "noopener noreferrer", className: classNames(styles.app), children: [app.icon ? (_jsx("img", { src: app.icon, alt: "", className: classNames(styles.appIcon) })) : (_jsx(Link, { size: "xlarge" })), app.name] }, app.name))) })] }, category))) }) }) }));
|
|
17
|
+
};
|
|
18
|
+
const resolveUri = (uri, env, tenant) => uri
|
|
19
|
+
?.replaceAll("${environment}", String(env))
|
|
20
|
+
.replaceAll("${tenant}", String(tenant));
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import styleInject from 'style-inject';
|
|
2
|
+
import json from './AppSelector.module.css.json';
|
|
3
|
+
styleInject(`
|
|
4
|
+
.AppSelector_content__L29wd {
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
gap: 16px;
|
|
8
|
+
padding: 16px;
|
|
9
|
+
width: 316px;
|
|
10
|
+
max-height: 485px;
|
|
11
|
+
overflow-y: auto;
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.AppSelector_group__L29wd {
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
gap: 12px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.AppSelector_grid__L29wd {
|
|
22
|
+
display: grid;
|
|
23
|
+
grid-template-columns: repeat(3, 1fr);
|
|
24
|
+
column-gap: 16px;
|
|
25
|
+
row-gap: 24px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.AppSelector_divider__L29wd {
|
|
29
|
+
margin-left: 12px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.AppSelector_app__L29wd {
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
align-items: center;
|
|
36
|
+
gap: 8px;
|
|
37
|
+
padding: 8px 0;
|
|
38
|
+
border-radius: 12px;
|
|
39
|
+
text-align: center;
|
|
40
|
+
font-size: 13px;
|
|
41
|
+
line-height: 1.3;
|
|
42
|
+
color: var(--reltio-color-text);
|
|
43
|
+
text-decoration: none;
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.AppSelector_app__L29wd:hover {
|
|
48
|
+
background: var(--reltio-color-bg-transparent-1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.AppSelector_appIcon__L29wd {
|
|
52
|
+
width: 48px;
|
|
53
|
+
height: 48px;
|
|
54
|
+
object-fit: contain;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.AppSelector_error__L29wd {
|
|
58
|
+
margin-bottom: -8px;
|
|
59
|
+
}
|
|
60
|
+
`);
|
|
61
|
+
export default json;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "content": "AppSelector_content__L29wd", "group": "AppSelector_group__L29wd", "grid": "AppSelector_grid__L29wd", "divider": "AppSelector_divider__L29wd", "app": "AppSelector_app__L29wd", "appIcon": "AppSelector_appIcon__L29wd", "error": "AppSelector_error__L29wd" }
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { HtmlProps } from "../../utils/types";
|
|
2
|
+
export type AppEntry = {
|
|
3
|
+
/** Application display name. Apps without a name are ignored. */
|
|
4
|
+
name?: string;
|
|
5
|
+
/** URL opened in a new tab on click. Apps without a URI are ignored. */
|
|
6
|
+
uri?: string;
|
|
7
|
+
/** Absolute URL to the application icon (SVG). Falls back to a generic link icon. */
|
|
8
|
+
icon?: string;
|
|
9
|
+
/** Application category for grouping in navigation.
|
|
10
|
+
* @default "Applications"
|
|
11
|
+
*/
|
|
12
|
+
category?: string;
|
|
13
|
+
};
|
|
14
|
+
export type AppSelectorProps = HtmlProps<"div", {
|
|
15
|
+
/** List of apps to display, grouped by category.
|
|
16
|
+
* Apps missing `name` or `uri` are silently ignored.
|
|
17
|
+
* The list of available apps for a given tenant
|
|
18
|
+
* can be retrieved from Reltio Config Service.
|
|
19
|
+
*/
|
|
20
|
+
apps: AppEntry[];
|
|
21
|
+
/** Environment identifier substituted into URI templates (`${environment}`). */
|
|
22
|
+
env?: string;
|
|
23
|
+
/** Tenant identifier substituted into URI templates (`${tenant}`). */
|
|
24
|
+
tenant?: string;
|
|
25
|
+
/** CSS `position-area` value controlling popover placement relative to the trigger.
|
|
26
|
+
* @default "right span-top"
|
|
27
|
+
*/
|
|
28
|
+
positionArea?: string;
|
|
29
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./AppSelector";
|
|
@@ -5,5 +5,8 @@ import type { ButtonProps } from "./Button.types";
|
|
|
5
5
|
* A flexible, accessible button component that supports multiple variants
|
|
6
6
|
* (filled, outlined, text), color options (primary, secondary, inherited),
|
|
7
7
|
* sizes, states, and can render as either a button or anchor element.
|
|
8
|
+
*
|
|
9
|
+
* Automatically switches to circular icon-only layout when children
|
|
10
|
+
* is a single React component element.
|
|
8
11
|
*/
|
|
9
12
|
export declare const Button: ({ variant, color, size, disabled, fullWidth, children, className, href, type, ...rest }: ButtonProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useRef } from "react";
|
|
2
|
+
import React, { useEffect, useRef } from "react";
|
|
3
3
|
import { classNames } from "../../utils/classNames";
|
|
4
4
|
import styles from "./Button.module.css";
|
|
5
5
|
/**
|
|
@@ -8,6 +8,9 @@ import styles from "./Button.module.css";
|
|
|
8
8
|
* A flexible, accessible button component that supports multiple variants
|
|
9
9
|
* (filled, outlined, text), color options (primary, secondary, inherited),
|
|
10
10
|
* sizes, states, and can render as either a button or anchor element.
|
|
11
|
+
*
|
|
12
|
+
* Automatically switches to circular icon-only layout when children
|
|
13
|
+
* is a single React component element.
|
|
11
14
|
*/
|
|
12
15
|
export const Button = ({ variant = "filled", color = "inherited", size = "medium", disabled = false, fullWidth = false, children, className, href, type = "button", ...rest }) => {
|
|
13
16
|
const buttonRef = useRef(null);
|
|
@@ -19,8 +22,11 @@ export const Button = ({ variant = "filled", color = "inherited", size = "medium
|
|
|
19
22
|
buttonRef.current.blur();
|
|
20
23
|
}
|
|
21
24
|
}, [disabled]);
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
const isIconOnly = React.Children.count(children) === 1 &&
|
|
26
|
+
React.isValidElement(children) &&
|
|
27
|
+
typeof children.type !== "string" &&
|
|
28
|
+
children.type !== React.Fragment;
|
|
29
|
+
const composedClassName = classNames(styles.root, styles[variant], styles[color], styles[size], disabled && styles.disabled, fullWidth && styles.fullWidth, isIconOnly && styles.iconOnly, className);
|
|
24
30
|
// Render as anchor if href is provided
|
|
25
31
|
if (href) {
|
|
26
32
|
return (_jsx("a", { ref: buttonRef, href: disabled ? undefined : href, className: composedClassName, "aria-disabled": disabled ? true : undefined, ...rest, children: children }));
|
|
@@ -115,20 +115,20 @@ styleInject(`
|
|
|
115
115
|
/* Size: Small */
|
|
116
116
|
.Button_small__L29wd {
|
|
117
117
|
min-height: 32px;
|
|
118
|
-
padding: 8px
|
|
118
|
+
padding: 8px 12px;
|
|
119
119
|
font-size: 0.875rem;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
/* Size: Medium */
|
|
123
123
|
.Button_medium__L29wd {
|
|
124
124
|
min-height: 40px;
|
|
125
|
-
padding: 12px
|
|
125
|
+
padding: 12px 18px;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
/* Size: Large */
|
|
129
129
|
.Button_large__L29wd {
|
|
130
130
|
min-height: 48px;
|
|
131
|
-
padding: 16px
|
|
131
|
+
padding: 16px 24px;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/* State: Disabled */
|
|
@@ -137,6 +137,12 @@ styleInject(`
|
|
|
137
137
|
cursor: not-allowed;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
/* Icon Only */
|
|
141
|
+
.Button_iconOnly__L29wd {
|
|
142
|
+
aspect-ratio: 1;
|
|
143
|
+
padding: 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
140
146
|
/* Full Width */
|
|
141
147
|
.Button_fullWidth__L29wd {
|
|
142
148
|
width: 100%;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{ "root": "Button_root__L29wd", "filled": "Button_filled__L29wd", "outlined": "Button_outlined__L29wd", "text": "Button_text__L29wd", "primary": "Button_primary__L29wd", "inherited": "Button_inherited__L29wd", "small": "Button_small__L29wd", "medium": "Button_medium__L29wd", "large": "Button_large__L29wd", "disabled": "Button_disabled__L29wd", "fullWidth": "Button_fullWidth__L29wd" }
|
|
1
|
+
{ "root": "Button_root__L29wd", "filled": "Button_filled__L29wd", "outlined": "Button_outlined__L29wd", "text": "Button_text__L29wd", "primary": "Button_primary__L29wd", "inherited": "Button_inherited__L29wd", "small": "Button_small__L29wd", "medium": "Button_medium__L29wd", "large": "Button_large__L29wd", "disabled": "Button_disabled__L29wd", "iconOnly": "Button_iconOnly__L29wd", "fullWidth": "Button_fullWidth__L29wd" }
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { classNames } from "../../utils/classNames";
|
|
3
|
+
import styles from "./Divider.module.css";
|
|
4
|
+
export const Divider = ({ align = "start", children, className, ...rest }) => {
|
|
5
|
+
return (_jsx("div", { role: "separator", className: classNames(styles.root, children ? styles.labeled : styles.plain, children ? styles[align] : undefined, className), ...rest, children: children }));
|
|
6
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import styleInject from 'style-inject';
|
|
2
|
+
import json from './Divider.module.css.json';
|
|
3
|
+
styleInject(`
|
|
4
|
+
.Divider_root__L29wd {
|
|
5
|
+
width: 100%;
|
|
6
|
+
margin: 0;
|
|
7
|
+
padding: 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.Divider_plain__L29wd {
|
|
11
|
+
height: 1px;
|
|
12
|
+
background-color: var(--reltio-color-surface-3);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.Divider_labeled__L29wd {
|
|
16
|
+
display: flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
gap: 8px;
|
|
19
|
+
font-size: 14px;
|
|
20
|
+
font-weight: 600;
|
|
21
|
+
color: var(--reltio-color-text-secondary);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.Divider_labeled__L29wd::before,
|
|
25
|
+
.Divider_labeled__L29wd::after {
|
|
26
|
+
content: "";
|
|
27
|
+
flex: 1;
|
|
28
|
+
height: 1px;
|
|
29
|
+
background-color: var(--reltio-color-surface-3);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.Divider_start__L29wd::before {
|
|
33
|
+
display: none;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.Divider_end__L29wd::after {
|
|
37
|
+
display: none;
|
|
38
|
+
}
|
|
39
|
+
`);
|
|
40
|
+
export default json;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "root": "Divider_root__L29wd", "plain": "Divider_plain__L29wd", "labeled": "Divider_labeled__L29wd", "start": "Divider_start__L29wd", "end": "Divider_end__L29wd" }
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import type { HtmlProps } from "../../utils/types";
|
|
3
|
+
export type DividerAlign = "start" | "center" | "end";
|
|
4
|
+
export type DividerProps = HtmlProps<"div", {
|
|
5
|
+
/** Position of the label text relative to the line.
|
|
6
|
+
* @default "start"
|
|
7
|
+
*/
|
|
8
|
+
align?: DividerAlign;
|
|
9
|
+
/** Optional label text displayed alongside the divider line. */
|
|
10
|
+
children?: React.ReactNode;
|
|
11
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Divider";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PopoverProps } from "./Popover.types";
|
|
2
|
+
/**
|
|
3
|
+
* Uncontrolled anchored popover built on the native Popover API and CSS Anchor Positioning.
|
|
4
|
+
*
|
|
5
|
+
* Use the `trigger` prop to provide the element that toggles the popover.
|
|
6
|
+
* Positioning is handled automatically via CSS Anchor Positioning relative
|
|
7
|
+
* to the trigger. Light dismiss (Esc + click outside) is built in.
|
|
8
|
+
*
|
|
9
|
+
* Optional `header` and `footer` props render in fixed areas — only `children`
|
|
10
|
+
* scroll when content overflows.
|
|
11
|
+
*
|
|
12
|
+
* **Auto-close on content click:** clicking any element inside the popover
|
|
13
|
+
* closes it (click bubbles to the root and triggers toggle). This is the
|
|
14
|
+
* expected behavior for menus and action lists. For interactive content
|
|
15
|
+
* (inputs, forms), call `e.stopPropagation()` on the container to prevent
|
|
16
|
+
* auto-close.
|
|
17
|
+
*
|
|
18
|
+
* **Focus management:** add a `data-autofocus` attribute to an element inside
|
|
19
|
+
* the popover to focus it when the popover opens.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* <Popover trigger={<Button>Actions</Button>}>
|
|
24
|
+
* <button onClick={handleEdit}>Edit</button>
|
|
25
|
+
* <button onClick={handleDelete}>Delete</button>
|
|
26
|
+
* </Popover>
|
|
27
|
+
*
|
|
28
|
+
* // With form content — stopPropagation prevents auto-close
|
|
29
|
+
* <Popover trigger={<Button>Add Note</Button>} header="New Note">
|
|
30
|
+
* <div onClick={e => e.stopPropagation()}>
|
|
31
|
+
* <TextArea label="Note" data-autofocus />
|
|
32
|
+
* </div>
|
|
33
|
+
* </Popover>
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare const Popover: ({ trigger, positionArea, onToggle, header, footer, children, className, style, ...rest }: PopoverProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef } from "react";
|
|
3
|
+
import { classNames } from "../../utils/classNames";
|
|
4
|
+
import styles from "./Popover.module.css";
|
|
5
|
+
/**
|
|
6
|
+
* Uncontrolled anchored popover built on the native Popover API and CSS Anchor Positioning.
|
|
7
|
+
*
|
|
8
|
+
* Use the `trigger` prop to provide the element that toggles the popover.
|
|
9
|
+
* Positioning is handled automatically via CSS Anchor Positioning relative
|
|
10
|
+
* to the trigger. Light dismiss (Esc + click outside) is built in.
|
|
11
|
+
*
|
|
12
|
+
* Optional `header` and `footer` props render in fixed areas — only `children`
|
|
13
|
+
* scroll when content overflows.
|
|
14
|
+
*
|
|
15
|
+
* **Auto-close on content click:** clicking any element inside the popover
|
|
16
|
+
* closes it (click bubbles to the root and triggers toggle). This is the
|
|
17
|
+
* expected behavior for menus and action lists. For interactive content
|
|
18
|
+
* (inputs, forms), call `e.stopPropagation()` on the container to prevent
|
|
19
|
+
* auto-close.
|
|
20
|
+
*
|
|
21
|
+
* **Focus management:** add a `data-autofocus` attribute to an element inside
|
|
22
|
+
* the popover to focus it when the popover opens.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```tsx
|
|
26
|
+
* <Popover trigger={<Button>Actions</Button>}>
|
|
27
|
+
* <button onClick={handleEdit}>Edit</button>
|
|
28
|
+
* <button onClick={handleDelete}>Delete</button>
|
|
29
|
+
* </Popover>
|
|
30
|
+
*
|
|
31
|
+
* // With form content — stopPropagation prevents auto-close
|
|
32
|
+
* <Popover trigger={<Button>Add Note</Button>} header="New Note">
|
|
33
|
+
* <div onClick={e => e.stopPropagation()}>
|
|
34
|
+
* <TextArea label="Note" data-autofocus />
|
|
35
|
+
* </div>
|
|
36
|
+
* </Popover>
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export const Popover = ({ trigger, positionArea = "bottom", onToggle, header, footer, children, className, style, ...rest }) => {
|
|
40
|
+
const contentRef = useRef(null);
|
|
41
|
+
const isOpen = useRef(false);
|
|
42
|
+
const handleToggle = (e) => {
|
|
43
|
+
isOpen.current = e.newState === "open";
|
|
44
|
+
if (isOpen.current) {
|
|
45
|
+
const target = contentRef.current?.querySelector("[data-autofocus]");
|
|
46
|
+
target?.focus();
|
|
47
|
+
}
|
|
48
|
+
onToggle?.(e);
|
|
49
|
+
};
|
|
50
|
+
const handleClick = () => {
|
|
51
|
+
if (isOpen.current) {
|
|
52
|
+
contentRef.current?.hidePopover();
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
contentRef.current?.showPopover();
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
return (
|
|
59
|
+
// biome-ignore lint/a11y/useKeyWithClickEvents: trigger child handles its own keyboard interaction
|
|
60
|
+
// biome-ignore lint/a11y/noStaticElementInteractions: wrapper delegates to interactive trigger child
|
|
61
|
+
_jsxs("span", { className: classNames(styles.root), onClick: handleClick, children: [trigger, _jsxs("div", { ref: contentRef, popover: "auto", className: classNames(styles.container, className), style: { ...style, positionArea }, onToggle: handleToggle, ...rest, children: [header && _jsx("div", { className: classNames(styles.header), children: header }), _jsx("div", { className: classNames(styles.body), children: children }), footer && _jsx("div", { className: classNames(styles.footer), children: footer })] })] }));
|
|
62
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import styleInject from 'style-inject';
|
|
2
|
+
import json from './Popover.module.css.json';
|
|
3
|
+
styleInject(`
|
|
4
|
+
.Popover_root__L29wd {
|
|
5
|
+
display: inline-flex;
|
|
6
|
+
width: fit-content;
|
|
7
|
+
anchor-scope: --trigger;
|
|
8
|
+
anchor-name: --trigger;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.Popover_container__L29wd {
|
|
12
|
+
position-anchor: --trigger;
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
margin: 8px;
|
|
15
|
+
padding: 0;
|
|
16
|
+
border: 1px solid var(--reltio-color-border-2);
|
|
17
|
+
border-radius: 12px;
|
|
18
|
+
background: var(--reltio-color-surface-1);
|
|
19
|
+
color: var(--reltio-color-text);
|
|
20
|
+
box-shadow:
|
|
21
|
+
0 4px 16px var(--reltio-color-shadow-2),
|
|
22
|
+
0 1px 4px var(--reltio-color-shadow-1);
|
|
23
|
+
inset: auto;
|
|
24
|
+
max-width: calc(100vw - 16px);
|
|
25
|
+
max-height: calc(100vh - 16px);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.Popover_container__L29wd:popover-open {
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.Popover_header__L29wd {
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
gap: 8px;
|
|
37
|
+
padding: 4px 12px;
|
|
38
|
+
border-bottom: 1px solid var(--reltio-color-border-1);
|
|
39
|
+
font-weight: 600;
|
|
40
|
+
font-size: 14px;
|
|
41
|
+
flex-shrink: 0;
|
|
42
|
+
min-height: 32px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.Popover_body__L29wd {
|
|
46
|
+
flex: 1 1 auto;
|
|
47
|
+
overflow-y: auto;
|
|
48
|
+
min-height: 0;
|
|
49
|
+
padding: 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.Popover_footer__L29wd {
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
justify-content: flex-end;
|
|
56
|
+
gap: 8px;
|
|
57
|
+
padding: 12px;
|
|
58
|
+
border-top: 1px solid var(--reltio-color-border-1);
|
|
59
|
+
flex-shrink: 0;
|
|
60
|
+
}
|
|
61
|
+
`);
|
|
62
|
+
export default json;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "root": "Popover_root__L29wd", "container": "Popover_container__L29wd", "header": "Popover_header__L29wd", "body": "Popover_body__L29wd", "footer": "Popover_footer__L29wd" }
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import type { HtmlProps } from "../../utils/types";
|
|
3
|
+
export type PopoverProps = HtmlProps<"div", {
|
|
4
|
+
/** The element that toggles the popover on click. Clicks on content also bubble to the root and close the popover — use `e.stopPropagation()` to prevent this for interactive content. */
|
|
5
|
+
trigger: React.ReactElement;
|
|
6
|
+
/** CSS `position-area` value controlling placement relative to the trigger.
|
|
7
|
+
* Common values: `"bottom"`, `"top"`, `"left"`, `"right"`, `"bottom span-right"`, `"top span-left"`.
|
|
8
|
+
* See https://developer.mozilla.org/en-US/docs/Web/CSS/position-area for more details.
|
|
9
|
+
* @default "bottom"
|
|
10
|
+
*/
|
|
11
|
+
positionArea?: string;
|
|
12
|
+
/** Called when the popover opens or closes. Forwards the native `toggle` event — check `event.newState` for `"open"` or `"closed"`. */
|
|
13
|
+
onToggle?: (event: React.SyntheticEvent<HTMLDivElement>) => void;
|
|
14
|
+
/** Content rendered in a fixed header area at the top, with a bottom border separator */
|
|
15
|
+
header?: React.ReactNode;
|
|
16
|
+
/** Content rendered in a fixed footer area at the bottom, right-aligned by default, with a top border separator */
|
|
17
|
+
footer?: React.ReactNode;
|
|
18
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Popover";
|
package/components/index.d.ts
CHANGED
package/components/index.js
CHANGED