@entifix/react-controls 0.19.0 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/i18n/catalog/en.d.ts.map +1 -1
- package/dist/i18n/catalog/en.js +55 -9
- package/dist/i18n/catalog/en.js.map +1 -1
- package/dist/i18n/catalog/es.d.ts +64 -5
- package/dist/i18n/catalog/es.d.ts.map +1 -1
- package/dist/i18n/catalog/es.js +61 -9
- package/dist/i18n/catalog/es.js.map +1 -1
- package/dist/i18n/catalog/index.d.ts +102 -10
- package/dist/i18n/catalog/index.d.ts.map +1 -1
- package/dist/i18n/entity-labels.d.ts +14 -0
- package/dist/i18n/entity-labels.d.ts.map +1 -1
- package/dist/i18n/entity-labels.js +15 -0
- package/dist/i18n/entity-labels.js.map +1 -1
- package/dist/i18n/i18n-context.js +2 -2
- package/dist/i18n/i18n-context.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/primitives.d.ts +0 -1
- package/dist/primitives.d.ts.map +1 -1
- package/dist/primitives.js +0 -1
- package/dist/primitives.js.map +1 -1
- package/dist/theme/index.d.ts +1 -0
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +1 -0
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/theme-context.d.ts +15 -3
- package/dist/theme/theme-context.d.ts.map +1 -1
- package/dist/theme/theme-context.js +49 -23
- package/dist/theme/theme-context.js.map +1 -1
- package/dist/theme/theme-cookie.d.ts +9 -0
- package/dist/theme/theme-cookie.d.ts.map +1 -0
- package/dist/theme/theme-cookie.js +8 -0
- package/dist/theme/theme-cookie.js.map +1 -0
- package/dist/theme/theme-script.d.ts +8 -6
- package/dist/theme/theme-script.d.ts.map +1 -1
- package/dist/theme/theme-script.js +21 -12
- package/dist/theme/theme-script.js.map +1 -1
- package/dist/ui/actions/action-surfaces.d.ts +1 -1
- package/dist/ui/actions/action-surfaces.js +1 -1
- package/dist/ui/actions/action-surfaces.js.map +1 -1
- package/dist/ui/actions/error-actions.d.ts +32 -0
- package/dist/ui/actions/error-actions.d.ts.map +1 -0
- package/dist/ui/actions/error-actions.js +40 -0
- package/dist/ui/actions/error-actions.js.map +1 -0
- package/dist/ui/actions/index.d.ts +3 -0
- package/dist/ui/actions/index.d.ts.map +1 -1
- package/dist/ui/actions/index.js +3 -0
- package/dist/ui/actions/index.js.map +1 -1
- package/dist/ui/actions/shortcut.d.ts +13 -0
- package/dist/ui/actions/shortcut.d.ts.map +1 -0
- package/dist/ui/actions/shortcut.js +18 -0
- package/dist/ui/actions/shortcut.js.map +1 -0
- package/dist/ui/actions/use-verb-invoker.d.ts +55 -0
- package/dist/ui/actions/use-verb-invoker.d.ts.map +1 -0
- package/dist/ui/actions/use-verb-invoker.js +98 -0
- package/dist/ui/actions/use-verb-invoker.js.map +1 -0
- package/dist/ui/atoms/cell-value/cell-value.d.ts.map +1 -1
- package/dist/ui/atoms/cell-value/cell-value.js +28 -14
- package/dist/ui/atoms/cell-value/cell-value.js.map +1 -1
- package/dist/ui/atoms/field-control/field-control.d.ts +1 -0
- package/dist/ui/atoms/field-control/field-control.d.ts.map +1 -1
- package/dist/ui/atoms/field-control/field-control.js +2 -0
- package/dist/ui/atoms/field-control/field-control.js.map +1 -1
- package/dist/ui/atoms/number-input/quantity-input.d.ts +8 -1
- package/dist/ui/atoms/number-input/quantity-input.d.ts.map +1 -1
- package/dist/ui/atoms/number-input/quantity-input.js +2 -1
- package/dist/ui/atoms/number-input/quantity-input.js.map +1 -1
- package/dist/ui/atoms/table/table.d.ts +12 -3
- package/dist/ui/atoms/table/table.d.ts.map +1 -1
- package/dist/ui/atoms/table/table.js +59 -10
- package/dist/ui/atoms/table/table.js.map +1 -1
- package/dist/ui/atoms/text/text.d.ts +2 -2
- package/dist/ui/atoms/text/text.d.ts.map +1 -1
- package/dist/ui/atoms/text/text.js.map +1 -1
- package/dist/ui/molecules/action-bar/action-bar.d.ts +76 -0
- package/dist/ui/molecules/action-bar/action-bar.d.ts.map +1 -0
- package/dist/ui/molecules/action-bar/action-bar.js +218 -0
- package/dist/ui/molecules/action-bar/action-bar.js.map +1 -0
- package/dist/ui/molecules/action-bar/index.d.ts +2 -0
- package/dist/ui/molecules/action-bar/index.d.ts.map +1 -0
- package/dist/ui/molecules/action-bar/index.js +3 -0
- package/dist/ui/molecules/action-bar/index.js.map +1 -0
- package/dist/ui/molecules/bulk-action-bar/bulk-action-bar.d.ts.map +1 -1
- package/dist/ui/molecules/bulk-action-bar/bulk-action-bar.js +8 -10
- package/dist/ui/molecules/bulk-action-bar/bulk-action-bar.js.map +1 -1
- package/dist/ui/molecules/bulk-result/bulk-result.d.ts +3 -3
- package/dist/ui/molecules/bulk-result/bulk-result.js +4 -4
- package/dist/ui/molecules/bulk-result/bulk-result.js.map +1 -1
- package/dist/ui/molecules/confirm-dialog/confirm-dialog.d.ts +24 -9
- package/dist/ui/molecules/confirm-dialog/confirm-dialog.d.ts.map +1 -1
- package/dist/ui/molecules/confirm-dialog/confirm-dialog.js +36 -11
- package/dist/ui/molecules/confirm-dialog/confirm-dialog.js.map +1 -1
- package/dist/ui/molecules/entity-record-card/entity-record-card.d.ts +33 -20
- package/dist/ui/molecules/entity-record-card/entity-record-card.d.ts.map +1 -1
- package/dist/ui/molecules/entity-record-card/entity-record-card.js +53 -20
- package/dist/ui/molecules/entity-record-card/entity-record-card.js.map +1 -1
- package/dist/ui/molecules/filter-builder/filter-builder.d.ts +8 -2
- package/dist/ui/molecules/filter-builder/filter-builder.d.ts.map +1 -1
- package/dist/ui/molecules/filter-builder/filter-builder.js +14 -5
- package/dist/ui/molecules/filter-builder/filter-builder.js.map +1 -1
- package/dist/ui/molecules/filter-chips/filter-chips.d.ts +16 -0
- package/dist/ui/molecules/filter-chips/filter-chips.d.ts.map +1 -0
- package/dist/ui/molecules/filter-chips/filter-chips.js +73 -0
- package/dist/ui/molecules/filter-chips/filter-chips.js.map +1 -0
- package/dist/ui/molecules/filter-chips/index.d.ts +2 -0
- package/dist/ui/molecules/filter-chips/index.d.ts.map +1 -0
- package/dist/ui/molecules/filter-chips/index.js +3 -0
- package/dist/ui/molecules/filter-chips/index.js.map +1 -0
- package/dist/ui/molecules/menu/menu.d.ts +11 -1
- package/dist/ui/molecules/menu/menu.d.ts.map +1 -1
- package/dist/ui/molecules/menu/menu.js +62 -2
- package/dist/ui/molecules/menu/menu.js.map +1 -1
- package/dist/ui/molecules/pagination/pagination.d.ts +5 -0
- package/dist/ui/molecules/pagination/pagination.d.ts.map +1 -1
- package/dist/ui/molecules/pagination/pagination.js +35 -18
- package/dist/ui/molecules/pagination/pagination.js.map +1 -1
- package/dist/ui/molecules/region-state/index.d.ts +2 -0
- package/dist/ui/molecules/region-state/index.d.ts.map +1 -0
- package/dist/ui/molecules/region-state/index.js +3 -0
- package/dist/ui/molecules/region-state/index.js.map +1 -0
- package/dist/ui/molecules/region-state/region-state.d.ts +42 -0
- package/dist/ui/molecules/region-state/region-state.d.ts.map +1 -0
- package/dist/ui/molecules/region-state/region-state.js +83 -0
- package/dist/ui/molecules/region-state/region-state.js.map +1 -0
- package/dist/ui/molecules/table-toolbar/table-toolbar.d.ts +3 -1
- package/dist/ui/molecules/table-toolbar/table-toolbar.d.ts.map +1 -1
- package/dist/ui/molecules/table-toolbar/table-toolbar.js +3 -2
- package/dist/ui/molecules/table-toolbar/table-toolbar.js.map +1 -1
- package/dist/ui/molecules/toast-region/toast-region.d.ts +63 -19
- package/dist/ui/molecules/toast-region/toast-region.d.ts.map +1 -1
- package/dist/ui/molecules/toast-region/toast-region.js +129 -42
- package/dist/ui/molecules/toast-region/toast-region.js.map +1 -1
- package/dist/ui/organisms/command-palette/command-palette.d.ts.map +1 -1
- package/dist/ui/organisms/command-palette/command-palette.js +19 -12
- package/dist/ui/organisms/command-palette/command-palette.js.map +1 -1
- package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.d.ts.map +1 -1
- package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.js +0 -1
- package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.js.map +1 -1
- package/dist/ui/organisms/entity-form/entity-form.d.ts +1 -1
- package/dist/ui/organisms/entity-form/entity-form.d.ts.map +1 -1
- package/dist/ui/organisms/entity-form/entity-form.js +322 -214
- package/dist/ui/organisms/entity-form/entity-form.js.map +1 -1
- package/dist/ui/organisms/entity-form/entity-form.types.d.ts +16 -0
- package/dist/ui/organisms/entity-form/entity-form.types.d.ts.map +1 -1
- package/dist/ui/organisms/entity-form/entity-form.types.js.map +1 -1
- package/dist/ui/organisms/entity-table/entity-table.d.ts +1 -1
- package/dist/ui/organisms/entity-table/entity-table.d.ts.map +1 -1
- package/dist/ui/organisms/entity-table/entity-table.js +332 -197
- package/dist/ui/organisms/entity-table/entity-table.js.map +1 -1
- package/dist/ui/organisms/entity-table/entity-table.types.d.ts +19 -4
- package/dist/ui/organisms/entity-table/entity-table.types.d.ts.map +1 -1
- package/dist/ui/organisms/entity-table/entity-table.types.js.map +1 -1
- package/dist/ui/organisms/entity-table/header-sort.d.ts +18 -0
- package/dist/ui/organisms/entity-table/header-sort.d.ts.map +1 -0
- package/dist/ui/organisms/entity-table/header-sort.js +54 -0
- package/dist/ui/organisms/entity-table/header-sort.js.map +1 -0
- package/dist/ui/organisms/entity-table/pivot.d.ts +25 -0
- package/dist/ui/organisms/entity-table/pivot.d.ts.map +1 -0
- package/dist/ui/organisms/entity-table/pivot.js +106 -0
- package/dist/ui/organisms/entity-table/pivot.js.map +1 -0
- package/dist/ui/organisms/entity-table/use-result-announcement.d.ts +13 -0
- package/dist/ui/organisms/entity-table/use-result-announcement.d.ts.map +1 -0
- package/dist/ui/organisms/entity-table/use-result-announcement.js +31 -0
- package/dist/ui/organisms/entity-table/use-result-announcement.js.map +1 -0
- package/dist/ui/organisms/top-bar/live-indicator.d.ts +11 -0
- package/dist/ui/organisms/top-bar/live-indicator.d.ts.map +1 -0
- package/dist/ui/organisms/top-bar/live-indicator.js +46 -0
- package/dist/ui/organisms/top-bar/live-indicator.js.map +1 -0
- package/dist/ui/organisms/top-bar/top-bar.d.ts +11 -2
- package/dist/ui/organisms/top-bar/top-bar.d.ts.map +1 -1
- package/dist/ui/organisms/top-bar/top-bar.js +14 -5
- package/dist/ui/organisms/top-bar/top-bar.js.map +1 -1
- package/dist/ui/organisms/wizard/wizard.d.ts +1 -1
- package/dist/ui/organisms/wizard/wizard.d.ts.map +1 -1
- package/dist/ui/organisms/wizard/wizard.js +7 -1
- package/dist/ui/organisms/wizard/wizard.js.map +1 -1
- package/dist/ui/organisms/wizard/wizard.types.d.ts +8 -0
- package/dist/ui/organisms/wizard/wizard.types.d.ts.map +1 -1
- package/dist/ui/organisms/wizard/wizard.types.js.map +1 -1
- package/dist/ui/utils/live-updates.d.ts +24 -0
- package/dist/ui/utils/live-updates.d.ts.map +1 -0
- package/dist/ui/utils/live-updates.js +20 -0
- package/dist/ui/utils/live-updates.js.map +1 -0
- package/dist/ui/utils/use-focus-on-arrival.d.ts +14 -0
- package/dist/ui/utils/use-focus-on-arrival.d.ts.map +1 -0
- package/dist/ui/utils/use-focus-on-arrival.js +42 -0
- package/dist/ui/utils/use-focus-on-arrival.js.map +1 -0
- package/dist/ui/utils/use-hotkey.d.ts +44 -25
- package/dist/ui/utils/use-hotkey.d.ts.map +1 -1
- package/dist/ui/utils/use-hotkey.js +80 -48
- package/dist/ui/utils/use-hotkey.js.map +1 -1
- package/package.json +5 -5
- package/dist/ui/molecules/split-button/index.d.ts +0 -2
- package/dist/ui/molecules/split-button/index.d.ts.map +0 -1
- package/dist/ui/molecules/split-button/index.js +0 -3
- package/dist/ui/molecules/split-button/index.js.map +0 -1
- package/dist/ui/molecules/split-button/split-button.d.ts +0 -21
- package/dist/ui/molecules/split-button/split-button.d.ts.map +0 -1
- package/dist/ui/molecules/split-button/split-button.js +0 -35
- package/dist/ui/molecules/split-button/split-button.js.map +0 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useErrorMessage, useT } from '../../../i18n/index.js';
|
|
4
|
+
import { useErrorAction } from '../../actions/error-actions.js';
|
|
5
|
+
import { Alert } from '../../atoms/alert/index.js';
|
|
6
|
+
import { Button } from '../../atoms/button/index.js';
|
|
7
|
+
import { EmptyState } from '../empty-state/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* What a region says when it is not simply showing its content
|
|
10
|
+
* ([ADR 0028](../../../../../../../docs/adr/0028-states-an-organism-must-render.md)):
|
|
11
|
+
*
|
|
12
|
+
* - a **load error** is an `Alert` with Retry — `forbidden` says so in its own
|
|
13
|
+
* words — and data kept from before it is marked possibly out of date;
|
|
14
|
+
* - an **empty result under a filter** says so and offers Clear filters;
|
|
15
|
+
* - an **empty region** says what appears here, and offers its first record.
|
|
16
|
+
*
|
|
17
|
+
* It renders nothing while loading, and nothing when there is content: the
|
|
18
|
+
* organism's own skeleton and rows speak then. An empty result and a dead
|
|
19
|
+
* backend never look alike.
|
|
20
|
+
*/ export function RegionState({ plural, isLoading = false, error, onRetry, isEmpty = false, isFiltered = false, onClearFilters, emptyAction, hasStaleData = false }) {
|
|
21
|
+
const t = useT();
|
|
22
|
+
const errorMessage = useErrorMessage();
|
|
23
|
+
const errorAction = useErrorAction();
|
|
24
|
+
// Read inside a sentence, so as a common noun: "sus facturas".
|
|
25
|
+
const noun = plural.toLocaleLowerCase();
|
|
26
|
+
if (error !== undefined) {
|
|
27
|
+
// The host's next step for this code — "Choose organization" — when it
|
|
28
|
+
// registered one and its catalog names it.
|
|
29
|
+
const next = errorAction(error);
|
|
30
|
+
return /*#__PURE__*/ _jsxs(Alert, {
|
|
31
|
+
tone: "danger",
|
|
32
|
+
"data-testid": "region-state-error",
|
|
33
|
+
action: next === undefined && onRetry === undefined ? undefined : /*#__PURE__*/ _jsxs("span", {
|
|
34
|
+
className: "flex flex-wrap gap-3xs",
|
|
35
|
+
children: [
|
|
36
|
+
next !== undefined && /*#__PURE__*/ _jsx(Button, {
|
|
37
|
+
type: "button",
|
|
38
|
+
variant: "primary",
|
|
39
|
+
size: "sm",
|
|
40
|
+
onClick: next.onAction,
|
|
41
|
+
children: next.label
|
|
42
|
+
}),
|
|
43
|
+
onRetry !== undefined && /*#__PURE__*/ _jsx(Button, {
|
|
44
|
+
type: "button",
|
|
45
|
+
variant: "secondary",
|
|
46
|
+
size: "sm",
|
|
47
|
+
onClick: onRetry,
|
|
48
|
+
children: t('state.retry')
|
|
49
|
+
})
|
|
50
|
+
]
|
|
51
|
+
}),
|
|
52
|
+
children: [
|
|
53
|
+
error.code === 'forbidden' ? t('state.forbidden', {
|
|
54
|
+
plural: noun
|
|
55
|
+
}) : errorMessage(error),
|
|
56
|
+
hasStaleData && ` ${t('state.stale')}`
|
|
57
|
+
]
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (isLoading || !isEmpty) return null;
|
|
61
|
+
if (isFiltered) {
|
|
62
|
+
return /*#__PURE__*/ _jsx(EmptyState, {
|
|
63
|
+
action: onClearFilters === undefined ? undefined : /*#__PURE__*/ _jsx(Button, {
|
|
64
|
+
type: "button",
|
|
65
|
+
variant: "secondary",
|
|
66
|
+
size: "sm",
|
|
67
|
+
onClick: onClearFilters,
|
|
68
|
+
children: t('state.clearFilters')
|
|
69
|
+
}),
|
|
70
|
+
children: t('state.noResults', {
|
|
71
|
+
plural: noun
|
|
72
|
+
})
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return /*#__PURE__*/ _jsx(EmptyState, {
|
|
76
|
+
action: emptyAction,
|
|
77
|
+
children: t('state.empty', {
|
|
78
|
+
plural: noun
|
|
79
|
+
})
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=region-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/region-state/region-state.tsx"],"sourcesContent":["'use client';\n\nimport type { ReactNode } from 'react';\n\nimport { useErrorMessage, useT } from '../../../i18n/index.js';\nimport { useErrorAction } from '../../actions/error-actions.js';\nimport { Alert } from '../../atoms/alert/index.js';\nimport { Button } from '../../atoms/button/index.js';\nimport { EmptyState } from '../empty-state/index.js';\n\n/** A failure as the organisms hold it: a typed failure, or anything thrown. */\nexport interface RegionStateError {\n message: string;\n code?: unknown;\n details?: Record<string, unknown>;\n}\n\nexport interface RegionStateProps {\n /** What the region holds, plural — \"invoices\" — for the state's sentence. */\n plural: string;\n /** The first load: the organism's skeleton speaks for it, so nothing here. */\n isLoading?: boolean;\n /** A load that failed. */\n error?: RegionStateError;\n /** Loads the region again. Without it, an error offers no Retry. */\n onRetry?: () => void;\n /** The load answered, with nothing in it. */\n isEmpty?: boolean;\n /** A filter is in effect, so an empty answer is the filter's, not the data's. */\n isFiltered?: boolean;\n /** Clears every filter — what an empty filtered region offers. */\n onClearFilters?: () => void;\n /** What an empty, unfiltered region offers: creating the first record. */\n emptyAction?: ReactNode;\n /** Data from before the failure is still on screen. */\n hasStaleData?: boolean;\n}\n\n/**\n * What a region says when it is not simply showing its content\n * ([ADR 0028](../../../../../../../docs/adr/0028-states-an-organism-must-render.md)):\n *\n * - a **load error** is an `Alert` with Retry — `forbidden` says so in its own\n * words — and data kept from before it is marked possibly out of date;\n * - an **empty result under a filter** says so and offers Clear filters;\n * - an **empty region** says what appears here, and offers its first record.\n *\n * It renders nothing while loading, and nothing when there is content: the\n * organism's own skeleton and rows speak then. An empty result and a dead\n * backend never look alike.\n */\nexport function RegionState({\n plural,\n isLoading = false,\n error,\n onRetry,\n isEmpty = false,\n isFiltered = false,\n onClearFilters,\n emptyAction,\n hasStaleData = false,\n}: RegionStateProps) {\n const t = useT();\n const errorMessage = useErrorMessage();\n const errorAction = useErrorAction();\n // Read inside a sentence, so as a common noun: \"sus facturas\".\n const noun = plural.toLocaleLowerCase();\n\n if (error !== undefined) {\n // The host's next step for this code — \"Choose organization\" — when it\n // registered one and its catalog names it.\n const next = errorAction(error);\n return (\n <Alert\n tone=\"danger\"\n data-testid=\"region-state-error\"\n action={\n next === undefined && onRetry === undefined ? undefined : (\n <span className=\"flex flex-wrap gap-3xs\">\n {next !== undefined && (\n <Button\n type=\"button\"\n variant=\"primary\"\n size=\"sm\"\n onClick={next.onAction}\n >\n {next.label}\n </Button>\n )}\n {onRetry !== undefined && (\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n onClick={onRetry}\n >\n {t('state.retry')}\n </Button>\n )}\n </span>\n )\n }\n >\n {error.code === 'forbidden'\n ? t('state.forbidden', { plural: noun })\n : errorMessage(error)}\n {hasStaleData && ` ${t('state.stale')}`}\n </Alert>\n );\n }\n\n if (isLoading || !isEmpty) return null;\n\n if (isFiltered) {\n return (\n <EmptyState\n action={\n onClearFilters === undefined ? undefined : (\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n onClick={onClearFilters}\n >\n {t('state.clearFilters')}\n </Button>\n )\n }\n >\n {t('state.noResults', { plural: noun })}\n </EmptyState>\n );\n }\n\n return (\n <EmptyState action={emptyAction}>\n {t('state.empty', { plural: noun })}\n </EmptyState>\n );\n}\n"],"names":["useErrorMessage","useT","useErrorAction","Alert","Button","EmptyState","RegionState","plural","isLoading","error","onRetry","isEmpty","isFiltered","onClearFilters","emptyAction","hasStaleData","t","errorMessage","errorAction","noun","toLocaleLowerCase","undefined","next","tone","data-testid","action","span","className","type","variant","size","onClick","onAction","label","code"],"mappings":"AAAA;;AAIA,SAASA,eAAe,EAAEC,IAAI,QAAQ,yBAAyB;AAC/D,SAASC,cAAc,QAAQ,iCAAiC;AAChE,SAASC,KAAK,QAAQ,6BAA6B;AACnD,SAASC,MAAM,QAAQ,8BAA8B;AACrD,SAASC,UAAU,QAAQ,0BAA0B;AA8BrD;;;;;;;;;;;;CAYC,GACD,OAAO,SAASC,YAAY,EAC1BC,MAAM,EACNC,YAAY,KAAK,EACjBC,KAAK,EACLC,OAAO,EACPC,UAAU,KAAK,EACfC,aAAa,KAAK,EAClBC,cAAc,EACdC,WAAW,EACXC,eAAe,KAAK,EACH;IACjB,MAAMC,IAAIf;IACV,MAAMgB,eAAejB;IACrB,MAAMkB,cAAchB;IACpB,+DAA+D;IAC/D,MAAMiB,OAAOZ,OAAOa,iBAAiB;IAErC,IAAIX,UAAUY,WAAW;QACvB,uEAAuE;QACvE,2CAA2C;QAC3C,MAAMC,OAAOJ,YAAYT;QACzB,qBACE,MAACN;YACCoB,MAAK;YACLC,eAAY;YACZC,QACEH,SAASD,aAAaX,YAAYW,YAAYA,0BAC5C,MAACK;gBAAKC,WAAU;;oBACbL,SAASD,2BACR,KAACjB;wBACCwB,MAAK;wBACLC,SAAQ;wBACRC,MAAK;wBACLC,SAAST,KAAKU,QAAQ;kCAErBV,KAAKW,KAAK;;oBAGdvB,YAAYW,2BACX,KAACjB;wBACCwB,MAAK;wBACLC,SAAQ;wBACRC,MAAK;wBACLC,SAASrB;kCAERM,EAAE;;;;;gBAOZP,MAAMyB,IAAI,KAAK,cACZlB,EAAE,mBAAmB;oBAAET,QAAQY;gBAAK,KACpCF,aAAaR;gBAChBM,gBAAgB,CAAC,CAAC,EAAEC,EAAE,gBAAgB;;;IAG7C;IAEA,IAAIR,aAAa,CAACG,SAAS,OAAO;IAElC,IAAIC,YAAY;QACd,qBACE,KAACP;YACCoB,QACEZ,mBAAmBQ,YAAYA,0BAC7B,KAACjB;gBACCwB,MAAK;gBACLC,SAAQ;gBACRC,MAAK;gBACLC,SAASlB;0BAERG,EAAE;;sBAKRA,EAAE,mBAAmB;gBAAET,QAAQY;YAAK;;IAG3C;IAEA,qBACE,KAACd;QAAWoB,QAAQX;kBACjBE,EAAE,eAAe;YAAET,QAAQY;QAAK;;AAGvC"}
|
|
@@ -6,8 +6,10 @@ export interface TableToolbarProps {
|
|
|
6
6
|
end?: ReactNode;
|
|
7
7
|
/** Expanded panel rendered below the bar (filters, sorting). */
|
|
8
8
|
panel?: ReactNode;
|
|
9
|
+
/** The panel's id, for the toggle that opens it to name in `aria-controls`. */
|
|
10
|
+
panelId?: string;
|
|
9
11
|
className?: string;
|
|
10
12
|
}
|
|
11
13
|
/** Bar above a table: actions on one line, an optional expanded panel below. */
|
|
12
|
-
export declare function TableToolbar({ start, end, panel, className, }: TableToolbarProps): import("react").JSX.Element;
|
|
14
|
+
export declare function TableToolbar({ start, end, panel, panelId, className, }: TableToolbarProps): import("react").JSX.Element;
|
|
13
15
|
//# sourceMappingURL=table-toolbar.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table-toolbar.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/table-toolbar/table-toolbar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvC,MAAM,WAAW,iBAAiB;IAChC,8DAA8D;IAC9D,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,mEAAmE;IACnE,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,gEAAgE;IAChE,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,GAAG,EACH,KAAK,EACL,SAAS,GACV,EAAE,iBAAiB,+
|
|
1
|
+
{"version":3,"file":"table-toolbar.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/table-toolbar/table-toolbar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvC,MAAM,WAAW,iBAAiB;IAChC,8DAA8D;IAC9D,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,mEAAmE;IACnE,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,gEAAgE;IAChE,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,GAAG,EACH,KAAK,EACL,OAAO,EACP,SAAS,GACV,EAAE,iBAAiB,+BAmBnB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from '../../utils/cn.js';
|
|
3
|
-
/** Bar above a table: actions on one line, an optional expanded panel below. */ export function TableToolbar({ start, end, panel, className }) {
|
|
3
|
+
/** Bar above a table: actions on one line, an optional expanded panel below. */ export function TableToolbar({ start, end, panel, panelId, className }) {
|
|
4
4
|
return /*#__PURE__*/ _jsxs("div", {
|
|
5
5
|
className: cn('flex flex-col gap-2xs', className),
|
|
6
6
|
children: [
|
|
@@ -8,7 +8,7 @@ import { cn } from '../../utils/cn.js';
|
|
|
8
8
|
className: "flex flex-wrap items-center justify-between gap-2xs",
|
|
9
9
|
children: [
|
|
10
10
|
/*#__PURE__*/ _jsx("div", {
|
|
11
|
-
className: "flex flex-wrap items-center gap-2xs",
|
|
11
|
+
className: "min-w-0 flex flex-1 flex-wrap items-center gap-2xs",
|
|
12
12
|
children: start
|
|
13
13
|
}),
|
|
14
14
|
/*#__PURE__*/ _jsx("div", {
|
|
@@ -18,6 +18,7 @@ import { cn } from '../../utils/cn.js';
|
|
|
18
18
|
]
|
|
19
19
|
}),
|
|
20
20
|
panel && /*#__PURE__*/ _jsx("div", {
|
|
21
|
+
id: panelId,
|
|
21
22
|
className: "rounded-xl border border-border bg-surface p-s",
|
|
22
23
|
children: panel
|
|
23
24
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/ui/molecules/table-toolbar/table-toolbar.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\n\nimport { cn } from '../../utils/cn.js';\n\nexport interface TableToolbarProps {\n /** Actions aligned to the start — titles, primary actions. */\n start?: ReactNode;\n /** Actions aligned to the end — column settings, panel toggles. */\n end?: ReactNode;\n /** Expanded panel rendered below the bar (filters, sorting). */\n panel?: ReactNode;\n className?: string;\n}\n\n/** Bar above a table: actions on one line, an optional expanded panel below. */\nexport function TableToolbar({\n start,\n end,\n panel,\n className,\n}: TableToolbarProps) {\n return (\n <div className={cn('flex flex-col gap-2xs', className)}>\n <div className=\"flex flex-wrap items-center justify-between gap-2xs\">\n <div className=\"flex flex-wrap items-center gap-2xs\"
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/table-toolbar/table-toolbar.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\n\nimport { cn } from '../../utils/cn.js';\n\nexport interface TableToolbarProps {\n /** Actions aligned to the start — titles, primary actions. */\n start?: ReactNode;\n /** Actions aligned to the end — column settings, panel toggles. */\n end?: ReactNode;\n /** Expanded panel rendered below the bar (filters, sorting). */\n panel?: ReactNode;\n /** The panel's id, for the toggle that opens it to name in `aria-controls`. */\n panelId?: string;\n className?: string;\n}\n\n/** Bar above a table: actions on one line, an optional expanded panel below. */\nexport function TableToolbar({\n start,\n end,\n panel,\n panelId,\n className,\n}: TableToolbarProps) {\n return (\n <div className={cn('flex flex-col gap-2xs', className)}>\n <div className=\"flex flex-wrap items-center justify-between gap-2xs\">\n <div className=\"min-w-0 flex flex-1 flex-wrap items-center gap-2xs\">\n {start}\n </div>\n <div className=\"flex flex-wrap items-center gap-2xs\">{end}</div>\n </div>\n {panel && (\n <div\n id={panelId}\n className=\"rounded-xl border border-border bg-surface p-s\"\n >\n {panel}\n </div>\n )}\n </div>\n );\n}\n"],"names":["cn","TableToolbar","start","end","panel","panelId","className","div","id"],"mappings":";AAEA,SAASA,EAAE,QAAQ,oBAAoB;AAcvC,8EAA8E,GAC9E,OAAO,SAASC,aAAa,EAC3BC,KAAK,EACLC,GAAG,EACHC,KAAK,EACLC,OAAO,EACPC,SAAS,EACS;IAClB,qBACE,MAACC;QAAID,WAAWN,GAAG,yBAAyBM;;0BAC1C,MAACC;gBAAID,WAAU;;kCACb,KAACC;wBAAID,WAAU;kCACZJ;;kCAEH,KAACK;wBAAID,WAAU;kCAAuCH;;;;YAEvDC,uBACC,KAACG;gBACCC,IAAIH;gBACJC,WAAU;0BAETF;;;;AAKX"}
|
|
@@ -5,39 +5,83 @@ import { useErrorMessage } from '../../../i18n/index.js';
|
|
|
5
5
|
* `code`, and anything else that reached the user keeps its `message`.
|
|
6
6
|
*/
|
|
7
7
|
export type ToastFailure = Parameters<ReturnType<typeof useErrorMessage>>[0];
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
/**
|
|
9
|
+
* What a toast reports
|
|
10
|
+
* ([ADR 0028](../../../../../../../docs/adr/0028-states-an-organism-must-render.md)):
|
|
11
|
+
* a write that worked, one that can still be taken back, something worth
|
|
12
|
+
* knowing, or a failure. Only a failure interrupts.
|
|
13
|
+
*/
|
|
14
|
+
export type ToastTone = 'success' | 'undo' | 'info' | 'failure';
|
|
15
|
+
/** The one thing a toast offers to do about what it reports. */
|
|
16
|
+
export interface ToastAction {
|
|
17
|
+
/** The button's text, already in the reader's language. */
|
|
18
|
+
readonly label: string;
|
|
19
|
+
readonly onAction: () => void;
|
|
12
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* A toast to show. A failure carries the failure itself, rendered through the
|
|
23
|
+
* same {@link useErrorMessage} a form's error slot uses; every other tone
|
|
24
|
+
* carries a sentence already in the reader's language. An `undo` toast without
|
|
25
|
+
* its Undo would be a success toast, so the type asks for the action.
|
|
26
|
+
*/
|
|
27
|
+
export type ToastInput = {
|
|
28
|
+
readonly tone: 'success' | 'info';
|
|
29
|
+
readonly message: string;
|
|
30
|
+
readonly action?: ToastAction;
|
|
31
|
+
} | {
|
|
32
|
+
readonly tone: 'undo';
|
|
33
|
+
readonly message: string;
|
|
34
|
+
readonly action: ToastAction;
|
|
35
|
+
} | {
|
|
36
|
+
readonly tone: 'failure';
|
|
37
|
+
readonly failure: ToastFailure;
|
|
38
|
+
readonly action?: ToastAction;
|
|
39
|
+
};
|
|
40
|
+
/** One toast waiting in the region, under the id it was shown with. */
|
|
41
|
+
export type Toast = ToastInput & {
|
|
42
|
+
readonly id: number;
|
|
43
|
+
};
|
|
13
44
|
export interface Toasts {
|
|
14
|
-
/** The
|
|
15
|
-
readonly
|
|
16
|
-
/** Shows a
|
|
17
|
-
readonly
|
|
45
|
+
/** The toasts shown, oldest first. */
|
|
46
|
+
readonly toasts: readonly Toast[];
|
|
47
|
+
/** Shows a toast, and answers its id. */
|
|
48
|
+
readonly show: (toast: ToastInput) => number;
|
|
18
49
|
readonly dismiss: (id: number) => void;
|
|
19
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* How long a toast that is neither a failure nor offers an action stays, in
|
|
53
|
+
* milliseconds. A failure stays until it is dismissed: it reports something
|
|
54
|
+
* that already happened, often after the screen that started it was left, and
|
|
55
|
+
* a message that times out is one a slow reader never gets. A toast with an
|
|
56
|
+
* action stays too, for the same reader.
|
|
57
|
+
*/
|
|
58
|
+
export declare const TOAST_TIMEOUT_MS = 8000;
|
|
59
|
+
export interface ToastProviderProps {
|
|
60
|
+
readonly children: ReactNode;
|
|
61
|
+
/** How long a success, undo or info toast stays. */
|
|
62
|
+
readonly timeoutMs?: number;
|
|
63
|
+
}
|
|
20
64
|
/**
|
|
21
65
|
* Holds the toasts one host shows. Mounted once, above everything that can
|
|
22
66
|
* fail asynchronously, with a {@link ToastRegion} somewhere inside it.
|
|
23
67
|
*/
|
|
24
|
-
export declare function ToastProvider({ children }:
|
|
25
|
-
readonly children: ReactNode;
|
|
26
|
-
}): import("react").JSX.Element;
|
|
68
|
+
export declare function ToastProvider({ children, timeoutMs, }: ToastProviderProps): import("react").JSX.Element;
|
|
27
69
|
/**
|
|
28
70
|
* The {@link ToastProvider} above, or `undefined` outside one: a caller with
|
|
29
|
-
* no region to show a
|
|
30
|
-
* vanish.
|
|
71
|
+
* no region to show a toast in keeps what it has to say where it can, rather
|
|
72
|
+
* than having it vanish.
|
|
31
73
|
*/
|
|
32
74
|
export declare const useToasts: () => Toasts | undefined;
|
|
33
75
|
/**
|
|
34
|
-
* Where a host's toasts render
|
|
35
|
-
*
|
|
36
|
-
* asynchronous failure reads exactly as a synchronous one would.
|
|
76
|
+
* Where a host's toasts render, each through {@link Alert}: a failure is
|
|
77
|
+
* `role="alert"` and every other tone `role="status"`.
|
|
37
78
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
79
|
+
* ⚠️ **An empty region takes no room and no clicks.** It stays mounted, so a
|
|
80
|
+
* toast lands in a region that already exists, but at zero size and with
|
|
81
|
+
* `pointer-events: none` — a fixed box that kept its padding once covered two
|
|
82
|
+
* focused links and swallowed clicks (WCAG 2.4.11). While toasts show, the
|
|
83
|
+
* document's bottom scroll padding is the region's height, so a control
|
|
84
|
+
* focused by keyboard scrolls clear of it. F6 moves focus into the region.
|
|
41
85
|
*/
|
|
42
86
|
export declare function ToastRegion(): import("react").JSX.Element | null;
|
|
43
87
|
//# sourceMappingURL=toast-region.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toast-region.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/toast-region/toast-region.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,SAAS,
|
|
1
|
+
{"version":3,"file":"toast-region.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/toast-region/toast-region.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,SAAS,EAOf,MAAM,OAAO,CAAC;AAEf,OAAO,EAAE,eAAe,EAAQ,MAAM,wBAAwB,CAAC;AAM/D;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE7E;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAEhE,gEAAgE;AAChE,MAAM,WAAW,WAAW;IAC1B,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAClB;IACE,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B,CAAC;AAEN,uEAAuE;AACvE,MAAM,MAAM,KAAK,GAAG,UAAU,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzD,MAAM,WAAW,MAAM;IACrB,sCAAsC;IACtC,QAAQ,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IAClC,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,MAAM,CAAC;IAC7C,QAAQ,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAIrC,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;IAC7B,oDAAoD;IACpD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,SAA4B,GAC7B,EAAE,kBAAkB,+BA4CpB;AAED;;;;GAIG;AACH,eAAO,MAAM,SAAS,QAAO,MAAM,GAAG,SAAqC,CAAC;AA0D5E;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,uCA8C1B"}
|
|
@@ -1,37 +1,64 @@
|
|
|
1
1
|
'use client';
|
|
2
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
3
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
5
|
import { useErrorMessage, useT } from '../../../i18n/index.js';
|
|
6
|
+
import { useErrorAction } from '../../actions/error-actions.js';
|
|
7
|
+
import { Alert } from '../../atoms/alert/index.js';
|
|
5
8
|
import { Button } from '../../atoms/button/index.js';
|
|
9
|
+
import { cn } from '../../utils/cn.js';
|
|
10
|
+
/**
|
|
11
|
+
* How long a toast that is neither a failure nor offers an action stays, in
|
|
12
|
+
* milliseconds. A failure stays until it is dismissed: it reports something
|
|
13
|
+
* that already happened, often after the screen that started it was left, and
|
|
14
|
+
* a message that times out is one a slow reader never gets. A toast with an
|
|
15
|
+
* action stays too, for the same reader.
|
|
16
|
+
*/ export const TOAST_TIMEOUT_MS = 8000;
|
|
6
17
|
const ToastContext = /*#__PURE__*/ createContext(undefined);
|
|
7
18
|
/**
|
|
8
19
|
* Holds the toasts one host shows. Mounted once, above everything that can
|
|
9
20
|
* fail asynchronously, with a {@link ToastRegion} somewhere inside it.
|
|
10
|
-
*/ export function ToastProvider({ children }) {
|
|
11
|
-
const [
|
|
21
|
+
*/ export function ToastProvider({ children, timeoutMs = TOAST_TIMEOUT_MS }) {
|
|
22
|
+
const [toasts, setToasts] = useState([]);
|
|
12
23
|
const next = useRef(0);
|
|
13
|
-
const
|
|
24
|
+
const timers = useRef(new Map());
|
|
25
|
+
const dismiss = useCallback((id)=>{
|
|
26
|
+
clearTimeout(timers.current.get(id));
|
|
27
|
+
timers.current.delete(id);
|
|
28
|
+
setToasts((shown)=>shown.filter((toast)=>toast.id !== id));
|
|
29
|
+
}, []);
|
|
30
|
+
const show = useCallback((toast)=>{
|
|
14
31
|
next.current += 1;
|
|
15
32
|
const id = next.current;
|
|
16
|
-
|
|
33
|
+
setToasts((shown)=>[
|
|
17
34
|
...shown,
|
|
18
|
-
{
|
|
19
|
-
id
|
|
20
|
-
|
|
21
|
-
}
|
|
35
|
+
_extends({}, toast, {
|
|
36
|
+
id
|
|
37
|
+
})
|
|
22
38
|
]);
|
|
39
|
+
// A failure stays until it is dismissed, and so does a toast that
|
|
40
|
+
// offers an action: Undo on a timer is a time limit on the reader's
|
|
41
|
+
// only way back (WCAG 2.2.1). The rest say their piece and go.
|
|
42
|
+
if (toast.tone !== 'failure' && toast.action === undefined) timers.current.set(id, setTimeout(()=>dismiss(id), timeoutMs));
|
|
23
43
|
return id;
|
|
24
|
-
}, [
|
|
25
|
-
|
|
26
|
-
|
|
44
|
+
}, [
|
|
45
|
+
dismiss,
|
|
46
|
+
timeoutMs
|
|
47
|
+
]);
|
|
48
|
+
useEffect(()=>{
|
|
49
|
+
const pending = timers.current;
|
|
50
|
+
return ()=>{
|
|
51
|
+
for (const timer of pending.values())clearTimeout(timer);
|
|
52
|
+
pending.clear();
|
|
53
|
+
};
|
|
27
54
|
}, []);
|
|
28
55
|
const value = useMemo(()=>({
|
|
29
|
-
|
|
30
|
-
|
|
56
|
+
toasts,
|
|
57
|
+
show,
|
|
31
58
|
dismiss
|
|
32
59
|
}), [
|
|
33
|
-
|
|
34
|
-
|
|
60
|
+
toasts,
|
|
61
|
+
show,
|
|
35
62
|
dismiss
|
|
36
63
|
]);
|
|
37
64
|
return /*#__PURE__*/ _jsx(ToastContext.Provider, {
|
|
@@ -41,41 +68,101 @@ const ToastContext = /*#__PURE__*/ createContext(undefined);
|
|
|
41
68
|
}
|
|
42
69
|
/**
|
|
43
70
|
* The {@link ToastProvider} above, or `undefined` outside one: a caller with
|
|
44
|
-
* no region to show a
|
|
45
|
-
* vanish.
|
|
71
|
+
* no region to show a toast in keeps what it has to say where it can, rather
|
|
72
|
+
* than having it vanish.
|
|
46
73
|
*/ export const useToasts = ()=>useContext(ToastContext);
|
|
74
|
+
const ALERT_TONE = {
|
|
75
|
+
success: 'success',
|
|
76
|
+
undo: 'info',
|
|
77
|
+
info: 'info',
|
|
78
|
+
failure: 'danger'
|
|
79
|
+
};
|
|
80
|
+
function ToastItem({ toast, onDismiss }) {
|
|
81
|
+
var _toast_action;
|
|
82
|
+
const t = useT();
|
|
83
|
+
const message = useErrorMessage();
|
|
84
|
+
const errorAction = useErrorAction();
|
|
85
|
+
// A failure with no action of its own offers the host's next step for its
|
|
86
|
+
// code, when there is one.
|
|
87
|
+
const next = toast.tone === 'failure' ? errorAction(toast.failure) : undefined;
|
|
88
|
+
const action = (_toast_action = toast.action) != null ? _toast_action : next === undefined ? undefined : {
|
|
89
|
+
label: next.label,
|
|
90
|
+
onAction: next.onAction
|
|
91
|
+
};
|
|
92
|
+
return /*#__PURE__*/ _jsx(Alert, {
|
|
93
|
+
tone: ALERT_TONE[toast.tone],
|
|
94
|
+
"data-toast-tone": toast.tone,
|
|
95
|
+
className: "pointer-events-auto bg-surface shadow-overlay",
|
|
96
|
+
action: /*#__PURE__*/ _jsxs("div", {
|
|
97
|
+
className: "flex gap-3xs",
|
|
98
|
+
children: [
|
|
99
|
+
action !== undefined && /*#__PURE__*/ _jsx(Button, {
|
|
100
|
+
type: "button",
|
|
101
|
+
variant: "secondary",
|
|
102
|
+
onClick: ()=>{
|
|
103
|
+
action.onAction();
|
|
104
|
+
onDismiss();
|
|
105
|
+
},
|
|
106
|
+
children: action.label
|
|
107
|
+
}),
|
|
108
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
109
|
+
type: "button",
|
|
110
|
+
variant: "ghost",
|
|
111
|
+
onClick: onDismiss,
|
|
112
|
+
children: t('toast.dismiss')
|
|
113
|
+
})
|
|
114
|
+
]
|
|
115
|
+
}),
|
|
116
|
+
children: toast.tone === 'failure' ? message(toast.failure) : toast.message
|
|
117
|
+
});
|
|
118
|
+
}
|
|
47
119
|
/**
|
|
48
|
-
* Where a host's toasts render
|
|
49
|
-
*
|
|
50
|
-
* asynchronous failure reads exactly as a synchronous one would.
|
|
120
|
+
* Where a host's toasts render, each through {@link Alert}: a failure is
|
|
121
|
+
* `role="alert"` and every other tone `role="status"`.
|
|
51
122
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
123
|
+
* ⚠️ **An empty region takes no room and no clicks.** It stays mounted, so a
|
|
124
|
+
* toast lands in a region that already exists, but at zero size and with
|
|
125
|
+
* `pointer-events: none` — a fixed box that kept its padding once covered two
|
|
126
|
+
* focused links and swallowed clicks (WCAG 2.4.11). While toasts show, the
|
|
127
|
+
* document's bottom scroll padding is the region's height, so a control
|
|
128
|
+
* focused by keyboard scrolls clear of it. F6 moves focus into the region.
|
|
55
129
|
*/ export function ToastRegion() {
|
|
130
|
+
var _ref;
|
|
56
131
|
const toasts = useToasts();
|
|
57
132
|
const t = useT();
|
|
58
|
-
const
|
|
133
|
+
const region = useRef(null);
|
|
134
|
+
const shown = (_ref = toasts == null ? void 0 : toasts.toasts) != null ? _ref : [];
|
|
135
|
+
const count = shown.length;
|
|
136
|
+
useEffect(()=>{
|
|
137
|
+
const root = document.documentElement;
|
|
138
|
+
if (count === 0) {
|
|
139
|
+
root.style.removeProperty('scroll-padding-bottom');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
// The region renders whenever a toast can: the ref is set by now.
|
|
143
|
+
const node = region.current;
|
|
144
|
+
root.style.setProperty('scroll-padding-bottom', `${node.offsetHeight}px`);
|
|
145
|
+
const onKeyDown = (event)=>{
|
|
146
|
+
if (event.key !== 'F6') return;
|
|
147
|
+
event.preventDefault();
|
|
148
|
+
node.focus();
|
|
149
|
+
};
|
|
150
|
+
document.addEventListener('keydown', onKeyDown);
|
|
151
|
+
return ()=>document.removeEventListener('keydown', onKeyDown);
|
|
152
|
+
}, [
|
|
153
|
+
count
|
|
154
|
+
]);
|
|
59
155
|
if (toasts === undefined) return null;
|
|
60
156
|
return /*#__PURE__*/ _jsx("section", {
|
|
157
|
+
ref: region,
|
|
61
158
|
"aria-label": t('toast.region'),
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
children: message(failure)
|
|
70
|
-
}),
|
|
71
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
72
|
-
type: "button",
|
|
73
|
-
variant: "ghost",
|
|
74
|
-
onClick: ()=>toasts.dismiss(id),
|
|
75
|
-
children: t('toast.dismiss')
|
|
76
|
-
})
|
|
77
|
-
]
|
|
78
|
-
}, id))
|
|
159
|
+
tabIndex: -1,
|
|
160
|
+
"data-testid": "toast-region",
|
|
161
|
+
className: cn('bottom-0 right-0 pointer-events-none fixed z-(--z-toast) flex flex-col gap-xs focus-ring', count === 0 ? 'size-0 overflow-hidden' : 'max-w-md w-full p-s'),
|
|
162
|
+
children: shown.map((toast)=>/*#__PURE__*/ _jsx(ToastItem, {
|
|
163
|
+
toast: toast,
|
|
164
|
+
onDismiss: ()=>toasts.dismiss(toast.id)
|
|
165
|
+
}, toast.id))
|
|
79
166
|
});
|
|
80
167
|
}
|
|
81
168
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/ui/molecules/toast-region/toast-region.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n type ReactNode,\n useCallback,\n useContext,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\nimport { useErrorMessage, useT } from '../../../i18n/index.js';\nimport { Button } from '../../atoms/button/index.js';\n\n/**\n * A failure as {@link useErrorMessage} renders it: a typed failure carries its\n * `code`, and anything else that reached the user keeps its `message`.\n */\nexport type ToastFailure = Parameters<ReturnType<typeof useErrorMessage>>[0];\n\n/** One failure waiting in the region, under the id it was shown with. */\nexport interface FailureToast {\n readonly id: number;\n readonly failure: ToastFailure;\n}\n\nexport interface Toasts {\n /** The failures shown, oldest first. */\n readonly failures: readonly FailureToast[];\n /** Shows a failure until it is dismissed, and answers its id. */\n readonly showFailure: (failure: ToastFailure) => number;\n readonly dismiss: (id: number) => void;\n}\n\nconst ToastContext = createContext<Toasts | undefined>(undefined);\n\n/**\n * Holds the toasts one host shows. Mounted once, above everything that can\n * fail asynchronously, with a {@link ToastRegion} somewhere inside it.\n */\nexport function ToastProvider({ children }: { readonly children: ReactNode }) {\n const [failures, setFailures] = useState<readonly FailureToast[]>([]);\n const next = useRef(0);\n\n const showFailure = useCallback((failure: ToastFailure) => {\n next.current += 1;\n const id = next.current;\n setFailures(shown => [...shown, { id, failure }]);\n return id;\n }, []);\n const dismiss = useCallback((id: number) => {\n setFailures(shown => shown.filter(toast => toast.id !== id));\n }, []);\n\n const value = useMemo(\n () => ({ failures, showFailure, dismiss }),\n [failures, showFailure, dismiss],\n );\n return (\n <ToastContext.Provider value={value}>{children}</ToastContext.Provider>\n );\n}\n\n/**\n * The {@link ToastProvider} above, or `undefined` outside one: a caller with\n * no region to show a failure in keeps it where it can, rather than having it\n * vanish.\n */\nexport const useToasts = (): Toasts | undefined => useContext(ToastContext);\n\n/**\n * Where a host's toasts render: each failure in the reader's language,\n * through the same {@link useErrorMessage} a form's error slot uses, so an\n * asynchronous failure reads exactly as a synchronous one would.\n *\n * A failure stays until it is dismissed. It reports something that already\n * happened, often after the screen that started it was left, and a message\n * that times out is one a slow reader never gets.\n */\nexport function ToastRegion() {\n const toasts = useToasts();\n const t = useT();\n const message = useErrorMessage();\n\n if (toasts === undefined) return null;\n return (\n <section\n aria-label={t('toast.region')}\n className=\"bottom-0 right-0 max-w-md fixed z-(--z-toast) flex w-full flex-col gap-xs p-s\"\n >\n {toasts.failures.map(({ id, failure }) => (\n <div\n key={id}\n role=\"alert\"\n className=\"flex items-start gap-xs rounded-lg border border-danger bg-surface p-xs shadow-overlay\"\n >\n <p className=\"flex-1 text-step-sm text-danger\">{message(failure)}</p>\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={() => toasts.dismiss(id)}\n >\n {t('toast.dismiss')}\n </Button>\n </div>\n ))}\n </section>\n );\n}\n"],"names":["createContext","useCallback","useContext","useMemo","useRef","useState","useErrorMessage","useT","Button","ToastContext","undefined","ToastProvider","children","failures","setFailures","next","showFailure","failure","current","id","shown","dismiss","filter","toast","value","Provider","useToasts","ToastRegion","toasts","t","message","section","aria-label","className","map","div","role","p","type","variant","onClick"],"mappings":"AAAA;;AAEA,SACEA,aAAa,EAEbC,WAAW,EACXC,UAAU,EACVC,OAAO,EACPC,MAAM,EACNC,QAAQ,QACH,QAAQ;AAEf,SAASC,eAAe,EAAEC,IAAI,QAAQ,yBAAyB;AAC/D,SAASC,MAAM,QAAQ,8BAA8B;AAsBrD,MAAMC,6BAAeT,cAAkCU;AAEvD;;;CAGC,GACD,OAAO,SAASC,cAAc,EAAEC,QAAQ,EAAoC;IAC1E,MAAM,CAACC,UAAUC,YAAY,GAAGT,SAAkC,EAAE;IACpE,MAAMU,OAAOX,OAAO;IAEpB,MAAMY,cAAcf,YAAY,CAACgB;QAC/BF,KAAKG,OAAO,IAAI;QAChB,MAAMC,KAAKJ,KAAKG,OAAO;QACvBJ,YAAYM,CAAAA,QAAS;mBAAIA;gBAAO;oBAAED;oBAAIF;gBAAQ;aAAE;QAChD,OAAOE;IACT,GAAG,EAAE;IACL,MAAME,UAAUpB,YAAY,CAACkB;QAC3BL,YAAYM,CAAAA,QAASA,MAAME,MAAM,CAACC,CAAAA,QAASA,MAAMJ,EAAE,KAAKA;IAC1D,GAAG,EAAE;IAEL,MAAMK,QAAQrB,QACZ,IAAO,CAAA;YAAEU;YAAUG;YAAaK;QAAQ,CAAA,GACxC;QAACR;QAAUG;QAAaK;KAAQ;IAElC,qBACE,KAACZ,aAAagB,QAAQ;QAACD,OAAOA;kBAAQZ;;AAE1C;AAEA;;;;CAIC,GACD,OAAO,MAAMc,YAAY,IAA0BxB,WAAWO,cAAc;AAE5E;;;;;;;;CAQC,GACD,OAAO,SAASkB;IACd,MAAMC,SAASF;IACf,MAAMG,IAAItB;IACV,MAAMuB,UAAUxB;IAEhB,IAAIsB,WAAWlB,WAAW,OAAO;IACjC,qBACE,KAACqB;QACCC,cAAYH,EAAE;QACdI,WAAU;kBAETL,OAAOf,QAAQ,CAACqB,GAAG,CAAC,CAAC,EAAEf,EAAE,EAAEF,OAAO,EAAE,iBACnC,MAACkB;gBAECC,MAAK;gBACLH,WAAU;;kCAEV,KAACI;wBAAEJ,WAAU;kCAAmCH,QAAQb;;kCACxD,KAACT;wBACC8B,MAAK;wBACLC,SAAQ;wBACRC,SAAS,IAAMZ,OAAOP,OAAO,CAACF;kCAE7BU,EAAE;;;eAVAV;;AAgBf"}
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/toast-region/toast-region.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\nimport { useErrorMessage, useT } from '../../../i18n/index.js';\nimport { useErrorAction } from '../../actions/error-actions.js';\nimport { Alert, type AlertTone } from '../../atoms/alert/index.js';\nimport { Button } from '../../atoms/button/index.js';\nimport { cn } from '../../utils/cn.js';\n\n/**\n * A failure as {@link useErrorMessage} renders it: a typed failure carries its\n * `code`, and anything else that reached the user keeps its `message`.\n */\nexport type ToastFailure = Parameters<ReturnType<typeof useErrorMessage>>[0];\n\n/**\n * What a toast reports\n * ([ADR 0028](../../../../../../../docs/adr/0028-states-an-organism-must-render.md)):\n * a write that worked, one that can still be taken back, something worth\n * knowing, or a failure. Only a failure interrupts.\n */\nexport type ToastTone = 'success' | 'undo' | 'info' | 'failure';\n\n/** The one thing a toast offers to do about what it reports. */\nexport interface ToastAction {\n /** The button's text, already in the reader's language. */\n readonly label: string;\n readonly onAction: () => void;\n}\n\n/**\n * A toast to show. A failure carries the failure itself, rendered through the\n * same {@link useErrorMessage} a form's error slot uses; every other tone\n * carries a sentence already in the reader's language. An `undo` toast without\n * its Undo would be a success toast, so the type asks for the action.\n */\nexport type ToastInput =\n | {\n readonly tone: 'success' | 'info';\n readonly message: string;\n readonly action?: ToastAction;\n }\n | {\n readonly tone: 'undo';\n readonly message: string;\n readonly action: ToastAction;\n }\n | {\n readonly tone: 'failure';\n readonly failure: ToastFailure;\n readonly action?: ToastAction;\n };\n\n/** One toast waiting in the region, under the id it was shown with. */\nexport type Toast = ToastInput & { readonly id: number };\n\nexport interface Toasts {\n /** The toasts shown, oldest first. */\n readonly toasts: readonly Toast[];\n /** Shows a toast, and answers its id. */\n readonly show: (toast: ToastInput) => number;\n readonly dismiss: (id: number) => void;\n}\n\n/**\n * How long a toast that is neither a failure nor offers an action stays, in\n * milliseconds. A failure stays until it is dismissed: it reports something\n * that already happened, often after the screen that started it was left, and\n * a message that times out is one a slow reader never gets. A toast with an\n * action stays too, for the same reader.\n */\nexport const TOAST_TIMEOUT_MS = 8000;\n\nconst ToastContext = createContext<Toasts | undefined>(undefined);\n\nexport interface ToastProviderProps {\n readonly children: ReactNode;\n /** How long a success, undo or info toast stays. */\n readonly timeoutMs?: number;\n}\n\n/**\n * Holds the toasts one host shows. Mounted once, above everything that can\n * fail asynchronously, with a {@link ToastRegion} somewhere inside it.\n */\nexport function ToastProvider({\n children,\n timeoutMs = TOAST_TIMEOUT_MS,\n}: ToastProviderProps) {\n const [toasts, setToasts] = useState<readonly Toast[]>([]);\n const next = useRef(0);\n const timers = useRef(new Map<number, ReturnType<typeof setTimeout>>());\n\n const dismiss = useCallback((id: number) => {\n clearTimeout(timers.current.get(id));\n timers.current.delete(id);\n setToasts(shown => shown.filter(toast => toast.id !== id));\n }, []);\n\n const show = useCallback(\n (toast: ToastInput) => {\n next.current += 1;\n const id = next.current;\n setToasts(shown => [...shown, { ...toast, id }]);\n // A failure stays until it is dismissed, and so does a toast that\n // offers an action: Undo on a timer is a time limit on the reader's\n // only way back (WCAG 2.2.1). The rest say their piece and go.\n if (toast.tone !== 'failure' && toast.action === undefined)\n timers.current.set(\n id,\n setTimeout(() => dismiss(id), timeoutMs),\n );\n return id;\n },\n [dismiss, timeoutMs],\n );\n\n useEffect(() => {\n const pending = timers.current;\n return () => {\n for (const timer of pending.values()) clearTimeout(timer);\n pending.clear();\n };\n }, []);\n\n const value = useMemo(\n () => ({ toasts, show, dismiss }),\n [toasts, show, dismiss],\n );\n return (\n <ToastContext.Provider value={value}>{children}</ToastContext.Provider>\n );\n}\n\n/**\n * The {@link ToastProvider} above, or `undefined` outside one: a caller with\n * no region to show a toast in keeps what it has to say where it can, rather\n * than having it vanish.\n */\nexport const useToasts = (): Toasts | undefined => useContext(ToastContext);\n\nconst ALERT_TONE: Readonly<Record<ToastTone, AlertTone>> = {\n success: 'success',\n undo: 'info',\n info: 'info',\n failure: 'danger',\n};\n\nfunction ToastItem({\n toast,\n onDismiss,\n}: {\n readonly toast: Toast;\n readonly onDismiss: () => void;\n}) {\n const t = useT();\n const message = useErrorMessage();\n const errorAction = useErrorAction();\n // A failure with no action of its own offers the host's next step for its\n // code, when there is one.\n const next =\n toast.tone === 'failure' ? errorAction(toast.failure) : undefined;\n const action =\n toast.action ??\n (next === undefined\n ? undefined\n : { label: next.label, onAction: next.onAction });\n return (\n <Alert\n tone={ALERT_TONE[toast.tone]}\n data-toast-tone={toast.tone}\n className=\"pointer-events-auto bg-surface shadow-overlay\"\n action={\n <div className=\"flex gap-3xs\">\n {action !== undefined && (\n <Button\n type=\"button\"\n variant=\"secondary\"\n onClick={() => {\n action.onAction();\n onDismiss();\n }}\n >\n {action.label}\n </Button>\n )}\n <Button type=\"button\" variant=\"ghost\" onClick={onDismiss}>\n {t('toast.dismiss')}\n </Button>\n </div>\n }\n >\n {toast.tone === 'failure' ? message(toast.failure) : toast.message}\n </Alert>\n );\n}\n\n/**\n * Where a host's toasts render, each through {@link Alert}: a failure is\n * `role=\"alert\"` and every other tone `role=\"status\"`.\n *\n * ⚠️ **An empty region takes no room and no clicks.** It stays mounted, so a\n * toast lands in a region that already exists, but at zero size and with\n * `pointer-events: none` — a fixed box that kept its padding once covered two\n * focused links and swallowed clicks (WCAG 2.4.11). While toasts show, the\n * document's bottom scroll padding is the region's height, so a control\n * focused by keyboard scrolls clear of it. F6 moves focus into the region.\n */\nexport function ToastRegion() {\n const toasts = useToasts();\n const t = useT();\n const region = useRef<HTMLElement>(null);\n const shown = toasts?.toasts ?? [];\n const count = shown.length;\n\n useEffect(() => {\n const root = document.documentElement;\n if (count === 0) {\n root.style.removeProperty('scroll-padding-bottom');\n return;\n }\n // The region renders whenever a toast can: the ref is set by now.\n const node = region.current as HTMLElement;\n root.style.setProperty('scroll-padding-bottom', `${node.offsetHeight}px`);\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'F6') return;\n event.preventDefault();\n node.focus();\n };\n document.addEventListener('keydown', onKeyDown);\n return () => document.removeEventListener('keydown', onKeyDown);\n }, [count]);\n\n if (toasts === undefined) return null;\n return (\n <section\n ref={region}\n aria-label={t('toast.region')}\n tabIndex={-1}\n data-testid=\"toast-region\"\n className={cn(\n 'bottom-0 right-0 pointer-events-none fixed z-(--z-toast) flex flex-col gap-xs focus-ring',\n count === 0 ? 'size-0 overflow-hidden' : 'max-w-md w-full p-s',\n )}\n >\n {shown.map(toast => (\n <ToastItem\n key={toast.id}\n toast={toast}\n onDismiss={() => toasts.dismiss(toast.id)}\n />\n ))}\n </section>\n );\n}\n"],"names":["createContext","useCallback","useContext","useEffect","useMemo","useRef","useState","useErrorMessage","useT","useErrorAction","Alert","Button","cn","TOAST_TIMEOUT_MS","ToastContext","undefined","ToastProvider","children","timeoutMs","toasts","setToasts","next","timers","Map","dismiss","id","clearTimeout","current","get","delete","shown","filter","toast","show","tone","action","set","setTimeout","pending","timer","values","clear","value","Provider","useToasts","ALERT_TONE","success","undo","info","failure","ToastItem","onDismiss","t","message","errorAction","label","onAction","data-toast-tone","className","div","type","variant","onClick","ToastRegion","region","count","length","root","document","documentElement","style","removeProperty","node","setProperty","offsetHeight","onKeyDown","event","key","preventDefault","focus","addEventListener","removeEventListener","section","ref","aria-label","tabIndex","data-testid","map"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-palette.d.ts","sourceRoot":"","sources":["../../../../src/ui/organisms/command-palette/command-palette.tsx"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,WAAW,EACjB,MAAM,eAAe,CAAC;AAevB,oFAAoF;AACpF,MAAM,WAAW,oBAAoB;IACnC,mFAAmF;IACnF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7C,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,+DAA+D;IAC/D,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IAC9B;;;;;;;OAOG;IACH,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,aAAa,EAAE,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,KAAK,IAAI,CAAC;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC;;;OAGG;IACH,QAAQ,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IAC1C,MAAM,EAAE,oBAAoB,CAAC;CAC9B;
|
|
1
|
+
{"version":3,"file":"command-palette.d.ts","sourceRoot":"","sources":["../../../../src/ui/organisms/command-palette/command-palette.tsx"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,WAAW,EACjB,MAAM,eAAe,CAAC;AAevB,oFAAoF;AACpF,MAAM,WAAW,oBAAoB;IACnC,mFAAmF;IACnF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7C,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,+DAA+D;IAC/D,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IAC9B;;;;;;;OAOG;IACH,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,aAAa,EAAE,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,KAAK,IAAI,CAAC;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC;;;OAGG;IACH,QAAQ,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IAC1C,MAAM,EAAE,oBAAoB,CAAC;CAC9B;AA2GD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,OAAO,EACP,KAAK,EACL,KAAK,EACL,aAAa,EACb,IAAI,EACJ,YAAY,EACZ,QAAQ,EACR,MAAM,GACP,EAAE,mBAAmB,+BAqJrB"}
|
|
@@ -43,18 +43,14 @@ function Group({ group, labels }) {
|
|
|
43
43
|
/*#__PURE__*/ _jsx(GroupHeading, {
|
|
44
44
|
label: group.label
|
|
45
45
|
}),
|
|
46
|
-
group.isLoading &&
|
|
46
|
+
group.isLoading && // The wait is said once, by the palette's status region: a listbox
|
|
47
|
+
// holds options, and text inside it is read as none of them.
|
|
48
|
+
/*#__PURE__*/ _jsx("div", {
|
|
47
49
|
role: "presentation",
|
|
48
50
|
className: "px-2xs py-3xs",
|
|
49
|
-
children:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
children: labels.loading
|
|
53
|
-
}),
|
|
54
|
-
/*#__PURE__*/ _jsx(SkeletonText, {
|
|
55
|
-
lines: 2
|
|
56
|
-
})
|
|
57
|
-
]
|
|
51
|
+
children: /*#__PURE__*/ _jsx(SkeletonText, {
|
|
52
|
+
lines: 2
|
|
53
|
+
})
|
|
58
54
|
}),
|
|
59
55
|
group.options.map((option)=>/*#__PURE__*/ _jsx(CommandRow, {
|
|
60
56
|
option: option
|
|
@@ -110,6 +106,7 @@ function Group({ group, labels }) {
|
|
|
110
106
|
// that fires after the bug is reported.
|
|
111
107
|
groups.forEach((group)=>group.options.forEach(commandEffectOf));
|
|
112
108
|
const isEmpty = groups.every(groupIsEmpty);
|
|
109
|
+
const isAnswering = groups.some((group)=>group.isLoading);
|
|
113
110
|
const pop = ()=>{
|
|
114
111
|
if (stack.length === 0) {
|
|
115
112
|
onClose();
|
|
@@ -185,9 +182,11 @@ function Group({ group, labels }) {
|
|
|
185
182
|
children: [
|
|
186
183
|
page.title !== undefined && /*#__PURE__*/ _jsx("button", {
|
|
187
184
|
type: "button",
|
|
188
|
-
"
|
|
185
|
+
// Its name holds the text it shows (WCAG 2.5.3): "Volver" is
|
|
186
|
+
// said, and "Nuevo" is both seen and said.
|
|
187
|
+
"aria-label": `${labels.back} ${page.title}`,
|
|
189
188
|
onClick: pop,
|
|
190
|
-
className: "shrink-0 rounded-md bg-surface px-2xs py-3xs text-step-xs text-content-muted focus-ring",
|
|
189
|
+
className: "inline-flex min-h-(--target-min) shrink-0 items-center rounded-md bg-surface px-2xs py-3xs text-step-xs text-content-muted focus-ring",
|
|
191
190
|
children: page.title
|
|
192
191
|
}),
|
|
193
192
|
/*#__PURE__*/ _jsx(ComboboxInput, {
|
|
@@ -201,13 +200,21 @@ function Group({ group, labels }) {
|
|
|
201
200
|
})
|
|
202
201
|
]
|
|
203
202
|
}),
|
|
203
|
+
/*#__PURE__*/ _jsx("span", {
|
|
204
|
+
role: "status",
|
|
205
|
+
className: "sr-only",
|
|
206
|
+
children: isEmpty ? labels.empty : isAnswering ? labels.loading : ''
|
|
207
|
+
}),
|
|
204
208
|
/*#__PURE__*/ _jsx(ComboboxOptions, {
|
|
205
209
|
static: true,
|
|
210
|
+
// Still filling: a listbox with no options yet is not a broken one.
|
|
211
|
+
"aria-busy": isAnswering || undefined,
|
|
206
212
|
className: cn('max-h-(--overlay-max-block) overflow-y-auto p-3xs', // The panel is a focus-managed CONTAINER, not a target: the ring
|
|
207
213
|
// belongs on the row inside it.
|
|
208
214
|
'focus:outline-none'),
|
|
209
215
|
children: isEmpty ? /*#__PURE__*/ _jsx("div", {
|
|
210
216
|
role: "presentation",
|
|
217
|
+
"aria-hidden": "true",
|
|
211
218
|
className: "px-2xs py-s text-step-sm text-content-muted",
|
|
212
219
|
children: labels.empty
|
|
213
220
|
}) : groups.filter((group)=>!groupIsEmpty(group)).map((group)=>/*#__PURE__*/ _jsx(Group, {
|