@parallelworks/ui 0.77.0 → 0.79.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 +42 -6
- package/dist/access/AccessDrawer.d.ts +49 -7
- package/dist/access/AccessDrawer.js +243 -38
- package/dist/access/AccessList.d.ts +19 -1
- package/dist/access/AccessList.js +220 -9
- package/dist/access/GrantView.d.ts +3 -1
- package/dist/access/GrantView.js +17 -4
- package/dist/access/PrincipalPicker.js +5 -5
- package/dist/access/focus.js +1 -1
- package/dist/access/index.d.ts +2 -2
- package/dist/access/index.js +2 -2
- package/dist/access/strings.d.ts +25 -1
- package/dist/access/strings.js +27 -3
- package/dist/access/types.d.ts +26 -0
- package/dist/ai/components/AllocationSelector.js +1 -1
- package/dist/ai/components/AttachmentManager.js +5 -5
- package/dist/ai/components/AttachmentUpload.js +2 -2
- package/dist/ai/components/BranchNavigator.js +1 -1
- package/dist/ai/components/ChatActivity.js +1 -1
- package/dist/ai/components/ChatEmptyState.js +3 -3
- package/dist/ai/components/ChatInput.js +3 -5
- package/dist/ai/components/ChatLayout.js +1 -1
- package/dist/ai/components/ChatMessage.js +1 -1
- package/dist/ai/components/ChatMessageList.js +23 -8
- package/dist/ai/components/ChatSidebar.js +3 -3
- package/dist/ai/components/ChatThread.js +4 -4
- package/dist/ai/components/ComposerAddMenu.js +9 -20
- package/dist/ai/components/ModelSelector.js +3 -3
- package/dist/ai/components/PastedText.js +5 -5
- package/dist/ai/components/SessionView.js +1 -1
- package/dist/ai/components/ShareDialog.js +3 -3
- package/dist/ai/components/SlashMenu.d.ts +31 -4
- package/dist/ai/components/SlashMenu.js +36 -63
- package/dist/ai/components/agent/ActivityLine.js +1 -1
- package/dist/ai/components/agent/AgentMessageParts.js +13 -13
- package/dist/ai/components/conversationActions.js +3 -3
- package/dist/ai/core/ChatProvider.js +1 -1
- package/dist/ai/core/config.js +1 -1
- package/dist/ai/ui/ConversationSidebar.js +3 -3
- package/dist/ai/ui/sidebar.js +3 -3
- package/dist/assets/yaml.worker-DOBV2lND.js +884 -0
- package/dist/components/ActionMenu.d.ts +38 -9
- package/dist/components/ActionMenu.js +48 -22
- package/dist/components/Avatar.d.ts +66 -6
- package/dist/components/Avatar.js +130 -18
- package/dist/components/Breadcrumbs.d.ts +4 -0
- package/dist/components/Breadcrumbs.js +26 -10
- package/dist/components/Button.d.ts +3 -2
- package/dist/components/Button.js +11 -1
- package/dist/components/Callout.d.ts +3 -2
- package/dist/components/Callout.js +3 -2
- package/dist/components/Card.d.ts +33 -9
- package/dist/components/Card.js +24 -8
- package/dist/components/Checkbox.d.ts +3 -1
- package/dist/components/Checkbox.js +4 -2
- package/dist/components/ColorInput.js +1 -1
- package/dist/components/Combobox.d.ts +23 -0
- package/dist/components/Combobox.js +252 -86
- package/dist/components/ConfirmModal.d.ts +28 -2
- package/dist/components/ConfirmModal.js +100 -15
- package/dist/components/ContextMenu.d.ts +29 -2
- package/dist/components/ContextMenu.js +60 -8
- package/dist/components/CopyButton.d.ts +21 -8
- package/dist/components/CopyButton.js +29 -9
- package/dist/components/CreateModal.d.ts +20 -4
- package/dist/components/CreateModal.js +28 -17
- package/dist/components/DatePicker.d.ts +31 -1
- package/dist/components/DatePicker.js +178 -47
- package/dist/components/DescriptionList.d.ts +13 -3
- package/dist/components/DescriptionList.js +43 -11
- package/dist/components/Disclosure.d.ts +10 -2
- package/dist/components/Disclosure.js +18 -7
- package/dist/components/Drawer.js +5 -5
- package/dist/components/EmptyState.d.ts +12 -4
- package/dist/components/EmptyState.js +18 -10
- package/dist/components/ErrorPage.d.ts +40 -0
- package/dist/components/ErrorPage.js +101 -0
- package/dist/components/ExternalLink.d.ts +4 -1
- package/dist/components/ExternalLink.js +4 -5
- package/dist/components/Field.d.ts +21 -6
- package/dist/components/Field.js +32 -16
- package/dist/components/FileButton.d.ts +19 -0
- package/dist/components/FileButton.js +32 -0
- package/dist/components/HoverCard.d.ts +26 -6
- package/dist/components/HoverCard.js +11 -5
- package/dist/components/IconButton.js +4 -2
- package/dist/components/InfoTip.js +3 -3
- package/dist/components/Input.d.ts +33 -3
- package/dist/components/Input.js +149 -14
- package/dist/components/Kbd.d.ts +40 -0
- package/dist/components/Kbd.js +168 -0
- package/dist/components/Listbox.d.ts +103 -0
- package/dist/components/Listbox.js +190 -0
- package/dist/components/Loader.d.ts +8 -2
- package/dist/components/Loader.js +16 -1
- package/dist/components/Markdown.js +1 -1
- package/dist/components/MarkdownImpl.js +1 -1
- package/dist/components/Menu.d.ts +20 -2
- package/dist/components/Menu.js +48 -19
- package/dist/components/MenuEntries.d.ts +41 -4
- package/dist/components/MenuEntries.js +266 -39
- package/dist/components/Meter.d.ts +50 -11
- package/dist/components/Meter.js +71 -21
- package/dist/components/Modal.d.ts +9 -3
- package/dist/components/Modal.js +68 -29
- package/dist/components/OneTimeCodeInput.js +1 -1
- package/dist/components/PageHeader.js +111 -7
- package/dist/components/PageHeaderAction.d.ts +8 -3
- package/dist/components/PageHeaderAction.js +122 -34
- package/dist/components/Pagination.d.ts +13 -1
- package/dist/components/Pagination.js +90 -19
- package/dist/components/Popover.d.ts +16 -2
- package/dist/components/Popover.js +20 -21
- package/dist/components/Provider.d.ts +172 -5
- package/dist/components/Provider.js +157 -14
- package/dist/components/RefreshButton.d.ts +13 -6
- package/dist/components/RefreshButton.js +17 -9
- package/dist/components/RunStatusIcon.d.ts +16 -7
- package/dist/components/RunStatusIcon.js +21 -8
- package/dist/components/Section.d.ts +11 -4
- package/dist/components/Section.js +33 -22
- package/dist/components/SegmentedControl.d.ts +16 -3
- package/dist/components/SegmentedControl.js +7 -3
- package/dist/components/Select.d.ts +19 -3
- package/dist/components/Select.js +114 -68
- package/dist/components/SelectionCopy.d.ts +10 -0
- package/dist/components/SelectionCopy.js +105 -0
- package/dist/components/SettingModeOption.js +1 -1
- package/dist/components/SettingsCard.js +1 -1
- package/dist/components/Skeleton.js +1 -1
- package/dist/components/StatusBadge.d.ts +55 -10
- package/dist/components/StatusBadge.js +91 -15
- package/dist/components/StickyBar.js +1 -1
- package/dist/components/Table.d.ts +22 -4
- package/dist/components/Table.js +46 -24
- package/dist/components/Tabs.d.ts +9 -4
- package/dist/components/Tabs.js +9 -4
- package/dist/components/Tag.d.ts +49 -0
- package/dist/components/Tag.js +199 -0
- package/dist/components/ThemeEditor.js +1 -1
- package/dist/components/Toast.js +1 -1
- package/dist/components/ToggleGroup.d.ts +4 -1
- package/dist/components/ToggleGroup.js +30 -13
- package/dist/components/Tooltip.d.ts +9 -3
- package/dist/components/Tooltip.js +55 -39
- package/dist/components/buttonClasses.d.ts +5 -2
- package/dist/components/buttonClasses.js +11 -10
- package/dist/components/chart.d.ts +69 -0
- package/dist/components/chart.js +145 -0
- package/dist/components/comboboxItems.d.ts +7 -1
- package/dist/components/comboboxItems.js +6 -2
- package/dist/components/dismiss.d.ts +5 -1
- package/dist/components/dismiss.js +6 -2
- package/dist/components/floating.d.ts +4 -4
- package/dist/components/floating.js +5 -5
- package/dist/components/headerActionsFit.d.ts +37 -0
- package/dist/components/headerActionsFit.js +81 -0
- package/dist/components/pageHeaderStore.d.ts +3 -0
- package/dist/components/pageHeaderStore.js +3 -0
- package/dist/components/pathBreaks.js +2 -2
- package/dist/components/popupContainer.d.ts +17 -0
- package/dist/components/popupContainer.js +69 -0
- package/dist/components/relativeTime.d.ts +21 -10
- package/dist/components/relativeTime.js +50 -22
- package/dist/components/segmented.d.ts +18 -4
- package/dist/components/segmented.js +48 -6
- package/dist/components/typedDate.d.ts +5 -0
- package/dist/components/typedDate.js +50 -0
- package/dist/components/useClosingValue.d.ts +4 -0
- package/dist/components/useClosingValue.js +12 -0
- package/dist/components/useCopy.d.ts +6 -0
- package/dist/components/useCopy.js +3 -3
- package/dist/components/useNarrow.d.ts +2 -0
- package/dist/components/useNarrow.js +16 -0
- package/dist/editor/Editor.js +1 -1
- package/dist/editor/FormCodePanel.js +1 -1
- package/dist/editor/Monaco.js +1 -1
- package/dist/editor/workers.js +1 -1
- package/dist/file-explorer/FileExplorer.js +16 -15
- package/dist/file-explorer/FileExplorerContext.js +2 -2
- package/dist/file-explorer/FilePreview/Viewer.js +3 -3
- package/dist/file-explorer/FilePreview/index.js +2 -2
- package/dist/file-explorer/FileRowsWindow.js +3 -3
- package/dist/file-explorer/FileTable.js +1 -1
- package/dist/file-explorer/LoadMoreRow.js +1 -1
- package/dist/file-explorer/TreeView.js +5 -5
- package/dist/file-explorer/helpers.js +2 -2
- package/dist/file-explorer/toasts.js +2 -2
- package/dist/form/Fields/CheckboxGroupField.js +1 -1
- package/dist/form/Fields/DropdownField.js +1 -1
- package/dist/form/Fields/DurationField.js +2 -2
- package/dist/form/Fields/MultiDropdownField.js +1 -1
- package/dist/form/Fields/NumberField.js +1 -1
- package/dist/form/Fields/ObjectField.js +1 -1
- package/dist/form/Fields/RangeField.js +1 -1
- package/dist/form/Fields/StringField.js +1 -1
- package/dist/form/Form.js +3 -3
- package/dist/form/FormLayout.js +1 -1
- package/dist/form/Wizard/WizardContainer.js +1 -1
- package/dist/form/Wizard/WizardNavigation.js +1 -1
- package/dist/form/Wizard/WizardStepContent.js +1 -1
- package/dist/form/fieldRegistry.js +1 -1
- package/dist/form/formEditing.js +1 -1
- package/dist/format.d.ts +17 -7
- package/dist/format.js +39 -16
- package/dist/graph/AnnotationBanner.js +2 -2
- package/dist/graph/Connectors.js +5 -5
- package/dist/graph/DependencyGraph.js +10 -9
- package/dist/graph/GraphEditor.js +4 -4
- package/dist/graph/GraphEditorDialogs.js +13 -12
- package/dist/graph/GraphNode.js +1 -1
- package/dist/graph/InputDialog.js +5 -5
- package/dist/graph/InputsEditor.js +10 -9
- package/dist/graph/MatrixGroup.js +2 -2
- package/dist/graph/ProblemPageLink.d.ts +9 -0
- package/dist/graph/ProblemPageLink.js +21 -0
- package/dist/graph/ProblemsButton.js +26 -16
- package/dist/graph/Reveal.js +1 -1
- package/dist/graph/RunWarningsList.js +21 -12
- package/dist/graph/ShortcutsButton.js +25 -8
- package/dist/graph/StepSummaries.js +1 -1
- package/dist/graph/TreeView.js +2 -2
- package/dist/graph/WorkflowSettingsDialog.js +3 -3
- package/dist/graph/editorApi.d.ts +10 -0
- package/dist/graph/editorFields.js +3 -3
- package/dist/graph/editorPrimitives.js +1 -1
- package/dist/graph/editorRows.js +8 -6
- package/dist/graph/editorStrings.d.ts +1 -0
- package/dist/graph/editorStrings.js +1 -0
- package/dist/graph/fieldProblems.js +2 -2
- package/dist/graph/inputRefs.js +7 -6
- package/dist/graph/runWarnings.d.ts +12 -1
- package/dist/graph/stepWith.js +2 -2
- package/dist/index.d.ts +30 -21
- package/dist/index.js +20 -12
- package/dist/list/ActionToolbar.d.ts +4 -0
- package/dist/list/ActionToolbar.js +35 -12
- package/dist/list/ListFilterBar.d.ts +42 -0
- package/dist/list/ListFilterBar.js +789 -0
- package/dist/list/ListPage.d.ts +42 -4
- package/dist/list/ListPage.js +56 -22
- package/dist/list/ListTable.d.ts +27 -3
- package/dist/list/ListTable.js +48 -23
- package/dist/list/ListToolbar.d.ts +31 -0
- package/dist/list/ListToolbar.js +43 -0
- package/dist/list/ListViewControls.d.ts +8 -4
- package/dist/list/ListViewControls.js +22 -14
- package/dist/list/UserHoverCard.d.ts +8 -2
- package/dist/list/UserHoverCard.js +26 -13
- package/dist/list/filterModel.d.ts +122 -0
- package/dist/list/filterModel.js +308 -0
- package/dist/list/index.d.ts +6 -2
- package/dist/list/index.js +6 -2
- package/dist/list/listSearch.d.ts +1 -1
- package/dist/list/listSearch.js +8 -4
- package/dist/list/listView.d.ts +24 -2
- package/dist/list/listView.js +47 -45
- package/dist/list/rowMenu.d.ts +10 -5
- package/dist/list/rowMenu.js +17 -7
- package/dist/list/useListFilters.d.ts +68 -0
- package/dist/list/useListFilters.js +252 -0
- package/dist/logviewer/LogViewer.js +4 -4
- package/dist/styles/theme.css +94 -0
- package/dist/styles.css +1 -1
- package/package.json +11 -11
- package/dist/assets/yaml.worker-k9AdOqzf.js +0 -1441
- package/dist/components/fullscreen.d.ts +0 -4
- package/dist/components/fullscreen.js +0 -18
|
@@ -6,11 +6,11 @@ var enterExit = "origin-(--transform-origin) transition-[transform,opacity] dura
|
|
|
6
6
|
var panelClassName = `rounded-lg border border-(--theme-border) bg-(--theme-app-bg) text-sm text-(--theme-app) shadow-(--theme-shadow-lg) outline-none ${enterExit}`;
|
|
7
7
|
/** A row in a panel, inside its `panelListClassName` gutter; its text lines up with a field's. */
|
|
8
8
|
var panelItemClassName = "flex w-full cursor-pointer select-none items-center gap-2 rounded-md px-2 py-1.5 text-left outline-none data-highlighted:bg-(--theme-hover) data-highlighted:text-(--theme-app) data-disabled:cursor-not-allowed data-disabled:opacity-50";
|
|
9
|
-
/** A tooltip's or
|
|
10
|
-
|
|
11
|
-
var
|
|
9
|
+
/** A tooltip's, infotip's or popover tip's surface: a small panel in the page's colors, so a
|
|
10
|
+
* hint reads as part of the page rather than a dark patch over it. */
|
|
11
|
+
var tipClassName = `max-w-64 rounded-lg border border-(--theme-border) bg-(--theme-panel-bg) px-2.5 py-1.5 text-xs text-(--theme-app) shadow-(--theme-shadow-lg) whitespace-pre-line break-words data-instant:transition-none ${enterExit}`;
|
|
12
12
|
/** A hover card's surface; the card sets its own width and padding. */
|
|
13
|
-
var cardClassName = `overflow-hidden rounded-
|
|
13
|
+
var cardClassName = `overflow-hidden rounded-lg border border-(--theme-border) bg-(--theme-panel-bg) text-(--theme-app) shadow-(--theme-shadow-lg) outline-none ${enterExit}`;
|
|
14
14
|
function pointRect(x, y) {
|
|
15
15
|
return {
|
|
16
16
|
x,
|
|
@@ -40,4 +40,4 @@ function toPositionerAnchor(at) {
|
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
42
|
//#endregion
|
|
43
|
-
export { cardClassName, panelClassName, panelItemClassName, positionerClassName,
|
|
43
|
+
export { cardClassName, panelClassName, panelItemClassName, positionerClassName, tipClassName, toPositionerAnchor };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { MenuEntry } from './MenuEntries.js';
|
|
2
|
+
/**
|
|
3
|
+
* Which of the page header's actions have moved into its "…" menu, and what
|
|
4
|
+
* each would be as a row of that menu. The actions render in the page's tree
|
|
5
|
+
* and the bar measures them in its own, so they meet here: each action
|
|
6
|
+
* registers its menu row and hides while it has moved, and the bar decides
|
|
7
|
+
* which move and lists them.
|
|
8
|
+
*/
|
|
9
|
+
export declare class HeaderActionsFit {
|
|
10
|
+
private entries;
|
|
11
|
+
private overflow;
|
|
12
|
+
private version;
|
|
13
|
+
private listeners;
|
|
14
|
+
subscribe: (listener: () => void) => (() => void);
|
|
15
|
+
/** Changes whenever the menu would: what moved, or a moved action's row. */
|
|
16
|
+
getVersion: () => number;
|
|
17
|
+
register(id: string, entry: MenuEntry): void;
|
|
18
|
+
unregister(id: string): void;
|
|
19
|
+
isOverflowed(id: string): boolean;
|
|
20
|
+
/** The moved actions' rows, in the bar's order. */
|
|
21
|
+
menu(): MenuEntry[];
|
|
22
|
+
setOverflow(ids: readonly string[]): void;
|
|
23
|
+
private changed;
|
|
24
|
+
}
|
|
25
|
+
/** Set around the header's `actions`, so the actions there can move into its
|
|
26
|
+
* menu; title actions sit beside the title and never do. */
|
|
27
|
+
export declare const HeaderActionsFitContext: import("react").Context<HeaderActionsFit | null>;
|
|
28
|
+
/**
|
|
29
|
+
* The trailing actions to move into the "…" menu so the rest fit in `room`
|
|
30
|
+
* pixels, last first, or none when everything fits. `shown` is the actions'
|
|
31
|
+
* width as they are now, with `moved` already in the menu, so what they would
|
|
32
|
+
* take with nothing moved is worked out from it and each action's width.
|
|
33
|
+
*/
|
|
34
|
+
export declare function overflowingActions(items: readonly {
|
|
35
|
+
id: string;
|
|
36
|
+
width: number;
|
|
37
|
+
}[], shown: number, moved: readonly string[], room: number): string[];
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
//#region src/components/headerActionsFit.ts
|
|
3
|
+
/**
|
|
4
|
+
* Which of the page header's actions have moved into its "…" menu, and what
|
|
5
|
+
* each would be as a row of that menu. The actions render in the page's tree
|
|
6
|
+
* and the bar measures them in its own, so they meet here: each action
|
|
7
|
+
* registers its menu row and hides while it has moved, and the bar decides
|
|
8
|
+
* which move and lists them.
|
|
9
|
+
*/
|
|
10
|
+
var HeaderActionsFit = class {
|
|
11
|
+
entries = /* @__PURE__ */ new Map();
|
|
12
|
+
overflow = [];
|
|
13
|
+
version = 0;
|
|
14
|
+
listeners = /* @__PURE__ */ new Set();
|
|
15
|
+
subscribe = (listener) => {
|
|
16
|
+
this.listeners.add(listener);
|
|
17
|
+
return () => {
|
|
18
|
+
this.listeners.delete(listener);
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
/** Changes whenever the menu would: what moved, or a moved action's row. */
|
|
22
|
+
getVersion = () => this.version;
|
|
23
|
+
register(id, entry) {
|
|
24
|
+
this.entries.set(id, entry);
|
|
25
|
+
if (this.overflow.includes(id)) this.changed();
|
|
26
|
+
}
|
|
27
|
+
unregister(id) {
|
|
28
|
+
this.entries.delete(id);
|
|
29
|
+
if (this.overflow.includes(id)) {
|
|
30
|
+
this.overflow = this.overflow.filter((other) => other !== id);
|
|
31
|
+
this.changed();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
isOverflowed(id) {
|
|
35
|
+
return this.overflow.includes(id);
|
|
36
|
+
}
|
|
37
|
+
/** The moved actions' rows, in the bar's order. */
|
|
38
|
+
menu() {
|
|
39
|
+
return this.overflow.flatMap((id) => {
|
|
40
|
+
const entry = this.entries.get(id);
|
|
41
|
+
return entry ? [entry] : [];
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
setOverflow(ids) {
|
|
45
|
+
if (ids.length === this.overflow.length && ids.every((id, i) => this.overflow[i] === id)) return;
|
|
46
|
+
this.overflow = ids;
|
|
47
|
+
this.changed();
|
|
48
|
+
}
|
|
49
|
+
changed() {
|
|
50
|
+
this.version++;
|
|
51
|
+
for (const listener of this.listeners) listener();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
/** Set around the header's `actions`, so the actions there can move into its
|
|
55
|
+
* menu; title actions sit beside the title and never do. */
|
|
56
|
+
var HeaderActionsFitContext = createContext(null);
|
|
57
|
+
var ACTION_GAP = 4;
|
|
58
|
+
/**
|
|
59
|
+
* The trailing actions to move into the "…" menu so the rest fit in `room`
|
|
60
|
+
* pixels, last first, or none when everything fits. `shown` is the actions'
|
|
61
|
+
* width as they are now, with `moved` already in the menu, so what they would
|
|
62
|
+
* take with nothing moved is worked out from it and each action's width.
|
|
63
|
+
*/
|
|
64
|
+
function overflowingActions(items, shown, moved, room) {
|
|
65
|
+
const width = new Map(items.map((item) => [item.id, item.width]));
|
|
66
|
+
let total = shown;
|
|
67
|
+
if (moved.length > 0) {
|
|
68
|
+
total += moved.reduce((sum, id) => sum + (width.get(id) ?? 0) + ACTION_GAP, 0);
|
|
69
|
+
total -= 28;
|
|
70
|
+
}
|
|
71
|
+
const next = [];
|
|
72
|
+
for (let i = items.length - 1; i >= 0 && total > room + .5; i--) {
|
|
73
|
+
const item = items[i];
|
|
74
|
+
if (next.length === 0) total += 28;
|
|
75
|
+
total -= item.width + ACTION_GAP;
|
|
76
|
+
next.unshift(item.id);
|
|
77
|
+
}
|
|
78
|
+
return next;
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { HeaderActionsFit, HeaderActionsFitContext, overflowingActions };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Breadcrumb } from './Breadcrumbs.js';
|
|
2
|
+
import { HeaderActionsFit } from './headerActionsFit.js';
|
|
2
3
|
export type PageHeaderRegion = 'titleActions' | 'actions' | 'toolbar';
|
|
3
4
|
/** The nodes a declaration portals its regions into. They are created with the
|
|
4
5
|
* declaration, before any bar exists, so its content renders on the first pass
|
|
@@ -25,6 +26,8 @@ export declare function sameCrumbs(a: readonly Breadcrumb[] | undefined, b: read
|
|
|
25
26
|
* provider.
|
|
26
27
|
*/
|
|
27
28
|
export declare class PageHeaderStore {
|
|
29
|
+
/** Which of the declared actions the bar has moved into its "…" menu. */
|
|
30
|
+
readonly actionsFit: HeaderActionsFit;
|
|
28
31
|
private declarations;
|
|
29
32
|
private listeners;
|
|
30
33
|
private snapshot;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { HeaderActionsFit } from "./headerActionsFit.js";
|
|
1
2
|
//#region src/components/pageHeaderStore.ts
|
|
2
3
|
var NO_NODES = [];
|
|
3
4
|
var NO_CRUMBS = [];
|
|
@@ -26,6 +27,8 @@ function sameNodes(a, b) {
|
|
|
26
27
|
* provider.
|
|
27
28
|
*/
|
|
28
29
|
var PageHeaderStore = class {
|
|
30
|
+
/** Which of the declared actions the bar has moved into its "…" menu. */
|
|
31
|
+
actionsFit = new HeaderActionsFit();
|
|
29
32
|
declarations = /* @__PURE__ */ new Map();
|
|
30
33
|
listeners = /* @__PURE__ */ new Set();
|
|
31
34
|
snapshot = EMPTY;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { Children, Fragment, cloneElement, isValidElement } from "react";
|
|
1
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Children, Fragment as Fragment$1, cloneElement, isValidElement } from "react";
|
|
3
3
|
//#region src/components/pathBreaks.tsx
|
|
4
4
|
var SEPARATOR = /(?<=[/\\])(?![/\\])/;
|
|
5
5
|
/** The pieces of `text` with a break allowed after each, split after each path
|
|
@@ -10,7 +10,7 @@ function pathPieces(text) {
|
|
|
10
10
|
function breakText(text) {
|
|
11
11
|
const pieces = pathPieces(text);
|
|
12
12
|
if (pieces.length < 2) return text;
|
|
13
|
-
return pieces.map((piece, i) => /* @__PURE__ */ jsxs(Fragment
|
|
13
|
+
return pieces.map((piece, i) => /* @__PURE__ */ jsxs(Fragment, { children: [piece, i < pieces.length - 1 && /* @__PURE__ */ jsx("wbr", {})] }, i));
|
|
14
14
|
}
|
|
15
15
|
/** `node` with a place to wrap after each path separator in its text: a string,
|
|
16
16
|
* or an element, such as a link, whose child is one. Deeper content is left as
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
/** Where a popup opening now from `trigger` belongs. A modal dialog sits in the
|
|
3
|
+
* browser's top layer, above any z-index, and leaves the rest of the page inert,
|
|
4
|
+
* so a popup portaled to the body would open under it, out of reach. Focus stays
|
|
5
|
+
* inside the dialog, so it also places a popup opened by code rather than by the
|
|
6
|
+
* reader. A fullscreen element is all the browser shows. Null keeps the default:
|
|
7
|
+
* the popup it opened from, or the body. */
|
|
8
|
+
export declare function popupContainerFor(trigger: Element | null): HTMLElement | null;
|
|
9
|
+
export interface PopupPortalProps {
|
|
10
|
+
/** Read when the portal mounts, which is when its popup opens. */
|
|
11
|
+
container: RefObject<HTMLElement | null>;
|
|
12
|
+
className: string;
|
|
13
|
+
}
|
|
14
|
+
/** Props for a popup's portal. The popup opens inside the innermost open modal
|
|
15
|
+
* `<dialog>` that holds its trigger, or else the fullscreen element, or else
|
|
16
|
+
* the body, so it shows above whatever the trigger sits in. */
|
|
17
|
+
export declare function usePopupContainer(): PopupPortalProps;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
//#region src/components/popupContainer.ts
|
|
3
|
+
var lastTarget = null;
|
|
4
|
+
var tracking = false;
|
|
5
|
+
var remember = (event) => {
|
|
6
|
+
if (event.target instanceof Element) lastTarget = event.target;
|
|
7
|
+
};
|
|
8
|
+
function track() {
|
|
9
|
+
if (tracking) return;
|
|
10
|
+
tracking = true;
|
|
11
|
+
for (const type of [
|
|
12
|
+
"pointerdown",
|
|
13
|
+
"pointerover",
|
|
14
|
+
"focusin",
|
|
15
|
+
"keydown",
|
|
16
|
+
"contextmenu"
|
|
17
|
+
]) document.addEventListener(type, remember, {
|
|
18
|
+
capture: true,
|
|
19
|
+
passive: true
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function subscribe(onChange) {
|
|
23
|
+
track();
|
|
24
|
+
document.addEventListener("fullscreenchange", onChange);
|
|
25
|
+
return () => document.removeEventListener("fullscreenchange", onChange);
|
|
26
|
+
}
|
|
27
|
+
function fullscreenElement() {
|
|
28
|
+
const element = document.fullscreenElement;
|
|
29
|
+
return element instanceof HTMLElement ? element : void 0;
|
|
30
|
+
}
|
|
31
|
+
/** The innermost `<dialog>` shown with `showModal()` that holds the node. */
|
|
32
|
+
function modalDialogAround(node) {
|
|
33
|
+
for (let dialog = node?.closest("dialog"); dialog; dialog = dialog.parentElement?.closest("dialog")) if (dialog.open && dialog.matches(":modal")) return dialog;
|
|
34
|
+
}
|
|
35
|
+
/** Where a popup opening now from `trigger` belongs. A modal dialog sits in the
|
|
36
|
+
* browser's top layer, above any z-index, and leaves the rest of the page inert,
|
|
37
|
+
* so a popup portaled to the body would open under it, out of reach. Focus stays
|
|
38
|
+
* inside the dialog, so it also places a popup opened by code rather than by the
|
|
39
|
+
* reader. A fullscreen element is all the browser shows. Null keeps the default:
|
|
40
|
+
* the popup it opened from, or the body. */
|
|
41
|
+
function popupContainerFor(trigger) {
|
|
42
|
+
return modalDialogAround(trigger?.isConnected ? trigger : null) ?? modalDialogAround(document.activeElement) ?? fullscreenElement() ?? null;
|
|
43
|
+
}
|
|
44
|
+
var portalClassName = "[dialog>&]:fixed [dialog>&]:top-0 [dialog>&]:left-0 [dialog>&]:z-(--layer-popover)";
|
|
45
|
+
function portalProps() {
|
|
46
|
+
return {
|
|
47
|
+
container: { get current() {
|
|
48
|
+
return popupContainerFor(lastTarget);
|
|
49
|
+
} },
|
|
50
|
+
className: portalClassName
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
var onPage = portalProps();
|
|
54
|
+
var inFullscreen = /* @__PURE__ */ new WeakMap();
|
|
55
|
+
/** Props for a popup's portal. The popup opens inside the innermost open modal
|
|
56
|
+
* `<dialog>` that holds its trigger, or else the fullscreen element, or else
|
|
57
|
+
* the body, so it shows above whatever the trigger sits in. */
|
|
58
|
+
function usePopupContainer() {
|
|
59
|
+
const fullscreen = useSyncExternalStore(subscribe, fullscreenElement, () => void 0);
|
|
60
|
+
if (!fullscreen) return onPage;
|
|
61
|
+
let props = inFullscreen.get(fullscreen);
|
|
62
|
+
if (!props) {
|
|
63
|
+
props = portalProps();
|
|
64
|
+
inFullscreen.set(fullscreen, props);
|
|
65
|
+
}
|
|
66
|
+
return props;
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { popupContainerFor, usePopupContainer };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type DateTimeStyle, type TimeValue } from '../format.js';
|
|
2
3
|
export type { TimeValue };
|
|
3
4
|
/** Something worked out from the time, kept current by one clock that ticks
|
|
4
5
|
* each second for every component reading it. A component re-renders only
|
|
@@ -7,20 +8,30 @@ export type { TimeValue };
|
|
|
7
8
|
* `running` is false the component leaves the clock and `select` sees the
|
|
8
9
|
* last tick's time. */
|
|
9
10
|
export declare function useClock<T>(select: (now: number) => T, running?: boolean): T;
|
|
10
|
-
/** `long` reads "5 minutes ago" in the host's locale
|
|
11
|
-
* for a row with
|
|
11
|
+
/** `long` reads "5 minutes ago" or "yesterday" in the host's locale (UIProvider's `locale`);
|
|
12
|
+
* `compact` reads "5m", "in 3h", then a date such as "Sep 4" once a week away, for a row with
|
|
13
|
+
* little room. */
|
|
12
14
|
export type RelativeTimeFormat = 'long' | 'compact';
|
|
13
15
|
/** How long ago, or until, `value` is, kept current as time passes. Empty for
|
|
14
16
|
* a missing or invalid time. */
|
|
15
17
|
export declare function useRelativeTime(value: TimeValue, format?: RelativeTimeFormat): string;
|
|
16
|
-
|
|
17
|
-
* on while it stays on screen, with the exact time on hover. Renders nothing
|
|
18
|
-
* for a missing time. */
|
|
19
|
-
export declare function RelativeTime({ value, format, className, }: {
|
|
18
|
+
export interface RelativeTimeProps {
|
|
20
19
|
value: TimeValue;
|
|
21
|
-
format?: RelativeTimeFormat;
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
format?: RelativeTimeFormat | undefined;
|
|
21
|
+
/** Shown in place of a missing or invalid time, such as "Never" or "—". Default nothing. */
|
|
22
|
+
fallback?: ReactNode;
|
|
23
|
+
/** How the exact time reads: in the tooltip, or in the text with `show="absolute"`. Default
|
|
24
|
+
* `seconds`; `full` adds the time zone. False leaves out the tooltip. */
|
|
25
|
+
exact?: DateTimeStyle | false | undefined;
|
|
26
|
+
/** `absolute` shows the exact time, with the relative one on hover, for a record read for
|
|
27
|
+
* its moment rather than its age. Default `relative`. */
|
|
28
|
+
show?: 'relative' | 'absolute' | undefined;
|
|
29
|
+
className?: string | undefined;
|
|
30
|
+
}
|
|
31
|
+
/** A time shown relative to now ("5 minutes ago", or "5m" compact), counting
|
|
32
|
+
* on while it stays on screen, with the exact time on hover. Renders the
|
|
33
|
+
* `fallback`, or nothing, for a missing time. */
|
|
34
|
+
export declare function RelativeTime({ value, format, fallback, exact, show, className, }: RelativeTimeProps): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null;
|
|
24
35
|
/** Milliseconds from `start` to `end`, or to now while `running`, in whole
|
|
25
36
|
* seconds so it changes at most once a second. Running defaults to having no
|
|
26
37
|
* end; pass false for something that stopped without recording when. Null
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { Tooltip } from "./Tooltip.js";
|
|
2
|
-
import { useStrings } from "./Provider.js";
|
|
2
|
+
import { useLocale, useStrings } from "./Provider.js";
|
|
3
3
|
import { formatDateTime, formatDuration, toDateTime } from "../format.js";
|
|
4
|
-
import { jsx } from "react/jsx-runtime";
|
|
5
4
|
import { useRef, useState, useSyncExternalStore } from "react";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
6
|
import { DateTime, Duration } from "luxon";
|
|
7
7
|
//#region src/components/relativeTime.tsx
|
|
8
8
|
var SECOND = 1e3;
|
|
9
9
|
var MINUTE = 60 * SECOND;
|
|
10
10
|
var HOUR = 60 * MINUTE;
|
|
11
11
|
var DAY = 24 * HOUR;
|
|
12
|
+
var WEEK = 7 * DAY;
|
|
12
13
|
var listeners = /* @__PURE__ */ new Set();
|
|
13
14
|
var timer;
|
|
14
15
|
var now = Date.now();
|
|
@@ -40,14 +41,35 @@ function useClock(select, running = true) {
|
|
|
40
41
|
const snapshot = () => select(now);
|
|
41
42
|
return useSyncExternalStore(running ? subscribe : stopped, snapshot, snapshot);
|
|
42
43
|
}
|
|
43
|
-
function compactLabel(age, t) {
|
|
44
|
-
const
|
|
44
|
+
function compactLabel(at, age, t, locale) {
|
|
45
|
+
const size = Math.abs(age);
|
|
46
|
+
if (size >= WEEK) return t.date(new Date(at), locale);
|
|
47
|
+
const minutes = Math.floor(size / MINUTE);
|
|
45
48
|
if (minutes < 1) return t.now;
|
|
46
|
-
if (minutes < 60) return t.minutes(minutes);
|
|
47
49
|
const hours = Math.floor(minutes / 60);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
const label = minutes < 60 ? t.minutes(minutes) : hours < 24 ? t.hours(hours) : t.days(Math.floor(hours / 24));
|
|
51
|
+
return age < 0 ? t.inFuture(label) : label;
|
|
52
|
+
}
|
|
53
|
+
var LONG_UNITS = [
|
|
54
|
+
["year", 365 * DAY],
|
|
55
|
+
["month", 30 * DAY],
|
|
56
|
+
["week", WEEK],
|
|
57
|
+
["day", DAY],
|
|
58
|
+
["hour", HOUR],
|
|
59
|
+
["minute", MINUTE],
|
|
60
|
+
["second", SECOND]
|
|
61
|
+
];
|
|
62
|
+
var relativeFormats = /* @__PURE__ */ new Map();
|
|
63
|
+
function longLabel(age, locale) {
|
|
64
|
+
const tag = locale ?? DateTime.now().locale ?? "en-US";
|
|
65
|
+
let format = relativeFormats.get(tag);
|
|
66
|
+
if (!format) {
|
|
67
|
+
format = new Intl.RelativeTimeFormat(tag, { numeric: "auto" });
|
|
68
|
+
relativeFormats.set(tag, format);
|
|
69
|
+
}
|
|
70
|
+
const size = Math.abs(age);
|
|
71
|
+
const [unit, length] = LONG_UNITS.find(([, length]) => size >= length) ?? ["second", SECOND];
|
|
72
|
+
return format.format(-Math.trunc(age / length), unit);
|
|
51
73
|
}
|
|
52
74
|
function unitOf(age) {
|
|
53
75
|
const size = Math.abs(age);
|
|
@@ -57,15 +79,16 @@ function unitOf(age) {
|
|
|
57
79
|
* a missing or invalid time. */
|
|
58
80
|
function useRelativeTime(value, format = "long") {
|
|
59
81
|
const t = useStrings().time;
|
|
82
|
+
const locale = useLocale();
|
|
60
83
|
const at = toDateTime(value)?.toMillis() ?? null;
|
|
61
84
|
const last = useRef(null);
|
|
62
85
|
return useClock((current) => {
|
|
63
86
|
if (at === null) return "";
|
|
64
87
|
const age = current - at;
|
|
65
88
|
const unit = unitOf(age);
|
|
66
|
-
const key = `${format}:${at}:${unit}:${Math.trunc(age / unit)}`;
|
|
89
|
+
const key = `${format}:${locale}:${at}:${unit}:${Math.trunc(age / unit)}`;
|
|
67
90
|
if (last.current?.key !== key) {
|
|
68
|
-
const label = format === "compact" ? compactLabel(age, t) : Math.abs(age) < SECOND ? t.now :
|
|
91
|
+
const label = format === "compact" ? compactLabel(at, age, t, locale) : Math.abs(age) < SECOND ? t.now : longLabel(age, locale);
|
|
69
92
|
last.current = {
|
|
70
93
|
key,
|
|
71
94
|
label
|
|
@@ -75,20 +98,24 @@ function useRelativeTime(value, format = "long") {
|
|
|
75
98
|
}, at !== null);
|
|
76
99
|
}
|
|
77
100
|
/** A time shown relative to now ("5 minutes ago", or "5m" compact), counting
|
|
78
|
-
* on while it stays on screen, with the exact time on hover. Renders
|
|
79
|
-
* for a missing time. */
|
|
80
|
-
function RelativeTime({ value, format = "long", className }) {
|
|
101
|
+
* on while it stays on screen, with the exact time on hover. Renders the
|
|
102
|
+
* `fallback`, or nothing, for a missing time. */
|
|
103
|
+
function RelativeTime({ value, format = "long", fallback = null, exact = "seconds", show = "relative", className }) {
|
|
81
104
|
const label = useRelativeTime(value, format);
|
|
105
|
+
const locale = useLocale();
|
|
82
106
|
const time = toDateTime(value);
|
|
83
|
-
if (!label || !time) return
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
})
|
|
107
|
+
if (!label || !time) return fallback;
|
|
108
|
+
const absolute = formatDateTime(time, exact || "seconds", locale);
|
|
109
|
+
const [text, tip] = show === "absolute" ? [absolute, label] : [label, exact ? absolute : null];
|
|
110
|
+
const element = /* @__PURE__ */ jsx("time", {
|
|
111
|
+
dateTime: time.toISO() ?? void 0,
|
|
112
|
+
className,
|
|
113
|
+
children: text
|
|
91
114
|
});
|
|
115
|
+
return tip ? /* @__PURE__ */ jsx(Tooltip, {
|
|
116
|
+
content: tip,
|
|
117
|
+
children: element
|
|
118
|
+
}) : element;
|
|
92
119
|
}
|
|
93
120
|
/** Milliseconds from `start` to `end`, or to now while `running`, in whole
|
|
94
121
|
* seconds so it changes at most once a second. Running defaults to having no
|
|
@@ -104,11 +131,12 @@ function useElapsed(start, end, running = toDateTime(end) === null) {
|
|
|
104
131
|
* Renders nothing without a start. */
|
|
105
132
|
function Elapsed({ start, end, running, className }) {
|
|
106
133
|
const elapsed = useElapsed(start, end, running);
|
|
134
|
+
const locale = useLocale();
|
|
107
135
|
if (elapsed === null) return null;
|
|
108
136
|
return /* @__PURE__ */ jsx("time", {
|
|
109
137
|
dateTime: Duration.fromMillis(elapsed).toISO() ?? void 0,
|
|
110
138
|
className,
|
|
111
|
-
children: formatDuration(elapsed)
|
|
139
|
+
children: formatDuration(elapsed, locale)
|
|
112
140
|
});
|
|
113
141
|
}
|
|
114
142
|
//#endregion
|
|
@@ -2,16 +2,30 @@ import type { ReactNode } from 'react';
|
|
|
2
2
|
/** The parts SegmentedControl and segmented Tabs share, so a picker and a set
|
|
3
3
|
* of views beside it look the same. */
|
|
4
4
|
export interface SegmentLayout {
|
|
5
|
-
/** Segments share the container's width equally.
|
|
6
|
-
|
|
5
|
+
/** Segments share the container's width equally. `narrow` does so only on a
|
|
6
|
+
* phone-width screen and sits at its own width from the `sm` breakpoint,
|
|
7
|
+
* such as a choice at the head of a form. */
|
|
8
|
+
fill?: boolean | 'narrow' | undefined;
|
|
7
9
|
/** Segments stack as icons with their labels in tooltips, for a collapsed rail. */
|
|
8
10
|
iconOnly?: boolean | undefined;
|
|
11
|
+
/** `sm` fits a page header, a toolbar or a pane's head beside other small
|
|
12
|
+
* controls; `md` stands on its own. */
|
|
13
|
+
size?: 'sm' | 'md' | undefined;
|
|
9
14
|
}
|
|
15
|
+
/** A segment's or tab's tone, for a view that needs attention, such as what failed. */
|
|
16
|
+
export type SegmentTone = 'warning' | 'danger';
|
|
10
17
|
export declare function segmentGroupClassName({ fill, iconOnly }: SegmentLayout): string;
|
|
11
|
-
export declare function segmentClassName(current: boolean, { fill, iconOnly }: SegmentLayout): string;
|
|
12
|
-
|
|
18
|
+
export declare function segmentClassName(current: boolean, { fill, iconOnly, size }: SegmentLayout, tone?: SegmentTone | undefined): string;
|
|
19
|
+
/** How many things a tab or segment holds, as a pill after its label. */
|
|
20
|
+
export declare function CountPill({ count, tone }: {
|
|
21
|
+
count: number;
|
|
22
|
+
tone?: SegmentTone | undefined;
|
|
23
|
+
}): import("react").JSX.Element;
|
|
24
|
+
export declare function SegmentContent({ label, icon, badge, count, tone, iconOnly, }: {
|
|
13
25
|
label: string;
|
|
14
26
|
icon?: ReactNode;
|
|
15
27
|
badge?: ReactNode;
|
|
28
|
+
count?: number | undefined;
|
|
29
|
+
tone?: SegmentTone | undefined;
|
|
16
30
|
iconOnly?: boolean | undefined;
|
|
17
31
|
}): import("react").JSX.Element;
|
|
@@ -1,22 +1,64 @@
|
|
|
1
1
|
import { cx } from "../cx.js";
|
|
2
|
+
import { useLocale } from "./Provider.js";
|
|
3
|
+
import { formatCount } from "../format.js";
|
|
2
4
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
5
|
//#region src/components/segmented.tsx
|
|
6
|
+
var fillKey = (fill) => fill === "narrow" ? "narrow" : fill ? "fill" : "none";
|
|
7
|
+
var fillGroup = {
|
|
8
|
+
fill: "grid auto-cols-fr grid-flow-col",
|
|
9
|
+
narrow: "grid auto-cols-fr grid-flow-col sm:inline-flex",
|
|
10
|
+
none: "inline-flex"
|
|
11
|
+
};
|
|
12
|
+
var segmentSizes = {
|
|
13
|
+
md: {
|
|
14
|
+
fill: "h-7 gap-1.5 px-1.5 text-[13px]",
|
|
15
|
+
narrow: "h-7 gap-1.5 px-1.5 text-[13px] sm:px-2.5",
|
|
16
|
+
none: "h-7 gap-1.5 px-2.5 text-[13px]"
|
|
17
|
+
},
|
|
18
|
+
sm: {
|
|
19
|
+
fill: "h-6 gap-1 px-1.5 text-xs",
|
|
20
|
+
narrow: "h-6 gap-1 px-1.5 text-xs sm:px-2",
|
|
21
|
+
none: "h-6 gap-1 px-2 text-xs"
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var toneText = {
|
|
25
|
+
warning: "text-(--theme-warning)",
|
|
26
|
+
danger: "text-(--theme-error-text)"
|
|
27
|
+
};
|
|
28
|
+
var tonePill = {
|
|
29
|
+
warning: "bg-(--theme-warning-muted) text-(--theme-warning)",
|
|
30
|
+
danger: "bg-(--theme-error-muted) text-(--theme-error-text)",
|
|
31
|
+
none: "bg-(--theme-muted-text-color)/12 text-(--theme-muted-text-color)"
|
|
32
|
+
};
|
|
4
33
|
function segmentGroupClassName({ fill, iconOnly }) {
|
|
5
|
-
return cx("gap-0.5", iconOnly ? "flex flex-col items-center" : cx("rounded-lg theme-muted-panel p-0.5", fill
|
|
34
|
+
return cx("gap-0.5", iconOnly ? "flex flex-col items-center" : cx("rounded-lg theme-muted-panel p-0.5", fillGroup[fillKey(fill)]));
|
|
6
35
|
}
|
|
7
|
-
function segmentClassName(current, { fill, iconOnly }) {
|
|
8
|
-
return cx("relative flex min-w-0 cursor-pointer items-center justify-center rounded-md font-medium transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-(--theme-focus) data-disabled:cursor-not-allowed data-disabled:opacity-50", current ? "bg-(--theme-panel-bg)
|
|
36
|
+
function segmentClassName(current, { fill, iconOnly, size = "md" }, tone) {
|
|
37
|
+
return cx("relative flex min-w-0 cursor-pointer items-center justify-center rounded-md font-medium transition-colors duration-150 focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-(--theme-focus) data-disabled:cursor-not-allowed data-disabled:opacity-50", current ? "bg-(--theme-panel-bg) shadow-sm ring-1 ring-(--theme-border)" : "hover:not-data-disabled:bg-(--theme-hover)", tone ? toneText[tone] : current ? "theme-text" : "theme-muted-text hover:not-data-disabled:theme-text", iconOnly ? "h-8 w-8" : segmentSizes[size][fillKey(fill)]);
|
|
9
38
|
}
|
|
10
|
-
|
|
39
|
+
/** How many things a tab or segment holds, as a pill after its label. */
|
|
40
|
+
function CountPill({ count, tone }) {
|
|
41
|
+
const locale = useLocale();
|
|
42
|
+
return /* @__PURE__ */ jsx("span", {
|
|
43
|
+
className: cx("inline-flex h-4 min-w-4 shrink-0 items-center justify-center rounded-full px-1.5 text-[11px] font-medium tabular-nums", tonePill[tone ?? "none"]),
|
|
44
|
+
children: formatCount(count, locale)
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function SegmentContent({ label, icon, badge, count, tone, iconOnly }) {
|
|
48
|
+
const locale = useLocale();
|
|
11
49
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
12
50
|
icon,
|
|
13
51
|
iconOnly ? /* @__PURE__ */ jsx("span", {
|
|
14
52
|
className: "sr-only",
|
|
15
|
-
children: label
|
|
53
|
+
children: count === void 0 ? label : `${label} ${formatCount(count, locale)}`
|
|
16
54
|
}) : /* @__PURE__ */ jsx("span", {
|
|
17
55
|
className: "truncate",
|
|
18
56
|
children: label
|
|
19
57
|
}),
|
|
58
|
+
!iconOnly && count !== void 0 && /* @__PURE__ */ jsx(CountPill, {
|
|
59
|
+
count,
|
|
60
|
+
tone
|
|
61
|
+
}),
|
|
20
62
|
badge && (iconOnly ? /* @__PURE__ */ jsx("span", {
|
|
21
63
|
className: "absolute top-1 right-1 inline-flex",
|
|
22
64
|
children: badge
|
|
@@ -24,4 +66,4 @@ function SegmentContent({ label, icon, badge, iconOnly }) {
|
|
|
24
66
|
] });
|
|
25
67
|
}
|
|
26
68
|
//#endregion
|
|
27
|
-
export { SegmentContent, segmentClassName, segmentGroupClassName };
|
|
69
|
+
export { CountPill, SegmentContent, segmentClassName, segmentGroupClassName };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** The local day typed text names: `yyyy-mm-dd`, or the locale's numeric date in its own
|
|
2
|
+
* order, such as `10/30/2026` or `30.10.26`; a day and month alone fall in this year. */
|
|
3
|
+
export declare function parseTypedDate(text: string, locale: string | undefined, today?: Date): Date | undefined;
|
|
4
|
+
/** A day as the locale writes it in numbers, the form `parseTypedDate` reads back. */
|
|
5
|
+
export declare function formatNumericDate(date: Date, locale: string | undefined): string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//#region src/components/typedDate.ts
|
|
2
|
+
var orders = /* @__PURE__ */ new Map();
|
|
3
|
+
function partOrder(locale) {
|
|
4
|
+
let order = orders.get(locale);
|
|
5
|
+
if (!order) {
|
|
6
|
+
order = new Intl.DateTimeFormat(locale, {
|
|
7
|
+
year: "numeric",
|
|
8
|
+
month: "numeric",
|
|
9
|
+
day: "numeric"
|
|
10
|
+
}).formatToParts(new Date(2001, 10, 22)).map((part) => part.type).filter((type) => type === "year" || type === "month" || type === "day");
|
|
11
|
+
orders.set(locale, order);
|
|
12
|
+
}
|
|
13
|
+
return order;
|
|
14
|
+
}
|
|
15
|
+
function calendarDay(year, month, day) {
|
|
16
|
+
const date = new Date(year, month - 1, day);
|
|
17
|
+
return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day ? date : void 0;
|
|
18
|
+
}
|
|
19
|
+
/** The local day typed text names: `yyyy-mm-dd`, or the locale's numeric date in its own
|
|
20
|
+
* order, such as `10/30/2026` or `30.10.26`; a day and month alone fall in this year. */
|
|
21
|
+
function parseTypedDate(text, locale, today = /* @__PURE__ */ new Date()) {
|
|
22
|
+
const trimmed = text.trim();
|
|
23
|
+
const iso = trimmed.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
|
|
24
|
+
if (iso) return calendarDay(Number(iso[1]), Number(iso[2]), Number(iso[3]));
|
|
25
|
+
const numbers = trimmed.split(/\D+/).filter(Boolean).map(Number);
|
|
26
|
+
if (numbers.length < 2 || numbers.length > 3) return;
|
|
27
|
+
const order = partOrder(locale);
|
|
28
|
+
const parts = numbers.length === 3 ? order : order.filter((part) => part !== "year");
|
|
29
|
+
const value = {
|
|
30
|
+
year: today.getFullYear(),
|
|
31
|
+
month: 0,
|
|
32
|
+
day: 0
|
|
33
|
+
};
|
|
34
|
+
parts.forEach((part, index) => {
|
|
35
|
+
value[part] = numbers[index] ?? 0;
|
|
36
|
+
});
|
|
37
|
+
if (numbers.length === 3 && value.year < 100) value.year += 2e3;
|
|
38
|
+
return calendarDay(value.year, value.month, value.day);
|
|
39
|
+
}
|
|
40
|
+
/** A day as the locale writes it in numbers, the form `parseTypedDate` reads back. */
|
|
41
|
+
function formatNumericDate(date, locale) {
|
|
42
|
+
return new Intl.DateTimeFormat(locale, {
|
|
43
|
+
year: "numeric",
|
|
44
|
+
month: "2-digit",
|
|
45
|
+
day: "2-digit",
|
|
46
|
+
numberingSystem: "latn"
|
|
47
|
+
}).format(date);
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { formatNumericDate, parseTypedDate };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** The last value that wasn't null or undefined, so a modal or drawer keeps showing what it was
|
|
2
|
+
* about while it animates closed, after the host has already let go of it:
|
|
3
|
+
* `<Drawer open={item !== null}>{useClosingValue(item)?.name}</Drawer>`. */
|
|
4
|
+
export declare function useClosingValue<T>(value: T | null | undefined): T | undefined;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
//#region src/components/useClosingValue.ts
|
|
3
|
+
/** The last value that wasn't null or undefined, so a modal or drawer keeps showing what it was
|
|
4
|
+
* about while it animates closed, after the host has already let go of it:
|
|
5
|
+
* `<Drawer open={item !== null}>{useClosingValue(item)?.name}</Drawer>`. */
|
|
6
|
+
function useClosingValue(value) {
|
|
7
|
+
const [last, setLast] = useState(value ?? void 0);
|
|
8
|
+
if (value !== null && value !== void 0 && !Object.is(value, last)) setLast(value);
|
|
9
|
+
return value ?? last;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { useClosingValue };
|