@surflane/ui 0.11.0 → 0.13.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.
Files changed (137) hide show
  1. package/dist/alert-dialog.d.ts +25 -7
  2. package/dist/alert-dialog.js +9 -4
  3. package/dist/app-shell.d.ts +10 -0
  4. package/dist/app-shell.js +7 -1
  5. package/dist/autocomplete.d.ts +2 -6
  6. package/dist/avatar.d.ts +7 -7
  7. package/dist/avatar.js +2 -2
  8. package/dist/bar-list.d.ts +31 -0
  9. package/dist/bar-list.js +31 -0
  10. package/dist/bottom-navigation.d.ts +6 -2
  11. package/dist/bottom-navigation.js +2 -2
  12. package/dist/button.d.ts +6 -11
  13. package/dist/button.js +8 -8
  14. package/dist/card.d.ts +15 -10
  15. package/dist/card.js +7 -2
  16. package/dist/chart.d.ts +58 -3
  17. package/dist/chart.js +330 -48
  18. package/dist/choice.d.ts +2 -2
  19. package/dist/class-names.d.ts +0 -4
  20. package/dist/class-names.js +0 -14
  21. package/dist/code.d.ts +19 -7
  22. package/dist/code.js +32 -38
  23. package/dist/combobox.d.ts +2 -8
  24. package/dist/combobox.js +2 -2
  25. package/dist/context-menu.d.ts +2 -8
  26. package/dist/context-menu.js +2 -2
  27. package/dist/css/bar-list.css +9 -0
  28. package/dist/css/chart.css +2 -0
  29. package/dist/css/dashboard.css +20 -0
  30. package/dist/css/rules/alert-dialog.css +8 -2
  31. package/dist/css/rules/app-shell.css +57 -10
  32. package/dist/css/rules/bar-list.css +101 -0
  33. package/dist/css/rules/bar.css +5 -12
  34. package/dist/css/rules/bottom-navigation.css +21 -2
  35. package/dist/css/rules/breadcrumbs.css +0 -6
  36. package/dist/css/rules/button.css +4 -12
  37. package/dist/css/rules/card.css +19 -0
  38. package/dist/css/rules/chart.css +87 -8
  39. package/dist/css/rules/choice.css +26 -2
  40. package/dist/css/rules/combobox.css +17 -16
  41. package/dist/css/rules/dashboard.css +118 -0
  42. package/dist/css/rules/date-picker.css +50 -27
  43. package/dist/css/rules/dialog.css +47 -7
  44. package/dist/css/rules/disclosure.css +17 -3
  45. package/dist/css/rules/field-control.css +9 -6
  46. package/dist/css/rules/file-upload.css +9 -5
  47. package/dist/css/rules/filter-bar.css +32 -2
  48. package/dist/css/rules/gauge.css +2 -2
  49. package/dist/css/rules/icon.css +0 -5
  50. package/dist/css/rules/input-group.css +23 -8
  51. package/dist/css/rules/link.css +0 -6
  52. package/dist/css/rules/menu.css +5 -2
  53. package/dist/css/rules/menubar.css +1 -1
  54. package/dist/css/rules/navigation-menu.css +31 -26
  55. package/dist/css/rules/number-field.css +8 -1
  56. package/dist/css/rules/otp-field.css +13 -3
  57. package/dist/css/rules/page-header.css +10 -0
  58. package/dist/css/rules/panel.css +59 -10
  59. package/dist/css/rules/popup.css +50 -0
  60. package/dist/css/rules/preview-card.css +0 -21
  61. package/dist/css/rules/scroll-area.css +20 -1
  62. package/dist/css/rules/select.css +29 -2
  63. package/dist/css/rules/sizing.css +25 -3
  64. package/dist/css/rules/skeleton.css +1 -1
  65. package/dist/css/rules/slider.css +16 -8
  66. package/dist/css/rules/spinner.css +1 -1
  67. package/dist/css/rules/stat.css +66 -0
  68. package/dist/css/rules/tabs.css +14 -6
  69. package/dist/css/rules/textarea.css +28 -0
  70. package/dist/css/rules/time-range-picker.css +14 -0
  71. package/dist/css/rules/toast.css +3 -1
  72. package/dist/css/rules/toggle.css +10 -3
  73. package/dist/css/rules/toolbar.css +10 -3
  74. package/dist/css/rules/tree.css +29 -4
  75. package/dist/css/styles.css +3 -0
  76. package/dist/css/time-range-picker.css +14 -0
  77. package/dist/dashboard.d.ts +58 -0
  78. package/dist/dashboard.js +60 -0
  79. package/dist/date-picker.d.ts +32 -3
  80. package/dist/date-picker.js +43 -4
  81. package/dist/dialog.d.ts +25 -7
  82. package/dist/dialog.js +9 -4
  83. package/dist/drawer.d.ts +10 -2
  84. package/dist/drawer.js +6 -3
  85. package/dist/icon.d.ts +15 -5
  86. package/dist/icon.js +16 -3
  87. package/dist/index.d.ts +9 -6
  88. package/dist/index.js +7 -4
  89. package/dist/inline-message.d.ts +2 -2
  90. package/dist/inline-message.js +7 -2
  91. package/dist/input.d.ts +2 -2
  92. package/dist/layout.d.ts +1 -3
  93. package/dist/layout.js +2 -2
  94. package/dist/native-select.d.ts +2 -2
  95. package/dist/navigation-menu.d.ts +16 -0
  96. package/dist/navigation-menu.js +6 -1
  97. package/dist/page-header.d.ts +10 -2
  98. package/dist/page-header.js +3 -3
  99. package/dist/panel.d.ts +6 -3
  100. package/dist/panel.js +2 -2
  101. package/dist/parts.d.ts +7 -3
  102. package/dist/parts.js +12 -4
  103. package/dist/popover.d.ts +6 -6
  104. package/dist/popover.js +3 -2
  105. package/dist/preview-card.d.ts +2 -8
  106. package/dist/preview-card.js +2 -2
  107. package/dist/select.d.ts +38 -8
  108. package/dist/select.js +110 -22
  109. package/dist/sheet.d.ts +10 -2
  110. package/dist/sheet.js +6 -3
  111. package/dist/size.d.ts +8 -6
  112. package/dist/size.js +2 -7
  113. package/dist/sizing.d.ts +13 -6
  114. package/dist/sizing.js +13 -4
  115. package/dist/sparkline.d.ts +2 -2
  116. package/dist/sparkline.js +2 -2
  117. package/dist/spinner.d.ts +2 -2
  118. package/dist/spinner.js +2 -2
  119. package/dist/stat.d.ts +24 -0
  120. package/dist/stat.js +15 -1
  121. package/dist/table.d.ts +12 -4
  122. package/dist/table.js +8 -3
  123. package/dist/textarea.d.ts +6 -3
  124. package/dist/textarea.js +2 -2
  125. package/dist/time-range-picker.d.ts +42 -0
  126. package/dist/time-range-picker.js +69 -0
  127. package/dist/toggle.d.ts +2 -2
  128. package/dist/toggle.js +2 -2
  129. package/dist/toolbar.d.ts +11 -10
  130. package/dist/toolbar.js +23 -1
  131. package/dist/tooltip.d.ts +1 -3
  132. package/dist/tooltip.js +4 -2
  133. package/dist/tree.d.ts +1 -3
  134. package/dist/tree.js +2 -2
  135. package/dist/typography.d.ts +10 -3
  136. package/dist/typography.js +4 -3
  137. package/package.json +18 -3
