@surflane/ui 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/app-shell.d.ts +44 -3
- package/dist/app-shell.js +81 -8
- package/dist/bottom-navigation.d.ts +66 -0
- package/dist/bottom-navigation.js +36 -0
- package/dist/chart.d.ts +16 -0
- package/dist/chart.js +103 -48
- package/dist/css/accordion.css +11 -0
- package/dist/css/alert-dialog.css +11 -0
- package/{src → dist/css}/all.css +1 -1
- package/dist/css/app-shell.css +12 -0
- package/dist/css/autocomplete.css +15 -0
- package/dist/css/avatar.css +10 -0
- package/dist/css/badge.css +10 -0
- package/dist/css/bottom-navigation.css +8 -0
- package/dist/css/breadcrumbs.css +12 -0
- package/dist/css/button.css +8 -0
- package/dist/css/card.css +11 -0
- package/dist/css/chart.css +13 -0
- package/dist/css/checkbox-group.css +9 -0
- package/dist/css/checkbox.css +9 -0
- package/dist/css/choice-label.css +9 -0
- package/dist/css/code.css +13 -0
- package/dist/css/collapsible.css +11 -0
- package/dist/css/combobox.css +14 -0
- package/dist/css/context-menu.css +15 -0
- package/dist/css/date-picker.css +12 -0
- package/dist/css/dialog.css +10 -0
- package/dist/css/drawer.css +11 -0
- package/dist/css/empty-state.css +9 -0
- package/dist/css/field.css +10 -0
- package/dist/css/fieldset.css +8 -0
- package/dist/css/file-upload.css +17 -0
- package/dist/css/filter-bar.css +8 -0
- package/dist/css/form.css +8 -0
- package/dist/css/icon.css +8 -0
- package/dist/css/inline-message.css +10 -0
- package/dist/css/input.css +9 -0
- package/dist/css/layout.css +9 -0
- package/dist/css/link.css +9 -0
- package/dist/css/list.css +9 -0
- package/dist/css/markdown.css +18 -0
- package/dist/css/menu.css +14 -0
- package/dist/css/menubar.css +9 -0
- package/dist/css/meter.css +9 -0
- package/dist/css/native-select.css +13 -0
- package/dist/css/navigation-menu.css +11 -0
- package/dist/css/number-field.css +11 -0
- package/dist/css/otp-field.css +10 -0
- package/dist/css/page-header.css +11 -0
- package/dist/css/pagination.css +11 -0
- package/dist/css/popover.css +9 -0
- package/dist/css/preview-card.css +11 -0
- package/dist/css/progress.css +9 -0
- package/dist/css/radio-group.css +9 -0
- package/dist/css/rules/accordion.css +21 -0
- package/dist/css/rules/alert-dialog.css +23 -0
- package/dist/css/rules/app-shell.css +227 -0
- package/dist/css/rules/autocomplete.css +3 -0
- package/dist/css/rules/avatar.css +17 -0
- package/dist/css/rules/badge.css +17 -0
- package/dist/css/rules/bar.css +38 -0
- package/dist/css/rules/base.css +46 -0
- package/dist/css/rules/bottom-navigation.css +142 -0
- package/dist/css/rules/breadcrumbs.css +9 -0
- package/dist/css/rules/button.css +131 -0
- package/dist/css/rules/card.css +10 -0
- package/dist/css/rules/chart.css +264 -0
- package/dist/css/rules/checkbox-group.css +3 -0
- package/dist/css/rules/checkbox.css +25 -0
- package/dist/css/rules/choice-label.css +52 -0
- package/dist/css/rules/choice.css +104 -0
- package/dist/css/rules/code.css +9 -0
- package/dist/css/rules/collapsible.css +12 -0
- package/dist/css/rules/combobox.css +177 -0
- package/dist/css/rules/context-menu.css +9 -0
- package/dist/css/rules/date-picker.css +301 -0
- package/dist/css/rules/dialog.css +160 -0
- package/dist/css/rules/disclosure.css +107 -0
- package/dist/css/rules/drawer.css +18 -0
- package/dist/css/rules/empty-state.css +9 -0
- package/dist/css/rules/field-control.css +70 -0
- package/dist/css/rules/field.css +56 -0
- package/dist/css/rules/fieldset.css +23 -0
- package/dist/css/rules/file-upload.css +156 -0
- package/dist/css/rules/filter-bar.css +56 -0
- package/dist/css/rules/form.css +15 -0
- package/dist/css/rules/gauge.css +61 -0
- package/dist/css/rules/icon.css +27 -0
- package/dist/css/rules/inline-message.css +10 -0
- package/dist/css/rules/input-group.css +69 -0
- package/dist/css/rules/input.css +33 -0
- package/dist/css/rules/item.css +14 -0
- package/dist/css/rules/layout.css +3 -0
- package/dist/css/rules/link.css +9 -0
- package/dist/css/rules/list.css +10 -0
- package/dist/css/rules/markdown.css +63 -0
- package/dist/css/rules/menu.css +97 -0
- package/dist/css/rules/menubar.css +18 -0
- package/dist/css/rules/meter.css +3 -0
- package/dist/css/rules/native-select.css +3 -0
- package/dist/css/rules/navigation-menu.css +159 -0
- package/dist/css/rules/number-field.css +55 -0
- package/dist/css/rules/otp-field.css +68 -0
- package/dist/css/rules/page-header.css +3 -0
- package/dist/css/rules/pagination.css +3 -0
- package/dist/css/rules/panel.css +221 -0
- package/dist/css/rules/popover.css +41 -0
- package/dist/css/rules/popup.css +27 -0
- package/dist/css/rules/preview-card.css +60 -0
- package/dist/css/rules/progress.css +14 -0
- package/dist/css/rules/radio-group.css +26 -0
- package/dist/css/rules/scroll-area.css +101 -0
- package/dist/css/rules/select.css +88 -0
- package/dist/css/rules/separator.css +3 -0
- package/dist/css/rules/sheet.css +3 -0
- package/dist/css/rules/sizing.css +140 -0
- package/dist/css/rules/skeleton.css +41 -0
- package/dist/css/rules/slider.css +123 -0
- package/dist/css/rules/sparkline.css +52 -0
- package/dist/css/rules/spinner.css +38 -0
- package/dist/css/rules/stat.css +59 -0
- package/dist/css/rules/switch.css +50 -0
- package/dist/css/rules/syntax-highlighter.css +82 -0
- package/dist/css/rules/table.css +28 -0
- package/dist/css/rules/tabs.css +126 -0
- package/dist/css/rules/textarea.css +21 -0
- package/dist/css/rules/toast.css +247 -0
- package/dist/css/rules/toggle-group.css +3 -0
- package/dist/css/rules/toggle.css +27 -0
- package/dist/css/rules/toolbar.css +62 -0
- package/dist/css/rules/tooltip.css +38 -0
- package/dist/css/rules/tree.css +98 -0
- package/dist/css/rules/typography.css +3 -0
- package/dist/css/scroll-area.css +8 -0
- package/dist/css/select.css +12 -0
- package/dist/css/separator.css +9 -0
- package/dist/css/sheet.css +11 -0
- package/dist/css/sizing.css +7 -0
- package/dist/css/skeleton.css +8 -0
- package/dist/css/slider.css +8 -0
- package/dist/css/sparkline.css +8 -0
- package/dist/css/spinner.css +8 -0
- package/dist/css/stat.css +9 -0
- package/dist/css/styles.css +100 -0
- package/dist/css/switch.css +9 -0
- package/dist/css/syntax-highlighter.css +9 -0
- package/dist/css/table.css +12 -0
- package/dist/css/tabs.css +8 -0
- package/dist/css/textarea.css +10 -0
- package/dist/css/toast.css +11 -0
- package/dist/css/toggle-group.css +9 -0
- package/dist/css/toggle.css +10 -0
- package/dist/css/toolbar.css +12 -0
- package/dist/css/tooltip.css +9 -0
- package/dist/css/tree.css +10 -0
- package/dist/css/typography.css +9 -0
- package/dist/format.d.ts +25 -0
- package/dist/format.js +42 -0
- package/dist/icon.d.ts +1 -0
- package/dist/icon.js +1 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +6 -2
- package/dist/sparkline.d.ts +41 -0
- package/dist/sparkline.js +50 -0
- package/dist/stat.d.ts +59 -0
- package/dist/stat.js +26 -0
- package/dist/status.d.ts +1 -1
- package/dist/status.js +1 -0
- package/dist/toast.d.ts +57 -1
- package/dist/toast.js +73 -2
- package/dist/tooltip.d.ts +9 -3
- package/dist/tooltip.js +4 -4
- package/package.json +90 -7
- package/src/styles.css +0 -3948
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Import the stylesheets once at the application root. `all.css` brings the tokens
|
|
|
18
18
|
@import '@surflane/ui/all.css';
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
The rules sit in `surflane.*` cascade layers, so your unlayered CSS overrides them. Set `data-theme="light"` or `data-theme="dark"` on `<html>` before the first paint and add `pui-typography` to `<body>`. The [install guide](https://surflane.page/install) walks through Vite, Next.js, React Router and Astro, with or without Tailwind CSS.
|
|
21
|
+
To ship only what you use, import component stylesheets instead, such as `@surflane/ui/button.css` and `@surflane/ui/dialog.css`. Each brings the tokens and the styles of what its component renders. The rules sit in `surflane.*` cascade layers, so your unlayered CSS overrides them. Set `data-theme="light"` or `data-theme="dark"` on `<html>` before the first paint and add `pui-typography` to `<body>`. With AppShell, which fills the window, also give `<body>` no margin and the `--chrome` background. The [install guide](https://surflane.page/install) walks through Vite, Next.js, React Router and Astro, with or without Tailwind CSS.
|
|
22
22
|
|
|
23
23
|
```tsx
|
|
24
24
|
import {
|
package/dist/app-shell.d.ts
CHANGED
|
@@ -1,11 +1,43 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
import { type ButtonProps } from './button.js';
|
|
3
|
+
export type AppShellRootProps = React.ComponentPropsWithoutRef<'div'> & {
|
|
4
|
+
layout?: 'header' | 'workspace';
|
|
5
|
+
/** Whether the sidebar is collapsed to its rail. Pass it to control the state. */
|
|
6
|
+
collapsed?: boolean;
|
|
7
|
+
/** The starting state when the shell controls it. */
|
|
8
|
+
defaultCollapsed?: boolean;
|
|
9
|
+
onCollapsedChange?: (collapsed: boolean) => void;
|
|
10
|
+
/** A localStorage key that remembers the state across visits. A server-rendered
|
|
11
|
+
* app reads a cookie into defaultCollapsed instead, so the first paint is right. */
|
|
12
|
+
persist?: string;
|
|
13
|
+
/** ⌘B, or Ctrl+B, toggles the sidebar unless focus is in a text field. */
|
|
14
|
+
shortcut?: boolean;
|
|
15
|
+
};
|
|
16
|
+
export type AppShellSidebarToggleProps = Omit<ButtonProps, 'children'> & {
|
|
17
|
+
/** The button's name in each state; it is also the tooltip. */
|
|
18
|
+
labels?: {
|
|
19
|
+
collapse?: string;
|
|
20
|
+
expand?: string;
|
|
21
|
+
};
|
|
22
|
+
children?: React.ReactNode;
|
|
23
|
+
};
|
|
2
24
|
export declare const AppShell: {
|
|
3
25
|
Root: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
|
|
4
|
-
collapsed?: boolean;
|
|
5
26
|
layout?: "header" | "workspace";
|
|
27
|
+
/** Whether the sidebar is collapsed to its rail. Pass it to control the state. */
|
|
28
|
+
collapsed?: boolean;
|
|
29
|
+
/** The starting state when the shell controls it. */
|
|
30
|
+
defaultCollapsed?: boolean;
|
|
31
|
+
onCollapsedChange?: (collapsed: boolean) => void;
|
|
32
|
+
/** A localStorage key that remembers the state across visits. A server-rendered
|
|
33
|
+
* app reads a cookie into defaultCollapsed instead, so the first paint is right. */
|
|
34
|
+
persist?: string;
|
|
35
|
+
/** ⌘B, or Ctrl+B, toggles the sidebar unless focus is in a text field. */
|
|
36
|
+
shortcut?: boolean;
|
|
6
37
|
} & React.RefAttributes<HTMLDivElement>>;
|
|
7
38
|
Header: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
8
39
|
Sidebar: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>, "ref"> & React.RefAttributes<HTMLElement>>;
|
|
40
|
+
SidebarToggle: React.ForwardRefExoticComponent<Omit<AppShellSidebarToggleProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
|
|
9
41
|
Content: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
10
42
|
Page: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
11
43
|
Footer: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -16,9 +48,18 @@ export declare const AppShell: {
|
|
|
16
48
|
} & React.RefAttributes<HTMLAnchorElement>>;
|
|
17
49
|
};
|
|
18
50
|
export declare const AppShellRoot: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
|
|
19
|
-
collapsed?: boolean;
|
|
20
51
|
layout?: "header" | "workspace";
|
|
21
|
-
|
|
52
|
+
/** Whether the sidebar is collapsed to its rail. Pass it to control the state. */
|
|
53
|
+
collapsed?: boolean;
|
|
54
|
+
/** The starting state when the shell controls it. */
|
|
55
|
+
defaultCollapsed?: boolean;
|
|
56
|
+
onCollapsedChange?: (collapsed: boolean) => void;
|
|
57
|
+
/** A localStorage key that remembers the state across visits. A server-rendered
|
|
58
|
+
* app reads a cookie into defaultCollapsed instead, so the first paint is right. */
|
|
59
|
+
persist?: string;
|
|
60
|
+
/** ⌘B, or Ctrl+B, toggles the sidebar unless focus is in a text field. */
|
|
61
|
+
shortcut?: boolean;
|
|
62
|
+
} & React.RefAttributes<HTMLDivElement>>, AppShellHeader: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>, AppShellSidebar: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>, "ref"> & React.RefAttributes<HTMLElement>>, AppShellSidebarToggle: React.ForwardRefExoticComponent<Omit<AppShellSidebarToggleProps, "ref"> & React.RefAttributes<HTMLButtonElement>>, AppShellContent: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>, AppShellPage: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>, AppShellFooter: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>, AppShellLabel: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & React.RefAttributes<HTMLSpanElement>>, AppShellLink: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, "ref">, "children"> & {
|
|
22
63
|
icon: React.ReactNode;
|
|
23
64
|
label: string;
|
|
24
65
|
} & React.RefAttributes<HTMLAnchorElement>>;
|
package/dist/app-shell.js
CHANGED
|
@@ -1,16 +1,85 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from 'react';
|
|
4
|
-
import { cx } from './parts.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import { cx, skin } from './parts.js';
|
|
5
|
+
import { Button } from './button.js';
|
|
6
|
+
import { Icon } from './icon.js';
|
|
7
|
+
import { Tooltip } from './tooltip.js';
|
|
8
|
+
const ShellContext = /* @__PURE__ */ React.createContext(null);
|
|
9
|
+
const SidebarContext = /* @__PURE__ */ React.createContext(false);
|
|
10
|
+
const editable = (target) => target instanceof HTMLElement &&
|
|
11
|
+
(target.isContentEditable ||
|
|
12
|
+
/^(input|textarea|select)$/i.test(target.tagName));
|
|
13
|
+
const apple = () => typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.platform);
|
|
14
|
+
/** The sidebar shortcut as this platform writes it. */
|
|
15
|
+
const shortcutLabel = () => (apple() ? '⌘B' : 'Ctrl+B');
|
|
16
|
+
/** Layout only. Routing, session, theme and mobile disclosure belong to the caller;
|
|
17
|
+
* the shell keeps the sidebar's collapsed state unless you control it. */
|
|
18
|
+
const Root = /* @__PURE__ */ React.forwardRef(function AppShellRoot({ collapsed, defaultCollapsed = false, onCollapsedChange, persist, shortcut = true, layout = 'header', className, children, ...props }, ref) {
|
|
19
|
+
const [own, setOwn] = React.useState(defaultCollapsed);
|
|
20
|
+
const value = collapsed ?? own;
|
|
21
|
+
const latest = React.useRef(value);
|
|
22
|
+
latest.current = value;
|
|
23
|
+
const setCollapsed = React.useCallback((next) => {
|
|
24
|
+
if (collapsed === undefined)
|
|
25
|
+
setOwn(next);
|
|
26
|
+
onCollapsedChange?.(next);
|
|
27
|
+
if (persist)
|
|
28
|
+
try {
|
|
29
|
+
localStorage.setItem(persist, next ? 'collapsed' : 'expanded');
|
|
30
|
+
}
|
|
31
|
+
catch { }
|
|
32
|
+
}, [collapsed, onCollapsedChange, persist]);
|
|
33
|
+
React.useLayoutEffect(() => {
|
|
34
|
+
if (!persist || collapsed !== undefined)
|
|
35
|
+
return;
|
|
36
|
+
try {
|
|
37
|
+
const stored = localStorage.getItem(persist);
|
|
38
|
+
if (stored)
|
|
39
|
+
setOwn(stored === 'collapsed');
|
|
40
|
+
}
|
|
41
|
+
catch { }
|
|
42
|
+
}, [persist, collapsed]);
|
|
43
|
+
React.useEffect(() => {
|
|
44
|
+
if (!shortcut)
|
|
45
|
+
return;
|
|
46
|
+
const toggle = (event) => {
|
|
47
|
+
if (event.key.toLowerCase() !== 'b' ||
|
|
48
|
+
event.altKey ||
|
|
49
|
+
event.shiftKey ||
|
|
50
|
+
!(apple() ? event.metaKey : event.ctrlKey) ||
|
|
51
|
+
editable(event.target))
|
|
52
|
+
return;
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
setCollapsed(!latest.current);
|
|
55
|
+
};
|
|
56
|
+
document.addEventListener('keydown', toggle);
|
|
57
|
+
return () => document.removeEventListener('keydown', toggle);
|
|
58
|
+
}, [shortcut, setCollapsed]);
|
|
59
|
+
const sidebarId = React.useId();
|
|
60
|
+
const shell = React.useMemo(() => ({ collapsed: value, setCollapsed, sidebarId, shortcut }), [value, setCollapsed, sidebarId, shortcut]);
|
|
61
|
+
return (_jsx(ShellContext.Provider, { value: shell, children: _jsx("div", { ...props, ref: ref, "data-layout": layout, "data-sidebar": value ? 'collapsed' : 'expanded', className: cx('pui-app-shell', className), children: children }) }));
|
|
62
|
+
});
|
|
63
|
+
/** Collapses the sidebar to its rail and back. Its tooltip names the shortcut. */
|
|
64
|
+
const SidebarToggle = /* @__PURE__ */ React.forwardRef(function AppShellSidebarToggle({ labels, className, children, onClick, ...props }, ref) {
|
|
65
|
+
const shell = React.useContext(ShellContext);
|
|
66
|
+
if (!shell)
|
|
67
|
+
throw new Error('AppShell.SidebarToggle belongs in AppShell.Root');
|
|
68
|
+
const label = shell.collapsed
|
|
69
|
+
? (labels?.expand ?? 'Expand sidebar')
|
|
70
|
+
: (labels?.collapse ?? 'Collapse sidebar');
|
|
71
|
+
return (_jsx(Tooltip, { label: label, shortcut: shell.shortcut ? shortcutLabel() : undefined, children: _jsx(Button, { variant: 'ghost', size: 'icon', ...props, ref: ref, "aria-label": label, "aria-expanded": !shell.collapsed, "aria-controls": shell.sidebarId, className: skin('pui-app-shell-toggle', className), onClick: (event) => {
|
|
72
|
+
onClick?.(event);
|
|
73
|
+
if (!event.defaultPrevented)
|
|
74
|
+
shell.setCollapsed(!shell.collapsed);
|
|
75
|
+
}, children: children ?? _jsx(Icon, { name: 'sidebar' }) }) }));
|
|
8
76
|
});
|
|
9
77
|
const Header = /* @__PURE__ */ React.forwardRef(function AppShellHeader({ className, ...props }, ref) {
|
|
10
78
|
return (_jsx("div", { ...props, ref: ref, className: cx('pui-app-shell-header', className) }));
|
|
11
79
|
});
|
|
12
|
-
const Sidebar = /* @__PURE__ */ React.forwardRef(function AppShellSidebar({ className, ...props }, ref) {
|
|
13
|
-
|
|
80
|
+
const Sidebar = /* @__PURE__ */ React.forwardRef(function AppShellSidebar({ className, id, ...props }, ref) {
|
|
81
|
+
const shell = React.useContext(ShellContext);
|
|
82
|
+
return (_jsx(SidebarContext.Provider, { value: true, children: _jsx("aside", { ...props, ref: ref, id: id ?? shell?.sidebarId, className: cx('pui-app-shell-sidebar', className) }) }));
|
|
14
83
|
});
|
|
15
84
|
const Content = /* @__PURE__ */ React.forwardRef(function AppShellContent({ className, ...props }, ref) {
|
|
16
85
|
return (_jsx("div", { ...props, ref: ref, className: cx('pui-app-shell-content', className) }));
|
|
@@ -29,12 +98,16 @@ const Label = /* @__PURE__ */ React.forwardRef(function AppShellLabel({ classNam
|
|
|
29
98
|
return (_jsx("span", { ...props, ref: ref, className: cx('pui-app-shell-label', className) }));
|
|
30
99
|
});
|
|
31
100
|
const Link = /* @__PURE__ */ React.forwardRef(function AppShellLink({ icon, label, className, ...props }, ref) {
|
|
32
|
-
|
|
101
|
+
const shell = React.useContext(ShellContext);
|
|
102
|
+
const inSidebar = React.useContext(SidebarContext);
|
|
103
|
+
// On the collapsed rail only the icon shows, so the name appears as a tooltip.
|
|
104
|
+
return (_jsx(Tooltip, { label: label, enabled: Boolean(shell?.collapsed && inSidebar), children: _jsxs("a", { ...props, ref: ref, "aria-label": props['aria-label'] ?? label, className: cx('pui-app-shell-link', className), children: [_jsx("span", { className: 'pui-app-shell-icon', "aria-hidden": 'true', children: icon }), _jsx(Label, { "aria-hidden": 'true', children: label })] }) }));
|
|
33
105
|
});
|
|
34
106
|
export const AppShell = {
|
|
35
107
|
Root,
|
|
36
108
|
Header,
|
|
37
109
|
Sidebar,
|
|
110
|
+
SidebarToggle,
|
|
38
111
|
Content,
|
|
39
112
|
Page,
|
|
40
113
|
Footer,
|
|
@@ -42,4 +115,4 @@ export const AppShell = {
|
|
|
42
115
|
Link,
|
|
43
116
|
};
|
|
44
117
|
// Flat names for React Server Components, which cannot dot into client exports.
|
|
45
|
-
export const { Root: AppShellRoot, Header: AppShellHeader, Sidebar: AppShellSidebar, Content: AppShellContent, Page: AppShellPage, Footer: AppShellFooter, Label: AppShellLabel, Link: AppShellLink, } = AppShell;
|
|
118
|
+
export const { Root: AppShellRoot, Header: AppShellHeader, Sidebar: AppShellSidebar, SidebarToggle: AppShellSidebarToggle, Content: AppShellContent, Page: AppShellPage, Footer: AppShellFooter, Label: AppShellLabel, Link: AppShellLink, } = AppShell;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useRender } from '@base-ui/react/use-render';
|
|
3
|
+
export type BottomNavigationRootProps = React.ComponentPropsWithoutRef<'nav'> & {
|
|
4
|
+
/** Names the navigation, such as "Primary". */
|
|
5
|
+
'aria-label': string;
|
|
6
|
+
/** mobile: shown at 760px and narrower, where AppShell hides its sidebar. always:
|
|
7
|
+
* at every width. */
|
|
8
|
+
visible?: 'mobile' | 'always';
|
|
9
|
+
/** fixed: pinned to the bottom of the viewport, above the safe area. static: in the
|
|
10
|
+
* flow, for a frame of your own. */
|
|
11
|
+
position?: 'fixed' | 'static';
|
|
12
|
+
};
|
|
13
|
+
export type BottomNavigationItemProps = Omit<useRender.ComponentProps<'a'>, 'ref' | 'children'> & {
|
|
14
|
+
icon: React.ReactNode;
|
|
15
|
+
label: string;
|
|
16
|
+
/** Marks the current destination with aria-current="page". */
|
|
17
|
+
current?: boolean;
|
|
18
|
+
/** A count, shown up to 99+, or true for a dot. */
|
|
19
|
+
badge?: number | boolean;
|
|
20
|
+
/** What the badge means, read after the label, such as "3 unread". A count reads
|
|
21
|
+
* as its number without it; a dot needs it. */
|
|
22
|
+
badgeLabel?: string;
|
|
23
|
+
};
|
|
24
|
+
export declare const BottomNavigation: {
|
|
25
|
+
Root: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>, "ref"> & {
|
|
26
|
+
/** Names the navigation, such as "Primary". */
|
|
27
|
+
'aria-label': string;
|
|
28
|
+
/** mobile: shown at 760px and narrower, where AppShell hides its sidebar. always:
|
|
29
|
+
* at every width. */
|
|
30
|
+
visible?: "mobile" | "always";
|
|
31
|
+
/** fixed: pinned to the bottom of the viewport, above the safe area. static: in the
|
|
32
|
+
* flow, for a frame of your own. */
|
|
33
|
+
position?: "fixed" | "static";
|
|
34
|
+
} & React.RefAttributes<HTMLElement>>;
|
|
35
|
+
Item: React.ForwardRefExoticComponent<Omit<useRender.ComponentProps<"a", {}, import("@base-ui/react/types").HTMLProps>, "children" | "ref"> & {
|
|
36
|
+
icon: React.ReactNode;
|
|
37
|
+
label: string;
|
|
38
|
+
/** Marks the current destination with aria-current="page". */
|
|
39
|
+
current?: boolean;
|
|
40
|
+
/** A count, shown up to 99+, or true for a dot. */
|
|
41
|
+
badge?: number | boolean;
|
|
42
|
+
/** What the badge means, read after the label, such as "3 unread". A count reads
|
|
43
|
+
* as its number without it; a dot needs it. */
|
|
44
|
+
badgeLabel?: string;
|
|
45
|
+
} & React.RefAttributes<HTMLAnchorElement>>;
|
|
46
|
+
};
|
|
47
|
+
export declare const BottomNavigationRoot: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>, "ref"> & {
|
|
48
|
+
/** Names the navigation, such as "Primary". */
|
|
49
|
+
'aria-label': string;
|
|
50
|
+
/** mobile: shown at 760px and narrower, where AppShell hides its sidebar. always:
|
|
51
|
+
* at every width. */
|
|
52
|
+
visible?: "mobile" | "always";
|
|
53
|
+
/** fixed: pinned to the bottom of the viewport, above the safe area. static: in the
|
|
54
|
+
* flow, for a frame of your own. */
|
|
55
|
+
position?: "fixed" | "static";
|
|
56
|
+
} & React.RefAttributes<HTMLElement>>, BottomNavigationItem: React.ForwardRefExoticComponent<Omit<useRender.ComponentProps<"a", {}, import("@base-ui/react/types").HTMLProps>, "children" | "ref"> & {
|
|
57
|
+
icon: React.ReactNode;
|
|
58
|
+
label: string;
|
|
59
|
+
/** Marks the current destination with aria-current="page". */
|
|
60
|
+
current?: boolean;
|
|
61
|
+
/** A count, shown up to 99+, or true for a dot. */
|
|
62
|
+
badge?: number | boolean;
|
|
63
|
+
/** What the badge means, read after the label, such as "3 unread". A count reads
|
|
64
|
+
* as its number without it; a dot needs it. */
|
|
65
|
+
badgeLabel?: string;
|
|
66
|
+
} & React.RefAttributes<HTMLAnchorElement>>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { useRender } from '@base-ui/react/use-render';
|
|
5
|
+
import { cx } from './parts.js';
|
|
6
|
+
/** A phone's primary destinations along the bottom edge, within thumb reach. Pad the
|
|
7
|
+
* scrolling content by --size-bottom-navigation so the last line stays visible. */
|
|
8
|
+
const Root = /* @__PURE__ */ React.forwardRef(function BottomNavigationRoot({ visible = 'mobile', position = 'fixed', className, children, ...props }, ref) {
|
|
9
|
+
return (_jsx("nav", { ...props, ref: ref, "data-visible": visible, "data-position": position, className: cx('pui-bottom-nav', className), children: _jsx("ul", { role: 'list', className: 'pui-bottom-nav-list', children: children }) }));
|
|
10
|
+
});
|
|
11
|
+
/** One destination: an icon above its label. Pass a router's link element as
|
|
12
|
+
* `render` to keep client navigation. */
|
|
13
|
+
const Item = /* @__PURE__ */ React.forwardRef(function BottomNavigationItem({ icon, label, current, badge, badgeLabel, render, className, ...props }, ref) {
|
|
14
|
+
const count = typeof badge === 'number' && badge > 0
|
|
15
|
+
? badge > 99
|
|
16
|
+
? '99+'
|
|
17
|
+
: String(badge)
|
|
18
|
+
: undefined;
|
|
19
|
+
const shown = Boolean(count || badge === true);
|
|
20
|
+
const spoken = badgeLabel ?? count;
|
|
21
|
+
const link = useRender({
|
|
22
|
+
defaultTagName: 'a',
|
|
23
|
+
render,
|
|
24
|
+
ref,
|
|
25
|
+
props: {
|
|
26
|
+
...props,
|
|
27
|
+
'aria-current': current ? 'page' : props['aria-current'],
|
|
28
|
+
className: cx('pui-bottom-nav-item', className),
|
|
29
|
+
children: (_jsxs(_Fragment, { children: [_jsxs("span", { className: 'pui-bottom-nav-icon', "aria-hidden": 'true', children: [icon, shown && (_jsx("span", { className: 'pui-bottom-nav-badge', "data-dot": count ? undefined : '', children: count }))] }), _jsx("span", { className: 'pui-bottom-nav-label', children: label }), shown && spoken && (_jsxs("span", { className: 'pui-feedback-sr', children: [", ", spoken] }))] })),
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
return _jsx("li", { className: 'pui-bottom-nav-entry', children: link });
|
|
33
|
+
});
|
|
34
|
+
export const BottomNavigation = { Root, Item };
|
|
35
|
+
// Flat names for React Server Components, which cannot dot into client exports.
|
|
36
|
+
export const { Root: BottomNavigationRoot, Item: BottomNavigationItem } = BottomNavigation;
|
package/dist/chart.d.ts
CHANGED
|
@@ -18,6 +18,12 @@ export type ChartProps = Omit<React.ComponentPropsWithoutRef<'figure'>, 'childre
|
|
|
18
18
|
yLabel: string;
|
|
19
19
|
type?: 'line' | 'bar' | 'area';
|
|
20
20
|
height?: number;
|
|
21
|
+
/** Keeps 0 on the value axis. Turn it off for values far from zero, such as
|
|
22
|
+
* prices, so the line uses the height; bars always start at zero. */
|
|
23
|
+
includeZero?: boolean;
|
|
24
|
+
/** neutral tells series apart by ink, dashes and markers. categorical adds six hues,
|
|
25
|
+
* for categories that color must separate, such as buckets or sectors. */
|
|
26
|
+
palette?: 'neutral' | 'categorical';
|
|
21
27
|
locale?: string;
|
|
22
28
|
formatValue?: (value: number) => string;
|
|
23
29
|
/** Text the chart shows or announces; pass translations here. */
|
|
@@ -40,6 +46,10 @@ export type ChartLabels = {
|
|
|
40
46
|
viewTable: string;
|
|
41
47
|
table: (title: string) => string;
|
|
42
48
|
tableCaption: (unit: string) => string;
|
|
49
|
+
/** The readout before any point is inspected. */
|
|
50
|
+
inspectHint: string;
|
|
51
|
+
/** The data table's only row when there is no data. */
|
|
52
|
+
noRows: string;
|
|
43
53
|
};
|
|
44
54
|
/** Cartesian trends with a persistent, keyboard-readable tooltip region and a
|
|
45
55
|
* full data-table alternative. Missing values are gaps, never fabricated zeros. */
|
|
@@ -53,6 +63,12 @@ export declare const Chart: React.ForwardRefExoticComponent<Omit<Omit<React.Deta
|
|
|
53
63
|
yLabel: string;
|
|
54
64
|
type?: "line" | "bar" | "area";
|
|
55
65
|
height?: number;
|
|
66
|
+
/** Keeps 0 on the value axis. Turn it off for values far from zero, such as
|
|
67
|
+
* prices, so the line uses the height; bars always start at zero. */
|
|
68
|
+
includeZero?: boolean;
|
|
69
|
+
/** neutral tells series apart by ink, dashes and markers. categorical adds six hues,
|
|
70
|
+
* for categories that color must separate, such as buckets or sectors. */
|
|
71
|
+
palette?: "neutral" | "categorical";
|
|
56
72
|
locale?: string;
|
|
57
73
|
formatValue?: (value: number) => string;
|
|
58
74
|
/** Text the chart shows or announces; pass translations here. */
|
package/dist/chart.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import * as React from 'react';
|
|
4
|
-
import { Area, Bar, CartesianGrid, ComposedChart, Line, ReferenceLine, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts';
|
|
4
|
+
import { Area, Bar, CartesianGrid, ComposedChart, Line, ReferenceLine, ResponsiveContainer, Tooltip, XAxis, YAxis, useActiveTooltipCoordinate, useActiveTooltipLabel, useIsTooltipActive, usePlotArea, } from 'recharts';
|
|
5
|
+
import { createPortal } from 'react-dom';
|
|
5
6
|
import { Table } from './table.js';
|
|
6
7
|
import { Button } from './button.js';
|
|
7
8
|
import { cx } from './parts.js';
|
|
@@ -15,9 +16,17 @@ const chartLabels = {
|
|
|
15
16
|
viewTable: 'View data table',
|
|
16
17
|
table: (title) => `${title} data table`,
|
|
17
18
|
tableCaption: (unit) => `All input measurements, including hidden series. Unit: ${unit}.`,
|
|
19
|
+
inspectHint: 'Hover, touch or use chart arrow keys to inspect a measurement.',
|
|
20
|
+
noRows: 'No rows.',
|
|
18
21
|
};
|
|
22
|
+
/** The round step at or above a raw step: 1, 2, 2.5 or 5 times a power of ten. */
|
|
23
|
+
function roundStep(raw) {
|
|
24
|
+
if (!(raw > 0))
|
|
25
|
+
return 0;
|
|
26
|
+
const power = 10 ** Math.floor(Math.log10(raw));
|
|
27
|
+
return power * ([1, 2, 2.5, 5, 10].find((f) => f * power >= raw) ?? 10);
|
|
28
|
+
}
|
|
19
29
|
const dashes = [undefined, '7 4', '2 4', '10 3 2 3', '12 5', '4 3'];
|
|
20
|
-
const colorFor = (index) => `var(--chart-series-${(index % 3) + 1})`;
|
|
21
30
|
function Marker({ cx, cy, color, index, }) {
|
|
22
31
|
if (typeof cx !== 'number' ||
|
|
23
32
|
typeof cy !== 'number' ||
|
|
@@ -30,9 +39,37 @@ function Marker({ cx, cy, color, index, }) {
|
|
|
30
39
|
function Pattern({ id, index, color, }) {
|
|
31
40
|
return (_jsxs("pattern", { id: id, patternUnits: 'userSpaceOnUse', width: '8', height: '8', children: [_jsx("rect", { width: '8', height: '8', fill: color }), index % 3 === 1 ? (_jsx("path", { d: 'M-2 2L2-2M0 8L8 0M6 10L10 6', stroke: 'var(--canvas)', strokeWidth: '1.5' })) : index % 3 === 2 ? (_jsx("circle", { cx: '4', cy: '4', r: '1.4', fill: 'var(--canvas)' })) : null] }));
|
|
32
41
|
}
|
|
42
|
+
/** A series' swatch, as the legend and the floating values draw it. */
|
|
43
|
+
function SeriesMark({ type, index, color, pattern, }) {
|
|
44
|
+
return (_jsx("svg", { className: 'pui-chart-mark', viewBox: '0 0 32 20', "aria-hidden": 'true', focusable: 'false', children: type === 'bar' ? (_jsxs(_Fragment, { children: [_jsx("defs", { children: _jsx(Pattern, { id: pattern, color: color, index: index }) }), _jsx("rect", { x: '4', y: '3', width: '24', height: '14', rx: '2', fill: `url(#${pattern})` })] })) : (_jsxs(_Fragment, { children: [_jsx("path", { d: 'M1 10h30', stroke: color, strokeWidth: '2', strokeDasharray: dashes[index % dashes.length] }), _jsx(Marker, { cx: 16, cy: 10, index: index, color: color })] })) }));
|
|
45
|
+
}
|
|
46
|
+
/** The active point's values beside it, for pointer and touch users. The readout
|
|
47
|
+
* below the plot announces the same values, so this copy stays out of the
|
|
48
|
+
* accessibility tree. It sits on the side of the point with more room and stays
|
|
49
|
+
* inside the plot. */
|
|
50
|
+
function FloatingValues({ host, hidden, render, }) {
|
|
51
|
+
const active = useIsTooltipActive();
|
|
52
|
+
const label = useActiveTooltipLabel();
|
|
53
|
+
const point = useActiveTooltipCoordinate();
|
|
54
|
+
const plot = usePlotArea();
|
|
55
|
+
const box = React.useRef(null);
|
|
56
|
+
const [width, setWidth] = React.useState(0);
|
|
57
|
+
const shown = Boolean(host && !hidden && active && label !== undefined && point && plot);
|
|
58
|
+
React.useLayoutEffect(() => {
|
|
59
|
+
if (shown && box.current)
|
|
60
|
+
setWidth(box.current.offsetWidth);
|
|
61
|
+
}, [shown, label, plot?.width]);
|
|
62
|
+
if (!shown || !point || !plot)
|
|
63
|
+
return null;
|
|
64
|
+
// The gap between the point and the box, in px: the --space-3 step.
|
|
65
|
+
const gap = 12;
|
|
66
|
+
const start = point.x > plot.x + plot.width / 2;
|
|
67
|
+
const left = Math.min(Math.max(start ? point.x - gap - width : point.x + gap, plot.x), plot.x + plot.width - width);
|
|
68
|
+
return createPortal(_jsx("div", { ref: box, className: 'pui-chart-tooltip', "aria-hidden": 'true', style: { left, top: plot.y, visibility: width ? undefined : 'hidden' }, children: render(String(label)) }), host);
|
|
69
|
+
}
|
|
33
70
|
/** Cartesian trends with a persistent, keyboard-readable tooltip region and a
|
|
34
71
|
* full data-table alternative. Missing values are gaps, never fabricated zeros. */
|
|
35
|
-
export const Chart = /* @__PURE__ */ React.forwardRef(function Chart({ title, description, data, series, unit, xLabel, yLabel, type = 'line', height = 280, locale = 'en-US', formatValue, labels, missingLabel, emptyLabel, hiddenSeries, defaultHiddenSeries = [], onHiddenSeriesChange, className, ...props }, ref) {
|
|
72
|
+
export const Chart = /* @__PURE__ */ React.forwardRef(function Chart({ title, description, data, series, unit, xLabel, yLabel, type = 'line', height = 280, includeZero = true, palette = 'neutral', locale = 'en-US', formatValue, labels, missingLabel, emptyLabel, hiddenSeries, defaultHiddenSeries = [], onHiddenSeriesChange, className, ...props }, ref) {
|
|
36
73
|
const text = {
|
|
37
74
|
...chartLabels,
|
|
38
75
|
...(missingLabel === undefined ? {} : { missing: missingLabel }),
|
|
@@ -40,10 +77,14 @@ export const Chart = /* @__PURE__ */ React.forwardRef(function Chart({ title, de
|
|
|
40
77
|
...labels,
|
|
41
78
|
};
|
|
42
79
|
const id = React.useId().replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
80
|
+
const colorFor = (index) => palette === 'categorical'
|
|
81
|
+
? `var(--chart-category-${(index % 6) + 1})`
|
|
82
|
+
: `var(--chart-series-${(index % 3) + 1})`;
|
|
43
83
|
const [internalHidden, setInternalHidden] = React.useState(defaultHiddenSeries);
|
|
44
84
|
const hidden = new Set(hiddenSeries ?? internalHidden);
|
|
45
85
|
const visible = series.filter((s) => !hidden.has(s.key));
|
|
46
86
|
const [host, setHost] = React.useState(null);
|
|
87
|
+
const [floatHost, setFloatHost] = React.useState(null);
|
|
47
88
|
const [dismissed, setDismissed] = React.useState(false);
|
|
48
89
|
const firstLegend = React.useRef(null);
|
|
49
90
|
const [restoreRequested, setRestoreRequested] = React.useState(false);
|
|
@@ -89,7 +130,16 @@ export const Chart = /* @__PURE__ */ React.forwardRef(function Chart({ title, de
|
|
|
89
130
|
const values = rows
|
|
90
131
|
.flatMap((row) => visible.map((s) => row.values[s.key]))
|
|
91
132
|
.filter((value) => typeof value === 'number');
|
|
92
|
-
|
|
133
|
+
// Bars grow from zero, so they always keep it on the axis. Without zero, the axis
|
|
134
|
+
// spans the values, widened to round steps so its ticks read cleanly.
|
|
135
|
+
const keepZero = includeZero || type === 'bar';
|
|
136
|
+
const start = keepZero ? 0 : (values[0] ?? 0);
|
|
137
|
+
const low = values.reduce((a, b) => Math.min(a, b), start), high = values.reduce((a, b) => Math.max(a, b), start);
|
|
138
|
+
const step = keepZero ? 0 : roundStep((high - low) / 4);
|
|
139
|
+
const min = step ? Math.floor(low / step) * step : low, max = step ? Math.ceil(high / step) * step : high;
|
|
140
|
+
const ticks = step
|
|
141
|
+
? Array.from({ length: Math.round((max - min) / step) + 1 }, (_, index) => min + index * step)
|
|
142
|
+
: undefined;
|
|
93
143
|
const hasValues = values.length > 0;
|
|
94
144
|
const changeHidden = (next) => {
|
|
95
145
|
if (hiddenSeries === undefined)
|
|
@@ -100,48 +150,53 @@ export const Chart = /* @__PURE__ */ React.forwardRef(function Chart({ title, de
|
|
|
100
150
|
.filter((s) => (s.key === key ? !hidden.has(key) : hidden.has(s.key)))
|
|
101
151
|
.map((s) => s.key));
|
|
102
152
|
const chartHeight = Number.isFinite(height) ? Math.max(200, height) : 280;
|
|
103
|
-
return (_jsxs("figure", { ...props, ref: ref, className: cx('pui-chart', className), "aria-labelledby": `${id}-title`, children: [_jsxs("figcaption", { className: 'pui-chart-caption', children: [_jsx("strong", { id: `${id}-title`, children: title }), _jsx("p", { id: `${id}-summary`, children: description })] }), _jsx("ul", { className: 'pui-chart-legend', "aria-label": `${title} series`, children: series.map((s, index) => (_jsx("li", { children: _jsxs("button", { ref: index === 0 ? firstLegend : undefined, type: 'button', className: 'pui-chart-legend-button', "aria-label": `${s.label} series`, "aria-pressed": !hidden.has(s.key), onClick: () => toggle(s.key), children: [_jsx(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
153
|
+
return (_jsxs("figure", { ...props, ref: ref, className: cx('pui-chart', className), "aria-labelledby": `${id}-title`, children: [_jsxs("figcaption", { className: 'pui-chart-caption', children: [_jsx("strong", { id: `${id}-title`, children: title }), _jsx("p", { id: `${id}-summary`, children: description })] }), _jsxs("div", { className: 'pui-chart-frame', children: [_jsxs("div", { className: 'pui-chart-key', children: [_jsxs("p", { className: 'pui-chart-axis-label', children: [yLabel, " (", unit, ")"] }), _jsx("ul", { className: 'pui-chart-legend', "aria-label": `${title} series`, children: series.map((s, index) => (_jsx("li", { children: _jsxs("button", { ref: index === 0 ? firstLegend : undefined, type: 'button', className: 'pui-chart-legend-button', "aria-label": `${s.label} series`, "aria-pressed": !hidden.has(s.key), onClick: () => toggle(s.key), children: [_jsx(SeriesMark, { type: type, index: index, color: colorFor(index), pattern: `${id}-legend-${index}` }), _jsx("span", { children: s.label })] }) }, s.key))) })] }), hasValues ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: 'pui-chart-plot', onKeyDownCapture: (event) => {
|
|
154
|
+
if (event.key === 'Escape')
|
|
155
|
+
setDismissed(true);
|
|
156
|
+
else if (event.key === 'ArrowLeft' ||
|
|
157
|
+
event.key === 'ArrowRight')
|
|
158
|
+
setDismissed(false);
|
|
159
|
+
}, onMouseMoveCapture: () => setDismissed(false), onTouchStartCapture: () => setDismissed(false), onFocusCapture: () => setDismissed(false), children: [_jsx(ResponsiveContainer, { width: '100%', height: chartHeight, minWidth: 0, initialDimension: { width: 320, height: chartHeight }, children: _jsxs(ComposedChart, { data: rows, accessibilityLayer: true, role: 'application', "aria-label": title, "aria-describedby": `${id}-summary ${id}-keys`, margin: { top: 16, right: 20, bottom: 8, left: 0 }, children: [_jsx("defs", { children: series.map((s, index) => (_jsx(Pattern, { id: `${id}-pattern-${index}`, index: index, color: colorFor(index) }, s.key))) }), _jsx(CartesianGrid, { vertical: false, stroke: 'var(--line)' }), _jsx(XAxis, { dataKey: 'id', tickFormatter: (value) => {
|
|
160
|
+
const label = byId.get(String(value))?.label ?? String(value);
|
|
161
|
+
// minTickGap already drops ticks that would overlap; the cap
|
|
162
|
+
// only keeps one very long label from crowding the axis.
|
|
163
|
+
return label.length > 24
|
|
164
|
+
? label.slice(0, 23) + '…'
|
|
165
|
+
: label;
|
|
166
|
+
}, tick: { fill: 'var(--ink-muted)', fontSize: 12 }, tickLine: false, axisLine: { stroke: 'var(--line)' }, minTickGap: 24, interval: 'preserveStartEnd' }), _jsx(YAxis, { width: 56, tickFormatter: (value) => compact.format(Number(value)), tick: { fill: 'var(--ink-muted)', fontSize: 12 }, tickLine: false, axisLine: false, domain: [min, max === min ? max + 1 : max], ticks: ticks }), _jsx(ReferenceLine, { y: 0, stroke: 'var(--ink-muted)' }), host && (_jsx(Tooltip, { portal: host, isAnimationActive: false, active: dismissed ? false : undefined, filterNull: false, cursor: dismissed
|
|
167
|
+
? false
|
|
168
|
+
: {
|
|
169
|
+
stroke: 'var(--control-border)',
|
|
170
|
+
fill: 'transparent',
|
|
171
|
+
}, wrapperStyle: {
|
|
172
|
+
position: 'static',
|
|
173
|
+
visibility: 'visible',
|
|
174
|
+
pointerEvents: 'auto',
|
|
175
|
+
}, content: (tooltip) => {
|
|
176
|
+
const row = tooltip.active && !dismissed
|
|
177
|
+
? byId.get(String(tooltip.label))
|
|
178
|
+
: undefined;
|
|
179
|
+
return (_jsx("div", { className: 'pui-chart-readout', "data-chart-readout": row?.id, children: row ? (_jsxs(_Fragment, { children: [_jsx("strong", { children: row.label }), _jsx("ul", { children: visible.map((s) => (_jsxs("li", { children: [s.label, ": ", format(row.values[s.key])] }, s.key))) })] })) : (_jsx("span", { children: text.inspectHint })) }));
|
|
180
|
+
} })), series.map((s, index) => {
|
|
181
|
+
const common = {
|
|
182
|
+
name: s.label,
|
|
183
|
+
dataKey: (row) => row.values[s.key] ?? null,
|
|
184
|
+
hide: hidden.has(s.key),
|
|
185
|
+
isAnimationActive: false,
|
|
186
|
+
stroke: colorFor(index),
|
|
187
|
+
};
|
|
188
|
+
return type === 'bar' ? (_jsx(Bar, { ...common, fill: `url(#${id}-pattern-${index})`, maxBarSize: 48 }, s.key)) : type === 'area' ? (_jsx(Area, { ...common, type: 'linear', strokeWidth: 2, strokeDasharray: dashes[index % dashes.length], fill: `url(#${id}-pattern-${index})`, fillOpacity: 0.12, connectNulls: false, dot: rows.length > 40
|
|
189
|
+
? false
|
|
190
|
+
: (dot) => (_jsx(Marker, { ...dot, color: colorFor(index), index: index })), activeDot: false }, s.key)) : (_jsx(Line, { ...common, type: 'linear', strokeWidth: 2, strokeDasharray: dashes[index % dashes.length], connectNulls: false, dot: rows.length > 40
|
|
191
|
+
? false
|
|
192
|
+
: (dot) => (_jsx(Marker, { ...dot, color: colorFor(index), index: index })), activeDot: false }, s.key));
|
|
193
|
+
}), _jsx(FloatingValues, { host: floatHost, hidden: dismissed, render: (label) => {
|
|
194
|
+
const row = byId.get(label);
|
|
195
|
+
if (!row)
|
|
196
|
+
return null;
|
|
197
|
+
return (_jsxs(_Fragment, { children: [_jsx("strong", { children: row.label }), _jsx("ul", { children: series.map((s, index) => hidden.has(s.key) ? null : (_jsxs("li", { children: [_jsx(SeriesMark, { type: type, index: index, color: colorFor(index), pattern: `${id}-float-${index}` }), _jsx("span", { children: s.label }), _jsx("span", { className: 'pui-chart-tooltip-value', children: format(row.values[s.key]) })] }, s.key))) })] }));
|
|
198
|
+
} })] }) }), _jsx("div", { className: 'pui-chart-float', ref: setFloatHost })] }), _jsx("p", { className: 'pui-chart-axis-label pui-chart-x-label', children: xLabel })] })) : (_jsx("div", { className: 'pui-chart-empty', role: 'status', children: visible.length === 0 && series.length > 0 ? (_jsxs(_Fragment, { children: [_jsx("p", { children: text.noSeries }), _jsx(Button, { variant: 'outline', onClick: () => {
|
|
199
|
+
setRestoreRequested(true);
|
|
200
|
+
changeHidden([]);
|
|
201
|
+
}, children: text.showAll })] })) : (_jsx("p", { children: text.empty })) }))] }), hasValues && (_jsx("p", { className: 'pui-chart-help', id: `${id}-keys`, children: text.keyboardHelp })), _jsx("div", { className: 'pui-chart-inspector', ref: setHost, role: 'status', "aria-live": 'polite', "aria-atomic": 'true', "aria-label": text.values(title), hidden: !hasValues }), _jsxs("details", { className: 'pui-chart-table', children: [_jsx("summary", { children: text.viewTable }), _jsx(Table.Container, { label: text.table(title), orientation: 'both', scrollChain: 'contain', children: _jsxs(Table.Root, { children: [_jsx(Table.Caption, { children: text.tableCaption(unit) }), _jsx(Table.Head, { children: _jsxs(Table.Row, { children: [_jsx(Table.HeaderCell, { scope: 'col', children: xLabel }), series.map((s) => (_jsxs(Table.HeaderCell, { scope: 'col', children: [s.label, " (", unit, ")"] }, s.key)))] }) }), _jsx(Table.Body, { children: rows.length ? (rows.map((row) => (_jsxs(Table.Row, { children: [_jsx(Table.HeaderCell, { scope: 'row', children: row.label }), series.map((s) => (_jsx(Table.Cell, { children: format(row.values[s.key]) }, s.key)))] }, row.id)))) : (_jsx(Table.Row, { children: _jsx(Table.Cell, { colSpan: series.length + 1, children: text.noRows }) })) })] }) })] })] }));
|
|
147
202
|
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* Accordion, with the styles of what it renders. */
|
|
2
|
+
@layer surflane.tokens, surflane.themes, surflane.base, surflane.components,
|
|
3
|
+
surflane.sizes;
|
|
4
|
+
@import '@surflane/design-tokens/tokens.css';
|
|
5
|
+
@import '@surflane/design-tokens/presentation/base.css';
|
|
6
|
+
@import './rules/base.css';
|
|
7
|
+
@import './rules/sizing.css';
|
|
8
|
+
@import './rules/icon.css';
|
|
9
|
+
@import './rules/button.css';
|
|
10
|
+
@import './rules/disclosure.css';
|
|
11
|
+
@import './rules/accordion.css';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* Alert Dialog, with the styles of what it renders. */
|
|
2
|
+
@layer surflane.tokens, surflane.themes, surflane.base, surflane.components,
|
|
3
|
+
surflane.sizes;
|
|
4
|
+
@import '@surflane/design-tokens/tokens.css';
|
|
5
|
+
@import '@surflane/design-tokens/presentation/base.css';
|
|
6
|
+
@import './rules/base.css';
|
|
7
|
+
@import './rules/sizing.css';
|
|
8
|
+
@import './rules/icon.css';
|
|
9
|
+
@import '@surflane/design-tokens/controls.css';
|
|
10
|
+
@import './rules/dialog.css';
|
|
11
|
+
@import './rules/alert-dialog.css';
|
package/{src → dist/css}/all.css
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Every Surflane stylesheet in one import, in the recommended order: tokens, controls,
|
|
2
2
|
* composition, document typography and components. Each file keeps its cascade layer. */
|
|
3
|
-
@layer surflane.tokens, surflane.themes, surflane.base, surflane.components;
|
|
3
|
+
@layer surflane.tokens, surflane.themes, surflane.base, surflane.components, surflane.sizes;
|
|
4
4
|
@import '@surflane/design-tokens/tokens.css';
|
|
5
5
|
@import '@surflane/design-tokens/controls.css';
|
|
6
6
|
@import '@surflane/design-tokens/foundations.css';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/* App shell / Sidebar, with the styles of what it renders. */
|
|
2
|
+
@layer surflane.tokens, surflane.themes, surflane.base, surflane.components,
|
|
3
|
+
surflane.sizes;
|
|
4
|
+
@import '@surflane/design-tokens/tokens.css';
|
|
5
|
+
@import '@surflane/design-tokens/presentation/base.css';
|
|
6
|
+
@import './rules/base.css';
|
|
7
|
+
@import './rules/sizing.css';
|
|
8
|
+
@import './rules/popup.css';
|
|
9
|
+
@import './rules/icon.css';
|
|
10
|
+
@import './rules/tooltip.css';
|
|
11
|
+
@import './rules/button.css';
|
|
12
|
+
@import './rules/app-shell.css';
|