@setsquare/ui 0.1.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/CHANGELOG.md +70 -0
- package/LICENSE +202 -0
- package/NOTICE +49 -0
- package/README.md +313 -0
- package/dist/index.d.ts +129 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/cn.d.ts +4 -0
- package/dist/lib/cn.d.ts.map +1 -0
- package/dist/lib/cn.js +59 -0
- package/dist/lib/cn.js.map +1 -0
- package/dist/lib/tokens.d.ts +5 -0
- package/dist/lib/tokens.d.ts.map +1 -0
- package/dist/lib/tokens.js +22 -0
- package/dist/lib/tokens.js.map +1 -0
- package/dist/lib/use-element-width.d.ts +4 -0
- package/dist/lib/use-element-width.d.ts.map +1 -0
- package/dist/lib/use-element-width.js +17 -0
- package/dist/lib/use-element-width.js.map +1 -0
- package/dist/lint/index.d.ts +41 -0
- package/dist/lint/index.d.ts.map +1 -0
- package/dist/lint/index.js +648 -0
- package/dist/lint/index.js.map +1 -0
- package/dist/shell/app-header.d.ts +13 -0
- package/dist/shell/app-header.d.ts.map +1 -0
- package/dist/shell/app-header.js +11 -0
- package/dist/shell/app-header.js.map +1 -0
- package/dist/shell/dashboard-shell.d.ts +67 -0
- package/dist/shell/dashboard-shell.d.ts.map +1 -0
- package/dist/shell/dashboard-shell.js +168 -0
- package/dist/shell/dashboard-shell.js.map +1 -0
- package/dist/shell/page-header.d.ts +19 -0
- package/dist/shell/page-header.d.ts.map +1 -0
- package/dist/shell/page-header.js +11 -0
- package/dist/shell/page-header.js.map +1 -0
- package/dist/shell/right-panel.d.ts +25 -0
- package/dist/shell/right-panel.d.ts.map +1 -0
- package/dist/shell/right-panel.js +37 -0
- package/dist/shell/right-panel.js.map +1 -0
- package/dist/shell/sidebar.d.ts +68 -0
- package/dist/shell/sidebar.d.ts.map +1 -0
- package/dist/shell/sidebar.js +68 -0
- package/dist/shell/sidebar.js.map +1 -0
- package/dist/shell/site-footer.d.ts +28 -0
- package/dist/shell/site-footer.d.ts.map +1 -0
- package/dist/shell/site-footer.js +15 -0
- package/dist/shell/site-footer.js.map +1 -0
- package/dist/shell/site-header-padding.d.ts +7 -0
- package/dist/shell/site-header-padding.d.ts.map +1 -0
- package/dist/shell/site-header-padding.js +27 -0
- package/dist/shell/site-header-padding.js.map +1 -0
- package/dist/shell/site-header.d.ts +23 -0
- package/dist/shell/site-header.d.ts.map +1 -0
- package/dist/shell/site-header.js +13 -0
- package/dist/shell/site-header.js.map +1 -0
- package/dist/shell/site-menu.d.ts +27 -0
- package/dist/shell/site-menu.d.ts.map +1 -0
- package/dist/shell/site-menu.js +56 -0
- package/dist/shell/site-menu.js.map +1 -0
- package/dist/shell/site-nav-link.d.ts +24 -0
- package/dist/shell/site-nav-link.d.ts.map +1 -0
- package/dist/shell/site-nav-link.js +35 -0
- package/dist/shell/site-nav-link.js.map +1 -0
- package/dist/shell/site-nav.d.ts +12 -0
- package/dist/shell/site-nav.d.ts.map +1 -0
- package/dist/shell/site-nav.js +9 -0
- package/dist/shell/site-nav.js.map +1 -0
- package/dist/shell/site-path.d.ts +8 -0
- package/dist/shell/site-path.d.ts.map +1 -0
- package/dist/shell/site-path.js +11 -0
- package/dist/shell/site-path.js.map +1 -0
- package/dist/shell/site-shell.d.ts +25 -0
- package/dist/shell/site-shell.d.ts.map +1 -0
- package/dist/shell/site-shell.js +17 -0
- package/dist/shell/site-shell.js.map +1 -0
- package/dist/styles/styles.css +10 -0
- package/dist/styles/theme.css +579 -0
- package/dist/styles/tokens.css +295 -0
- package/dist/testing/index.d.ts +321 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +1175 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/tokens.json +982 -0
- package/dist/ui/alert.d.ts +35 -0
- package/dist/ui/alert.d.ts.map +1 -0
- package/dist/ui/alert.js +65 -0
- package/dist/ui/alert.js.map +1 -0
- package/dist/ui/avatar.d.ts +16 -0
- package/dist/ui/avatar.d.ts.map +1 -0
- package/dist/ui/avatar.js +13 -0
- package/dist/ui/avatar.js.map +1 -0
- package/dist/ui/badge.d.ts +13 -0
- package/dist/ui/badge.d.ts.map +1 -0
- package/dist/ui/badge.js +24 -0
- package/dist/ui/badge.js.map +1 -0
- package/dist/ui/breadcrumbs.d.ts +13 -0
- package/dist/ui/breadcrumbs.d.ts.map +1 -0
- package/dist/ui/breadcrumbs.js +12 -0
- package/dist/ui/breadcrumbs.js.map +1 -0
- package/dist/ui/button-styles.d.ts +24 -0
- package/dist/ui/button-styles.d.ts.map +1 -0
- package/dist/ui/button-styles.js +50 -0
- package/dist/ui/button-styles.js.map +1 -0
- package/dist/ui/button.d.ts +27 -0
- package/dist/ui/button.d.ts.map +1 -0
- package/dist/ui/button.js +30 -0
- package/dist/ui/button.js.map +1 -0
- package/dist/ui/calendar.d.ts +19 -0
- package/dist/ui/calendar.d.ts.map +1 -0
- package/dist/ui/calendar.js +52 -0
- package/dist/ui/calendar.js.map +1 -0
- package/dist/ui/card.d.ts +30 -0
- package/dist/ui/card.d.ts.map +1 -0
- package/dist/ui/card.js +18 -0
- package/dist/ui/card.js.map +1 -0
- package/dist/ui/checkbox.d.ts +36 -0
- package/dist/ui/checkbox.d.ts.map +1 -0
- package/dist/ui/checkbox.js +38 -0
- package/dist/ui/checkbox.js.map +1 -0
- package/dist/ui/code-block.d.ts +20 -0
- package/dist/ui/code-block.d.ts.map +1 -0
- package/dist/ui/code-block.js +22 -0
- package/dist/ui/code-block.js.map +1 -0
- package/dist/ui/combo-box.d.ts +17 -0
- package/dist/ui/combo-box.d.ts.map +1 -0
- package/dist/ui/combo-box.js +14 -0
- package/dist/ui/combo-box.js.map +1 -0
- package/dist/ui/copy-button.d.ts +14 -0
- package/dist/ui/copy-button.d.ts.map +1 -0
- package/dist/ui/copy-button.js +36 -0
- package/dist/ui/copy-button.js.map +1 -0
- package/dist/ui/date-field.d.ts +38 -0
- package/dist/ui/date-field.d.ts.map +1 -0
- package/dist/ui/date-field.js +78 -0
- package/dist/ui/date-field.js.map +1 -0
- package/dist/ui/dialog.d.ts +41 -0
- package/dist/ui/dialog.d.ts.map +1 -0
- package/dist/ui/dialog.js +69 -0
- package/dist/ui/dialog.js.map +1 -0
- package/dist/ui/disclosure.d.ts +18 -0
- package/dist/ui/disclosure.d.ts.map +1 -0
- package/dist/ui/disclosure.js +15 -0
- package/dist/ui/disclosure.js.map +1 -0
- package/dist/ui/empty-state.d.ts +17 -0
- package/dist/ui/empty-state.d.ts.map +1 -0
- package/dist/ui/empty-state.js +9 -0
- package/dist/ui/empty-state.js.map +1 -0
- package/dist/ui/error-state.d.ts +45 -0
- package/dist/ui/error-state.d.ts.map +1 -0
- package/dist/ui/error-state.js +88 -0
- package/dist/ui/error-state.js.map +1 -0
- package/dist/ui/field.d.ts +46 -0
- package/dist/ui/field.d.ts.map +1 -0
- package/dist/ui/field.js +79 -0
- package/dist/ui/field.js.map +1 -0
- package/dist/ui/file-upload.d.ts +54 -0
- package/dist/ui/file-upload.d.ts.map +1 -0
- package/dist/ui/file-upload.js +170 -0
- package/dist/ui/file-upload.js.map +1 -0
- package/dist/ui/form.d.ts +56 -0
- package/dist/ui/form.d.ts.map +1 -0
- package/dist/ui/form.js +137 -0
- package/dist/ui/form.js.map +1 -0
- package/dist/ui/grid-list.d.ts +24 -0
- package/dist/ui/grid-list.d.ts.map +1 -0
- package/dist/ui/grid-list.js +21 -0
- package/dist/ui/grid-list.js.map +1 -0
- package/dist/ui/hero.d.ts +36 -0
- package/dist/ui/hero.d.ts.map +1 -0
- package/dist/ui/hero.js +19 -0
- package/dist/ui/hero.js.map +1 -0
- package/dist/ui/icon-button.d.ts +18 -0
- package/dist/ui/icon-button.d.ts.map +1 -0
- package/dist/ui/icon-button.js +13 -0
- package/dist/ui/icon-button.js.map +1 -0
- package/dist/ui/icon.d.ts +20 -0
- package/dist/ui/icon.d.ts.map +1 -0
- package/dist/ui/icon.js +26 -0
- package/dist/ui/icon.js.map +1 -0
- package/dist/ui/kbd.d.ts +7 -0
- package/dist/ui/kbd.d.ts.map +1 -0
- package/dist/ui/kbd.js +7 -0
- package/dist/ui/kbd.js.map +1 -0
- package/dist/ui/link-card.d.ts +23 -0
- package/dist/ui/link-card.d.ts.map +1 -0
- package/dist/ui/link-card.js +19 -0
- package/dist/ui/link-card.js.map +1 -0
- package/dist/ui/list-box.d.ts +17 -0
- package/dist/ui/list-box.d.ts.map +1 -0
- package/dist/ui/list-box.js +18 -0
- package/dist/ui/list-box.js.map +1 -0
- package/dist/ui/menu.d.ts +31 -0
- package/dist/ui/menu.d.ts.map +1 -0
- package/dist/ui/menu.js +40 -0
- package/dist/ui/menu.js.map +1 -0
- package/dist/ui/number-field.d.ts +19 -0
- package/dist/ui/number-field.d.ts.map +1 -0
- package/dist/ui/number-field.js +19 -0
- package/dist/ui/number-field.js.map +1 -0
- package/dist/ui/prose.d.ts +12 -0
- package/dist/ui/prose.d.ts.map +1 -0
- package/dist/ui/prose.js +11 -0
- package/dist/ui/prose.js.map +1 -0
- package/dist/ui/radio-group.d.ts +20 -0
- package/dist/ui/radio-group.d.ts.map +1 -0
- package/dist/ui/radio-group.js +16 -0
- package/dist/ui/radio-group.js.map +1 -0
- package/dist/ui/scroll-region.d.ts +18 -0
- package/dist/ui/scroll-region.d.ts.map +1 -0
- package/dist/ui/scroll-region.js +29 -0
- package/dist/ui/scroll-region.js.map +1 -0
- package/dist/ui/search-field.d.ts +12 -0
- package/dist/ui/search-field.d.ts.map +1 -0
- package/dist/ui/search-field.js +12 -0
- package/dist/ui/search-field.js.map +1 -0
- package/dist/ui/search.d.ts +43 -0
- package/dist/ui/search.d.ts.map +1 -0
- package/dist/ui/search.js +45 -0
- package/dist/ui/search.js.map +1 -0
- package/dist/ui/section.d.ts +50 -0
- package/dist/ui/section.d.ts.map +1 -0
- package/dist/ui/section.js +25 -0
- package/dist/ui/section.js.map +1 -0
- package/dist/ui/segmented-control.d.ts +18 -0
- package/dist/ui/segmented-control.d.ts.map +1 -0
- package/dist/ui/segmented-control.js +17 -0
- package/dist/ui/segmented-control.js.map +1 -0
- package/dist/ui/select.d.ts +18 -0
- package/dist/ui/select.d.ts.map +1 -0
- package/dist/ui/select.js +13 -0
- package/dist/ui/select.js.map +1 -0
- package/dist/ui/selection-bar.d.ts +28 -0
- package/dist/ui/selection-bar.d.ts.map +1 -0
- package/dist/ui/selection-bar.js +35 -0
- package/dist/ui/selection-bar.js.map +1 -0
- package/dist/ui/sheet.d.ts +21 -0
- package/dist/ui/sheet.d.ts.map +1 -0
- package/dist/ui/sheet.js +16 -0
- package/dist/ui/sheet.js.map +1 -0
- package/dist/ui/skeleton.d.ts +31 -0
- package/dist/ui/skeleton.d.ts.map +1 -0
- package/dist/ui/skeleton.js +38 -0
- package/dist/ui/skeleton.js.map +1 -0
- package/dist/ui/slider.d.ts +31 -0
- package/dist/ui/slider.d.ts.map +1 -0
- package/dist/ui/slider.js +33 -0
- package/dist/ui/slider.js.map +1 -0
- package/dist/ui/spinner.d.ts +27 -0
- package/dist/ui/spinner.d.ts.map +1 -0
- package/dist/ui/spinner.js +21 -0
- package/dist/ui/spinner.js.map +1 -0
- package/dist/ui/split.d.ts +14 -0
- package/dist/ui/split.d.ts.map +1 -0
- package/dist/ui/split.js +11 -0
- package/dist/ui/split.js.map +1 -0
- package/dist/ui/stat-list.d.ts +25 -0
- package/dist/ui/stat-list.d.ts.map +1 -0
- package/dist/ui/stat-list.js +14 -0
- package/dist/ui/stat-list.js.map +1 -0
- package/dist/ui/steps.d.ts +23 -0
- package/dist/ui/steps.d.ts.map +1 -0
- package/dist/ui/steps.js +16 -0
- package/dist/ui/steps.js.map +1 -0
- package/dist/ui/switch.d.ts +13 -0
- package/dist/ui/switch.d.ts.map +1 -0
- package/dist/ui/switch.js +12 -0
- package/dist/ui/switch.js.map +1 -0
- package/dist/ui/table.d.ts +25 -0
- package/dist/ui/table.d.ts.map +1 -0
- package/dist/ui/table.js +29 -0
- package/dist/ui/table.js.map +1 -0
- package/dist/ui/tag.d.ts +14 -0
- package/dist/ui/tag.d.ts.map +1 -0
- package/dist/ui/tag.js +11 -0
- package/dist/ui/tag.js.map +1 -0
- package/dist/ui/text-field.d.ts +27 -0
- package/dist/ui/text-field.d.ts.map +1 -0
- package/dist/ui/text-field.js +17 -0
- package/dist/ui/text-field.js.map +1 -0
- package/dist/ui/text-link.d.ts +17 -0
- package/dist/ui/text-link.d.ts.map +1 -0
- package/dist/ui/text-link.js +15 -0
- package/dist/ui/text-link.js.map +1 -0
- package/dist/ui/theme-switcher.d.ts +26 -0
- package/dist/ui/theme-switcher.d.ts.map +1 -0
- package/dist/ui/theme-switcher.js +32 -0
- package/dist/ui/theme-switcher.js.map +1 -0
- package/dist/ui/theme.d.ts +54 -0
- package/dist/ui/theme.d.ts.map +1 -0
- package/dist/ui/theme.js +135 -0
- package/dist/ui/theme.js.map +1 -0
- package/dist/ui/toast.d.ts +34 -0
- package/dist/ui/toast.d.ts.map +1 -0
- package/dist/ui/toast.js +69 -0
- package/dist/ui/toast.js.map +1 -0
- package/dist/ui/tooltip.d.ts +10 -0
- package/dist/ui/tooltip.d.ts.map +1 -0
- package/dist/ui/tooltip.js +10 -0
- package/dist/ui/tooltip.js.map +1 -0
- package/package.json +123 -0
- package/templates/next/error.tsx +32 -0
- package/templates/next/loading.tsx +19 -0
- package/templates/next/not-found.tsx +18 -0
- package/templates/next/page-transition.tsx +33 -0
- package/templates/next/portal-layout.tsx +70 -0
- package/templates/next/prefetch-on-intent.ts +31 -0
- package/templates/next/root-layout.tsx +16 -0
- package/templates/next/site-layout.tsx +58 -0
- package/templates/next/site-nav.ts +16 -0
- package/templates/next/site-not-found.tsx +21 -0
- package/templates/next/site-router.tsx +17 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Component, type ErrorInfo, type ReactNode } from 'react';
|
|
2
|
+
export type ErrorKind = 'load' | 'offline' | 'forbidden' | 'not-found' | 'server';
|
|
3
|
+
export interface ErrorStateProps {
|
|
4
|
+
kind?: ErrorKind;
|
|
5
|
+
/** Override the default title — name the thing: "Projects didn't load". */
|
|
6
|
+
title?: string;
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
/** Shows "Try again"; may return a promise (the button shows pending while it runs). */
|
|
9
|
+
onRetry?: () => void | Promise<void>;
|
|
10
|
+
/** An error ID or digest for support, shown with a Copy button. */
|
|
11
|
+
reference?: string;
|
|
12
|
+
/** More ways forward: a link home, contact support. */
|
|
13
|
+
actions?: ReactNode;
|
|
14
|
+
headingLevel?: 1 | 2 | 3;
|
|
15
|
+
/** Move focus to the heading when it replaces content the person was using. */
|
|
16
|
+
autoFocus?: boolean;
|
|
17
|
+
size?: 'page' | 'section';
|
|
18
|
+
className?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Something failed: says what happened in plain words, that it isn't the person's fault when it isn't,
|
|
22
|
+
* and what to do next. Never a raw error message or stack trace.
|
|
23
|
+
*/
|
|
24
|
+
export declare function ErrorState({ kind, title, children, onRetry, reference, actions, headingLevel, autoFocus, size, className, }: ErrorStateProps): import("react").JSX.Element;
|
|
25
|
+
export interface ErrorBoundaryProps {
|
|
26
|
+
children: ReactNode;
|
|
27
|
+
/** Render the fallback; call `reset` to try the children again. Default: an ErrorState with Try again. */
|
|
28
|
+
fallback?: (error: Error, reset: () => void) => ReactNode;
|
|
29
|
+
onError?: (error: Error, info: ErrorInfo) => void;
|
|
30
|
+
}
|
|
31
|
+
/** Contains a failing widget so the rest of the page keeps working. (Route segments use Next's error.tsx.) */
|
|
32
|
+
export declare class ErrorBoundary extends Component<ErrorBoundaryProps, {
|
|
33
|
+
error: Error | null;
|
|
34
|
+
}> {
|
|
35
|
+
state: {
|
|
36
|
+
error: Error | null;
|
|
37
|
+
};
|
|
38
|
+
static getDerivedStateFromError(error: Error): {
|
|
39
|
+
error: Error;
|
|
40
|
+
};
|
|
41
|
+
componentDidCatch(error: Error, info: ErrorInfo): void;
|
|
42
|
+
reset: () => void;
|
|
43
|
+
render(): 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 | undefined;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=error-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-state.d.ts","sourceRoot":"","sources":["../../src/ui/error-state.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,SAAS,EAKT,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAef,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,GAAG,QAAQ,CAAC;AA8BlF,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,wFAAwF;IACxF,OAAO,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACzB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EACzB,IAAa,EACb,KAAK,EACL,QAAQ,EACR,OAAO,EACP,SAAS,EACT,OAAO,EACP,YAAgB,EAChB,SAAS,EACT,IAAa,EACb,SAAS,GACV,EAAE,eAAe,+BA0FjB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,SAAS,CAAC;IACpB,0GAA0G;IAC1G,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,IAAI,KAAK,SAAS,CAAC;IAC1D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;CACnD;AAED,8GAA8G;AAC9G,qBAAa,aAAc,SAAQ,SAAS,CAAC,kBAAkB,EAAE;IAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;CAAE,CAAC;IAC9E,KAAK;QAAK,KAAK,EAAU,KAAK,GAAG,IAAI;MAAG;IACjD,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,KAAK;;MAE3C;IACQ,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,QAEvD;IACD,KAAK,aAAwC;IACpC,MAAM,gUASd;CACF"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Component, useEffect, useId, useRef, useState, } from 'react';
|
|
4
|
+
import { CircleAlert, Copy, FileQuestionMark, Lock, RefreshCw, ServerCrash, WifiOff, } from 'lucide-react';
|
|
5
|
+
import { cn } from '../lib/cn.js';
|
|
6
|
+
import { Button } from './button.js';
|
|
7
|
+
import { StatusMessage } from './form.js';
|
|
8
|
+
const kinds = {
|
|
9
|
+
load: {
|
|
10
|
+
Icon: CircleAlert,
|
|
11
|
+
title: "This didn't load",
|
|
12
|
+
body: 'Check your connection and try again. If it keeps happening, contact support and quote the reference below.',
|
|
13
|
+
},
|
|
14
|
+
offline: {
|
|
15
|
+
Icon: WifiOff,
|
|
16
|
+
title: "You're offline",
|
|
17
|
+
body: "This page needs a connection. It will load when you're back online, or you can try again now.",
|
|
18
|
+
},
|
|
19
|
+
forbidden: {
|
|
20
|
+
Icon: Lock,
|
|
21
|
+
title: "You don't have access to this",
|
|
22
|
+
body: 'Ask a workspace owner to give you access, or switch to a workspace you belong to.',
|
|
23
|
+
},
|
|
24
|
+
'not-found': {
|
|
25
|
+
Icon: FileQuestionMark,
|
|
26
|
+
title: 'Page not found',
|
|
27
|
+
body: 'The link may be out of date, or the page was moved or deleted. Check the address, or go back to the overview.',
|
|
28
|
+
},
|
|
29
|
+
server: {
|
|
30
|
+
Icon: ServerCrash,
|
|
31
|
+
title: 'Something went wrong on our side',
|
|
32
|
+
body: "It isn't anything you did. Try again in a minute; if it keeps happening, contact support and quote the reference below.",
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Something failed: says what happened in plain words, that it isn't the person's fault when it isn't,
|
|
37
|
+
* and what to do next. Never a raw error message or stack trace.
|
|
38
|
+
*/
|
|
39
|
+
export function ErrorState({ kind = 'load', title, children, onRetry, reference, actions, headingLevel = 2, autoFocus, size = 'page', className, }) {
|
|
40
|
+
const k = kinds[kind];
|
|
41
|
+
const H = `h${headingLevel}`;
|
|
42
|
+
const headingRef = useRef(null);
|
|
43
|
+
const headingId = useId();
|
|
44
|
+
const [pending, setPending] = useState(false);
|
|
45
|
+
const [copied, setCopied] = useState('');
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (autoFocus)
|
|
48
|
+
headingRef.current?.focus();
|
|
49
|
+
}, [autoFocus]);
|
|
50
|
+
const retry = async () => {
|
|
51
|
+
if (!onRetry)
|
|
52
|
+
return;
|
|
53
|
+
setPending(true);
|
|
54
|
+
try {
|
|
55
|
+
await onRetry();
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
setPending(false);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
return (_jsxs("div", { role: "group", "aria-labelledby": headingId, className: cn('mx-auto flex max-w-(--measure) flex-col items-center px-6 text-center', size === 'page' ? 'py-16' : 'py-10', className), children: [_jsx("span", { "aria-hidden": "true", className: cn('mb-4 grid place-items-center rounded-full bg-destructive-subtle text-destructive', size === 'page' ? 'size-14' : 'size-11'), children: _jsx(k.Icon, { className: size === 'page' ? 'size-7' : 'size-5' }) }), _jsx(H, { ref: headingRef, id: headingId, tabIndex: -1, className: cn('outline-none focus-visible:focus-ring rounded-sm', size === 'page' ? 'text-heading' : 'text-subheading'), children: title ?? k.title }), _jsx("div", { className: "mt-2 text-[15px] leading-6 text-muted-foreground", children: children ?? k.body }), (onRetry || actions) && (_jsxs("div", { className: "mt-6 flex flex-wrap items-center justify-center gap-(--control-gap)", children: [onRetry && (_jsxs(Button, { variant: "primary", isPending: pending, onPress: retry, children: [_jsx(RefreshCw, { "aria-hidden": "true" }), "Try again"] })), actions] })), reference && (_jsxs("div", { className: "mt-6 flex flex-col items-center gap-1", children: [_jsxs("p", { className: "flex flex-wrap items-center justify-center gap-2 text-[13px] text-muted-foreground", children: ["Reference", ' ', _jsx("code", { className: "rounded-sm bg-muted px-1.5 py-0.5 font-mono text-foreground", children: reference }), _jsxs(Button, { variant: "ghost", visualSize: "sm", "aria-label": `Copy reference ${reference}`, onPress: async () => {
|
|
62
|
+
try {
|
|
63
|
+
await navigator.clipboard.writeText(reference);
|
|
64
|
+
setCopied('Reference copied');
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
setCopied('Select the reference and copy it');
|
|
68
|
+
}
|
|
69
|
+
}, children: [_jsx(Copy, { "aria-hidden": "true" }), "Copy"] })] }), _jsx(StatusMessage, { tone: "neutral", children: copied })] }))] }));
|
|
70
|
+
}
|
|
71
|
+
/** Contains a failing widget so the rest of the page keeps working. (Route segments use Next's error.tsx.) */
|
|
72
|
+
export class ErrorBoundary extends Component {
|
|
73
|
+
state = { error: null };
|
|
74
|
+
static getDerivedStateFromError(error) {
|
|
75
|
+
return { error };
|
|
76
|
+
}
|
|
77
|
+
componentDidCatch(error, info) {
|
|
78
|
+
this.props.onError?.(error, info);
|
|
79
|
+
}
|
|
80
|
+
reset = () => this.setState({ error: null });
|
|
81
|
+
render() {
|
|
82
|
+
const { error } = this.state;
|
|
83
|
+
if (error)
|
|
84
|
+
return this.props.fallback ? (this.props.fallback(error, this.reset)) : (_jsx(ErrorState, { size: "section", onRetry: this.reset }));
|
|
85
|
+
return this.props.children;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=error-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-state.js","sourceRoot":"","sources":["../../src/ui/error-state.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/** Which fields show a visible necessity marker. Set once on <Form>. */
|
|
3
|
+
export type NecessityIndicator = 'required' | 'optional' | 'none';
|
|
4
|
+
export declare const NecessityContext: import("react").Context<NecessityIndicator>;
|
|
5
|
+
export type FieldWidth = 'xs' | 'sm' | 'md' | 'lg' | 'full';
|
|
6
|
+
/** Size a field to the answer it expects: a postcode is not as wide as an address. */
|
|
7
|
+
export declare const fieldWidths: Record<FieldWidth, string>;
|
|
8
|
+
/** Layer 2: a wrapper that paints the input box behind its children. Put the input and any buttons inside. */
|
|
9
|
+
export declare const fieldBox: string;
|
|
10
|
+
/** Layer 1: a text input inside `fieldBox` — the whole target, with the padding of the painted box. */
|
|
11
|
+
export declare const fieldInput = "field-input flex-1 cursor-text px-(--control-pad-x) text-sm text-foreground placeholder:text-muted-foreground data-disabled:cursor-not-allowed";
|
|
12
|
+
/** A multi-line input is taller than any target, so it is one layer: the textarea paints its own box. */
|
|
13
|
+
export declare const textAreaStyles: string;
|
|
14
|
+
export interface FieldLabelProps {
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
isRequired?: boolean;
|
|
17
|
+
/** Render as a plain span (group labels that are not <label>). */
|
|
18
|
+
elementType?: 'label' | 'span';
|
|
19
|
+
className?: string;
|
|
20
|
+
}
|
|
21
|
+
/** The visible label. Adds "(required)" or "(optional)" per the form's NecessityIndicator — hidden from AT, which gets aria-required instead. */
|
|
22
|
+
export declare function FieldLabel({ children, isRequired, elementType, className, }: FieldLabelProps): import("react").JSX.Element;
|
|
23
|
+
/** Hint text under the label: what to enter, in what format. Linked to the input by aria-describedby. */
|
|
24
|
+
export declare function FieldDescription({ children, className, }: {
|
|
25
|
+
children: ReactNode;
|
|
26
|
+
className?: string;
|
|
27
|
+
}): import("react").JSX.Element;
|
|
28
|
+
/**
|
|
29
|
+
* The error, shown only when the field is invalid. Sits between the hint and the input so it is next to the
|
|
30
|
+
* label at high zoom, starts with an icon and a hidden "Error:", and is linked by aria-describedby.
|
|
31
|
+
*/
|
|
32
|
+
export declare function FieldError({ children, requiredMessage, className, }: {
|
|
33
|
+
children?: ReactNode;
|
|
34
|
+
requiredMessage?: string;
|
|
35
|
+
className?: string;
|
|
36
|
+
}): import("react").JSX.Element;
|
|
37
|
+
/** Lower-case the first letter unless the label starts with an acronym ("VAT number"). */
|
|
38
|
+
export declare function lcFirst(label: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* Wrap a field's validate function so an empty required field gets a message that says what to do,
|
|
41
|
+
* instead of the browser's "Please fill out this field".
|
|
42
|
+
*/
|
|
43
|
+
export declare function withRequired<T>(isRequired: boolean | undefined, message: string, isEmpty: (value: T) => boolean, validate?: (value: T) => string | string[] | true | null | undefined): ((value: T) => string | true | string[] | null | undefined) | undefined;
|
|
44
|
+
/** Label, description and error stacked with consistent spacing. */
|
|
45
|
+
export declare const fieldStack = "group/field relative flex w-full flex-col gap-1.5";
|
|
46
|
+
//# sourceMappingURL=field.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../src/ui/field.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAKlE,wEAAwE;AACxE,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAC;AAClE,eAAO,MAAM,gBAAgB,6CAAgD,CAAC;AAE9E,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;AAC5D,sFAAsF;AACtF,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAMlD,CAAC;AAYF,8GAA8G;AAC9G,eAAO,MAAM,QAAQ,QAIgJ,CAAC;AAEtK,uGAAuG;AACvG,eAAO,MAAM,UAAU,mJAC2H,CAAC;AAEnJ,yGAAyG;AACzG,eAAO,MAAM,cAAc,QAG4E,CAAC;AAExG,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,SAAS,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kEAAkE;IAClE,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,iJAAiJ;AACjJ,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,UAAU,EACV,WAAqB,EACrB,SAAS,GACV,EAAE,eAAe,+BAqBjB;AAED,yGAAyG;AACzG,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BAYA;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,eAAe,EACf,SAAS,GACV,EAAE;IACD,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BAuBA;AAED,0FAA0F;AAC1F,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAC5B,UAAU,EAAE,OAAO,GAAG,SAAS,EAC/B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,EAC9B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,YAGrD,CAAC,8DAIjB;AAED,oEAAoE;AACpE,eAAO,MAAM,UAAU,sDAAsD,CAAC"}
|
package/dist/ui/field.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext } from 'react';
|
|
4
|
+
import { FieldError as RACFieldError, Label as RACLabel, Text } from 'react-aria-components';
|
|
5
|
+
import { CircleAlert } from 'lucide-react';
|
|
6
|
+
import { cn } from '../lib/cn.js';
|
|
7
|
+
export const NecessityContext = createContext('required');
|
|
8
|
+
/** Size a field to the answer it expects: a postcode is not as wide as an address. */
|
|
9
|
+
export const fieldWidths = {
|
|
10
|
+
xs: 'max-w-[8rem]',
|
|
11
|
+
sm: 'max-w-[14rem]',
|
|
12
|
+
md: 'max-w-[24rem]',
|
|
13
|
+
lg: 'max-w-[36rem]',
|
|
14
|
+
full: '',
|
|
15
|
+
};
|
|
16
|
+
/*
|
|
17
|
+
* Fields in three layers.
|
|
18
|
+
* 1. Target — the <input> itself (or the trigger button, or the date segments): max(--target-min,
|
|
19
|
+
* --control-height) tall, so it is the whole target and nothing routes clicks to it.
|
|
20
|
+
* 2. Visual — the box people see, painted behind it by the wrapper's ::before at --control-height.
|
|
21
|
+
* The focus ring, the invalid edge and the disabled ground all go on that pseudo-element.
|
|
22
|
+
* 3. Content — the text, placeholder and icons.
|
|
23
|
+
* At AA the two heights are equal. At AAA the input is 44px and the box is still 36px.
|
|
24
|
+
*/
|
|
25
|
+
/** Layer 2: a wrapper that paints the input box behind its children. Put the input and any buttons inside. */
|
|
26
|
+
export const fieldBox = 'field-paint flex w-full min-w-0 items-stretch text-sm text-foreground ' +
|
|
27
|
+
'has-[input:focus]:before:focus-ring has-[[role=spinbutton]:focus]:before:focus-ring ' +
|
|
28
|
+
'group-data-invalid/field:before:border-destructive group-data-invalid/field:before:shadow-[inset_0_0_0_1px_var(--destructive)] ' +
|
|
29
|
+
'group-data-disabled/field:cursor-not-allowed group-data-disabled/field:opacity-60 group-data-disabled/field:before:bg-muted has-[input:read-only]:before:bg-muted';
|
|
30
|
+
/** Layer 1: a text input inside `fieldBox` — the whole target, with the padding of the painted box. */
|
|
31
|
+
export const fieldInput = 'field-input flex-1 cursor-text px-(--control-pad-x) text-sm text-foreground placeholder:text-muted-foreground data-disabled:cursor-not-allowed';
|
|
32
|
+
/** A multi-line input is taller than any target, so it is one layer: the textarea paints its own box. */
|
|
33
|
+
export const textAreaStyles = 'w-full min-w-0 min-h-[calc(var(--control-height)*2)] cursor-text resize-y rounded-md border border-input bg-background px-(--control-pad-x) py-2 text-sm leading-6 text-foreground outline-none ' +
|
|
34
|
+
'placeholder:text-muted-foreground data-focused:focus-ring data-invalid:border-destructive data-invalid:shadow-[inset_0_0_0_1px_var(--destructive)] ' +
|
|
35
|
+
'data-disabled:cursor-not-allowed data-disabled:bg-muted data-disabled:opacity-60 read-only:bg-muted';
|
|
36
|
+
/** The visible label. Adds "(required)" or "(optional)" per the form's NecessityIndicator — hidden from AT, which gets aria-required instead. */
|
|
37
|
+
export function FieldLabel({ children, isRequired, elementType = 'label', className, }) {
|
|
38
|
+
const necessity = useContext(NecessityContext);
|
|
39
|
+
const marker = necessity === 'required' && isRequired
|
|
40
|
+
? ' (required)'
|
|
41
|
+
: necessity === 'optional' && !isRequired
|
|
42
|
+
? ' (optional)'
|
|
43
|
+
: '';
|
|
44
|
+
return (_jsxs(RACLabel, { elementType: elementType, className: cn('block text-sm leading-5 font-semibold text-foreground', className), children: [children, marker && (_jsx("span", { "aria-hidden": "true", className: "font-normal text-muted-foreground", children: marker }))] }));
|
|
45
|
+
}
|
|
46
|
+
/** Hint text under the label: what to enter, in what format. Linked to the input by aria-describedby. */
|
|
47
|
+
export function FieldDescription({ children, className, }) {
|
|
48
|
+
return (_jsx(Text, { slot: "description", className: cn('block max-w-(--measure) text-[13px] leading-5 text-muted-foreground', className), children: children }));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The error, shown only when the field is invalid. Sits between the hint and the input so it is next to the
|
|
52
|
+
* label at high zoom, starts with an icon and a hidden "Error:", and is linked by aria-describedby.
|
|
53
|
+
*/
|
|
54
|
+
export function FieldError({ children, requiredMessage, className, }) {
|
|
55
|
+
return (_jsx(RACFieldError, { "data-field-error": true, className: cn('flex items-start gap-1.5 text-[13px] leading-5 font-semibold text-destructive', className), children: ({ validationErrors, validationDetails }) => (_jsxs(_Fragment, { children: [_jsx(CircleAlert, { "aria-hidden": "true", className: "mt-0.5 size-4 shrink-0" }), _jsxs("span", { "data-field-error-text": true, children: [_jsx("span", { className: "sr-only", children: "Error: " }), children ??
|
|
56
|
+
(requiredMessage && validationDetails?.valueMissing && !validationDetails.customError
|
|
57
|
+
? requiredMessage
|
|
58
|
+
: validationErrors.join(' '))] })] })) }));
|
|
59
|
+
}
|
|
60
|
+
/** Lower-case the first letter unless the label starts with an acronym ("VAT number"). */
|
|
61
|
+
export function lcFirst(label) {
|
|
62
|
+
return /^[A-Z][a-z]/.test(label) ? label[0].toLowerCase() + label.slice(1) : label;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Wrap a field's validate function so an empty required field gets a message that says what to do,
|
|
66
|
+
* instead of the browser's "Please fill out this field".
|
|
67
|
+
*/
|
|
68
|
+
export function withRequired(isRequired, message, isEmpty, validate) {
|
|
69
|
+
if (!isRequired && !validate)
|
|
70
|
+
return undefined;
|
|
71
|
+
return (value) => {
|
|
72
|
+
if (isRequired && isEmpty(value))
|
|
73
|
+
return message;
|
|
74
|
+
return validate ? validate(value) : null;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** Label, description and error stacked with consistent spacing. */
|
|
78
|
+
export const fieldStack = 'group/field relative flex w-full flex-col gap-1.5';
|
|
79
|
+
//# sourceMappingURL=field.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field.js","sourceRoot":"","sources":["../../src/ui/field.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,aAAa,EAAE,UAAU,EAAkB,MAAM,OAAO,CAAC;AAClE,OAAO,EAAE,UAAU,IAAI,aAAa,EAAE,KAAK,IAAI,QAAQ,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7F,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAIlC,MAAM,CAAC,MAAM,gBAAgB,GAAG,aAAa,CAAqB,UAAU,CAAC,CAAC;AAG9E,sFAAsF;AACtF,MAAM,CAAC,MAAM,WAAW,GAA+B;IACrD,EAAE,EAAE,cAAc;IAClB,EAAE,EAAE,eAAe;IACnB,EAAE,EAAE,eAAe;IACnB,EAAE,EAAE,eAAe;IACnB,IAAI,EAAE,EAAE;CACT,CAAC;AAEF;;;;;;;;GAQG;AAEH,8GAA8G;AAC9G,MAAM,CAAC,MAAM,QAAQ,GACnB,wEAAwE;IACxE,sFAAsF;IACtF,iIAAiI;IACjI,mKAAmK,CAAC;AAEtK,uGAAuG;AACvG,MAAM,CAAC,MAAM,UAAU,GACrB,gJAAgJ,CAAC;AAEnJ,yGAAyG;AACzG,MAAM,CAAC,MAAM,cAAc,GACzB,kMAAkM;IAClM,qJAAqJ;IACrJ,qGAAqG,CAAC;AAUxG,iJAAiJ;AACjJ,MAAM,UAAU,UAAU,CAAC,EACzB,QAAQ,EACR,UAAU,EACV,WAAW,GAAG,OAAO,EACrB,SAAS,GACO;IAChB,MAAM,SAAS,GAAG,UAAU,CAAC,gBAAgB,CAAC,CAAC;IAC/C,MAAM,MAAM,GACV,SAAS,KAAK,UAAU,IAAI,UAAU;QACpC,CAAC,CAAC,aAAa;QACf,CAAC,CAAC,SAAS,KAAK,UAAU,IAAI,CAAC,UAAU;YACvC,CAAC,CAAC,aAAa;YACf,CAAC,CAAC,EAAE,CAAC;IACX,OAAO,CACL,MAAC,QAAQ,IACP,WAAW,EAAE,WAAW,EACxB,SAAS,EAAE,EAAE,CAAC,uDAAuD,EAAE,SAAS,CAAC,aAEhF,QAAQ,EACR,MAAM,IAAI,CACT,8BAAkB,MAAM,EAAC,SAAS,EAAC,mCAAmC,YACnE,MAAM,GACF,CACR,IACQ,CACZ,CAAC;AACJ,CAAC;AAED,yGAAyG;AACzG,MAAM,UAAU,gBAAgB,CAAC,EAC/B,QAAQ,EACR,SAAS,GAIV;IACC,OAAO,CACL,KAAC,IAAI,IACH,IAAI,EAAC,aAAa,EAClB,SAAS,EAAE,EAAE,CACX,qEAAqE,EACrE,SAAS,CACV,YAEA,QAAQ,GACJ,CACR,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,EACzB,QAAQ,EACR,eAAe,EACf,SAAS,GAKV;IACC,OAAO,CACL,KAAC,aAAa,8BAEZ,SAAS,EAAE,EAAE,CACX,+EAA+E,EAC/E,SAAS,CACV,YAEA,CAAC,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,EAAE,EAAE,CAAC,CAC5C,8BACE,KAAC,WAAW,mBAAa,MAAM,EAAC,SAAS,EAAC,wBAAwB,GAAG,EACrE,0DACE,eAAM,SAAS,EAAC,SAAS,wBAAe,EACvC,QAAQ;4BACP,CAAC,eAAe,IAAI,iBAAiB,EAAE,YAAY,IAAI,CAAC,iBAAiB,CAAC,WAAW;gCACnF,CAAC,CAAC,eAAe;gCACjB,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAC5B,IACN,CACJ,GACa,CACjB,CAAC;AACJ,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,OAAO,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACrF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,UAA+B,EAC/B,OAAe,EACf,OAA8B,EAC9B,QAAoE;IAEpE,IAAI,CAAC,UAAU,IAAI,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC/C,OAAO,CAAC,KAAQ,EAAE,EAAE;QAClB,IAAI,UAAU,IAAI,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,OAAO,CAAC;QACjD,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3C,CAAC,CAAC;AACJ,CAAC;AAED,oEAAoE;AACpE,MAAM,CAAC,MAAM,UAAU,GAAG,mDAAmD,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export type FileUploadStatus = 'uploading' | 'done' | 'error';
|
|
3
|
+
/** One file the app is uploading or has uploaded. The app owns this list; the component only draws it. */
|
|
4
|
+
export interface FileUploadItem {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
/** Bytes. */
|
|
8
|
+
size: number;
|
|
9
|
+
status: FileUploadStatus;
|
|
10
|
+
/** 0–100 while uploading. Leave unset if the upload can't report progress. */
|
|
11
|
+
progress?: number;
|
|
12
|
+
/** Why it failed, saying what to do: "The connection dropped. Try again." */
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface FileRejection {
|
|
16
|
+
file: File;
|
|
17
|
+
reason: string;
|
|
18
|
+
}
|
|
19
|
+
export interface FileUploadProps {
|
|
20
|
+
label: string;
|
|
21
|
+
/** What to upload, and the limits in words: "PDF or PNG, up to 10 MB each". */
|
|
22
|
+
description?: ReactNode;
|
|
23
|
+
/** MIME types ("application/pdf", "image/*") and/or extensions (".csv"). */
|
|
24
|
+
acceptedFileTypes?: string[];
|
|
25
|
+
/** How to name the accepted types in messages: "a PDF or PNG". Default lists the extensions. */
|
|
26
|
+
acceptedTypesLabel?: string;
|
|
27
|
+
/** Bytes. */
|
|
28
|
+
maxFileSize?: number;
|
|
29
|
+
/** Total files allowed, counting ones already in `items`. */
|
|
30
|
+
maxFiles?: number;
|
|
31
|
+
allowsMultiple?: boolean;
|
|
32
|
+
items: FileUploadItem[];
|
|
33
|
+
/** Files that passed the type, size and count checks. Start uploading them and add them to `items`. */
|
|
34
|
+
onSelect: (files: File[]) => void;
|
|
35
|
+
onRemove: (id: string) => void;
|
|
36
|
+
/** Show "Try again" on failed uploads. */
|
|
37
|
+
onRetry?: (id: string) => void;
|
|
38
|
+
/** Also told about files that were turned away (they are listed with the reason either way). */
|
|
39
|
+
onReject?: (rejections: FileRejection[]) => void;
|
|
40
|
+
isDisabled?: boolean;
|
|
41
|
+
className?: string;
|
|
42
|
+
}
|
|
43
|
+
/** "2.4 MB", in the page's locale. */
|
|
44
|
+
export declare function formatBytes(bytes: number): string;
|
|
45
|
+
/**
|
|
46
|
+
* Upload files by choosing them or dropping them. "Choose files" is always there, so dragging is never the
|
|
47
|
+
* only way (a keyboard or switch user never has to drag). Pasting a file onto the drop area also works.
|
|
48
|
+
*
|
|
49
|
+
* Type, size and count are checked here, before anything uploads, and every file that is turned away stays
|
|
50
|
+
* in the list with a reason that says what to do. Additions, completions and failures are announced once
|
|
51
|
+
* each through a polite live region. Removing a file moves focus to the next file, or back to "Choose files".
|
|
52
|
+
*/
|
|
53
|
+
export declare function FileUpload({ label, description, acceptedFileTypes, acceptedTypesLabel, maxFileSize, maxFiles, allowsMultiple, items, onSelect, onRemove, onRetry, onReject, isDisabled, className, }: FileUploadProps): import("react").JSX.Element;
|
|
54
|
+
//# sourceMappingURL=file-upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-upload.d.ts","sourceRoot":"","sources":["../../src/ui/file-upload.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA2B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAehE,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,MAAM,GAAG,OAAO,CAAC;AAE9D,0GAA0G;AAC1G,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,aAAa;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,gBAAgB,CAAC;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,4EAA4E;IAC5E,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,gGAAgG;IAChG,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,uGAAuG;IACvG,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IAClC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,0CAA0C;IAC1C,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,gGAAgG;IAChG,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,aAAa,EAAE,KAAK,IAAI,CAAC;IACjD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAGD,sCAAsC;AACtC,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAajD;AAiCD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,WAAW,EACX,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,QAAQ,EACR,cAAqB,EACrB,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,UAAU,EACV,SAAS,GACV,EAAE,eAAe,+BA6QjB"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId, useRef, useState } from 'react';
|
|
4
|
+
import { DropZone as RACDropZone, FileTrigger, ProgressBar as RACProgressBar, Text, isFileDropItem, } from 'react-aria-components';
|
|
5
|
+
import { CircleAlert, CircleCheck, FileText, RotateCcw, Upload, X } from 'lucide-react';
|
|
6
|
+
import { cn } from '../lib/cn.js';
|
|
7
|
+
import { Button } from './button.js';
|
|
8
|
+
import { fieldStack } from './field.js';
|
|
9
|
+
import { IconButton } from './icon-button.js';
|
|
10
|
+
const units = ['byte', 'kilobyte', 'megabyte', 'gigabyte'];
|
|
11
|
+
/** "2.4 MB", in the page's locale. */
|
|
12
|
+
export function formatBytes(bytes) {
|
|
13
|
+
let i = 0;
|
|
14
|
+
let n = bytes;
|
|
15
|
+
while (n >= 1000 && i < units.length - 1) {
|
|
16
|
+
n /= 1000;
|
|
17
|
+
i++;
|
|
18
|
+
}
|
|
19
|
+
return new Intl.NumberFormat(undefined, {
|
|
20
|
+
style: 'unit',
|
|
21
|
+
unit: units[i],
|
|
22
|
+
unitDisplay: i === 0 ? 'long' : 'short',
|
|
23
|
+
maximumFractionDigits: n < 10 && i > 0 ? 1 : 0,
|
|
24
|
+
}).format(n);
|
|
25
|
+
}
|
|
26
|
+
function matchesType(file, accepted) {
|
|
27
|
+
const name = file.name.toLowerCase();
|
|
28
|
+
const type = file.type.toLowerCase();
|
|
29
|
+
return accepted.some((a) => {
|
|
30
|
+
const t = a.toLowerCase().trim();
|
|
31
|
+
if (t.startsWith('.'))
|
|
32
|
+
return name.endsWith(t);
|
|
33
|
+
if (t.endsWith('/*'))
|
|
34
|
+
return type.startsWith(t.slice(0, -1));
|
|
35
|
+
return type === t;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function typesLabel(accepted) {
|
|
39
|
+
const names = accepted.map((a) => a.startsWith('.')
|
|
40
|
+
? a.slice(1).toUpperCase()
|
|
41
|
+
: a.endsWith('/*')
|
|
42
|
+
? a.slice(0, -2)
|
|
43
|
+
: (a.split('/')[1] ?? a).toUpperCase());
|
|
44
|
+
return names.length <= 1
|
|
45
|
+
? `a ${names[0] ?? 'supported'} file`
|
|
46
|
+
: `a ${names.slice(0, -1).join(', ')} or ${names[names.length - 1]} file`;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Upload files by choosing them or dropping them. "Choose files" is always there, so dragging is never the
|
|
50
|
+
* only way (a keyboard or switch user never has to drag). Pasting a file onto the drop area also works.
|
|
51
|
+
*
|
|
52
|
+
* Type, size and count are checked here, before anything uploads, and every file that is turned away stays
|
|
53
|
+
* in the list with a reason that says what to do. Additions, completions and failures are announced once
|
|
54
|
+
* each through a polite live region. Removing a file moves focus to the next file, or back to "Choose files".
|
|
55
|
+
*/
|
|
56
|
+
export function FileUpload({ label, description, acceptedFileTypes, acceptedTypesLabel, maxFileSize, maxFiles, allowsMultiple = true, items, onSelect, onRemove, onRetry, onReject, isDisabled, className, }) {
|
|
57
|
+
const labelId = useId();
|
|
58
|
+
const descriptionId = useId();
|
|
59
|
+
const listRef = useRef(null);
|
|
60
|
+
const triggerRef = useRef(null);
|
|
61
|
+
const [rejected, setRejected] = useState([]);
|
|
62
|
+
const [announcement, setAnnouncement] = useState('');
|
|
63
|
+
const nextId = useRef(0);
|
|
64
|
+
// Announce status changes the app makes (uploading → done / error), once each — worked out during render by
|
|
65
|
+
// comparing with the previous list, rather than in an effect.
|
|
66
|
+
const [prevItems, setPrevItems] = useState(items);
|
|
67
|
+
if (items !== prevItems) {
|
|
68
|
+
const before = new Map(prevItems.map((i) => [i.id, i.status]));
|
|
69
|
+
const messages = [];
|
|
70
|
+
for (const item of items) {
|
|
71
|
+
const was = before.get(item.id);
|
|
72
|
+
if (was && was !== item.status) {
|
|
73
|
+
if (item.status === 'done')
|
|
74
|
+
messages.push(`${item.name} uploaded`);
|
|
75
|
+
if (item.status === 'error')
|
|
76
|
+
messages.push(`${item.name} didn't upload. ${item.error ?? ''}`.trim());
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
setPrevItems(items);
|
|
80
|
+
if (messages.length)
|
|
81
|
+
setAnnouncement(messages.join('. '));
|
|
82
|
+
}
|
|
83
|
+
const accept = (files) => {
|
|
84
|
+
const ok = [];
|
|
85
|
+
const no = [];
|
|
86
|
+
let room = maxFiles != null ? Math.max(0, maxFiles - items.length) : Infinity;
|
|
87
|
+
for (const file of allowsMultiple ? files : files.slice(0, 1)) {
|
|
88
|
+
if (acceptedFileTypes?.length && !matchesType(file, acceptedFileTypes)) {
|
|
89
|
+
no.push({
|
|
90
|
+
file,
|
|
91
|
+
reason: `${file.name} isn't ${acceptedTypesLabel ?? typesLabel(acceptedFileTypes)}. Choose ${acceptedTypesLabel ?? typesLabel(acceptedFileTypes)}.`,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
else if (maxFileSize != null && file.size > maxFileSize) {
|
|
95
|
+
no.push({
|
|
96
|
+
file,
|
|
97
|
+
reason: `${file.name} is ${formatBytes(file.size)}. Choose a file under ${formatBytes(maxFileSize)}.`,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
else if (room <= 0) {
|
|
101
|
+
no.push({
|
|
102
|
+
file,
|
|
103
|
+
reason: `${file.name} wasn't added: you can add up to ${maxFiles} files. Remove one to add another.`,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
ok.push(file);
|
|
108
|
+
room--;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (ok.length)
|
|
112
|
+
onSelect(ok);
|
|
113
|
+
if (no.length) {
|
|
114
|
+
onReject?.(no);
|
|
115
|
+
setRejected((r) => [
|
|
116
|
+
...r,
|
|
117
|
+
...no.map((x) => ({
|
|
118
|
+
id: `rejected-${++nextId.current}`,
|
|
119
|
+
name: x.file.name,
|
|
120
|
+
size: x.file.size,
|
|
121
|
+
reason: x.reason,
|
|
122
|
+
})),
|
|
123
|
+
]);
|
|
124
|
+
}
|
|
125
|
+
const parts = [];
|
|
126
|
+
if (ok.length)
|
|
127
|
+
parts.push(ok.length === 1 ? `${ok[0].name} added` : `${ok.length} files added`);
|
|
128
|
+
if (no.length)
|
|
129
|
+
parts.push(no.length === 1 ? no[0].reason : `${no.length} files weren't added`);
|
|
130
|
+
setAnnouncement(parts.join('. '));
|
|
131
|
+
};
|
|
132
|
+
const onDrop = async (e) => {
|
|
133
|
+
const files = await Promise.all(e.items.filter(isFileDropItem).map((item) => item.getFile()));
|
|
134
|
+
accept(files);
|
|
135
|
+
};
|
|
136
|
+
// Focus goes to the next row's first button, else the previous row's, else "Choose files".
|
|
137
|
+
const removeAndRefocus = (id, remove) => {
|
|
138
|
+
const rows = Array.from(listRef.current?.querySelectorAll('[data-file-row]') ?? []);
|
|
139
|
+
const at = rows.findIndex((r) => r.dataset.fileRow === id);
|
|
140
|
+
const neighbour = rows[at + 1] ?? rows[at - 1];
|
|
141
|
+
const neighbourId = neighbour?.dataset.fileRow;
|
|
142
|
+
remove();
|
|
143
|
+
requestAnimationFrame(() => {
|
|
144
|
+
const target = neighbourId
|
|
145
|
+
? listRef.current?.querySelector(`[data-file-row="${CSS.escape(neighbourId)}"] button`)
|
|
146
|
+
: null;
|
|
147
|
+
(target ?? triggerRef.current)?.focus();
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
const rows = [
|
|
151
|
+
...items.map((item) => ({ kind: 'item', ...item })),
|
|
152
|
+
...rejected.map((r) => ({ kind: 'rejected', ...r })),
|
|
153
|
+
];
|
|
154
|
+
return (_jsxs("div", { role: "group", "aria-labelledby": labelId, "aria-describedby": description ? descriptionId : undefined, className: cn(fieldStack, 'max-w-[36rem]', className), children: [_jsx("span", { id: labelId, className: "block text-sm leading-5 font-semibold text-foreground", children: label }), description && (_jsx("p", { id: descriptionId, className: "max-w-(--measure) text-[13px] leading-5 text-muted-foreground", children: description })), _jsx(RACDropZone, { isDisabled: isDisabled, getDropOperation: () => 'copy', onDrop: onDrop, className: cn('relative flex flex-col items-center gap-3 rounded-lg border-2 border-dashed border-input bg-card px-4 py-6 text-center outline-none', 'data-drop-target:border-solid data-drop-target:border-primary data-drop-target:bg-brand-subtle data-focus-visible:focus-ring data-disabled:opacity-60'), children: ({ isDropTarget }) => (_jsxs(_Fragment, { children: [_jsx(Upload, { "aria-hidden": "true", className: "size-6 text-muted-foreground" }), _jsx(Text, { slot: "label", className: "text-sm leading-5 text-foreground", children: isDropTarget
|
|
155
|
+
? 'Drop to add'
|
|
156
|
+
: allowsMultiple
|
|
157
|
+
? 'Drop files here'
|
|
158
|
+
: 'Drop a file here' }), _jsx("span", { "aria-hidden": "true", className: "-my-1 text-[13px] leading-5 text-muted-foreground", children: "or" }), _jsx(FileTrigger, { acceptedFileTypes: acceptedFileTypes, allowsMultiple: allowsMultiple, onSelect: (list) => list && accept(Array.from(list)), children: _jsx(Button, { ref: triggerRef, isDisabled: isDisabled, children: allowsMultiple ? 'Choose files' : 'Choose a file' }) })] })) }), rows.length > 0 && (_jsx("ul", { ref: listRef, "aria-label": `${label}: files`, className: "mt-1 flex flex-col gap-2", children: rows.map((row) => (_jsxs("li", { "data-file-row": row.id, className: cn('flex items-start gap-3 rounded-md border px-3 py-2', row.kind === 'rejected' || row.status === 'error'
|
|
159
|
+
? 'border-destructive'
|
|
160
|
+
: 'border-border'), children: [row.kind === 'rejected' || row.status === 'error' ? (_jsx(CircleAlert, { "aria-hidden": "true", className: "mt-0.5 size-5 shrink-0 text-destructive" })) : row.status === 'done' ? (_jsx(CircleCheck, { "aria-hidden": "true", className: "mt-0.5 size-5 shrink-0 text-success" })) : (_jsx(FileText, { "aria-hidden": "true", className: "mt-0.5 size-5 shrink-0 text-muted-foreground" })), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [_jsx("span", { className: "text-sm leading-5 font-semibold [overflow-wrap:anywhere]", children: row.name }), _jsxs("span", { className: "text-[13px] leading-5 text-muted-foreground", children: [formatBytes(row.size), row.kind === 'item' && row.status === 'done' && ' · Uploaded', row.kind === 'item' && row.status === 'uploading' && ' · Uploading', row.kind === 'rejected' && ' · Not added', row.kind === 'item' && row.status === 'error' && ' · Not uploaded'] }), row.kind === 'item' && row.status === 'uploading' && (_jsx(RACProgressBar, { "aria-label": `Uploading ${row.name}`, value: row.progress, isIndeterminate: row.progress == null, className: "flex items-center gap-3", children: ({ percentage, valueText }) => (_jsxs(_Fragment, { children: [_jsx("div", { className: "h-2 flex-1 overflow-hidden rounded-full border border-input bg-muted aaa:h-3", children: _jsx("div", { className: cn('h-full rounded-full bg-primary transition-[width] duration-(--duration-fast) motion-reduce:transition-none', row.progress == null && 'w-1/3 animate-pulse'), style: row.progress == null ? undefined : { width: `${percentage ?? 0}%` } }) }), row.progress != null && (_jsx("span", { className: "text-[13px] text-muted-foreground tabular-nums", children: valueText }))] })) })), (row.kind === 'rejected' || row.status === 'error') && (_jsxs("span", { className: "text-[13px] leading-5 font-semibold text-destructive", children: [_jsx("span", { className: "sr-only", children: "Error: " }), row.kind === 'rejected'
|
|
161
|
+
? row.reason
|
|
162
|
+
: (row.error ?? 'It didn’t upload. Try again.')] }))] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [row.kind === 'item' && row.status === 'error' && onRetry && (_jsx(IconButton, { label: `Try uploading ${row.name} again`, icon: RotateCcw, visualSize: "icon-sm", onPress: () => onRetry(row.id) })), _jsx(IconButton, { label: row.kind === 'rejected'
|
|
163
|
+
? `Dismiss ${row.name}`
|
|
164
|
+
: row.status === 'uploading'
|
|
165
|
+
? `Cancel ${row.name}`
|
|
166
|
+
: `Remove ${row.name}`, icon: X, visualSize: "icon-sm", onPress: () => removeAndRefocus(row.id, () => row.kind === 'rejected'
|
|
167
|
+
? setRejected((r) => r.filter((x) => x.id !== row.id))
|
|
168
|
+
: onRemove(row.id)) })] })] }, row.id))) })), _jsx("p", { role: "status", className: "sr-only", children: announcement })] }));
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=file-upload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-upload.js","sourceRoot":"","sources":["../../src/ui/file-upload.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type FormProps as RACFormProps } from 'react-aria-components';
|
|
3
|
+
import { type NecessityIndicator } from './field.js';
|
|
4
|
+
export interface ErrorItem {
|
|
5
|
+
/** id of the input to move focus to. */
|
|
6
|
+
id: string;
|
|
7
|
+
message: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ErrorSummaryProps {
|
|
10
|
+
errors: ErrorItem[];
|
|
11
|
+
title?: string;
|
|
12
|
+
/** Move focus here when it appears (server errors, where no field has focus). */
|
|
13
|
+
autoFocus?: boolean;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Every problem in one place at the top of the form, each a link that moves focus to its field
|
|
18
|
+
* (WCAG 3.3.1, 3.3.3). The messages match the ones beside the fields, word for word.
|
|
19
|
+
*/
|
|
20
|
+
export declare function ErrorSummary({ errors, title, autoFocus, className, }: ErrorSummaryProps): import("react").JSX.Element | null;
|
|
21
|
+
export interface FormProps extends Omit<RACFormProps, 'className'> {
|
|
22
|
+
/** Which fields carry a visible "(required)" or "(optional)". Default 'required'. */
|
|
23
|
+
necessityIndicator?: NecessityIndicator;
|
|
24
|
+
/** Show the ErrorSummary above the fields when submitting fails. Default true. */
|
|
25
|
+
errorSummary?: boolean;
|
|
26
|
+
className?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* React Aria Form, validated natively on submit: invalid fields show their message and the first gets focus;
|
|
30
|
+
* the ErrorSummary lists them all. Server errors (`validationErrors`, keyed by field name) show the same way,
|
|
31
|
+
* and the summary takes focus because no field has it.
|
|
32
|
+
*/
|
|
33
|
+
export declare function Form({ necessityIndicator, errorSummary, validationErrors, onSubmit, className, children, ...props }: FormProps): import("react").JSX.Element;
|
|
34
|
+
export interface FormSectionProps {
|
|
35
|
+
title: string;
|
|
36
|
+
description?: ReactNode;
|
|
37
|
+
children: ReactNode;
|
|
38
|
+
className?: string;
|
|
39
|
+
}
|
|
40
|
+
/** A titled group of related fields: heading and hint on the left at wide sizes, fields on the right. */
|
|
41
|
+
export declare function FormSection({ title, description, children, className }: FormSectionProps): import("react").JSX.Element;
|
|
42
|
+
/** The form's buttons: the primary action first in reading order at the end of the row, cancel beside it. */
|
|
43
|
+
export declare function FormActions({ children, className }: {
|
|
44
|
+
children: ReactNode;
|
|
45
|
+
className?: string;
|
|
46
|
+
}): import("react").JSX.Element;
|
|
47
|
+
/**
|
|
48
|
+
* A polite live region for results that don't move focus: "Saved", "3 results" (WCAG 4.1.3).
|
|
49
|
+
* Render it once, empty, and change its text.
|
|
50
|
+
*/
|
|
51
|
+
export declare function StatusMessage({ children, tone, className, }: {
|
|
52
|
+
children?: ReactNode;
|
|
53
|
+
tone?: 'success' | 'neutral';
|
|
54
|
+
className?: string;
|
|
55
|
+
}): import("react").JSX.Element;
|
|
56
|
+
//# sourceMappingURL=form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../src/ui/form.tsx"],"names":[],"mappings":"AAEA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EAAmB,KAAK,SAAS,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAGxF,OAAO,EAAoB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEvE,MAAM,WAAW,SAAS;IACxB,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,KAA4B,EAC5B,SAAS,EACT,SAAS,GACV,EAAE,iBAAiB,sCA+CnB;AAED,MAAM,WAAW,SAAU,SAAQ,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC;IAChE,qFAAqF;IACrF,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,kFAAkF;IAClF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAwCD;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,EACnB,kBAA+B,EAC/B,YAAmB,EACnB,gBAAgB,EAChB,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,SAAS,+BA6DX;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,yGAAyG;AACzG,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,gBAAgB,+BAwBxF;AAED,6GAA6G;AAC7G,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAW/F;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,IAAgB,EAChB,SAAS,GACV,EAAE;IACD,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BAkBA"}
|