@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,14 @@
|
|
|
1
|
+
export interface SparklineProps {
|
|
2
|
+
/** Series values */
|
|
3
|
+
data: number[];
|
|
4
|
+
/** @default 120 */ width?: number;
|
|
5
|
+
/** @default 32 */ height?: number;
|
|
6
|
+
/** Color override; inferred from first vs last value */
|
|
7
|
+
direction?: 'up' | 'down' | 'flat';
|
|
8
|
+
/** @default 1.5 */ strokeWidth?: number;
|
|
9
|
+
/** Faint area fill @default true */
|
|
10
|
+
area?: boolean;
|
|
11
|
+
style?: React.CSSProperties;
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function Sparkline(props: SparklineProps): React.JSX.Element;
|
|
@@ -0,0 +1,42 @@
|
|
|
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
|
+
export function Sparkline({
|
|
4
|
+
data = [],
|
|
5
|
+
width = 120,
|
|
6
|
+
height = 32,
|
|
7
|
+
direction,
|
|
8
|
+
strokeWidth = 1.5,
|
|
9
|
+
area = true,
|
|
10
|
+
style,
|
|
11
|
+
className,
|
|
12
|
+
...rest
|
|
13
|
+
}) {
|
|
14
|
+
if (!data.length) return null;
|
|
15
|
+
const min = Math.min(...data),
|
|
16
|
+
max = Math.max(...data),
|
|
17
|
+
span = max - min || 1;
|
|
18
|
+
const pad = strokeWidth;
|
|
19
|
+
const pts = data.map((v, i) => [pad + i / (data.length - 1) * (width - pad * 2), pad + (1 - (v - min) / span) * (height - pad * 2)]);
|
|
20
|
+
const dir = direction || (data[data.length - 1] >= data[0] ? 'up' : 'down');
|
|
21
|
+
const color = dir === 'up' ? 'var(--success-600)' : dir === 'down' ? 'var(--danger-600)' : 'var(--text-primary)';
|
|
22
|
+
const line = pts.map(p => p.map(n => n.toFixed(1)).join(',')).join(' ');
|
|
23
|
+
return /*#__PURE__*/React.createElement("svg", _extends({}, rest, {
|
|
24
|
+
width: width,
|
|
25
|
+
height: height,
|
|
26
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
27
|
+
style: style,
|
|
28
|
+
className: className,
|
|
29
|
+
"aria-hidden": "true"
|
|
30
|
+
}), area && /*#__PURE__*/React.createElement("polygon", {
|
|
31
|
+
points: `${pad},${height - pad} ${line} ${width - pad},${height - pad}`,
|
|
32
|
+
fill: color,
|
|
33
|
+
opacity: "0.09"
|
|
34
|
+
}), /*#__PURE__*/React.createElement("polyline", {
|
|
35
|
+
points: line,
|
|
36
|
+
fill: "none",
|
|
37
|
+
stroke: color,
|
|
38
|
+
strokeWidth: strokeWidth,
|
|
39
|
+
strokeLinejoin: "round",
|
|
40
|
+
strokeLinecap: "round"
|
|
41
|
+
}));
|
|
42
|
+
}
|
package/data/Stat.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface StatProps {
|
|
2
|
+
label: React.ReactNode;
|
|
3
|
+
/** Pre-formatted, e.g. "$48.2k" */
|
|
4
|
+
value: React.ReactNode;
|
|
5
|
+
/** e.g. "12.4%" */
|
|
6
|
+
delta?: React.ReactNode;
|
|
7
|
+
/** Colors + arrows the delta @default 'flat' */
|
|
8
|
+
direction?: 'up' | 'down' | 'flat';
|
|
9
|
+
/** Muted line under the value */
|
|
10
|
+
hint?: React.ReactNode;
|
|
11
|
+
style?: React.CSSProperties;
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function Stat(props: StatProps): React.JSX.Element;
|
package/data/Stat.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
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 "../forms/Button.js";
|
|
4
|
+
const CSS = `
|
|
5
|
+
.ef-stat__label{font-size:var(--text-sm);color:var(--text-muted);font-weight:var(--weight-medium)}
|
|
6
|
+
.ef-stat__row{display:flex;align-items:baseline;gap:8px;margin-top:6px}
|
|
7
|
+
.ef-stat__value{font-family:var(--font-mono);font-size:26px;font-weight:var(--weight-semibold);letter-spacing:-0.01em;color:var(--text-primary)}
|
|
8
|
+
.ef-stat__delta{font-size:var(--text-xs);font-weight:var(--weight-medium);white-space:nowrap}
|
|
9
|
+
.ef-stat__delta--up{color:var(--success-600)}
|
|
10
|
+
.ef-stat__delta--down{color:var(--danger-600)}
|
|
11
|
+
.ef-stat__delta--flat{color:var(--text-muted)}
|
|
12
|
+
.ef-stat__hint{font-size:var(--text-xs);color:var(--text-muted);margin-top:4px}
|
|
13
|
+
`;
|
|
14
|
+
export function Stat({
|
|
15
|
+
label,
|
|
16
|
+
value,
|
|
17
|
+
delta,
|
|
18
|
+
direction = 'flat',
|
|
19
|
+
hint,
|
|
20
|
+
style,
|
|
21
|
+
className,
|
|
22
|
+
...rest
|
|
23
|
+
}) {
|
|
24
|
+
injectEfCss('ef-css-stat', CSS);
|
|
25
|
+
const arrow = direction === 'up' ? '↑ ' : direction === 'down' ? '↓ ' : '';
|
|
26
|
+
return /*#__PURE__*/React.createElement("div", _extends({}, rest, {
|
|
27
|
+
className: className,
|
|
28
|
+
style: style
|
|
29
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
30
|
+
className: "ef-stat__label"
|
|
31
|
+
}, label), /*#__PURE__*/React.createElement("div", {
|
|
32
|
+
className: "ef-stat__row"
|
|
33
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
34
|
+
className: "ef-stat__value"
|
|
35
|
+
}, value), delta != null ? /*#__PURE__*/React.createElement("span", {
|
|
36
|
+
className: `ef-stat__delta ef-stat__delta--${direction}`
|
|
37
|
+
}, arrow, delta) : null), hint ? /*#__PURE__*/React.createElement("div", {
|
|
38
|
+
className: "ef-stat__hint"
|
|
39
|
+
}, hint) : null);
|
|
40
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface StatusDotProps {
|
|
2
|
+
/** @default 'ok' */
|
|
3
|
+
state?: 'ok' | 'warn' | 'err' | 'busy' | 'off';
|
|
4
|
+
label?: React.ReactNode;
|
|
5
|
+
/** Radiating pulse for live states */
|
|
6
|
+
pulse?: boolean;
|
|
7
|
+
style?: React.CSSProperties;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function StatusDot(props: StatusDotProps): React.JSX.Element;
|
|
@@ -0,0 +1,34 @@
|
|
|
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 "../forms/Button.js";
|
|
4
|
+
const CSS = `
|
|
5
|
+
.ef-status{display:inline-flex;align-items:center;gap:7px;font-size:var(--text-sm);color:var(--text-primary)}
|
|
6
|
+
.ef-status__dot{position:relative;width:8px;height:8px;border-radius:var(--radius-full);flex:none}
|
|
7
|
+
.ef-status--ok .ef-status__dot{background:var(--success-600)}
|
|
8
|
+
.ef-status--warn .ef-status__dot{background:var(--warning-600)}
|
|
9
|
+
.ef-status--err .ef-status__dot{background:var(--danger-600)}
|
|
10
|
+
.ef-status--busy .ef-status__dot{background:var(--caramel-500)}
|
|
11
|
+
.ef-status--off .ef-status__dot{background:var(--sand-400)}
|
|
12
|
+
@keyframes ef-status-pulse{0%{box-shadow:0 0 0 0 currentColor;opacity:.5}100%{box-shadow:0 0 0 6px currentColor;opacity:0}}
|
|
13
|
+
.ef-status--pulse .ef-status__dot::after{content:'';position:absolute;inset:0;border-radius:var(--radius-full);animation:ef-status-pulse 1.6s var(--ease-out) infinite}
|
|
14
|
+
.ef-status--ok .ef-status__dot::after{color:var(--success-600)}
|
|
15
|
+
.ef-status--warn .ef-status__dot::after{color:var(--warning-600)}
|
|
16
|
+
.ef-status--err .ef-status__dot::after{color:var(--danger-600)}
|
|
17
|
+
.ef-status--busy .ef-status__dot::after{color:var(--caramel-500)}
|
|
18
|
+
`;
|
|
19
|
+
export function StatusDot({
|
|
20
|
+
state = 'ok',
|
|
21
|
+
label,
|
|
22
|
+
pulse,
|
|
23
|
+
style,
|
|
24
|
+
className,
|
|
25
|
+
...rest
|
|
26
|
+
}) {
|
|
27
|
+
injectEfCss('ef-css-status', CSS);
|
|
28
|
+
return /*#__PURE__*/React.createElement("span", _extends({}, rest, {
|
|
29
|
+
className: `ef-status ef-status--${state}${pulse ? ' ef-status--pulse' : ''}${className ? ' ' + className : ''}`,
|
|
30
|
+
style: style
|
|
31
|
+
}), /*#__PURE__*/React.createElement("span", {
|
|
32
|
+
className: "ef-status__dot"
|
|
33
|
+
}), label ? /*#__PURE__*/React.createElement("span", null, label) : null);
|
|
34
|
+
}
|
package/data/Table.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export interface TableColumn {
|
|
2
|
+
/** Row field to read */
|
|
3
|
+
key: string;
|
|
4
|
+
label: React.ReactNode;
|
|
5
|
+
width?: number | string;
|
|
6
|
+
align?: 'left' | 'center' | 'right';
|
|
7
|
+
/** Mono tabular figures */
|
|
8
|
+
numeric?: boolean;
|
|
9
|
+
/** Show a sort control in the header for this column */
|
|
10
|
+
sortable?: boolean;
|
|
11
|
+
/** Value to sort on, when it is not simply row[key] */
|
|
12
|
+
sortAccessor?: (row: any) => any;
|
|
13
|
+
/** Custom cell: (value, row, rowIndex) => node */
|
|
14
|
+
render?: (value: any, row: any, index: number) => React.ReactNode;
|
|
15
|
+
}
|
|
16
|
+
export interface TableSort {
|
|
17
|
+
key: string;
|
|
18
|
+
direction: 'asc' | 'desc';
|
|
19
|
+
}
|
|
20
|
+
export interface TableProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
21
|
+
columns: TableColumn[];
|
|
22
|
+
rows: any[];
|
|
23
|
+
/** Field name or fn for React keys @default index */
|
|
24
|
+
rowKey?: string | ((row: any) => string);
|
|
25
|
+
onRowClick?: (row: any) => void;
|
|
26
|
+
dense?: boolean;
|
|
27
|
+
/** Controlled sort. Omit and the table sorts itself. */
|
|
28
|
+
sort?: TableSort | null;
|
|
29
|
+
defaultSort?: TableSort;
|
|
30
|
+
onSortChange?: (sort: TableSort) => void;
|
|
31
|
+
/** Adds a leading checkbox column with a select-all header */
|
|
32
|
+
selectable?: boolean;
|
|
33
|
+
/** Controlled selection, as row keys */
|
|
34
|
+
selected?: Array<string | number>;
|
|
35
|
+
defaultSelected?: Array<string | number>;
|
|
36
|
+
onSelectionChange?: (selected: Array<string | number>) => void;
|
|
37
|
+
/** Pin the header while the body scrolls (implies a scroll box) */
|
|
38
|
+
stickyHeader?: boolean;
|
|
39
|
+
/** Height of the scroll box @default 320 when sticky */
|
|
40
|
+
maxHeight?: number | string;
|
|
41
|
+
/** Replace the body with shimmer rows */
|
|
42
|
+
loading?: boolean;
|
|
43
|
+
/** @default 3 */
|
|
44
|
+
loadingRows?: number;
|
|
45
|
+
/** Shown when there are no rows @default 'Nothing to show yet.' */
|
|
46
|
+
empty?: React.ReactNode;
|
|
47
|
+
style?: React.CSSProperties;
|
|
48
|
+
className?: string;
|
|
49
|
+
}
|
|
50
|
+
/** Ref lands on the scrolling wrapper element. */
|
|
51
|
+
export declare const Table: React.ForwardRefExoticComponent<
|
|
52
|
+
TableProps & React.RefAttributes<HTMLDivElement>
|
|
53
|
+
>;
|
package/data/Table.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
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 "../forms/Button.js";
|
|
4
|
+
import { Checkbox } from "../forms/Checkbox.js";
|
|
5
|
+
import { Icon } from "../icons/Icon.js";
|
|
6
|
+
const CSS = `
|
|
7
|
+
.ef-table-wrap{max-width:100%;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch}
|
|
8
|
+
.ef-table-wrap--sticky{overflow-y:auto}
|
|
9
|
+
.ef-table{width:100%;border-collapse:collapse}
|
|
10
|
+
.ef-table th{text-align:left;padding:10px var(--table-pad-x);font-size:11px;font-weight:var(--weight-semibold);letter-spacing:0.06em;text-transform:uppercase;color:var(--text-muted);border-bottom:1px solid var(--border-default);white-space:nowrap}
|
|
11
|
+
.ef-table--sticky thead th{position:sticky;top:0;z-index:1;background:var(--surface-card)}
|
|
12
|
+
.ef-table td{padding:var(--table-pad-y) var(--table-pad-x);font-size:var(--text-md);border-bottom:1px solid var(--border-default);vertical-align:middle}
|
|
13
|
+
.ef-table tr:last-child td{border-bottom:none}
|
|
14
|
+
.ef-table--dense td{padding:8px var(--table-pad-x);font-size:var(--text-sm)}
|
|
15
|
+
.ef-table__row--click{cursor:pointer;transition:background var(--dur-fast) var(--ease-out)}
|
|
16
|
+
.ef-table__row--click:hover{background:var(--surface-subtle)}
|
|
17
|
+
.ef-table__row--sel{background:var(--accent-subtle)}
|
|
18
|
+
.ef-table .ef-num{font-family:var(--font-mono);font-size:13px}
|
|
19
|
+
.ef-table__sort{display:inline-flex;align-items:center;gap:4px;border:none;background:none;padding:0;margin:0;font:inherit;letter-spacing:inherit;text-transform:inherit;color:inherit;cursor:pointer}
|
|
20
|
+
.ef-table__sort:hover{color:var(--text-primary)}
|
|
21
|
+
.ef-table__sort:focus-visible{outline:none;box-shadow:var(--focus-ring);border-radius:var(--radius-sm)}
|
|
22
|
+
.ef-table__sort__i{display:inline-flex;opacity:0;transition:opacity var(--dur-fast) var(--ease-out)}
|
|
23
|
+
.ef-table__sort:hover .ef-table__sort__i,.ef-table__sort--on .ef-table__sort__i{opacity:1}
|
|
24
|
+
.ef-table__sel{width:1%;white-space:nowrap}
|
|
25
|
+
.ef-table__msg{padding:28px var(--table-pad-x);text-align:center;font-size:var(--text-md);color:var(--text-muted)}
|
|
26
|
+
.ef-table__skel{height:12px;border-radius:var(--radius-sm);background:var(--surface-sunken);animation:ef-table-pulse 1.2s var(--ease-in-out) infinite}
|
|
27
|
+
@keyframes ef-table-pulse{50%{opacity:.45}}
|
|
28
|
+
`;
|
|
29
|
+
const compare = (a, b) => {
|
|
30
|
+
if (a == null && b == null) return 0;
|
|
31
|
+
if (a == null) return -1;
|
|
32
|
+
if (b == null) return 1;
|
|
33
|
+
if (typeof a === 'number' && typeof b === 'number') return a - b;
|
|
34
|
+
return String(a).localeCompare(String(b), undefined, {
|
|
35
|
+
numeric: true,
|
|
36
|
+
sensitivity: 'base'
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
export const Table = /*#__PURE__*/React.forwardRef(function Table({
|
|
40
|
+
columns,
|
|
41
|
+
rows,
|
|
42
|
+
rowKey,
|
|
43
|
+
onRowClick,
|
|
44
|
+
dense,
|
|
45
|
+
sort: sortProp,
|
|
46
|
+
defaultSort,
|
|
47
|
+
onSortChange,
|
|
48
|
+
selectable,
|
|
49
|
+
selected: selectedProp,
|
|
50
|
+
defaultSelected,
|
|
51
|
+
onSelectionChange,
|
|
52
|
+
stickyHeader,
|
|
53
|
+
maxHeight,
|
|
54
|
+
loading,
|
|
55
|
+
loadingRows = 3,
|
|
56
|
+
empty = 'Nothing to show yet.',
|
|
57
|
+
style,
|
|
58
|
+
className,
|
|
59
|
+
...rest
|
|
60
|
+
}, ref) {
|
|
61
|
+
injectEfCss('ef-css-table', CSS);
|
|
62
|
+
const key = (row, i) => rowKey ? typeof rowKey === 'function' ? rowKey(row) : row[rowKey] : i;
|
|
63
|
+
const [innerSort, setInnerSort] = React.useState(defaultSort || null);
|
|
64
|
+
const sort = sortProp !== undefined ? sortProp : innerSort;
|
|
65
|
+
const applySort = next => {
|
|
66
|
+
if (sortProp === undefined) setInnerSort(next);
|
|
67
|
+
if (onSortChange) onSortChange(next);
|
|
68
|
+
};
|
|
69
|
+
const toggleSort = colKey => {
|
|
70
|
+
const dir = sort && sort.key === colKey && sort.direction === 'asc' ? 'desc' : 'asc';
|
|
71
|
+
applySort({
|
|
72
|
+
key: colKey,
|
|
73
|
+
direction: dir
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
const [innerSel, setInnerSel] = React.useState(defaultSelected || []);
|
|
77
|
+
const selected = selectedProp !== undefined ? selectedProp : innerSel;
|
|
78
|
+
const applySel = next => {
|
|
79
|
+
if (selectedProp === undefined) setInnerSel(next);
|
|
80
|
+
if (onSelectionChange) onSelectionChange(next);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Sorting is applied here, not expected of the caller: a table that renders
|
|
84
|
+
// a sort affordance it does not honour is worse than one with no affordance.
|
|
85
|
+
const sorted = React.useMemo(() => {
|
|
86
|
+
if (!sort || !sort.key) return rows;
|
|
87
|
+
const col = columns.find(c => c.key === sort.key);
|
|
88
|
+
if (!col) return rows;
|
|
89
|
+
const get = col.sortAccessor || (row => row[col.key]);
|
|
90
|
+
const out = rows.slice().sort((x, y) => compare(get(x), get(y)));
|
|
91
|
+
return sort.direction === 'desc' ? out.reverse() : out;
|
|
92
|
+
}, [rows, columns, sort]);
|
|
93
|
+
const allKeys = sorted.map((r, i) => key(r, i));
|
|
94
|
+
const allOn = allKeys.length > 0 && allKeys.every(k => selected.includes(k));
|
|
95
|
+
const someOn = !allOn && allKeys.some(k => selected.includes(k));
|
|
96
|
+
const span = columns.length + (selectable ? 1 : 0);
|
|
97
|
+
const wrapStyle = stickyHeader || maxHeight ? {
|
|
98
|
+
maxHeight: maxHeight || 320,
|
|
99
|
+
...style
|
|
100
|
+
} : style;
|
|
101
|
+
return /*#__PURE__*/React.createElement("div", _extends({
|
|
102
|
+
ref: ref
|
|
103
|
+
}, rest, {
|
|
104
|
+
className: `ef-table-wrap${stickyHeader || maxHeight ? ' ef-table-wrap--sticky' : ''}`,
|
|
105
|
+
style: wrapStyle
|
|
106
|
+
}), /*#__PURE__*/React.createElement("table", {
|
|
107
|
+
className: `ef-table${dense ? ' ef-table--dense' : ''}${stickyHeader ? ' ef-table--sticky' : ''}${className ? ' ' + className : ''}`
|
|
108
|
+
}, /*#__PURE__*/React.createElement("thead", null, /*#__PURE__*/React.createElement("tr", null, selectable ? /*#__PURE__*/React.createElement("th", {
|
|
109
|
+
className: "ef-table__sel"
|
|
110
|
+
}, /*#__PURE__*/React.createElement(Checkbox, {
|
|
111
|
+
"aria-label": allOn ? 'Deselect all rows' : 'Select all rows',
|
|
112
|
+
checked: allOn,
|
|
113
|
+
ref: el => {
|
|
114
|
+
if (el) el.indeterminate = someOn;
|
|
115
|
+
},
|
|
116
|
+
onChange: e => applySel(e.target.checked ? allKeys : [])
|
|
117
|
+
})) : null, columns.map(c => {
|
|
118
|
+
const on = sort && sort.key === c.key;
|
|
119
|
+
return /*#__PURE__*/React.createElement("th", {
|
|
120
|
+
key: c.key,
|
|
121
|
+
style: {
|
|
122
|
+
width: c.width,
|
|
123
|
+
textAlign: c.align
|
|
124
|
+
},
|
|
125
|
+
"aria-sort": c.sortable ? on ? sort.direction === 'asc' ? 'ascending' : 'descending' : 'none' : undefined
|
|
126
|
+
}, c.sortable ? /*#__PURE__*/React.createElement("button", {
|
|
127
|
+
type: "button",
|
|
128
|
+
className: `ef-table__sort${on ? ' ef-table__sort--on' : ''}`,
|
|
129
|
+
onClick: () => toggleSort(c.key)
|
|
130
|
+
}, c.label, /*#__PURE__*/React.createElement("span", {
|
|
131
|
+
className: "ef-table__sort__i"
|
|
132
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
133
|
+
name: on && sort.direction === 'desc' ? 'chevron-down' : 'chevron-up',
|
|
134
|
+
size: 13
|
|
135
|
+
}))) : c.label);
|
|
136
|
+
}))), /*#__PURE__*/React.createElement("tbody", null, loading ? Array.from({
|
|
137
|
+
length: loadingRows
|
|
138
|
+
}, (_, i) => /*#__PURE__*/React.createElement("tr", {
|
|
139
|
+
key: 'sk' + i
|
|
140
|
+
}, Array.from({
|
|
141
|
+
length: span
|
|
142
|
+
}, (_, j) => /*#__PURE__*/React.createElement("td", {
|
|
143
|
+
key: j
|
|
144
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
145
|
+
className: "ef-table__skel",
|
|
146
|
+
style: {
|
|
147
|
+
width: j === 0 ? '55%' : '75%'
|
|
148
|
+
}
|
|
149
|
+
}))))) : sorted.length === 0 ? /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", {
|
|
150
|
+
className: "ef-table__msg",
|
|
151
|
+
colSpan: span
|
|
152
|
+
}, empty)) : sorted.map((row, i) => {
|
|
153
|
+
const k = key(row, i);
|
|
154
|
+
const isSel = selectable && selected.includes(k);
|
|
155
|
+
return /*#__PURE__*/React.createElement("tr", {
|
|
156
|
+
key: k,
|
|
157
|
+
className: `${onRowClick ? 'ef-table__row--click' : ''}${isSel ? ' ef-table__row--sel' : ''}`.trim() || undefined,
|
|
158
|
+
onClick: onRowClick ? () => onRowClick(row) : undefined
|
|
159
|
+
}, selectable ? /*#__PURE__*/React.createElement("td", {
|
|
160
|
+
className: "ef-table__sel",
|
|
161
|
+
onClick: e => e.stopPropagation()
|
|
162
|
+
}, /*#__PURE__*/React.createElement(Checkbox, {
|
|
163
|
+
"aria-label": `Select row ${i + 1}`,
|
|
164
|
+
checked: !!isSel,
|
|
165
|
+
onChange: e => applySel(e.target.checked ? [...selected, k] : selected.filter(x => x !== k))
|
|
166
|
+
})) : null, columns.map(c => /*#__PURE__*/React.createElement("td", {
|
|
167
|
+
key: c.key,
|
|
168
|
+
className: c.numeric ? 'ef-num' : '',
|
|
169
|
+
style: {
|
|
170
|
+
textAlign: c.align
|
|
171
|
+
}
|
|
172
|
+
}, c.render ? c.render(row[c.key], row, i) : row[c.key])));
|
|
173
|
+
}))));
|
|
174
|
+
});
|
|
@@ -0,0 +1,143 @@
|
|
|
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 { IconButton } from "../forms/IconButton.js";
|
|
4
|
+
import { injectEfCss } from "../forms/Button.js";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.ef-cal{width:252px;user-select:none}
|
|
7
|
+
.ef-cal__head{display:flex;align-items:center;gap:4px;margin-bottom:8px}
|
|
8
|
+
.ef-cal__month{flex:1;text-align:center;font-size:var(--text-sm);font-weight:var(--weight-semibold)}
|
|
9
|
+
.ef-cal__grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px}
|
|
10
|
+
.ef-cal__row{display:contents}
|
|
11
|
+
.ef-cal__dow{text-align:center;font-size:10px;font-weight:var(--weight-semibold);letter-spacing:0.06em;text-transform:uppercase;color:var(--text-muted);padding:4px 0}
|
|
12
|
+
.ef-cal__day{height:32px;border:1px solid transparent;border-radius:var(--radius-sm);background:none;cursor:pointer;font-family:var(--font-sans);font-size:var(--text-sm);color:var(--text-primary);transition:background var(--dur-fast) var(--ease-out)}
|
|
13
|
+
.ef-cal__day:hover{background:var(--sand-100)}
|
|
14
|
+
.ef-cal__day:focus-visible{outline:none;box-shadow:var(--focus-ring)}
|
|
15
|
+
.ef-cal__day--out{color:var(--sand-400)}
|
|
16
|
+
.ef-cal__day--today{border-color:var(--sand-400)}
|
|
17
|
+
.ef-cal__day--sel{background:var(--accent);color:var(--accent-contrast);font-weight:var(--weight-semibold)}
|
|
18
|
+
.ef-cal__day--sel:hover{background:var(--sand-900)}
|
|
19
|
+
`;
|
|
20
|
+
const DOW = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'];
|
|
21
|
+
const DOW_FULL = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
|
22
|
+
const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
|
23
|
+
const iso = d => d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
|
|
24
|
+
export function Calendar({
|
|
25
|
+
value,
|
|
26
|
+
onChange,
|
|
27
|
+
style,
|
|
28
|
+
className,
|
|
29
|
+
...rest
|
|
30
|
+
}) {
|
|
31
|
+
injectEfCss('ef-css-cal', CSS);
|
|
32
|
+
const sel = value ? new Date(value + 'T00:00:00') : null;
|
|
33
|
+
const [view, setView] = React.useState(() => {
|
|
34
|
+
const b = sel || new Date();
|
|
35
|
+
return [b.getFullYear(), b.getMonth()];
|
|
36
|
+
});
|
|
37
|
+
const [y, m] = view;
|
|
38
|
+
const first = new Date(y, m, 1);
|
|
39
|
+
const offset = (first.getDay() + 6) % 7;
|
|
40
|
+
const start = new Date(y, m, 1 - offset);
|
|
41
|
+
const today = iso(new Date());
|
|
42
|
+
const cells = Array.from({
|
|
43
|
+
length: 42
|
|
44
|
+
}, (_, i) => new Date(start.getFullYear(), start.getMonth(), start.getDate() + i));
|
|
45
|
+
const gridRef = React.useRef(null);
|
|
46
|
+
const [focusIso, setFocusIso] = React.useState(() => sel ? iso(sel) : today);
|
|
47
|
+
const focusTarget = cells.some(d => iso(d) === focusIso) ? focusIso : iso(first);
|
|
48
|
+
const move = days => {
|
|
49
|
+
const base = new Date(focusTarget + 'T00:00:00');
|
|
50
|
+
const next = new Date(base.getFullYear(), base.getMonth(), base.getDate() + days);
|
|
51
|
+
setFocusIso(iso(next));
|
|
52
|
+
if (next.getMonth() !== m || next.getFullYear() !== y) setView([next.getFullYear(), next.getMonth()]);
|
|
53
|
+
};
|
|
54
|
+
React.useEffect(() => {
|
|
55
|
+
const g = gridRef.current;
|
|
56
|
+
if (!g || !g.contains(document.activeElement)) return;
|
|
57
|
+
const btn = g.querySelector(`[data-iso="${focusTarget}"]`);
|
|
58
|
+
if (btn) btn.focus();
|
|
59
|
+
}, [focusTarget, y, m]);
|
|
60
|
+
const onGridKey = e => {
|
|
61
|
+
const map = {
|
|
62
|
+
ArrowLeft: -1,
|
|
63
|
+
ArrowRight: 1,
|
|
64
|
+
ArrowUp: -7,
|
|
65
|
+
ArrowDown: 7
|
|
66
|
+
};
|
|
67
|
+
if (e.key in map) {
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
move(map[e.key]);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const dow = () => (new Date(focusTarget + 'T00:00:00').getDay() + 6) % 7;
|
|
73
|
+
if (e.key === 'Home') {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
move(-dow());
|
|
76
|
+
} else if (e.key === 'End') {
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
move(6 - dow());
|
|
79
|
+
} else if (e.key === 'PageUp') {
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
setView(m === 0 ? [y - 1, 11] : [y, m - 1]);
|
|
82
|
+
} else if (e.key === 'PageDown') {
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
setView(m === 11 ? [y + 1, 0] : [y, m + 1]);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const rows = Array.from({
|
|
88
|
+
length: 6
|
|
89
|
+
}, (_, r) => cells.slice(r * 7, r * 7 + 7));
|
|
90
|
+
return /*#__PURE__*/React.createElement("div", _extends({}, rest, {
|
|
91
|
+
className: `ef-cal${className ? ' ' + className : ''}`,
|
|
92
|
+
style: style
|
|
93
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
94
|
+
className: "ef-cal__head"
|
|
95
|
+
}, /*#__PURE__*/React.createElement(IconButton, {
|
|
96
|
+
icon: "chevron-left",
|
|
97
|
+
label: "Previous month",
|
|
98
|
+
size: "sm",
|
|
99
|
+
onClick: () => setView(m === 0 ? [y - 1, 11] : [y, m - 1])
|
|
100
|
+
}), /*#__PURE__*/React.createElement("span", {
|
|
101
|
+
className: "ef-cal__month",
|
|
102
|
+
"aria-live": "polite"
|
|
103
|
+
}, MONTHS[m], " ", y), /*#__PURE__*/React.createElement(IconButton, {
|
|
104
|
+
icon: "chevron-right",
|
|
105
|
+
label: "Next month",
|
|
106
|
+
size: "sm",
|
|
107
|
+
onClick: () => setView(m === 11 ? [y + 1, 0] : [y, m + 1])
|
|
108
|
+
})), /*#__PURE__*/React.createElement("div", {
|
|
109
|
+
role: "grid",
|
|
110
|
+
ref: gridRef,
|
|
111
|
+
onKeyDown: onGridKey,
|
|
112
|
+
"aria-label": `${MONTHS[m]} ${y}`,
|
|
113
|
+
className: "ef-cal__grid"
|
|
114
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
115
|
+
role: "row",
|
|
116
|
+
className: "ef-cal__row"
|
|
117
|
+
}, DOW.map((d, i) => /*#__PURE__*/React.createElement("span", {
|
|
118
|
+
key: d,
|
|
119
|
+
role: "columnheader",
|
|
120
|
+
"aria-label": DOW_FULL[i],
|
|
121
|
+
className: "ef-cal__dow"
|
|
122
|
+
}, d))), rows.map((row, ri) => /*#__PURE__*/React.createElement("div", {
|
|
123
|
+
role: "row",
|
|
124
|
+
className: "ef-cal__row",
|
|
125
|
+
key: ri
|
|
126
|
+
}, row.map(d => {
|
|
127
|
+
const id = iso(d);
|
|
128
|
+
return /*#__PURE__*/React.createElement("button", {
|
|
129
|
+
key: id,
|
|
130
|
+
role: "gridcell",
|
|
131
|
+
"data-iso": id,
|
|
132
|
+
tabIndex: id === focusTarget ? 0 : -1,
|
|
133
|
+
"aria-selected": sel ? id === iso(sel) : undefined,
|
|
134
|
+
"aria-current": id === today ? 'date' : undefined,
|
|
135
|
+
"aria-label": `${d.getDate()} ${MONTHS[d.getMonth()]} ${d.getFullYear()}`,
|
|
136
|
+
className: `ef-cal__day${d.getMonth() !== m ? ' ef-cal__day--out' : ''}${id === today ? ' ef-cal__day--today' : ''}${sel && id === iso(sel) ? ' ef-cal__day--sel' : ''}`,
|
|
137
|
+
onClick: () => {
|
|
138
|
+
setFocusIso(id);
|
|
139
|
+
if (onChange) onChange(id);
|
|
140
|
+
}
|
|
141
|
+
}, d.getDate());
|
|
142
|
+
})))));
|
|
143
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface DatePickerProps {
|
|
2
|
+
label?: string;
|
|
3
|
+
/** Controlled YYYY-MM-DD */
|
|
4
|
+
value?: string;
|
|
5
|
+
defaultValue?: string;
|
|
6
|
+
onChange?: (date: string) => void;
|
|
7
|
+
/** @default 'Pick a date' */
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
style?: React.CSSProperties;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function DatePicker(props: DatePickerProps): React.JSX.Element;
|
|
@@ -0,0 +1,118 @@
|
|
|
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 { Calendar } from "./Calendar.js";
|
|
5
|
+
import { injectEfCss } from "../forms/Button.js";
|
|
6
|
+
import { useFieldProps } from "../forms/FormField.js";
|
|
7
|
+
import { Portal, useAnchoredStyle } from "../overlay/Portal.js";
|
|
8
|
+
const CSS = `
|
|
9
|
+
.ef-datepicker{position:relative;display:inline-flex;flex-direction:column;gap:6px}
|
|
10
|
+
.ef-datepicker__label{font-size:var(--text-sm);font-weight:var(--weight-semibold);color:var(--text-primary)}
|
|
11
|
+
.ef-datepicker__btn{display:inline-flex;align-items:center;gap:8px;height:var(--control-h-md);padding:0 12px;border:1px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--surface-card);font-family:var(--font-sans);font-size:var(--text-md);color:var(--text-primary);cursor:pointer;transition:border-color var(--dur-fast) var(--ease-out)}
|
|
12
|
+
.ef-datepicker__btn:hover{border-color:var(--sand-400)}
|
|
13
|
+
.ef-datepicker__btn:focus-visible{outline:none;border-color:var(--accent);box-shadow:var(--focus-ring)}
|
|
14
|
+
.ef-datepicker__btn--empty{color:var(--text-muted)}
|
|
15
|
+
.ef-datepicker__pop{position:absolute;top:calc(100% + 6px);left:0;background:var(--surface-card);border:1px solid var(--border-strong);border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:12px;z-index:var(--z-dropdown);animation:ef-dp-in var(--dur-fast) var(--ease-out)}
|
|
16
|
+
@keyframes ef-dp-in{from{opacity:0;transform:translateY(-3px)}}
|
|
17
|
+
`;
|
|
18
|
+
const fmt = v => {
|
|
19
|
+
if (!v) return null;
|
|
20
|
+
const d = new Date(v + 'T00:00:00');
|
|
21
|
+
return d.toLocaleDateString('en-US', {
|
|
22
|
+
month: 'short',
|
|
23
|
+
day: 'numeric',
|
|
24
|
+
year: 'numeric'
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
export function DatePicker({
|
|
28
|
+
label,
|
|
29
|
+
value,
|
|
30
|
+
defaultValue,
|
|
31
|
+
onChange,
|
|
32
|
+
placeholder = 'Pick a date',
|
|
33
|
+
style,
|
|
34
|
+
className,
|
|
35
|
+
...rest
|
|
36
|
+
}) {
|
|
37
|
+
injectEfCss('ef-css-datepicker', CSS);
|
|
38
|
+
// Picks up id / aria wiring when nested in a FormField; standalone this is a no-op.
|
|
39
|
+
const field = useFieldProps({
|
|
40
|
+
id: rest.id,
|
|
41
|
+
'aria-describedby': rest['aria-describedby']
|
|
42
|
+
});
|
|
43
|
+
const [inner, setInner] = React.useState(defaultValue || '');
|
|
44
|
+
const v = value != null ? value : inner;
|
|
45
|
+
const [open, setOpen] = React.useState(false);
|
|
46
|
+
const ref = React.useRef(null);
|
|
47
|
+
const btnRef = React.useRef(null);
|
|
48
|
+
const panelRef = React.useRef(null);
|
|
49
|
+
const {
|
|
50
|
+
style: anchored
|
|
51
|
+
} = useAnchoredStyle(ref, panelRef, {
|
|
52
|
+
open,
|
|
53
|
+
placement: 'bottom',
|
|
54
|
+
align: 'start'
|
|
55
|
+
});
|
|
56
|
+
React.useEffect(() => {
|
|
57
|
+
if (!open) return;
|
|
58
|
+
// the calendar is portaled out of `ref`, so look for it in the panel
|
|
59
|
+
const day = panelRef.current && panelRef.current.querySelector('[role="gridcell"][tabindex="0"]');
|
|
60
|
+
if (day) day.focus();
|
|
61
|
+
const away = e => {
|
|
62
|
+
const inTrigger = ref.current && ref.current.contains(e.target);
|
|
63
|
+
const inPanel = panelRef.current && panelRef.current.contains(e.target);
|
|
64
|
+
if (!inTrigger && !inPanel) setOpen(false);
|
|
65
|
+
};
|
|
66
|
+
const key = e => {
|
|
67
|
+
if (e.key === 'Escape') {
|
|
68
|
+
setOpen(false);
|
|
69
|
+
if (btnRef.current) btnRef.current.focus();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
document.addEventListener('mousedown', away);
|
|
73
|
+
document.addEventListener('keydown', key);
|
|
74
|
+
return () => {
|
|
75
|
+
document.removeEventListener('mousedown', away);
|
|
76
|
+
document.removeEventListener('keydown', key);
|
|
77
|
+
};
|
|
78
|
+
}, [open]);
|
|
79
|
+
return /*#__PURE__*/React.createElement("span", _extends({}, rest, {
|
|
80
|
+
ref: ref,
|
|
81
|
+
className: `ef-datepicker${className ? ' ' + className : ''}`,
|
|
82
|
+
style: style
|
|
83
|
+
}), label ? /*#__PURE__*/React.createElement("span", {
|
|
84
|
+
className: "ef-datepicker__label"
|
|
85
|
+
}, label) : null, /*#__PURE__*/React.createElement("button", _extends({}, field.controlProps, {
|
|
86
|
+
type: "button",
|
|
87
|
+
ref: btnRef,
|
|
88
|
+
"aria-haspopup": "dialog",
|
|
89
|
+
"aria-expanded": open,
|
|
90
|
+
className: `ef-datepicker__btn${v ? '' : ' ef-datepicker__btn--empty'}`,
|
|
91
|
+
onClick: () => setOpen(o => !o)
|
|
92
|
+
}), /*#__PURE__*/React.createElement(Icon, {
|
|
93
|
+
name: "calendar",
|
|
94
|
+
size: 15,
|
|
95
|
+
style: {
|
|
96
|
+
color: 'var(--text-muted)'
|
|
97
|
+
}
|
|
98
|
+
}), fmt(v) || placeholder, /*#__PURE__*/React.createElement(Icon, {
|
|
99
|
+
name: "chevron-down",
|
|
100
|
+
size: 14,
|
|
101
|
+
style: {
|
|
102
|
+
color: 'var(--text-muted)',
|
|
103
|
+
marginLeft: 'auto'
|
|
104
|
+
}
|
|
105
|
+
})), open && /*#__PURE__*/React.createElement(Portal, null, /*#__PURE__*/React.createElement("div", {
|
|
106
|
+
ref: panelRef,
|
|
107
|
+
className: "ef-datepicker__pop",
|
|
108
|
+
style: anchored
|
|
109
|
+
}, /*#__PURE__*/React.createElement(Calendar, {
|
|
110
|
+
value: v || undefined,
|
|
111
|
+
onChange: d => {
|
|
112
|
+
if (value == null) setInner(d);
|
|
113
|
+
if (onChange) onChange(d);
|
|
114
|
+
setOpen(false);
|
|
115
|
+
if (btnRef.current) btnRef.current.focus();
|
|
116
|
+
}
|
|
117
|
+
}))));
|
|
118
|
+
}
|