package/dist/code.js CHANGED
@@ -2,22 +2,24 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as React from 'react';
4
4
  import { Icon } from './icon.js';
5
- import { cx } from './parts.js';
5
+ import { cx, skin } from './parts.js';
6
6
  import { Button } from './button.js';
7
7
  import { SyntaxHighlighter } from './syntax-highlighter.js';
8
8
  import { ScrollArea } from './scroll-area.js';
9
9
  const copyIcons = { idle: 'copy', copied: 'check', failed: 'alert' };
10
- // Feedback stays inside the copy button and returns to the action after this long.
10
+ // Feedback stays inside the button and returns to the action after this long.
11
11
  const copyFeedbackDuration = 2000;
12
- export const CodeBlock = function CodeBlock({ code, label = 'Code', language, wrap = false, labels, copyLabel: legacyCopyLabel, copiedLabel: legacyCopiedLabel, copyErrorLabel: legacyCopyErrorLabel, onCopy, className, ref, ...props }) {
13
- const copyLabel = labels?.copy ?? legacyCopyLabel ?? 'Copy code';
14
- const copiedLabel = labels?.copied ?? legacyCopiedLabel ?? 'Copied';
15
- const copyErrorLabel = labels?.copyError ?? legacyCopyErrorLabel ?? 'Copy unavailable';
12
+ /** Copies text and says what happened in its own label: the result replaces the
13
+ * action in place, as a polite live region, then the action returns after 2s.
14
+ * The button is as wide as its longest label, so nothing around it moves. */
15
+ export const CopyButton = function CopyButton({ value, labels, onCopy, onResult, variant = 'outline', size = 'small', className, ref, ...props }) {
16
+ const copyLabel = labels?.copy ?? 'Copy';
17
+ const copiedLabel = labels?.copied ?? 'Copied';
18
+ const copyErrorLabel = labels?.copyError ?? 'Copy unavailable';
16
19
  const [state, setState] = React.useState('idle');
17
20
  // After the first copy, each change of label fades in; the first render does not.
18
21
  const [used, setUsed] = React.useState(false);
19
22
  const revision = React.useRef(0), mounted = React.useRef(false), timer = React.useRef(undefined);
20
- const button = React.useRef(null), source = React.useRef(null);
21
23
  React.useEffect(() => {
22
24
  mounted.current = true;
23
25
  return () => {
@@ -26,29 +28,13 @@ export const CodeBlock = function CodeBlock({ code, label = 'Code', language, wr
26
28
  clearTimeout(timer.current);
27
29
  };
28
30
  }, []);
31
+ // New text clears a result, and a late answer for the old text is dropped.
32
+ const source = typeof value === 'string' ? value : null;
29
33
  React.useEffect(() => {
30
34
  revision.current++;
31
35
  clearTimeout(timer.current);
32
36
  setState('idle');
33
- }, [code]);
34
- const resting = React.useRef(true);
35
- resting.current = state === 'idle';
36
- // The button keeps its resting width, so the feedback never moves the toolbar.
37
- React.useLayoutEffect(() => {
38
- const element = button.current;
39
- if (!element || state !== 'idle')
40
- return;
41
- const measure = () => {
42
- element.style.minInlineSize = '';
43
- element.style.minInlineSize = `${element.getBoundingClientRect().width}px`;
44
- };
45
- measure();
46
- // Fonts can finish loading after a copy; the result label must not set the width.
47
- document.fonts?.ready.then(() => {
48
- if (mounted.current && resting.current)
49
- measure();
50
- });
51
- }, [copyLabel, state]);
37
+ }, [source]);
52
38
  async function copy() {
53
39
  const request = ++revision.current;
54
40
  clearTimeout(timer.current);
@@ -56,10 +42,11 @@ export const CodeBlock = function CodeBlock({ code, label = 'Code', language, wr
56
42
  setState('busy');
57
43
  let outcome;
58
44
  try {
45
+ const text = typeof value === 'string' ? value : await value();
59
46
  if (onCopy)
60
- await onCopy(code);
47
+ await onCopy(text);
61
48
  else
62
- await navigator.clipboard.writeText(code);
49
+ await navigator.clipboard.writeText(text);
63
50
  outcome = 'copied';
64
51
  }
65
52
  catch {
@@ -68,20 +55,27 @@ export const CodeBlock = function CodeBlock({ code, label = 'Code', language, wr
68
55
  if (!mounted.current || request !== revision.current)
69
56
  return;
70
57
  setState(outcome);
71
- // A failed copy selects the source, so the keyboard copy shortcut works next.
72
- if (outcome === 'failed' && source.current)
73
- window.getSelection()?.selectAllChildren(source.current);
58
+ onResult?.(outcome);
74
59
  timer.current = setTimeout(() => {
75
60
  if (mounted.current && request === revision.current)
76
61
  setState('idle');
77
62
  }, copyFeedbackDuration);
78
63
  }
79
64
  const icon = state === 'copied' || state === 'failed' ? state : 'idle';
80
- return (_jsxs("div", { ...props, ref: ref, "data-wrap": wrap || undefined, className: cx('surf-code-block', className), children: [_jsxs("div", { className: 'surf-code-toolbar', children: [_jsxs("span", { className: 'surf-code-label', children: [label, language && _jsx("span", { className: 'surf-code-language', children: language })] }), _jsxs(Button, { ref: button, type: 'button', variant: 'outline', size: 'small', className: 'surf-code-copy', onClick: copy,
81
- // A pending copy blocks a second start but keeps focus on the button.
82
- disabled: state === 'busy', focusableWhenDisabled: true, "aria-busy": state === 'busy' || undefined, "data-copy-state": icon === 'idle' ? undefined : icon, "data-copy-used": used || undefined, children: [_jsx(Icon, { name: copyIcons[icon], className: 'surf-code-copy-icon' }, icon), _jsx("span", { "aria-live": 'polite', children: _jsx("span", { className: 'surf-code-copy-label', children: state === 'copied'
83
- ? copiedLabel
84
- : state === 'failed'
85
- ? copyErrorLabel
86
- : copyLabel }, icon) })] })] }), _jsx(ScrollArea, { label: label, orientation: 'horizontal', children: _jsx("pre", { ref: source, className: 'surf-code-text', children: _jsx(SyntaxHighlighter, { code: code, language: language, inline: true }) }) })] }));
65
+ return (_jsxs(Button, { ...props, ref: ref, type: 'button', variant: variant, size: size, className: skin('surf-copy-button', className), onClick: copy,
66
+ // A pending copy blocks a second start but keeps focus on the button.
67
+ disabled: state === 'busy', focusableWhenDisabled: true, "aria-busy": state === 'busy' || undefined, "data-copy-state": icon === 'idle' ? undefined : icon, "data-copy-used": used || undefined, children: [_jsx(Icon, { name: copyIcons[icon], className: 'surf-copy-icon' }, icon), _jsxs("span", { className: 'surf-copy-text', children: [_jsx("span", { "aria-live": 'polite', children: _jsx("span", { className: 'surf-copy-label', children: state === 'copied'
68
+ ? copiedLabel
69
+ : state === 'failed'
70
+ ? copyErrorLabel
71
+ : copyLabel }, icon) }), [copyLabel, copiedLabel, copyErrorLabel].map((label, index) => (_jsx("span", { className: 'surf-copy-room', "data-label": label, "aria-hidden": 'true' }, index)))] })] }));
72
+ };
73
+ export const CodeBlock = function CodeBlock({ code, label = 'Code', language, wrap = false, labels, onCopy, className, ref, ...props }) {
74
+ const source = React.useRef(null);
75
+ return (_jsxs("div", { ...props, ref: ref, "data-wrap": wrap || undefined, className: cx('surf-code-block', className), children: [_jsxs("div", { className: 'surf-code-toolbar', children: [_jsxs("span", { className: 'surf-code-label', children: [label, language && _jsx("span", { className: 'surf-code-language', children: language })] }), _jsx(CopyButton, { className: 'surf-code-copy', value: code, onCopy: onCopy, labels: { ...labels, copy: labels?.copy ?? 'Copy code' },
76
+ // A failed copy selects the source, so the keyboard copy shortcut works next.
77
+ onResult: (result) => {
78
+ if (result === 'failed' && source.current)
79
+ window.getSelection()?.selectAllChildren(source.current);
80
+ } })] }), _jsx(ScrollArea, { label: label, orientation: 'horizontal', children: _jsx("pre", { ref: source, className: 'surf-code-text', children: _jsx(SyntaxHighlighter, { code: code, language: language, inline: true }) }) })] }));
87
81
  };
@@ -4,10 +4,7 @@ declare function ComboboxRoot<Value, Multiple extends boolean | undefined = fals
4
4
  export declare const Combobox: {
5
5
  Root: typeof ComboboxRoot;
6
6
  Portal: React.ForwardRefExoticComponent<Omit<import("@base-ui/react/combobox").ComboboxPortalProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
7
- Content: ({ side, align, sideOffset, collisionPadding, positionerProps, ref, ...props }: React.ComponentPropsWithoutRef<typeof BaseCombobox.Popup> & Pick<React.ComponentPropsWithoutRef<typeof BaseCombobox.Positioner>, "side" | "align" | "sideOffset" | "collisionPadding"> & {
8
- /** @deprecated Pass side, align, sideOffset and collisionPadding directly. */
9
- positionerProps?: React.ComponentPropsWithoutRef<typeof BaseCombobox.Positioner>;
10
- } & {
7
+ Content: ({ side, align, sideOffset, collisionPadding, ref, ...props }: React.ComponentPropsWithoutRef<typeof BaseCombobox.Popup> & Pick<React.ComponentPropsWithoutRef<typeof BaseCombobox.Positioner>, "side" | "align" | "sideOffset" | "collisionPadding"> & {
11
8
  ref?: React.Ref<HTMLDivElement>;
12
9
  }) => React.JSX.Element;
13
10
  Popup: ({ ref, ...props }: Omit<Omit<Omit<import("@base-ui/react/combobox").ComboboxPopupProps, "ref"> & React.RefAttributes<HTMLDivElement>, "ref">, "size"> & {
@@ -159,9 +156,6 @@ export declare const ComboboxArrow: React.ForwardRefExoticComponent<Omit<import(
159
156
  ref?: React.Ref<HTMLDivElement> | undefined;
160
157
  }) => React.JSX.Element, ComboboxTrigger: ({ children, className, ref, ...props }: React.ComponentPropsWithoutRef<typeof BaseCombobox.Trigger> & {
161
158
  ref?: React.Ref<HTMLButtonElement>;
162
- }) => React.JSX.Element, ComboboxValue: typeof BaseCombobox.Value, ComboboxContent: ({ side, align, sideOffset, collisionPadding, positionerProps, ref, ...props }: React.ComponentPropsWithoutRef<typeof BaseCombobox.Popup> & Pick<React.ComponentPropsWithoutRef<typeof BaseCombobox.Positioner>, "side" | "align" | "sideOffset" | "collisionPadding"> & {
163
- /** @deprecated Pass side, align, sideOffset and collisionPadding directly. */
164
- positionerProps?: React.ComponentPropsWithoutRef<typeof BaseCombobox.Positioner>;
165
- } & {
159
+ }) => React.JSX.Element, ComboboxValue: typeof BaseCombobox.Value, ComboboxContent: ({ side, align, sideOffset, collisionPadding, ref, ...props }: React.ComponentPropsWithoutRef<typeof BaseCombobox.Popup> & Pick<React.ComponentPropsWithoutRef<typeof BaseCombobox.Positioner>, "side" | "align" | "sideOffset" | "collisionPadding"> & {
166
160
  ref?: React.Ref<HTMLDivElement>;
167
161
  }) => React.JSX.Element;
package/dist/combobox.js CHANGED
@@ -14,8 +14,8 @@ function ComboboxRoot({ children, form, ...props }) {
14
14
  }
15
15
  const ComboPopup = /* @__PURE__ */ part(BaseCombobox.Popup, 'surf-combobox-popup', { portal: true });
16
16
  const ComboPositioner = /* @__PURE__ */ part(BaseCombobox.Positioner, 'surf-positioner', { portal: true });
17
- const ComboContent = function ComboContent({ side, align, sideOffset, collisionPadding, positionerProps, ref, ...props }) {
18
- return (_jsx(OwnPortal, { portal: BaseCombobox.Portal, children: _jsx(ComboPositioner, { sideOffset: 6, ...positionerProps, ...defined({ side, align, sideOffset, collisionPadding }), children: _jsx(ComboPopup, { ...props, ref: ref }) }) }));
17
+ const ComboContent = function ComboContent({ side, align, sideOffset, collisionPadding, ref, ...props }) {
18
+ return (_jsx(OwnPortal, { portal: BaseCombobox.Portal, children: _jsx(ComboPositioner, { sideOffset: 6, ...defined({ side, align, sideOffset, collisionPadding }), children: _jsx(ComboPopup, { ...props, ref: ref }) }) }));
19
19
  };
20
20
  const ComboTrigger = function ComboTrigger({ children, className, ref, ...props }) {
21
21
  return (_jsx(BaseCombobox.Trigger, { "aria-label": 'Show options', ...props, ref: ref, className: skin('surf-combobox-action', className), children: children ?? _jsx(Icon, { name: 'chevron-down' }) }));
@@ -14,10 +14,7 @@ export declare const ContextMenu: {
14
14
  } & {
15
15
  ref?: React.Ref<HTMLDivElement> | undefined;
16
16
  }) => React.JSX.Element;
17
- Content: ({ side, align, sideOffset, collisionPadding, positionerProps, ref, ...props }: React.ComponentPropsWithoutRef<typeof BaseContextMenu.Popup> & Placement<typeof BaseContextMenu.Positioner> & {
18
- /** @deprecated Pass side, align, sideOffset and collisionPadding directly. */
19
- positionerProps?: React.ComponentPropsWithoutRef<typeof BaseContextMenu.Positioner>;
20
- } & {
17
+ Content: ({ side, align, sideOffset, collisionPadding, ref, ...props }: React.ComponentPropsWithoutRef<typeof BaseContextMenu.Popup> & Placement<typeof BaseContextMenu.Positioner> & {
21
18
  ref?: React.Ref<HTMLDivElement>;
22
19
  }) => React.JSX.Element;
23
20
  Popup: ({ ref, ...props }: Omit<Omit<Omit<import("@base-ui/react/context-menu").ContextMenuPopupProps, "ref"> & React.RefAttributes<HTMLDivElement>, "ref">, "size"> & {
@@ -117,9 +114,6 @@ export declare const ContextMenuArrow: React.ForwardRefExoticComponent<Omit<impo
117
114
  size?: import("./size.js").ComponentSize;
118
115
  } & {
119
116
  ref?: React.Ref<HTMLDivElement> | undefined;
120
- }) => React.JSX.Element, ContextMenuContent: ({ side, align, sideOffset, collisionPadding, positionerProps, ref, ...props }: React.ComponentPropsWithoutRef<typeof BaseContextMenu.Popup> & Placement<typeof BaseContextMenu.Positioner> & {
121
- /** @deprecated Pass side, align, sideOffset and collisionPadding directly. */
122
- positionerProps?: React.ComponentPropsWithoutRef<typeof BaseContextMenu.Positioner>;
123
- } & {
117
+ }) => React.JSX.Element, ContextMenuContent: ({ side, align, sideOffset, collisionPadding, ref, ...props }: React.ComponentPropsWithoutRef<typeof BaseContextMenu.Popup> & Placement<typeof BaseContextMenu.Positioner> & {
124
118
  ref?: React.Ref<HTMLDivElement>;
125
119
  }) => React.JSX.Element;
@@ -4,8 +4,8 @@ import { ContextMenu as BaseContextMenu } from '@base-ui/react/context-menu';
4
4
  import { Menu } from './menu.js';
5
5
  import { OwnPortal, defined, part, scopedPortal, } from './parts.js';
6
6
  const ContextPositioner = /* @__PURE__ */ part(BaseContextMenu.Positioner, 'surf-positioner', { portal: true });
7
- const ContextContent = function ContextContent({ side, align, sideOffset, collisionPadding, positionerProps, ref, ...props }) {
8
- return (_jsx(OwnPortal, { portal: BaseContextMenu.Portal, children: _jsx(ContextPositioner, { ...positionerProps, ...defined({ side, align, sideOffset, collisionPadding }), children: _jsx(Menu.Popup, { ...props, ref: ref }) }) }));
7
+ const ContextContent = function ContextContent({ side, align, sideOffset, collisionPadding, ref, ...props }) {
8
+ return (_jsx(OwnPortal, { portal: BaseContextMenu.Portal, children: _jsx(ContextPositioner, { ...defined({ side, align, sideOffset, collisionPadding }), children: _jsx(Menu.Popup, { ...props, ref: ref }) }) }));
9
9
  };
10
10
  /** Same menu anatomy and styling, with the context-specific cursor positioner. */
11
11
  export const ContextMenu = {
@@ -0,0 +1,9 @@
1
+ /* Bar list, 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/css/presentation/base.css';
6
+ @import './rules/base.css';
7
+ @import './rules/sizing.css';
8
+ @import './rules/scroll-area.css';
9
+ @import './rules/bar-list.css';
@@ -5,7 +5,9 @@
5
5
  @import '@surflane/css/presentation/base.css';
6
6
  @import './rules/base.css';
7
7
  @import './rules/sizing.css';
8
+ @import './rules/popup.css';
8
9
  @import './rules/icon.css';
10
+ @import './rules/popover.css';
9
11
  @import './rules/button.css';
10
12
  @import './rules/scroll-area.css';
11
13
  @import '@surflane/css/presentation/table.css';
@@ -0,0 +1,20 @@
1
+ /* Dashboard, 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/css/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/popover.css';
11
+ @import './rules/button.css';
12
+ @import './rules/spinner.css';
13
+ @import './rules/skeleton.css';
14
+ @import '@surflane/css/foundations/layout.css';
15
+ @import './rules/layout.css';
16
+ @import '@surflane/css/presentation/card.css';
17
+ @import './rules/card.css';
18
+ @import '@surflane/css/presentation/inline-message.css';
19
+ @import './rules/inline-message.css';
20
+ @import './rules/dashboard.css';
@@ -6,18 +6,24 @@
6
6
  .surf-confirm-content {
7
7
  --surf-dialog-width: var(--surf-size-dialog-small);
8
8
  }
9
+ /* A confirmation is one short message: its actions sit on the same plane. */
9
10
  .surf-confirm-content .surf-dialog-footer {
10
- border-top: 0;
11
11
  padding-top: var(--surf-space-2);
12
+ background: none;
13
+ --surf-plane: var(--surf-canvas);
14
+ --surf-recessed: var(--surf-surface);
12
15
  }
13
16
  }
14
17
  @layer surflane.sizes {
15
18
  .surf-confirm-content .surf-dialog-header {
16
19
  padding-inline-end: var(--surf-space-6);
17
- border-bottom: 0;
18
20
  }
19
21
  :where([data-ui-size], [data-ui-size] *).surf-confirm-content
20
22
  .surf-dialog-header {
21
23
  padding-inline-end: var(--surf-ui-card-padding);
22
24
  }
25
+ :where([data-ui-size], [data-ui-size] *).surf-confirm-content
26
+ .surf-dialog-footer {
27
+ padding-top: var(--surf-space-2);
28
+ }
23
29
  }
@@ -16,13 +16,9 @@
16
16
  that frames it and clips it. Only the page surface inside it is rounded. */
17
17
  .surf-app-shell {
18
18
  --surf-app-shell-inset: var(--surf-space-3);
19
- /* --app-shell-radius, if a caller still sets it, keeps deriving the page radius. */
20
19
  --surf-app-shell-page-radius: max(
21
20
  0px,
22
- calc(
23
- var(--app-shell-radius, var(--surf-radius-sheet)) -
24
- var(--surf-app-shell-inset)
25
- )
21
+ calc(var(--surf-radius-sheet) - var(--surf-app-shell-inset))
26
22
  );
27
23
  --surf-app-shell-sidebar-width: 216px;
28
24
  /* The collapsed sidebar's width. Set it on the shell for a narrower rail. */
@@ -34,6 +30,8 @@
34
30
  min-inline-size: 0;
35
31
  padding: var(--surf-app-shell-inset);
36
32
  background: var(--surf-chrome);
33
+ --surf-plane: var(--surf-chrome);
34
+ --surf-recessed: var(--surf-canvas);
37
35
  }
38
36
  .surf-app-shell[data-sidebar='collapsed'] {
39
37
  --surf-app-shell-sidebar-width: var(--surf-app-shell-rail-width);
@@ -61,6 +59,8 @@
61
59
  background: var(--surf-canvas);
62
60
  border-radius: var(--surf-app-shell-page-radius);
63
61
  overflow-wrap: anywhere;
62
+ --surf-plane: var(--surf-canvas);
63
+ --surf-recessed: var(--surf-surface);
64
64
  }
65
65
  .surf-app-shell-footer {
66
66
  margin-block-start: auto;
@@ -88,13 +88,19 @@
88
88
  transition: background-color var(--surf-duration-fast) var(--surf-ease-out);
89
89
  }
90
90
  .surf-app-shell-link:hover {
91
- background: var(--surf-interaction-hover-bg);
91
+ background-image: linear-gradient(
92
+ var(--surf-interaction-hover-bg),
93
+ var(--surf-interaction-hover-bg)
94
+ );
92
95
  }
93
96
  .surf-app-shell-link:active:not([aria-current='page']) {
94
- background: var(--surf-interaction-active-bg);
97
+ background-image: linear-gradient(
98
+ var(--surf-interaction-active-bg),
99
+ var(--surf-interaction-active-bg)
100
+ );
95
101
  }
96
102
  .surf-app-shell-link[aria-current='page'] {
97
- background: var(--surf-selected);
103
+ background-color: var(--surf-selected);
98
104
  color: var(--surf-ink);
99
105
  font-weight: var(--surf-weight-medium);
100
106
  }
@@ -211,6 +217,16 @@
211
217
  border-radius: 0;
212
218
  }
213
219
  }
220
+ /* The menu trigger shows only where the sidebar hides, at the header's end. */
221
+ .surf-app-shell-menu-trigger.surf-button {
222
+ display: none;
223
+ }
224
+ @media (max-width: 760px) {
225
+ .surf-app-shell-menu-trigger.surf-button {
226
+ display: inline-flex;
227
+ margin-inline-start: auto;
228
+ }
229
+ }
214
230
  @media (max-width: 760px), (pointer: coarse) {
215
231
  .surf-app-shell-link {
216
232
  min-block-size: var(--surf-control-target-size);
@@ -223,7 +239,9 @@
223
239
  }
224
240
  @media (forced-colors: active) {
225
241
  .surf-app-shell-link[aria-current='page'] {
226
- outline: 1px solid Highlight;
242
+ background-color: Highlight;
243
+ color: HighlightText;
244
+ forced-color-adjust: none;
227
245
  }
228
246
  }
229
247
  /* Workspace variant: product sidebar owns the brand; the content scrollport is caller-provided. */
@@ -267,7 +285,7 @@
267
285
  position: absolute;
268
286
  inset-block-start: var(--surf-space-5);
269
287
  inset-inline-end: calc(var(--surf-app-shell-toggle-size) / -2);
270
- border: 1px solid var(--surf-line);
288
+ border: 1px solid var(--surf-control-border);
271
289
  background: var(--surf-canvas);
272
290
  z-index: 1;
273
291
  }
@@ -281,6 +299,25 @@
281
299
  border-color: transparent;
282
300
  background: transparent;
283
301
  }
302
+ /* On touch the toggle grows to the target size, expanded or collapsed. */
303
+ @media (pointer: coarse) {
304
+ .surf-app-shell[data-layout='workspace']
305
+ > .surf-app-shell-sidebar
306
+ > .surf-app-shell-toggle {
307
+ --surf-app-shell-toggle-size: max(
308
+ var(--surf-space-8),
309
+ var(--surf-control-target-size)
310
+ );
311
+ }
312
+ .surf-app-shell[data-layout='workspace'][data-sidebar='collapsed']
313
+ > .surf-app-shell-sidebar
314
+ > .surf-app-shell-toggle {
315
+ --surf-app-shell-toggle-size: max(
316
+ var(--surf-space-10),
317
+ var(--surf-control-target-size)
318
+ );
319
+ }
320
+ }
284
321
  /* A ScrollArea in the sidebar takes the room between the brand and the footer, and
285
322
  * one in the content fills it, so the menu and the page scroll on their own. */
286
323
  .surf-app-shell[data-layout='workspace']
@@ -340,6 +377,8 @@
340
377
  padding: var(--surf-space-6);
341
378
  background: var(--surf-canvas);
342
379
  border-radius: var(--surf-app-shell-page-radius);
380
+ --surf-plane: var(--surf-canvas);
381
+ --surf-recessed: var(--surf-surface);
343
382
  }
344
383
  /* On a phone the page meets the screen's edges, below the mobile header. */
345
384
  @media (max-width: 760px) {
@@ -391,6 +430,14 @@
391
430
  }
392
431
  }
393
432
  @layer surflane.sizes {
433
+ /* Compact density: the page and the content keep the 12px the blocks inside keep,
434
+ on a phone too. */
435
+ :where([data-ui-density='compact'], [data-ui-density='compact'] *):is(
436
+ .surf-app-shell-page,
437
+ .surf-app-shell-content
438
+ ) {
439
+ padding: var(--surf-ui-density-gap);
440
+ }
394
441
  /* A row rendered as a Button, such as Menu.Trigger, keeps the row's shape and the
395
442
  * sidebar's text over the button's size scheme. */
396
443
  .surf-app-shell-link.surf-button {
@@ -0,0 +1,101 @@
1
+ /* Bar list, with the styles of what it renders. */
2
+ @layer surflane.tokens, surflane.themes, surflane.base, surflane.components,
3
+ surflane.sizes;
4
+
5
+ @layer surflane.components {
6
+ /* Every row is one height, so a list that scrolls shows whole rows: a name line,
7
+ and a detail line when any row has one. */
8
+ .surf-bar-list {
9
+ --surf-bar-list-row: calc(
10
+ var(--surf-type-body-size) * var(--surf-type-body-leading) +
11
+ var(--surf-space-2)
12
+ );
13
+ min-inline-size: 0;
14
+ }
15
+ .surf-bar-list[data-detail] {
16
+ --surf-bar-list-row: calc(
17
+ var(--surf-type-body-size) * var(--surf-type-body-leading) +
18
+ var(--surf-type-caption-size) * var(--surf-type-caption-leading) +
19
+ var(--surf-space-2)
20
+ );
21
+ }
22
+ .surf-bar-list .surf-scroll-viewport {
23
+ max-block-size: calc(var(--surf-bar-list-rows) * var(--surf-bar-list-row));
24
+ }
25
+ /* Name, bar and value share one line, in columns every row shares, so the bars
26
+ start together and the values line up at the end. */
27
+ .surf-bar-list-items {
28
+ display: grid;
29
+ grid-template-columns: minmax(0, 2fr) minmax(var(--surf-space-12), 3fr) auto;
30
+ column-gap: var(--surf-space-3);
31
+ margin: 0;
32
+ padding: 0;
33
+ list-style: none;
34
+ }
35
+ .surf-bar-list-item {
36
+ display: grid;
37
+ grid-column: 1 / -1;
38
+ grid-template-columns: subgrid;
39
+ align-items: center;
40
+ box-sizing: border-box;
41
+ block-size: var(--surf-bar-list-row);
42
+ padding-block: var(--surf-space-1);
43
+ color: var(--surf-ink-body);
44
+ font: var(--surf-weight-regular)
45
+ var(--surf-type-body-size)/var(--surf-type-body-leading)
46
+ var(--surf-font-body);
47
+ }
48
+ .surf-bar-list-label {
49
+ display: grid;
50
+ min-inline-size: 0;
51
+ }
52
+ .surf-bar-list-name,
53
+ .surf-bar-list-detail {
54
+ overflow: hidden;
55
+ text-overflow: ellipsis;
56
+ white-space: nowrap;
57
+ }
58
+ .surf-bar-list-name {
59
+ color: var(--surf-ink);
60
+ }
61
+ .surf-bar-list-detail {
62
+ color: var(--surf-ink-muted);
63
+ font-size: var(--surf-type-caption-size);
64
+ line-height: var(--surf-type-caption-leading);
65
+ }
66
+ /* The pair Progress and Slider use: the brand mark on the recessed step. */
67
+ .surf-bar-list-bar {
68
+ display: block;
69
+ block-size: var(--surf-space-2);
70
+ border-radius: var(--surf-radius-full);
71
+ background: var(--surf-recessed);
72
+ overflow: hidden;
73
+ }
74
+ .surf-bar-list-fill {
75
+ display: block;
76
+ block-size: 100%;
77
+ border-radius: inherit;
78
+ background: var(--surf-brand-mark);
79
+ }
80
+ .surf-bar-list-value {
81
+ color: var(--surf-ink);
82
+ font-variant-numeric: tabular-nums;
83
+ text-align: end;
84
+ white-space: nowrap;
85
+ }
86
+ @media (forced-colors: active) {
87
+ .surf-bar-list-bar {
88
+ background: Canvas;
89
+ outline: 1px solid CanvasText;
90
+ }
91
+ .surf-bar-list-fill {
92
+ background: CanvasText;
93
+ }
94
+ }
95
+ }
96
+ @layer surflane.sizes {
97
+ /* The bar is as thick as Meter's and Progress's at each size. */
98
+ :where([data-ui-size], [data-ui-size] *).surf-bar-list-bar {
99
+ block-size: var(--surf-ui-gauge);
100
+ }
101
+ }
@@ -20,19 +20,12 @@
20
20
  width: fit-content;
21
21
  max-width: 100%;
22
22
  }
23
- .surf-toggle,
24
- .surf-toolbar-button {
25
- position: relative;
26
- }
23
+ /* A toolbar or menubar is a recessed plane; forced colors draw its edge. */
27
24
  @media (forced-colors: active) {
28
- .surf-toggle[data-pressed]::after,
29
- .surf-toolbar-button[aria-pressed='true']::after {
30
- content: '';
31
- position: absolute;
32
- inset: 3px;
33
- border: 1px solid CanvasText;
34
- border-radius: inherit;
35
- pointer-events: none;
25
+ .surf-toolbar,
26
+ .surf-menubar {
27
+ outline: 1px solid CanvasText;
28
+ outline-offset: -1px;
36
29
  }
37
30
  }
38
31
  }
@@ -75,7 +75,7 @@
75
75
  color: var(--surf-ink);
76
76
  }
77
77
  .surf-bottom-nav-item[aria-current='page'] .surf-bottom-nav-icon {
78
- background: var(--surf-selected);
78
+ background-color: var(--surf-selected);
79
79
  }
80
80
  @media (hover: hover) {
81
81
  .surf-bottom-nav-item:hover .surf-bottom-nav-icon {
@@ -117,6 +117,22 @@
117
117
  font-variant-numeric: tabular-nums;
118
118
  text-align: center;
119
119
  }
120
+ /* Status fills, each 4.5:1 against the canvas-colored count. */
121
+ .surf-bottom-nav-badge[data-tone='caution'] {
122
+ background: var(--surf-status-caution-fg);
123
+ }
124
+ .surf-bottom-nav-badge[data-tone='success'] {
125
+ background: var(--surf-status-success-fg);
126
+ }
127
+ .surf-bottom-nav-badge[data-tone='info'] {
128
+ background: var(--surf-status-info-fg);
129
+ }
130
+ .surf-bottom-nav-badge[data-tone='progress'] {
131
+ background: var(--surf-status-progress-fg);
132
+ }
133
+ .surf-bottom-nav-badge[data-tone='neutral'] {
134
+ background: var(--surf-status-neutral-fg);
135
+ }
120
136
  .surf-bottom-nav-badge[data-dot] {
121
137
  inset-block-start: 0;
122
138
  min-inline-size: 0;
@@ -134,10 +150,13 @@
134
150
  border-block-start-color: CanvasText;
135
151
  }
136
152
  .surf-bottom-nav-item[aria-current='page'] .surf-bottom-nav-icon {
137
- outline: 1px solid Highlight;
153
+ background-color: Highlight;
154
+ color: HighlightText;
155
+ forced-color-adjust: none;
138
156
  }
139
157
  .surf-bottom-nav-badge {
140
158
  border: 1px solid CanvasText;
159
+ forced-color-adjust: auto;
141
160
  }
142
161
  }
143
162
  }
@@ -1,9 +1,3 @@
1
1
  /* Breadcrumbs, with the styles of what it renders. */
2
2
  @layer surflane.tokens, surflane.themes, surflane.base, surflane.components,
3
3
  surflane.sizes;
4
-
5
- @layer surflane.sizes {
6
- :where([data-ui-size], [data-ui-size] *):is(.surf-breadcrumbs) {
7
- font-size: var(--surf-ui-font);
8
- }
9
- }
@@ -24,7 +24,7 @@
24
24
  text-decoration: none;
25
25
  cursor: pointer;
26
26
  }
27
- .surf-button:is([data-variant='default'], [data-variant='primary']) {
27
+ .surf-button[data-variant='primary'] {
28
28
  /* On the brand fill the layer is its ink: light ink barely moves bright cyan. */
29
29
  --surf-interaction-hover-bg: oklch(from var(--surf-on-brand) l c h / 0.08);
30
30
  --surf-interaction-active-bg: oklch(from var(--surf-on-brand) l c h / 0.14);
@@ -33,7 +33,7 @@
33
33
  color: var(--surf-on-brand);
34
34
  }
35
35
  .surf-button[data-variant='outline'] {
36
- border-color: var(--surf-line);
36
+ border-color: var(--surf-control-border);
37
37
  background: transparent;
38
38
  }
39
39
  .surf-button[data-variant='ghost'] {
@@ -66,7 +66,7 @@
66
66
  }
67
67
  }
68
68
  .surf-button[data-variant='secondary'] {
69
- background: var(--surf-selected);
69
+ background-color: var(--surf-selected);
70
70
  color: var(--surf-ink);
71
71
  }
72
72
  .surf-button[data-variant='destructive'] {
@@ -100,14 +100,6 @@
100
100
  }
101
101
  }
102
102
  @layer surflane.sizes {
103
- .surf-button[data-size='xs'] {
104
- height: 24px;
105
- min-height: 24px;
106
- padding-inline: var(--surf-space-2);
107
- font-size: var(--surf-type-caption-size);
108
- letter-spacing: var(--surf-type-caption-tracking);
109
- gap: var(--surf-space-1);
110
- }
111
103
  .surf-button[data-size^='icon'] {
112
104
  width: var(--surf-control-height);
113
105
  padding: 0;
@@ -117,7 +109,7 @@
117
109
  min-height: var(--surf-control-height);
118
110
  padding-inline: var(--surf-ui-button-padding);
119
111
  font-size: var(--surf-ui-font);
120
- line-height: var(--surf-ui-leading);
112
+ line-height: var(--surf-ui-label-leading);
121
113
  }
122
114
  :where([data-ui-size], [data-ui-size] *).surf-button[data-size^='icon'] {
123
115
  inline-size: var(--surf-control-height);