@efolusi/meridian 1.5.0
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/README.md +12 -0
- package/ai/AgentRun.d.ts +20 -0
- package/ai/AgentRun.js +100 -0
- package/ai/ChatMessage.d.ts +19 -0
- package/ai/ChatMessage.js +81 -0
- package/ai/Citation.d.ts +23 -0
- package/ai/Citation.js +146 -0
- package/ai/Confirmation.d.ts +26 -0
- package/ai/Confirmation.js +84 -0
- package/ai/Conversation.d.ts +15 -0
- package/ai/Conversation.js +81 -0
- package/ai/DocumentCard.d.ts +17 -0
- package/ai/DocumentCard.js +101 -0
- package/ai/FeedbackBar.d.ts +14 -0
- package/ai/FeedbackBar.js +84 -0
- package/ai/GeneratedImage.d.ts +22 -0
- package/ai/GeneratedImage.js +100 -0
- package/ai/ModelSelector.d.ts +21 -0
- package/ai/ModelSelector.js +112 -0
- package/ai/Player.d.ts +18 -0
- package/ai/Player.js +201 -0
- package/ai/PromptComposer.d.ts +28 -0
- package/ai/PromptComposer.js +111 -0
- package/ai/PromptSteps.d.ts +18 -0
- package/ai/PromptSteps.js +167 -0
- package/ai/Reasoning.d.ts +17 -0
- package/ai/Reasoning.js +57 -0
- package/ai/RichComposer.d.ts +25 -0
- package/ai/RichComposer.js +247 -0
- package/ai/Sandbox.d.ts +20 -0
- package/ai/Sandbox.js +96 -0
- package/ai/SelectionQuote.d.ts +15 -0
- package/ai/SelectionQuote.js +123 -0
- package/ai/SourceCard.d.ts +17 -0
- package/ai/SourceCard.js +57 -0
- package/ai/Suggestions.d.ts +10 -0
- package/ai/Suggestions.js +38 -0
- package/ai/Task.d.ts +15 -0
- package/ai/Task.js +49 -0
- package/ai/Todo.d.ts +13 -0
- package/ai/Todo.js +87 -0
- package/ai/ToolCall.d.ts +30 -0
- package/ai/ToolCall.js +147 -0
- package/ai/Transcript.d.ts +27 -0
- package/ai/Transcript.js +96 -0
- package/ai/UsageMeter.d.ts +21 -0
- package/ai/UsageMeter.js +81 -0
- package/ai/WebPreview.d.ts +19 -0
- package/ai/WebPreview.js +179 -0
- package/assets/fonts/BricolageGrotesque-Variable.ttf +0 -0
- package/assets/fonts/Figtree-Italic-Variable.ttf +0 -0
- package/assets/fonts/Figtree-Variable.ttf +0 -0
- package/assets/fonts/JetBrainsMono-Italic-Variable.ttf +0 -0
- package/assets/fonts/JetBrainsMono-Variable.ttf +0 -0
- package/assets/fonts/OFL-BricolageGrotesque.txt +93 -0
- package/assets/fonts/OFL-Figtree.txt +93 -0
- package/assets/fonts/OFL-JetBrainsMono.txt +93 -0
- package/code/CodeBlock.d.ts +17 -0
- package/code/CodeBlock.js +99 -0
- package/code/Console.d.ts +19 -0
- package/code/Console.js +124 -0
- package/code/CopyField.d.ts +10 -0
- package/code/CopyField.js +54 -0
- package/code/Diff.d.ts +26 -0
- package/code/Diff.js +358 -0
- package/code/EnvList.d.ts +14 -0
- package/code/EnvList.js +89 -0
- package/code/Exception.d.ts +25 -0
- package/code/Exception.js +90 -0
- package/code/Terminal.d.ts +20 -0
- package/code/Terminal.js +58 -0
- package/data/BarChart.d.ts +14 -0
- package/data/BarChart.js +44 -0
- package/data/DonutChart.d.ts +17 -0
- package/data/DonutChart.js +114 -0
- package/data/EmptyState.d.ts +13 -0
- package/data/EmptyState.js +39 -0
- package/data/KeyValueList.d.ts +14 -0
- package/data/KeyValueList.js +34 -0
- package/data/LineChart.d.ts +15 -0
- package/data/LineChart.js +85 -0
- package/data/Skeleton.d.ts +11 -0
- package/data/Skeleton.js +49 -0
- package/data/Sparkline.d.ts +14 -0
- package/data/Sparkline.js +42 -0
- package/data/Stat.d.ts +14 -0
- package/data/Stat.js +40 -0
- package/data/StatusDot.d.ts +10 -0
- package/data/StatusDot.js +34 -0
- package/data/Table.d.ts +53 -0
- package/data/Table.js +174 -0
- package/dates/Calendar.d.ts +8 -0
- package/dates/Calendar.js +143 -0
- package/dates/DatePicker.d.ts +12 -0
- package/dates/DatePicker.js +118 -0
- package/display/Accordion.d.ts +15 -0
- package/display/Accordion.js +52 -0
- package/display/AspectRatio.d.ts +8 -0
- package/display/AspectRatio.js +24 -0
- package/display/Avatar.d.ts +17 -0
- package/display/Avatar.js +52 -0
- package/display/Badge.d.ts +10 -0
- package/display/Badge.js +34 -0
- package/display/Card.d.ts +17 -0
- package/display/Card.js +51 -0
- package/display/Carousel.d.ts +16 -0
- package/display/Carousel.js +97 -0
- package/display/Collapsible.d.ts +12 -0
- package/display/Collapsible.js +46 -0
- package/display/Divider.d.ts +9 -0
- package/display/Divider.js +33 -0
- package/display/Kbd.d.ts +7 -0
- package/display/Kbd.js +18 -0
- package/display/Link.d.ts +12 -0
- package/display/Link.js +41 -0
- package/display/ListItem.d.ts +19 -0
- package/display/ListItem.js +61 -0
- package/display/Resizable.d.ts +16 -0
- package/display/Resizable.js +90 -0
- package/display/ScrollArea.d.ts +8 -0
- package/display/ScrollArea.js +28 -0
- package/display/Tag.d.ts +9 -0
- package/display/Tag.js +41 -0
- package/display/Toolbar.d.ts +22 -0
- package/display/Toolbar.js +55 -0
- package/display/TreeList.d.ts +20 -0
- package/display/TreeList.js +101 -0
- package/feedback/Alert.d.ts +14 -0
- package/feedback/Alert.js +56 -0
- package/feedback/Banner.d.ts +15 -0
- package/feedback/Banner.js +49 -0
- package/feedback/ConfirmDialog.d.ts +18 -0
- package/feedback/ConfirmDialog.js +49 -0
- package/feedback/Dialog.d.ts +13 -0
- package/feedback/Dialog.js +107 -0
- package/feedback/Loader.d.ts +12 -0
- package/feedback/Loader.js +41 -0
- package/feedback/Progress.d.ts +15 -0
- package/feedback/Progress.js +48 -0
- package/feedback/Spinner.d.ts +9 -0
- package/feedback/Spinner.js +38 -0
- package/feedback/Toast.d.ts +70 -0
- package/feedback/Toast.js +216 -0
- package/feedback/Tooltip.d.ts +12 -0
- package/feedback/Tooltip.js +86 -0
- package/files/FileDrop.d.ts +15 -0
- package/files/FileDrop.js +75 -0
- package/files/FileTile.d.ts +19 -0
- package/files/FileTile.js +96 -0
- package/files/FileTypeIcon.d.ts +11 -0
- package/files/FileTypeIcon.js +121 -0
- package/finance/PaymentCard.d.ts +17 -0
- package/finance/PaymentCard.js +109 -0
- package/forms/Button.d.ts +15 -0
- package/forms/Button.js +82 -0
- package/forms/ButtonGroup.d.ts +5 -0
- package/forms/ButtonGroup.js +22 -0
- package/forms/ButtonTile.d.ts +21 -0
- package/forms/ButtonTile.js +69 -0
- package/forms/Checkbox.d.ts +6 -0
- package/forms/Checkbox.js +52 -0
- package/forms/Combobox.d.ts +17 -0
- package/forms/Combobox.js +162 -0
- package/forms/DigitEntry.d.ts +15 -0
- package/forms/DigitEntry.js +88 -0
- package/forms/FormField.d.ts +39 -0
- package/forms/FormField.js +127 -0
- package/forms/IconButton.d.ts +11 -0
- package/forms/IconButton.js +41 -0
- package/forms/Input.d.ts +14 -0
- package/forms/Input.js +75 -0
- package/forms/InputGroup.d.ts +15 -0
- package/forms/InputGroup.js +59 -0
- package/forms/Label.d.ts +11 -0
- package/forms/Label.js +29 -0
- package/forms/Radio.d.ts +6 -0
- package/forms/Radio.js +46 -0
- package/forms/Select.d.ts +10 -0
- package/forms/Select.js +68 -0
- package/forms/Slider.d.ts +18 -0
- package/forms/Slider.js +75 -0
- package/forms/Switch.d.ts +6 -0
- package/forms/Switch.js +46 -0
- package/forms/Textarea.d.ts +8 -0
- package/forms/Textarea.js +58 -0
- package/forms/Toggle.d.ts +26 -0
- package/forms/Toggle.js +85 -0
- package/icons/Icon.d.ts +13 -0
- package/icons/Icon.js +54 -0
- package/index.d.ts +106 -0
- package/index.js +106 -0
- package/navigation/Breadcrumbs.d.ts +12 -0
- package/navigation/Breadcrumbs.js +41 -0
- package/navigation/Menubar.d.ts +10 -0
- package/navigation/Menubar.js +173 -0
- package/navigation/PageControl.d.ts +11 -0
- package/navigation/PageControl.js +36 -0
- package/navigation/Pagination.d.ts +9 -0
- package/navigation/Pagination.js +68 -0
- package/navigation/SegmentedControl.d.ts +17 -0
- package/navigation/SegmentedControl.js +42 -0
- package/navigation/SideNav.d.ts +18 -0
- package/navigation/SideNav.js +87 -0
- package/navigation/Steps.d.ts +15 -0
- package/navigation/Steps.js +61 -0
- package/navigation/Tabs.d.ts +17 -0
- package/navigation/Tabs.js +76 -0
- package/navigation/TopNav.d.ts +10 -0
- package/navigation/TopNav.js +26 -0
- package/overlay/CommandPalette.d.ts +21 -0
- package/overlay/CommandPalette.js +164 -0
- package/overlay/ContextMenu.d.ts +11 -0
- package/overlay/ContextMenu.js +140 -0
- package/overlay/Drawer.d.ts +14 -0
- package/overlay/Drawer.js +99 -0
- package/overlay/HoverCard.d.ts +17 -0
- package/overlay/HoverCard.js +74 -0
- package/overlay/Menu.d.ts +24 -0
- package/overlay/Menu.js +153 -0
- package/overlay/Popover.d.ts +16 -0
- package/overlay/Popover.js +96 -0
- package/overlay/Portal.d.ts +10 -0
- package/overlay/Portal.js +127 -0
- package/package.json +41 -0
- package/styles.css +6 -0
- package/tokens/base.css +10 -0
- package/tokens/colors.css +38 -0
- package/tokens/effects.css +22 -0
- package/tokens/fonts.css +5 -0
- package/tokens/spacing.css +12 -0
- package/tokens/typography.css +9 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Icon } from "../icons/Icon.js";
|
|
4
|
+
import { injectEfCss } from "./Button.js";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.ef-tilegroup{display:grid;gap:10px}
|
|
7
|
+
.ef-tile{position:relative;display:flex;flex-direction:column;align-items:flex-start;gap:8px;padding:16px;border:1px solid var(--border-strong);border-radius:var(--radius-md);background:var(--surface-card);cursor:pointer;text-align:left;font-family:var(--font-sans);color:var(--text-primary);transition:border-color var(--dur-fast) var(--ease-out),background var(--dur-fast) var(--ease-out)}
|
|
8
|
+
.ef-tile:hover:not(:disabled){border-color:var(--sand-400)}
|
|
9
|
+
.ef-tile:focus-visible{outline:none;box-shadow:var(--focus-ring)}
|
|
10
|
+
.ef-tile:disabled{opacity:.45;cursor:not-allowed}
|
|
11
|
+
.ef-tile--on{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent)}
|
|
12
|
+
.ef-tile__icon{display:inline-flex;color:var(--text-secondary)}
|
|
13
|
+
.ef-tile--on .ef-tile__icon{color:var(--text-primary)}
|
|
14
|
+
.ef-tile__title{font-size:var(--text-md);font-weight:var(--weight-semibold)}
|
|
15
|
+
.ef-tile__desc{font-size:var(--text-sm);color:var(--text-muted);line-height:1.45}
|
|
16
|
+
.ef-tile__check{position:absolute;top:12px;right:12px;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:var(--radius-full);background:var(--accent);color:var(--accent-contrast);opacity:0;transform:scale(.6);transition:opacity var(--dur-fast) var(--ease-out),transform var(--dur-med) var(--ease-spring)}
|
|
17
|
+
.ef-tile--on .ef-tile__check{opacity:1;transform:scale(1)}
|
|
18
|
+
`;
|
|
19
|
+
export function ButtonTile({
|
|
20
|
+
icon,
|
|
21
|
+
title,
|
|
22
|
+
description,
|
|
23
|
+
selected,
|
|
24
|
+
disabled,
|
|
25
|
+
onClick,
|
|
26
|
+
style,
|
|
27
|
+
className,
|
|
28
|
+
...rest
|
|
29
|
+
}) {
|
|
30
|
+
injectEfCss('ef-css-tile', CSS);
|
|
31
|
+
return /*#__PURE__*/React.createElement("button", _extends({}, rest, {
|
|
32
|
+
className: `ef-tile${selected ? ' ef-tile--on' : ''}${className ? ' ' + className : ''}`,
|
|
33
|
+
disabled: disabled,
|
|
34
|
+
"aria-pressed": !!selected,
|
|
35
|
+
onClick: onClick,
|
|
36
|
+
style: style
|
|
37
|
+
}), icon ? /*#__PURE__*/React.createElement("span", {
|
|
38
|
+
className: "ef-tile__icon"
|
|
39
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
40
|
+
name: icon,
|
|
41
|
+
size: 20
|
|
42
|
+
})) : null, /*#__PURE__*/React.createElement("span", {
|
|
43
|
+
className: "ef-tile__title"
|
|
44
|
+
}, title), description ? /*#__PURE__*/React.createElement("span", {
|
|
45
|
+
className: "ef-tile__desc"
|
|
46
|
+
}, description) : null, /*#__PURE__*/React.createElement("span", {
|
|
47
|
+
className: "ef-tile__check"
|
|
48
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
49
|
+
name: "check",
|
|
50
|
+
size: 11,
|
|
51
|
+
strokeWidth: 3
|
|
52
|
+
})));
|
|
53
|
+
}
|
|
54
|
+
export function ButtonTileGroup({
|
|
55
|
+
columns = 3,
|
|
56
|
+
children,
|
|
57
|
+
style,
|
|
58
|
+
className,
|
|
59
|
+
...rest
|
|
60
|
+
}) {
|
|
61
|
+
injectEfCss('ef-css-tile', CSS);
|
|
62
|
+
return /*#__PURE__*/React.createElement("div", _extends({}, rest, {
|
|
63
|
+
className: `ef-tilegroup${className ? ' ' + className : ''}`,
|
|
64
|
+
style: {
|
|
65
|
+
gridTemplateColumns: `repeat(${columns},1fr)`,
|
|
66
|
+
...style
|
|
67
|
+
}
|
|
68
|
+
}), children);
|
|
69
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
2
|
+
label?: React.ReactNode;
|
|
3
|
+
/** Muted second line under the label */
|
|
4
|
+
description?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const Checkbox: React.ForwardRefExoticComponent<CheckboxProps & React.RefAttributes<HTMLInputElement>>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Icon } from "../icons/Icon.js";
|
|
4
|
+
import { injectEfCss } from "./Button.js";
|
|
5
|
+
import { useFieldProps } from "./FormField.js";
|
|
6
|
+
const CSS = `
|
|
7
|
+
.ef-check{display:inline-flex;align-items:flex-start;gap:10px;cursor:pointer;user-select:none}
|
|
8
|
+
.ef-check--disabled{opacity:.45;cursor:not-allowed}
|
|
9
|
+
.ef-check__input{position:absolute;opacity:0;width:0;height:0}
|
|
10
|
+
.ef-check__box{display:inline-flex;align-items:center;justify-content:center;flex:none;width:18px;height:18px;margin-top:1px;border:1.5px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--surface-card);color:var(--accent-contrast);transition:background var(--dur-fast) var(--ease-out),border-color var(--dur-fast) var(--ease-out),transform var(--dur-med) var(--ease-spring)}
|
|
11
|
+
.ef-check__box svg{opacity:0;transform:scale(.5);transition:opacity var(--dur-fast) var(--ease-out),transform var(--dur-med) var(--ease-spring)}
|
|
12
|
+
.ef-check__input:checked+.ef-check__box{background:var(--accent);border-color:var(--accent)}
|
|
13
|
+
.ef-check__input:checked+.ef-check__box svg{opacity:1;transform:scale(1)}
|
|
14
|
+
.ef-check:active .ef-check__box{transform:scale(.95)}
|
|
15
|
+
.ef-check__input:focus-visible+.ef-check__box{box-shadow:var(--focus-ring)}
|
|
16
|
+
.ef-check__label{font-size:var(--text-md);color:var(--text-primary);line-height:1.4}
|
|
17
|
+
.ef-check__desc{display:block;font-size:var(--text-sm);color:var(--text-muted)}
|
|
18
|
+
`;
|
|
19
|
+
export const Checkbox = /*#__PURE__*/React.forwardRef(function Checkbox({
|
|
20
|
+
label,
|
|
21
|
+
description,
|
|
22
|
+
disabled,
|
|
23
|
+
style,
|
|
24
|
+
className,
|
|
25
|
+
...rest
|
|
26
|
+
}, ref) {
|
|
27
|
+
injectEfCss('ef-css-check', CSS);
|
|
28
|
+
// Picks up id / aria wiring when nested in a FormField; standalone this is a no-op.
|
|
29
|
+
const field = useFieldProps({
|
|
30
|
+
id: rest.id,
|
|
31
|
+
'aria-describedby': rest['aria-describedby']
|
|
32
|
+
});
|
|
33
|
+
return /*#__PURE__*/React.createElement("label", {
|
|
34
|
+
className: `ef-check${disabled ? ' ef-check--disabled' : ''}${className ? ' ' + className : ''}`,
|
|
35
|
+
style: style
|
|
36
|
+
}, /*#__PURE__*/React.createElement("input", _extends({
|
|
37
|
+
ref: ref,
|
|
38
|
+
type: "checkbox",
|
|
39
|
+
className: "ef-check__input",
|
|
40
|
+
disabled: disabled
|
|
41
|
+
}, rest, field.controlProps)), /*#__PURE__*/React.createElement("span", {
|
|
42
|
+
className: "ef-check__box"
|
|
43
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
44
|
+
name: "check",
|
|
45
|
+
size: 13,
|
|
46
|
+
strokeWidth: 3
|
|
47
|
+
})), label ? /*#__PURE__*/React.createElement("span", {
|
|
48
|
+
className: "ef-check__label"
|
|
49
|
+
}, label, description ? /*#__PURE__*/React.createElement("span", {
|
|
50
|
+
className: "ef-check__desc"
|
|
51
|
+
}, description) : null) : null);
|
|
52
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface ComboOption { value: string; label: string; icon?: string; }
|
|
2
|
+
export interface ComboboxProps {
|
|
3
|
+
label?: string;
|
|
4
|
+
hint?: string;
|
|
5
|
+
/** Options as strings or {value,label,icon} */
|
|
6
|
+
options: Array<string | ComboOption>;
|
|
7
|
+
/** Selected value (single) or values (multiple) */
|
|
8
|
+
value?: string | string[] | null;
|
|
9
|
+
onChange?: (value: any) => void;
|
|
10
|
+
/** Multi-select with removable chips */
|
|
11
|
+
multiple?: boolean;
|
|
12
|
+
/** @default 'Search…' */
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
style?: React.CSSProperties;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function Combobox(props: ComboboxProps): React.JSX.Element;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Icon } from "../icons/Icon.js";
|
|
4
|
+
import { Tag } from "../display/Tag.js";
|
|
5
|
+
import { injectEfCss } from "./Button.js";
|
|
6
|
+
import { useFieldProps } from "./FormField.js";
|
|
7
|
+
import { Portal, useAnchoredStyle } from "../overlay/Portal.js";
|
|
8
|
+
const CSS = `
|
|
9
|
+
.ef-combo{position:relative}
|
|
10
|
+
.ef-combo__control{display:flex;align-items:center;flex-wrap:wrap;gap:6px;min-height:var(--control-h-md);padding:4px 32px 4px 8px;border:1px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--surface-card);cursor:text;transition:border-color var(--dur-fast) var(--ease-out),box-shadow var(--dur-fast) var(--ease-out)}
|
|
11
|
+
.ef-combo__control:hover{border-color:var(--sand-400)}
|
|
12
|
+
.ef-combo--open .ef-combo__control{border-color:var(--accent);box-shadow:var(--focus-ring)}
|
|
13
|
+
.ef-combo__input{flex:1;min-width:80px;border:none;outline:none;background:none;font-family:var(--font-sans);font-size:var(--text-md);color:var(--text-primary);height:26px;padding:0 4px}
|
|
14
|
+
.ef-combo__input::placeholder{color:var(--text-muted)}
|
|
15
|
+
.ef-combo__chevron{position:absolute;right:10px;top:50%;transform:translateY(-50%);color:var(--text-muted);display:inline-flex;pointer-events:none}
|
|
16
|
+
.ef-combo__panel{position:absolute;top:calc(100% + 6px);left:0;right:0;max-height:240px;overflow-y:auto;background:var(--surface-card);border:1px solid var(--border-strong);border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:4px;z-index:var(--z-dropdown);animation:ef-combo-in var(--dur-fast) var(--ease-out)}
|
|
17
|
+
@keyframes ef-combo-in{from{opacity:0;transform:translateY(-3px)}}
|
|
18
|
+
.ef-combo__opt{display:flex;align-items:center;gap:9px;width:100%;padding:7px 10px;border:none;border-radius:var(--radius-sm);background:none;cursor:pointer;text-align:left;font-family:var(--font-sans);font-size:var(--text-sm);color:var(--text-primary)}
|
|
19
|
+
.ef-combo__opt--hi{background:var(--surface-sunken)}
|
|
20
|
+
.ef-combo__opt__check{margin-left:auto;color:var(--accent);display:inline-flex}
|
|
21
|
+
.ef-combo__empty{padding:14px 10px;font-size:var(--text-sm);color:var(--text-muted);text-align:center}
|
|
22
|
+
`;
|
|
23
|
+
export function Combobox({
|
|
24
|
+
label,
|
|
25
|
+
hint,
|
|
26
|
+
options,
|
|
27
|
+
value,
|
|
28
|
+
onChange,
|
|
29
|
+
multiple,
|
|
30
|
+
placeholder = 'Search…',
|
|
31
|
+
style,
|
|
32
|
+
className,
|
|
33
|
+
...rest
|
|
34
|
+
}) {
|
|
35
|
+
injectEfCss('ef-css-combo', CSS);
|
|
36
|
+
// Picks up id / aria wiring when nested in a FormField; standalone this is a no-op.
|
|
37
|
+
const field = useFieldProps({
|
|
38
|
+
id: rest.id,
|
|
39
|
+
'aria-describedby': rest['aria-describedby']
|
|
40
|
+
});
|
|
41
|
+
const [open, setOpen] = React.useState(false);
|
|
42
|
+
const [q, setQ] = React.useState('');
|
|
43
|
+
const [hi, setHi] = React.useState(0);
|
|
44
|
+
const ref = React.useRef(null);
|
|
45
|
+
const panelRef = React.useRef(null);
|
|
46
|
+
const {
|
|
47
|
+
style: anchored
|
|
48
|
+
} = useAnchoredStyle(ref, panelRef, {
|
|
49
|
+
open,
|
|
50
|
+
placement: 'bottom',
|
|
51
|
+
align: 'start',
|
|
52
|
+
matchWidth: true
|
|
53
|
+
});
|
|
54
|
+
const sel = multiple ? value || [] : value != null ? [value] : [];
|
|
55
|
+
const opts = options.map(o => typeof o === 'string' ? {
|
|
56
|
+
value: o,
|
|
57
|
+
label: o
|
|
58
|
+
} : o);
|
|
59
|
+
const shown = opts.filter(o => o.label.toLowerCase().includes(q.toLowerCase()) && (!multiple || !sel.includes(o.value)));
|
|
60
|
+
React.useEffect(() => {
|
|
61
|
+
if (!open) return;
|
|
62
|
+
const away = e => {
|
|
63
|
+
const inControl = ref.current && ref.current.contains(e.target);
|
|
64
|
+
const inPanel = panelRef.current && panelRef.current.contains(e.target);
|
|
65
|
+
if (!inControl && !inPanel) setOpen(false);
|
|
66
|
+
};
|
|
67
|
+
document.addEventListener('mousedown', away);
|
|
68
|
+
return () => document.removeEventListener('mousedown', away);
|
|
69
|
+
}, [open]);
|
|
70
|
+
const pick = v => {
|
|
71
|
+
if (multiple) {
|
|
72
|
+
onChange && onChange([...sel, v]);
|
|
73
|
+
setQ('');
|
|
74
|
+
} else {
|
|
75
|
+
onChange && onChange(v);
|
|
76
|
+
setQ('');
|
|
77
|
+
setOpen(false);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const unpick = v => onChange && onChange(multiple ? sel.filter(x => x !== v) : null);
|
|
81
|
+
const key = e => {
|
|
82
|
+
if (e.key === 'ArrowDown') {
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
setHi(h => Math.min(h + 1, shown.length - 1));
|
|
85
|
+
setOpen(true);
|
|
86
|
+
} else if (e.key === 'ArrowUp') {
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
setHi(h => Math.max(h - 1, 0));
|
|
89
|
+
} else if (e.key === 'Enter' && open && shown[hi]) {
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
pick(shown[hi].value);
|
|
92
|
+
} else if (e.key === 'Escape') setOpen(false);else if (e.key === 'Backspace' && !q && multiple && sel.length) unpick(sel[sel.length - 1]);
|
|
93
|
+
};
|
|
94
|
+
const control = /*#__PURE__*/React.createElement("div", {
|
|
95
|
+
ref: ref,
|
|
96
|
+
className: `ef-combo${open ? ' ef-combo--open' : ''}`
|
|
97
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
98
|
+
className: "ef-combo__control",
|
|
99
|
+
onClick: () => {
|
|
100
|
+
setOpen(true);
|
|
101
|
+
ref.current.querySelector('input').focus();
|
|
102
|
+
}
|
|
103
|
+
}, multiple && sel.map(v => {
|
|
104
|
+
const o = opts.find(x => x.value === v);
|
|
105
|
+
return /*#__PURE__*/React.createElement(Tag, {
|
|
106
|
+
key: v,
|
|
107
|
+
onRemove: () => unpick(v)
|
|
108
|
+
}, o ? o.label : v);
|
|
109
|
+
}), /*#__PURE__*/React.createElement("input", _extends({}, field.controlProps, {
|
|
110
|
+
className: "ef-combo__input",
|
|
111
|
+
role: "combobox",
|
|
112
|
+
"aria-expanded": open,
|
|
113
|
+
placeholder: sel.length && multiple ? '' : !multiple && sel.length ? (opts.find(x => x.value === sel[0]) || {}).label : placeholder,
|
|
114
|
+
value: q,
|
|
115
|
+
onChange: e => {
|
|
116
|
+
setQ(e.target.value);
|
|
117
|
+
setOpen(true);
|
|
118
|
+
setHi(0);
|
|
119
|
+
},
|
|
120
|
+
onFocus: () => setOpen(true),
|
|
121
|
+
onKeyDown: key
|
|
122
|
+
})), /*#__PURE__*/React.createElement("span", {
|
|
123
|
+
className: "ef-combo__chevron"
|
|
124
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
125
|
+
name: "chevron-down",
|
|
126
|
+
size: 16
|
|
127
|
+
}))), open && /*#__PURE__*/React.createElement(Portal, null, /*#__PURE__*/React.createElement("div", {
|
|
128
|
+
ref: panelRef,
|
|
129
|
+
className: "ef-combo__panel",
|
|
130
|
+
role: "listbox",
|
|
131
|
+
style: anchored
|
|
132
|
+
}, shown.length === 0 && /*#__PURE__*/React.createElement("div", {
|
|
133
|
+
className: "ef-combo__empty"
|
|
134
|
+
}, "Nothing matches \u201C", q, "\u201D."), shown.map((o, i) => /*#__PURE__*/React.createElement("button", {
|
|
135
|
+
key: o.value,
|
|
136
|
+
role: "option",
|
|
137
|
+
"aria-selected": !multiple && sel.includes(o.value),
|
|
138
|
+
className: `ef-combo__opt${i === hi ? ' ef-combo__opt--hi' : ''}`,
|
|
139
|
+
onMouseEnter: () => setHi(i),
|
|
140
|
+
onClick: () => pick(o.value)
|
|
141
|
+
}, o.icon ? /*#__PURE__*/React.createElement(Icon, {
|
|
142
|
+
name: o.icon,
|
|
143
|
+
size: 15
|
|
144
|
+
}) : null, o.label, !multiple && sel.includes(o.value) ? /*#__PURE__*/React.createElement("span", {
|
|
145
|
+
className: "ef-combo__opt__check"
|
|
146
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
147
|
+
name: "check",
|
|
148
|
+
size: 15
|
|
149
|
+
})) : null)))));
|
|
150
|
+
if (!label && !hint) return /*#__PURE__*/React.createElement("div", _extends({}, rest, {
|
|
151
|
+
className: className,
|
|
152
|
+
style: style
|
|
153
|
+
}), control);
|
|
154
|
+
return /*#__PURE__*/React.createElement("div", _extends({}, rest, {
|
|
155
|
+
className: `ef-field${className ? ' ' + className : ''}`,
|
|
156
|
+
style: style
|
|
157
|
+
}), label ? /*#__PURE__*/React.createElement("span", {
|
|
158
|
+
className: "ef-field__label"
|
|
159
|
+
}, label) : null, control, hint ? /*#__PURE__*/React.createElement("span", {
|
|
160
|
+
className: "ef-field__hint"
|
|
161
|
+
}, hint) : null);
|
|
162
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface DigitEntryProps {
|
|
2
|
+
/** Number of cells @default 6 */
|
|
3
|
+
length?: number;
|
|
4
|
+
/** Controlled value (digits only) */
|
|
5
|
+
value?: string;
|
|
6
|
+
onChange?: (value: string) => void;
|
|
7
|
+
/** Fires when all cells are filled */
|
|
8
|
+
onComplete?: (value: string) => void;
|
|
9
|
+
label?: string;
|
|
10
|
+
invalid?: boolean;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
style?: React.CSSProperties;
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function DigitEntry(props: DigitEntryProps): React.JSX.Element;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { injectEfCss } from "./Button.js";
|
|
4
|
+
import { useFieldProps } from "./FormField.js";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.ef-digits{display:flex;gap:8px}
|
|
7
|
+
.ef-digits__cell{width:44px;height:52px;border:1px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--surface-card);color:var(--text-primary);font-family:var(--font-mono);font-size:22px;font-weight:600;text-align:center;transition:border-color var(--dur-fast) var(--ease-out),box-shadow var(--dur-fast) var(--ease-out)}
|
|
8
|
+
.ef-digits__cell:hover:not(:disabled){border-color:var(--sand-400)}
|
|
9
|
+
.ef-digits__cell:focus{outline:none;border-color:var(--accent);box-shadow:var(--focus-ring)}
|
|
10
|
+
.ef-digits__cell:disabled{background:var(--surface-sunken);cursor:not-allowed}
|
|
11
|
+
.ef-digits--invalid .ef-digits__cell{border-color:var(--danger-600)}
|
|
12
|
+
`;
|
|
13
|
+
export function DigitEntry({
|
|
14
|
+
length = 6,
|
|
15
|
+
value,
|
|
16
|
+
onChange,
|
|
17
|
+
onComplete,
|
|
18
|
+
label,
|
|
19
|
+
invalid,
|
|
20
|
+
disabled,
|
|
21
|
+
style,
|
|
22
|
+
className,
|
|
23
|
+
...rest
|
|
24
|
+
}) {
|
|
25
|
+
injectEfCss('ef-css-digits', CSS);
|
|
26
|
+
// Picks up id / aria wiring when nested in a FormField; standalone this is a no-op.
|
|
27
|
+
const field = useFieldProps({
|
|
28
|
+
invalid,
|
|
29
|
+
id: rest.id,
|
|
30
|
+
'aria-describedby': rest['aria-describedby']
|
|
31
|
+
});
|
|
32
|
+
const [inner, setInner] = React.useState('');
|
|
33
|
+
const v = value != null ? value : inner;
|
|
34
|
+
const refs = React.useRef([]);
|
|
35
|
+
const set = next => {
|
|
36
|
+
next = next.replace(/\D/g, '').slice(0, length);
|
|
37
|
+
if (value == null) setInner(next);
|
|
38
|
+
if (onChange) onChange(next);
|
|
39
|
+
if (next.length === length && onComplete) onComplete(next);
|
|
40
|
+
};
|
|
41
|
+
const keyAt = (i, e) => {
|
|
42
|
+
if (e.key === 'Backspace') {
|
|
43
|
+
e.preventDefault();
|
|
44
|
+
if (v[i]) set(v.slice(0, i) + v.slice(i + 1));else if (i > 0) {
|
|
45
|
+
set(v.slice(0, i - 1) + v.slice(i));
|
|
46
|
+
refs.current[i - 1] && refs.current[i - 1].focus();
|
|
47
|
+
}
|
|
48
|
+
} else if (e.key === 'ArrowLeft' && i > 0) refs.current[i - 1].focus();else if (e.key === 'ArrowRight' && i < length - 1) refs.current[i + 1].focus();
|
|
49
|
+
};
|
|
50
|
+
const inputAt = (i, e) => {
|
|
51
|
+
const ch = e.target.value.replace(/\D/g, '');
|
|
52
|
+
if (!ch) return;
|
|
53
|
+
const next = (v.slice(0, i) + ch + v.slice(i + ch.length)).slice(0, length);
|
|
54
|
+
set(next);
|
|
55
|
+
const to = Math.min(i + ch.length, length - 1);
|
|
56
|
+
refs.current[to] && refs.current[to].focus();
|
|
57
|
+
};
|
|
58
|
+
const cells = Array.from({
|
|
59
|
+
length
|
|
60
|
+
}, (_, i) => /*#__PURE__*/React.createElement("input", _extends({
|
|
61
|
+
key: i
|
|
62
|
+
}, i === 0 ? field.controlProps : null, {
|
|
63
|
+
ref: el => refs.current[i] = el,
|
|
64
|
+
className: "ef-digits__cell",
|
|
65
|
+
inputMode: "numeric",
|
|
66
|
+
autoComplete: i === 0 ? 'one-time-code' : 'off',
|
|
67
|
+
maxLength: length,
|
|
68
|
+
value: v[i] || '',
|
|
69
|
+
disabled: disabled,
|
|
70
|
+
"aria-label": `Digit ${i + 1} of ${length}`,
|
|
71
|
+
onChange: e => inputAt(i, e),
|
|
72
|
+
onKeyDown: e => keyAt(i, e),
|
|
73
|
+
onFocus: e => e.target.select()
|
|
74
|
+
})));
|
|
75
|
+
const group = /*#__PURE__*/React.createElement("div", {
|
|
76
|
+
className: `ef-digits${invalid ? ' ef-digits--invalid' : ''}`
|
|
77
|
+
}, cells);
|
|
78
|
+
if (!label) return /*#__PURE__*/React.createElement("div", _extends({}, rest, {
|
|
79
|
+
className: className,
|
|
80
|
+
style: style
|
|
81
|
+
}), group);
|
|
82
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
83
|
+
className: `ef-field${className ? ' ' + className : ''}`,
|
|
84
|
+
style: style
|
|
85
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
86
|
+
className: "ef-field__label"
|
|
87
|
+
}, label), group);
|
|
88
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface FieldWiring {
|
|
2
|
+
/** The control's id. `null` in `group` mode, where no single child owns it. */
|
|
3
|
+
id: string | null;
|
|
4
|
+
invalid: boolean;
|
|
5
|
+
required: boolean;
|
|
6
|
+
/** Spread on the real control element, after `{...rest}`. */
|
|
7
|
+
controlProps: {
|
|
8
|
+
id?: string;
|
|
9
|
+
'aria-describedby'?: string;
|
|
10
|
+
'aria-invalid'?: true;
|
|
11
|
+
'aria-required'?: true;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export interface FormFieldProps {
|
|
15
|
+
/** Rendered as `<label htmlFor>`, or a `<span id>` when `group`. */
|
|
16
|
+
label?: React.ReactNode;
|
|
17
|
+
/** Muted helper text below the control. Hidden while `error` is set. */
|
|
18
|
+
hint?: React.ReactNode;
|
|
19
|
+
/** Error message below the control. Truthy implies invalid. */
|
|
20
|
+
error?: React.ReactNode;
|
|
21
|
+
/** Invalid state with no message. @default false */
|
|
22
|
+
invalid?: boolean;
|
|
23
|
+
/** Red asterisk plus aria-required on the control. @default false */
|
|
24
|
+
required?: boolean;
|
|
25
|
+
/** Label a set of controls: role="group" + aria-labelledby. @default false */
|
|
26
|
+
group?: boolean;
|
|
27
|
+
/** The control's id. Generated with React.useId() when omitted. */
|
|
28
|
+
id?: string;
|
|
29
|
+
/** A control, or a function receiving the wiring to spread yourself. */
|
|
30
|
+
children?: React.ReactNode | ((field: FieldWiring) => React.ReactNode);
|
|
31
|
+
style?: React.CSSProperties;
|
|
32
|
+
className?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Label / hint / error / required chrome around a control, and the owner of the
|
|
36
|
+
* id linking them. Design-system controls pick the wiring up from context; for
|
|
37
|
+
* anything else, pass a function and spread `controlProps` on the control.
|
|
38
|
+
*/
|
|
39
|
+
export declare function FormField(props: FormFieldProps): React.JSX.Element;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Icon } from "../icons/Icon.js";
|
|
4
|
+
import { injectEfCss } from "./Button.js";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.ef-field{display:flex;flex-direction:column;gap:6px}
|
|
7
|
+
.ef-field__label{font-size:var(--text-sm);font-weight:var(--weight-semibold);color:var(--text-primary)}
|
|
8
|
+
.ef-field__req{color:var(--danger-600);margin-left:2px}
|
|
9
|
+
.ef-field__hint{font-size:var(--text-xs);color:var(--text-muted)}
|
|
10
|
+
.ef-field__error{font-size:var(--text-xs);color:var(--danger-600);display:flex;align-items:center;gap:4px}
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
// Module-private, never exported: a control outside a FormField simply sees
|
|
14
|
+
// null and behaves standalone. Same shape as GroupCtx in Toggle.jsx.
|
|
15
|
+
const FieldCtx = /*#__PURE__*/React.createContext(null);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* How a control joins an enclosing FormField.
|
|
19
|
+
*
|
|
20
|
+
* Lowercase, so it stays off the public namespace. That is deliberate: no
|
|
21
|
+
* consumer needs it — design-system controls are wired by their own props, and
|
|
22
|
+
* anything else is wired through FormField's render prop — and keeping it
|
|
23
|
+
* unreachable stops a seventh field dialect being invented in a showcase.
|
|
24
|
+
*/
|
|
25
|
+
export function useFieldProps(own = {}) {
|
|
26
|
+
const ctx = React.useContext(FieldCtx);
|
|
27
|
+
const invalid = !!(own.invalid || own.error || ctx && ctx.invalid);
|
|
28
|
+
const required = own.required != null ? !!own.required : !!(ctx && ctx.required);
|
|
29
|
+
const describedBy = [own['aria-describedby'], ctx && ctx.describedBy].filter(Boolean).join(' ');
|
|
30
|
+
const id = own.id || ctx && ctx.id || undefined;
|
|
31
|
+
// Keys are added conditionally, never assigned undefined: a control spreads
|
|
32
|
+
// {...rest} and then {...controlProps}, and an always-present key would reset
|
|
33
|
+
// a caller's own aria-invalid back to absent.
|
|
34
|
+
const controlProps = {};
|
|
35
|
+
if (id) controlProps.id = id;
|
|
36
|
+
if (describedBy) controlProps['aria-describedby'] = describedBy;
|
|
37
|
+
if (invalid) controlProps['aria-invalid'] = true;
|
|
38
|
+
if (required) controlProps['aria-required'] = true;
|
|
39
|
+
return {
|
|
40
|
+
inField: !!ctx,
|
|
41
|
+
id: id || null,
|
|
42
|
+
invalid,
|
|
43
|
+
required,
|
|
44
|
+
controlProps
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Label, hint, error and required chrome around any control, and the owner of
|
|
50
|
+
* the id that ties them together.
|
|
51
|
+
*
|
|
52
|
+
* Wrap a control as children and design-system controls pick the wiring up
|
|
53
|
+
* automatically. For anything else — a third-party input, a control with no
|
|
54
|
+
* label prop — pass a function and spread the wiring yourself.
|
|
55
|
+
*/
|
|
56
|
+
export function FormField({
|
|
57
|
+
label,
|
|
58
|
+
hint,
|
|
59
|
+
error,
|
|
60
|
+
invalid,
|
|
61
|
+
required,
|
|
62
|
+
group,
|
|
63
|
+
id,
|
|
64
|
+
children,
|
|
65
|
+
style,
|
|
66
|
+
className,
|
|
67
|
+
...rest
|
|
68
|
+
}) {
|
|
69
|
+
injectEfCss('ef-css-field', CSS);
|
|
70
|
+
const uid = React.useId();
|
|
71
|
+
const controlId = id || uid + 'c';
|
|
72
|
+
const labelId = uid + 'l';
|
|
73
|
+
const noteId = uid + 'n';
|
|
74
|
+
const hasError = error != null && error !== false && error !== '';
|
|
75
|
+
const hasHint = !hasError && hint != null && hint !== false && hint !== '';
|
|
76
|
+
const note = hasError ? 'error' : hasHint ? 'hint' : null;
|
|
77
|
+
const bad = !!(invalid || hasError);
|
|
78
|
+
const ctx = React.useMemo(() => ({
|
|
79
|
+
// In group mode no single child owns the id — five radios cannot share one —
|
|
80
|
+
// so the group element carries the label and description instead.
|
|
81
|
+
id: group ? null : controlId,
|
|
82
|
+
describedBy: note && !group ? noteId : undefined,
|
|
83
|
+
invalid: bad,
|
|
84
|
+
required: !!required
|
|
85
|
+
}), [group, controlId, note, noteId, bad, required]);
|
|
86
|
+
const wiring = React.useMemo(() => {
|
|
87
|
+
const p = {};
|
|
88
|
+
if (ctx.id) p.id = ctx.id;
|
|
89
|
+
if (ctx.describedBy) p['aria-describedby'] = ctx.describedBy;
|
|
90
|
+
if (bad) p['aria-invalid'] = true;
|
|
91
|
+
if (required) p['aria-required'] = true;
|
|
92
|
+
return {
|
|
93
|
+
id: ctx.id,
|
|
94
|
+
invalid: bad,
|
|
95
|
+
required: !!required,
|
|
96
|
+
controlProps: p
|
|
97
|
+
};
|
|
98
|
+
}, [ctx, bad, required]);
|
|
99
|
+
const star = required ? /*#__PURE__*/React.createElement("span", {
|
|
100
|
+
className: "ef-field__req",
|
|
101
|
+
"aria-hidden": "true"
|
|
102
|
+
}, "*") : null;
|
|
103
|
+
return /*#__PURE__*/React.createElement("div", _extends({}, rest, {
|
|
104
|
+
role: group ? 'group' : undefined,
|
|
105
|
+
"aria-labelledby": group && label ? labelId : undefined,
|
|
106
|
+
"aria-describedby": group && note ? noteId : undefined,
|
|
107
|
+
className: `ef-field${className ? ' ' + className : ''}`,
|
|
108
|
+
style: style
|
|
109
|
+
}), label ? group ? /*#__PURE__*/React.createElement("span", {
|
|
110
|
+
id: labelId,
|
|
111
|
+
className: "ef-field__label"
|
|
112
|
+
}, label, star) : /*#__PURE__*/React.createElement("label", {
|
|
113
|
+
htmlFor: controlId,
|
|
114
|
+
className: "ef-field__label"
|
|
115
|
+
}, label, star) : null, /*#__PURE__*/React.createElement(FieldCtx.Provider, {
|
|
116
|
+
value: ctx
|
|
117
|
+
}, typeof children === 'function' ? children(wiring) : children), hasError ? /*#__PURE__*/React.createElement("span", {
|
|
118
|
+
id: noteId,
|
|
119
|
+
className: "ef-field__error"
|
|
120
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
121
|
+
name: "circle-alert",
|
|
122
|
+
size: 13
|
|
123
|
+
}), error) : hasHint ? /*#__PURE__*/React.createElement("span", {
|
|
124
|
+
id: noteId,
|
|
125
|
+
className: "ef-field__hint"
|
|
126
|
+
}, hint) : null);
|
|
127
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface IconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
2
|
+
/** Lucide icon name */
|
|
3
|
+
icon: string;
|
|
4
|
+
/** Accessible label (required — rendered as aria-label + title) */
|
|
5
|
+
label: string;
|
|
6
|
+
/** @default 'quiet' */
|
|
7
|
+
variant?: 'quiet' | 'outline' | 'solid';
|
|
8
|
+
/** @default 'md' */
|
|
9
|
+
size?: 'sm' | 'md' | 'lg';
|
|
10
|
+
}
|
|
11
|
+
export declare function IconButton(props: IconButtonProps): React.JSX.Element;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Icon } from "../icons/Icon.js";
|
|
4
|
+
import { injectEfCss } from "./Button.js";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.ef-iconbtn{display:inline-flex;align-items:center;justify-content:center;flex:none;border:1px solid transparent;border-radius:var(--radius-sm);background:transparent;color:var(--text-secondary);cursor:pointer;transition:background var(--dur-fast) var(--ease-out),color var(--dur-fast) var(--ease-out),box-shadow var(--dur-fast) var(--ease-out),transform var(--dur-med) var(--ease-spring)}
|
|
7
|
+
.ef-iconbtn:focus-visible{outline:none;box-shadow:var(--focus-ring)}
|
|
8
|
+
.ef-iconbtn:active:not(:disabled){transform:scale(.96)}
|
|
9
|
+
.ef-iconbtn:disabled{opacity:.45;cursor:not-allowed}
|
|
10
|
+
.ef-iconbtn--sm{width:var(--control-h-sm);height:var(--control-h-sm)}
|
|
11
|
+
.ef-iconbtn--md{width:var(--control-h-md);height:var(--control-h-md)}
|
|
12
|
+
.ef-iconbtn--lg{width:var(--control-h-lg);height:var(--control-h-lg)}
|
|
13
|
+
.ef-iconbtn--quiet:hover:not(:disabled){background:var(--surface-sunken);color:var(--text-primary)}
|
|
14
|
+
.ef-iconbtn--outline{border-color:var(--border-strong);background:transparent;color:var(--text-primary)}
|
|
15
|
+
.ef-iconbtn--outline:hover:not(:disabled){background:var(--surface-sunken)}
|
|
16
|
+
.ef-iconbtn--solid{background:var(--accent);color:var(--accent-contrast)}
|
|
17
|
+
.ef-iconbtn--solid:hover:not(:disabled){background:var(--accent-hover)}
|
|
18
|
+
`;
|
|
19
|
+
export function IconButton({
|
|
20
|
+
icon,
|
|
21
|
+
label,
|
|
22
|
+
variant = 'quiet',
|
|
23
|
+
size = 'md',
|
|
24
|
+
disabled,
|
|
25
|
+
style,
|
|
26
|
+
className,
|
|
27
|
+
...rest
|
|
28
|
+
}) {
|
|
29
|
+
injectEfCss('ef-css-iconbtn', CSS);
|
|
30
|
+
const isz = size === 'sm' ? 16 : size === 'lg' ? 20 : 18;
|
|
31
|
+
return /*#__PURE__*/React.createElement("button", _extends({
|
|
32
|
+
"aria-label": label,
|
|
33
|
+
title: label,
|
|
34
|
+
className: `ef-iconbtn ef-iconbtn--${variant} ef-iconbtn--${size}${className ? ' ' + className : ''}`,
|
|
35
|
+
disabled: disabled,
|
|
36
|
+
style: style
|
|
37
|
+
}, rest), /*#__PURE__*/React.createElement(Icon, {
|
|
38
|
+
name: icon,
|
|
39
|
+
size: isz
|
|
40
|
+
}));
|
|
41
|
+
}
|
package/forms/Input.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
|
|
2
|
+
/** Field label rendered above */
|
|
3
|
+
label?: string;
|
|
4
|
+
/** Muted helper text below */
|
|
5
|
+
hint?: string;
|
|
6
|
+
/** Error message below — also sets invalid styling */
|
|
7
|
+
error?: string;
|
|
8
|
+
/** Lucide icon name inside the field, left */
|
|
9
|
+
iconLeft?: string;
|
|
10
|
+
/** @default 'md' */
|
|
11
|
+
size?: 'sm' | 'md' | 'lg';
|
|
12
|
+
invalid?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function Input(props: InputProps): React.JSX.Element;
|