@ciridae/ui 1.0.3 → 1.0.5

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 (124) hide show
  1. package/dist/chat/conversation-source.d.ts +15 -1
  2. package/dist/chat/describe-tool-call.d.ts +3 -3
  3. package/dist/chat/describe-tool-call.js +3 -3
  4. package/dist/chat/message-text.js +2 -2
  5. package/dist/ciridae.css +16 -4
  6. package/dist/components/activity-feed.d.ts +1 -0
  7. package/dist/components/activity-feed.js +1 -0
  8. package/dist/components/app-frame.d.ts +13 -1
  9. package/dist/components/app-frame.js +45 -3
  10. package/dist/components/app-navigation.d.ts +28 -1
  11. package/dist/components/app-navigation.js +110 -14
  12. package/dist/components/badge.d.ts +29 -4
  13. package/dist/components/badge.js +134 -15
  14. package/dist/components/bulk-action-bar.js +3 -3
  15. package/dist/components/button.d.ts +10 -3
  16. package/dist/components/button.js +77 -44
  17. package/dist/components/calendar.js +44 -17
  18. package/dist/components/centered-page.js +2 -2
  19. package/dist/components/chart.d.ts +1 -1
  20. package/dist/components/chart.js +16 -11
  21. package/dist/components/chat-client-source.js +47 -6
  22. package/dist/components/chat-sheet-controller.d.ts +1 -0
  23. package/dist/components/chat-sheet-controller.js +1 -0
  24. package/dist/components/chat-sheet.d.ts +10 -2
  25. package/dist/components/chat-sheet.js +74 -37
  26. package/dist/components/chat.d.ts +90 -14
  27. package/dist/components/chat.js +401 -101
  28. package/dist/components/checkbox-item.d.ts +5 -2
  29. package/dist/components/checkbox-item.js +9 -5
  30. package/dist/components/chip.d.ts +5 -0
  31. package/dist/components/chip.js +27 -1
  32. package/dist/components/column-settings.js +1 -1
  33. package/dist/components/command-menu.d.ts +5 -1
  34. package/dist/components/command-menu.js +98 -32
  35. package/dist/components/command-palette.d.ts +6 -2
  36. package/dist/components/command-palette.js +20 -10
  37. package/dist/components/data-display.d.ts +35 -4
  38. package/dist/components/data-display.js +138 -19
  39. package/dist/components/data-grid.d.ts +14 -2
  40. package/dist/components/data-grid.js +61 -26
  41. package/dist/components/date-range-picker.js +9 -20
  42. package/dist/components/drawer.d.ts +5 -0
  43. package/dist/components/drawer.js +91 -54
  44. package/dist/components/editable-field.d.ts +17 -3
  45. package/dist/components/editable-field.js +57 -27
  46. package/dist/components/email-input.js +6 -5
  47. package/dist/components/environment-frame.d.ts +1 -1
  48. package/dist/components/environment-frame.js +37 -6
  49. package/dist/components/filter-bar.js +39 -28
  50. package/dist/components/form-controls.d.ts +9 -0
  51. package/dist/components/form-controls.js +46 -22
  52. package/dist/components/form-layout.d.ts +7 -3
  53. package/dist/components/form-layout.js +68 -23
  54. package/dist/components/glass-menu.d.ts +3 -0
  55. package/dist/components/glass-menu.js +63 -0
  56. package/dist/components/gradient-edge.d.ts +9 -0
  57. package/dist/components/gradient-edge.js +31 -0
  58. package/dist/components/input.d.ts +4 -2
  59. package/dist/components/input.js +26 -15
  60. package/dist/components/keycap.d.ts +9 -5
  61. package/dist/components/keycap.js +57 -19
  62. package/dist/components/lightbox.js +2 -1
  63. package/dist/components/locale.d.ts +10 -0
  64. package/dist/components/locale.js +18 -2
  65. package/dist/components/log-entry.d.ts +40 -0
  66. package/dist/components/log-entry.js +146 -0
  67. package/dist/components/mention-input.js +4 -3
  68. package/dist/components/menu.d.ts +6 -2
  69. package/dist/components/menu.js +34 -37
  70. package/dist/components/meter.d.ts +9 -1
  71. package/dist/components/meter.js +64 -6
  72. package/dist/components/navigation.d.ts +21 -4
  73. package/dist/components/navigation.js +84 -19
  74. package/dist/components/notification-tray.js +5 -4
  75. package/dist/components/overlays.d.ts +3 -1
  76. package/dist/components/overlays.js +69 -40
  77. package/dist/components/page-frame.js +1 -1
  78. package/dist/components/panel.d.ts +1 -1
  79. package/dist/components/panel.js +18 -12
  80. package/dist/components/radio-card.js +10 -9
  81. package/dist/components/record.d.ts +25 -1
  82. package/dist/components/record.js +147 -18
  83. package/dist/components/segmented-bar.d.ts +3 -1
  84. package/dist/components/segmented-bar.js +16 -6
  85. package/dist/components/select.d.ts +5 -2
  86. package/dist/components/select.js +103 -127
  87. package/dist/components/shell-skeleton.js +1 -1
  88. package/dist/components/shell.d.ts +2 -2
  89. package/dist/components/shell.js +43 -45
  90. package/dist/components/sidebar.d.ts +7 -2
  91. package/dist/components/sidebar.js +55 -27
  92. package/dist/components/single-line-text.d.ts +2 -0
  93. package/dist/components/single-line-text.js +16 -0
  94. package/dist/components/site-banner.d.ts +5 -2
  95. package/dist/components/site-banner.js +17 -6
  96. package/dist/components/skeleton.d.ts +7 -6
  97. package/dist/components/skeleton.js +28 -16
  98. package/dist/components/skeletons.d.ts +12 -0
  99. package/dist/components/skeletons.js +71 -8
  100. package/dist/components/stages.d.ts +78 -6
  101. package/dist/components/stages.js +512 -156
  102. package/dist/components/stat-card.d.ts +10 -2
  103. package/dist/components/stat-card.js +92 -37
  104. package/dist/components/status-banner.js +1 -1
  105. package/dist/components/tabs.js +26 -13
  106. package/dist/components/theme-toggle.d.ts +4 -0
  107. package/dist/components/theme-toggle.js +67 -0
  108. package/dist/components/toast.d.ts +9 -2
  109. package/dist/components/toast.js +78 -29
  110. package/dist/preset.js +291 -33
  111. package/dist/styled-system/styles.css +3107 -1039
  112. package/dist/styled-system/tokens/index.mjs +602 -22
  113. package/dist/styled-system/tokens/tokens.d.ts +11 -11
  114. package/dist/styled-system/types/prop-type.d.ts +2 -2
  115. package/dist/styles.css +16 -4
  116. package/dist/theme/color.d.ts +3 -0
  117. package/dist/theme/color.js +5 -0
  118. package/dist/theme/derive.d.ts +9 -2
  119. package/dist/theme/derive.js +72 -12
  120. package/dist/theme/palettes.d.ts +49 -6
  121. package/dist/theme/palettes.js +84 -15
  122. package/dist/theme/tokens.d.ts +3 -3
  123. package/dist/theme/tokens.js +108 -32
  124. package/package.json +9 -1
@@ -3,21 +3,14 @@ import { Menu as BaseMenu } from "@base-ui/react/menu";
3
3
  import { CaretRightIcon, CheckIcon, DotsThreeIcon } from "@phosphor-icons/react";
4
4
  import { css, cx } from "#primitives/css";
5
5
  import { IconButton } from "./button.js";
6
+ import { glassMenuDivider, glassMenuRow, glassMenuSurface } from "./glass-menu.js";
6
7
  import { HotkeyLayer } from "./hotkeys.js";
7
8
  import { directional, icon, Spinner } from "./icon.js";
8
9
  import { ShortcutHint } from "./keycap.js";
9
- /* Flush, like the select menu: rows run edge to edge and the popup's corners clip them. */
10
- const popup = css({
11
- minWidth: "overlay.menu",
12
- padding: "none",
13
- overflow: "hidden",
14
- border: "1px solid token(colors.border.control)",
15
- borderRadius: "action",
16
- background: "bg.card",
17
- color: "fg.primary",
18
- textStyle: "body",
19
- boxShadow: "card",
20
- });
10
+ import { singleLineTextClass } from "./single-line-text.js";
11
+ const popup = css(glassMenuSurface, { minWidth: "overlay.menu" });
12
+ // As wide as its trigger, such as a switcher whose menu hangs under a full-width button.
13
+ const triggerWidthPopup = css(glassMenuSurface, { width: "var(--anchor-width)" });
21
14
  // A submenu joins its parent: the corner it opens from goes square, so the two read as one panel.
22
15
  const submenuPopup = css({
23
16
  "&:is([data-side=right], [data-side=inline-end])": { borderStartStartRadius: "0" },
@@ -25,28 +18,34 @@ const submenuPopup = css({
25
18
  });
26
19
  /** The positioner is the positioned element, so it carries the stacking order. */
27
20
  const positioner = css({ zIndex: "popover" });
28
- const item = css({
29
- display: "flex",
30
- alignItems: "center",
31
- gap: "layout.controlGap",
32
- minHeight: "menu.item",
33
- paddingInline: "control.compactInline",
34
- fontSize: "caption",
35
- lineHeight: "body",
36
- cursor: "pointer",
37
- outline: "none",
38
- "&[data-highlighted], &[data-popup-open]": { background: "bg.rowHover" },
39
- "&[data-disabled]": { opacity: "disabled", cursor: "not-allowed" },
21
+ // A submenu's row stays lit while its submenu is open. A label stays on the row's one line, as the
22
+ // design's 32px rows set it, and ends in an ellipsis in a menu held to its trigger's width.
23
+ const itemStyle = css.raw(glassMenuRow, {
24
+ "&[data-popup-open]": {
25
+ background: "bg.hover",
26
+ boxShadow: "inset 0 0 0 1px token(colors.border.menuHover)",
27
+ },
40
28
  "& > [data-label]": { flex: "1", minWidth: "0" },
41
29
  "& > [data-trailing]": { color: "fg.muted" },
42
30
  });
43
- const danger = css({ color: "accent.red" });
31
+ const item = css(itemStyle);
32
+ const dangerItem = css(itemStyle, { color: "accent.red" });
33
+ // Every icon sits in one 14px slot, so labels line up whatever the icon's own size. A wider mark,
34
+ // such as an avatar, widens its slot instead of spilling into the gap before the label.
35
+ const iconSlot = css({
36
+ display: "inline-grid",
37
+ placeItems: "center",
38
+ flexShrink: "0",
39
+ minWidth: "mark.inline",
40
+ minHeight: "mark.inline",
41
+ fontSize: "icon.small",
42
+ });
44
43
  /* Not an item: a row that names what the menu belongs to, such as the signed-in account. */
45
44
  const header = css({
46
45
  display: "flex",
47
46
  alignItems: "center",
48
47
  gap: "layout.controlGap",
49
- paddingInline: "control.compactInline",
48
+ paddingInline: "menu.itemInline",
50
49
  paddingBlock: "control.compactBlock",
51
50
  fontSize: "caption",
52
51
  lineHeight: "compact",
@@ -64,7 +63,7 @@ const groupHeading = css({
64
63
  display: "flex",
65
64
  alignItems: "center",
66
65
  minHeight: "menu.item",
67
- paddingInline: "control.compactInline",
66
+ paddingInline: "menu.itemInline",
68
67
  color: "fg.muted",
69
68
  fontSize: "code",
70
69
  fontWeight: "medium",
@@ -72,18 +71,16 @@ const groupHeading = css({
72
71
  cursor: "default",
73
72
  });
74
73
  const itemMeta = css({
74
+ flexShrink: "1",
75
75
  minWidth: "0",
76
76
  overflow: "hidden",
77
77
  textOverflow: "ellipsis",
78
78
  whiteSpace: "nowrap",
79
- color: "fg.muted",
80
- });
81
- const separator = css({
82
- height: "border.hairline",
83
- background: "border.subtle",
79
+ fontSize: "caption",
84
80
  });
85
- export function Menu({ trigger, items, align = "start", ...rootProps }) {
86
- return (_jsxs(BaseMenu.Root, { ...rootProps, children: [_jsx(BaseMenu.Trigger, { render: trigger }), _jsx(BaseMenu.Portal, { children: _jsx(HotkeyLayer, { children: _jsx(BaseMenu.Positioner, { className: positioner, sideOffset: 6, align: align, children: _jsx(BaseMenu.Popup, { className: popup, "data-ciridae-ui": "", children: _jsx(MenuItems, { items: items }) }) }) }) })] }));
81
+ const separator = css(glassMenuDivider);
82
+ export function Menu({ trigger, items, align = "start", sideOffset = 4, matchTriggerWidth = false, ...rootProps }) {
83
+ return (_jsxs(BaseMenu.Root, { ...rootProps, children: [_jsx(BaseMenu.Trigger, { render: trigger }), _jsx(BaseMenu.Portal, { children: _jsx(HotkeyLayer, { children: _jsx(BaseMenu.Positioner, { className: positioner, sideOffset: sideOffset, align: align, children: _jsx(BaseMenu.Popup, { className: matchTriggerWidth ? triggerWidthPopup : popup, "data-ciridae-ui": "", children: _jsx(MenuItems, { items: items }) }) }) }) })] }));
87
84
  }
88
85
  /** The "more" button: an `IconButton` with three dots that opens a `Menu` of `items`. */
89
86
  export function OverflowMenu({ label, size = "medium", variant = "raised", align = "end", disabled, ref, ...props }) {
@@ -100,10 +97,10 @@ function MenuItems({ items }) {
100
97
  return (_jsxs("div", { className: header, role: "presentation", children: [menuItem.icon, _jsxs("span", { className: headerCopy, children: [_jsx("span", { children: menuItem.label }), menuItem.detail && _jsx("span", { children: menuItem.detail })] })] }, index));
101
98
  }
102
99
  if (menuItem.items) {
103
- return (_jsxs(BaseMenu.SubmenuRoot, { children: [_jsxs(BaseMenu.SubmenuTrigger, { className: item, disabled: menuItem.disabled, children: [menuItem.icon, _jsx("span", { "data-label": "", children: menuItem.label }), _jsx(CaretRightIcon, { "data-trailing": "", className: cx(icon({ size: "tiny" }), directional) })] }), _jsx(BaseMenu.Portal, { children: _jsx(BaseMenu.Positioner, { className: positioner, sideOffset: 0, align: "start", alignOffset: -1, children: _jsx(BaseMenu.Popup, { className: cx(popup, submenuPopup), "data-ciridae-ui": "", children: _jsx(MenuItems, { items: menuItem.items }) }) }) })] }, menuItem.label));
100
+ return (_jsxs(BaseMenu.SubmenuRoot, { children: [_jsxs(BaseMenu.SubmenuTrigger, { className: item, disabled: menuItem.disabled, children: [menuItem.icon && _jsx("span", { className: iconSlot, children: menuItem.icon }), _jsx("span", { "data-label": "", className: singleLineTextClass, children: menuItem.label }), _jsx(CaretRightIcon, { "data-trailing": "", className: cx(icon({ size: "small" }), directional) })] }), _jsx(BaseMenu.Portal, { children: _jsx(BaseMenu.Positioner, { className: positioner, sideOffset: 0, align: "start", alignOffset: -5, children: _jsx(BaseMenu.Popup, { className: cx(popup, submenuPopup), "data-ciridae-ui": "", children: _jsx(MenuItems, { items: menuItem.items }) }) }) })] }, menuItem.label));
104
101
  }
105
- const content = (_jsxs(_Fragment, { children: [menuItem.loading ? _jsx(Spinner, { "aria-hidden": "true" }) : menuItem.icon, _jsxs("span", { "data-label": "", children: [menuItem.label, menuItem.meta !== undefined && _jsxs("span", { className: itemMeta, children: [" ", menuItem.meta] })] }), menuItem.shortcut && _jsx(ShortcutHint, { "data-trailing": "", hotkey: menuItem.shortcut }), menuItem.checked && _jsx(CheckIcon, { "data-trailing": "", className: icon({ size: "small" }) })] }));
106
- const className = cx(item, menuItem.variant === "danger" && danger);
102
+ const content = (_jsxs(_Fragment, { children: [(menuItem.loading || menuItem.icon) && (_jsx("span", { className: iconSlot, children: menuItem.loading ? _jsx(Spinner, { "aria-hidden": "true" }) : menuItem.icon })), _jsx("span", { "data-label": "", className: singleLineTextClass, children: menuItem.label }), menuItem.meta !== undefined && (_jsx("span", { "data-trailing": "", className: itemMeta, children: menuItem.meta })), menuItem.shortcut && _jsx(ShortcutHint, { "data-trailing": "", boxed: true, hotkey: menuItem.shortcut }), menuItem.checked && _jsx(CheckIcon, { "data-trailing": "", className: icon({ size: "small" }) })] }));
103
+ const className = menuItem.variant === "danger" ? dangerItem : item;
107
104
  if (menuItem.href !== undefined || menuItem.render !== undefined) {
108
105
  return (_jsx(BaseMenu.LinkItem, { className: className, href: menuItem.href, render: menuItem.render, closeOnClick: true, onClick: menuItem.onSelect, children: content }, menuItem.label));
109
106
  }
@@ -1,5 +1,5 @@
1
1
  import type { Tone } from "./badge.js";
2
- import type { ComponentProps, ReactNode } from "react";
2
+ import type { ComponentProps, HTMLAttributes, ReactNode } from "react";
3
3
  export type MeterProps = Omit<ComponentProps<"span">, "children"> & {
4
4
  value: number;
5
5
  /** 1 by default, so a share can be passed as is. */
@@ -12,3 +12,11 @@ export type MeterProps = Omit<ComponentProps<"span">, "children"> & {
12
12
  valueLabel?: ReactNode;
13
13
  };
14
14
  export declare function Meter({ value, max, tone, label, valueLabel, className, ...props }: MeterProps): import("react").JSX.Element;
15
+ /** A button with `onClick`, otherwise a plain row; either way it takes the caller's attributes. */
16
+ export type BudgetRowProps = Omit<HTMLAttributes<HTMLElement>, "children"> & Pick<MeterProps, "value" | "max" | "tone" | "valueLabel"> & {
17
+ icon: ReactNode;
18
+ name: string;
19
+ /** Under the name, such as "$184K of $200K of approved budget". */
20
+ detail: ReactNode;
21
+ };
22
+ export declare function BudgetRow({ icon, name, detail, value, max, tone, valueLabel, className, ...props }: BudgetRowProps): import("react").JSX.Element;
@@ -1,8 +1,9 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  /*
3
3
  * A measured amount against its limit, such as a budget spent: a thin track filled to
4
4
  * `value / max`, with the figure above it at the end. Past the limit the track stays full. It is
5
- * phrasing content, so it can sit inside a row's button.
5
+ * phrasing content, so it can sit inside a row's button. On track (`info`) and at risk (`warning`)
6
+ * fill as the chart's progress bar does, a gradient over the hue's track.
6
7
  */
7
8
  import { css, cva, cx } from "#primitives/css";
8
9
  const meter = css({
@@ -19,9 +20,9 @@ const track = cva({
19
20
  variants: {
20
21
  tone: {
21
22
  neutral: { background: "bg.hover" },
22
- info: { background: "bg.infoSubtle" },
23
+ info: { background: "chart.progressTrack" },
23
24
  success: { background: "bg.successSubtle" },
24
- warning: { background: "bg.warningSubtle" },
25
+ warning: { background: "chart.atRiskTrack" },
25
26
  danger: { background: "bg.errorSubtle" },
26
27
  },
27
28
  },
@@ -31,9 +32,9 @@ const fill = cva({
31
32
  variants: {
32
33
  tone: {
33
34
  neutral: { background: "fg.subtle" },
34
- info: { background: "accent.blue" },
35
+ info: { background: "token(gradients.chart.progress)" },
35
36
  success: { background: "accent.green" },
36
- warning: { background: "accent.yellow" },
37
+ warning: { background: "token(gradients.chart.atRisk)" },
37
38
  danger: { background: "accent.red" },
38
39
  },
39
40
  },
@@ -42,3 +43,60 @@ export function Meter({ value, max = 1, tone = "info", label, valueLabel, classN
42
43
  const share = max > 0 ? Math.min(Math.max(value / max, 0), 1) : 0;
43
44
  return (_jsxs("span", { role: "meter", "aria-label": label, "aria-valuenow": value, "aria-valuemin": 0, "aria-valuemax": max, "aria-valuetext": typeof valueLabel === "string" ? valueLabel : undefined, ...props, className: cx(meter, className), "data-ciridae-ui": "", children: [valueLabel, _jsx("span", { className: track({ tone }), children: _jsx("span", { className: fill({ tone }), style: { width: `${share * 100}%` } }) })] }));
44
45
  }
46
+ /*
47
+ * Budget row: a job's spend against its budget as a row, the kit's Budget row: the icon on the
48
+ * raised tile, the name over what is spent of what, and a 170px meter at the end. A row with
49
+ * `onClick` is a button that opens the job. Rows in a list part with a hairline above each.
50
+ */
51
+ const budgetRow = css({
52
+ display: "flex",
53
+ alignItems: "center",
54
+ justifyContent: "space-between",
55
+ gap: "meter.rowGap",
56
+ width: "100%",
57
+ paddingBlock: "panel.default",
58
+ paddingInline: "panel.comfortable",
59
+ border: "0",
60
+ borderTop: "1px solid token(colors.border.divider)",
61
+ background: "transparent",
62
+ color: "fg.primary",
63
+ font: "inherit",
64
+ fontWeight: "medium",
65
+ letterSpacing: "compact",
66
+ lineHeight: "tight",
67
+ textAlign: "start",
68
+ _first: { borderTop: "0" },
69
+ "&:is(button)": {
70
+ cursor: "pointer",
71
+ _hover: { background: "bg.rowHover" },
72
+ _focusVisible: { focusRing: "inset" },
73
+ },
74
+ });
75
+ const budgetLeading = css({
76
+ display: "flex",
77
+ alignItems: "center",
78
+ gap: "layout.contentGap",
79
+ flex: "1",
80
+ minWidth: "0",
81
+ });
82
+ const budgetTile = css({
83
+ display: "inline-grid",
84
+ placeItems: "center",
85
+ flexShrink: "0",
86
+ width: "tile.icon",
87
+ height: "tile.icon",
88
+ border: "1px solid transparent",
89
+ borderRadius: "action",
90
+ background: "token(gradients.raised)",
91
+ fontSize: "icon.medium",
92
+ "--edge": "token(gradients.border.raised)",
93
+ });
94
+ const budgetCopy = css({ display: "grid", gap: "layout.tightGap", minWidth: "0" });
95
+ const budgetName = css({ fontSize: "body" });
96
+ const budgetDetail = css({ color: "fg.secondary", fontSize: "code" });
97
+ // Fluid under 170px, so the job's name keeps a readable line on a phone.
98
+ const budgetMeter = css({ width: "min(170px, 40%)", flexShrink: "0" });
99
+ export function BudgetRow({ icon, name, detail, value, max, tone, valueLabel, className, ...props }) {
100
+ const content = (_jsxs(_Fragment, { children: [_jsxs("span", { className: budgetLeading, children: [_jsx("span", { className: budgetTile, "data-ciridae-ui": "", "data-edge": "gradient", children: icon }), _jsxs("span", { className: budgetCopy, children: [_jsx("span", { className: budgetName, children: name }), _jsx("span", { className: budgetDetail, children: detail })] })] }), _jsx(Meter, { className: budgetMeter, value: value, max: max, tone: tone, label: name, valueLabel: valueLabel })] }));
101
+ return props.onClick ? (_jsx("button", { type: "button", ...props, className: cx(budgetRow, className), "data-ciridae-ui": "", children: content })) : (_jsx("div", { ...props, className: cx(budgetRow, className), "data-ciridae-ui": "", children: content }));
102
+ }
@@ -4,18 +4,35 @@ export type PaginationProps = Omit<ComponentProps<"nav">, "children"> & {
4
4
  page: number;
5
5
  pages: number;
6
6
  onPageChange: (page: number) => void;
7
+ /** At the bar's start, such as a `Select` of how many rows a page shows. */
8
+ pageSizeSelect?: ReactNode;
9
+ /** Before the pager, such as "Showing 1 to 25 of 1,284". */
10
+ summary?: ReactNode;
11
+ /** A button per page between Previous and Next, for a short run of pages. */
12
+ numbered?: boolean;
7
13
  labels?: Partial<CiridaeLabels["pagination"]>;
8
14
  };
9
- export declare function Pagination({ page, pages, onPageChange, labels: ownLabels, className, ...props }: PaginationProps): import("react").JSX.Element;
15
+ export declare function Pagination({ page, pages, onPageChange, pageSizeSelect, summary, numbered, labels: ownLabels, className, ...props }: PaginationProps): import("react").JSX.Element;
10
16
  export type EmptyStateProps = Omit<ComponentProps<"section">, "title"> & {
11
17
  icon?: ReactNode;
12
18
  title: ReactNode;
13
19
  description?: ReactNode;
20
+ /** The main way forward: a primary medium button on a page, a secondary small one in a card. */
14
21
  action?: ReactNode;
22
+ /** A second way forward before `action`, such as a secondary medium button on a page. */
23
+ secondaryAction?: ReactNode;
15
24
  /**
16
- * `block` frames a page's or panel's empty state; `bare` is the same without the frame, its icon
17
- * in a ring, for a table or card that frames it; `inline` is one muted line in a list.
25
+ * `block` is a page's or panel's empty state; `bare` the same, tighter, for a table or card
26
+ * that frames it; `inline` is one muted line in a list.
18
27
  */
19
28
  variant?: "block" | "bare" | "inline";
20
29
  };
21
- export declare function EmptyState({ icon, title, description, action, variant, className, ...props }: EmptyStateProps): import("react").JSX.Element;
30
+ export declare function EmptyState({ icon, title, description, action, secondaryAction, variant, className, ...props }: EmptyStateProps): import("react").JSX.Element;
31
+ export type EmptyTileProps = Omit<ComponentProps<"div">, "children"> & {
32
+ /** What the tile would count, as its stat tile names it. */
33
+ label: ReactNode;
34
+ /** Why there is no figure, such as "No invoices this period". */
35
+ note?: ReactNode;
36
+ };
37
+ /** A stat tile with no figure: its label, a dash in the figure's place, and why. */
38
+ export declare function EmptyTile({ label, note, className, ...props }: EmptyTileProps): import("react").JSX.Element;
@@ -1,30 +1,67 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CaretLeftIcon, CaretRightIcon } from "@phosphor-icons/react";
2
3
  import { css, cva, cx } from "#primitives/css";
3
- import { Button } from "./button.js";
4
+ import { Button, IconButton } from "./button.js";
5
+ import { icon } from "./icon.js";
4
6
  import { Heading } from "./layout.js";
5
7
  import { useCiridaeLocale } from "./locale.js";
6
- export function Pagination({ page, pages, onPageChange, labels: ownLabels, className, ...props }) {
8
+ import { cardSurface } from "./panel.js";
9
+ /*
10
+ * The bar under a table, the kit's Pagination. Full: the page size at the start, then the summary
11
+ * and the pager at the end, inset 12px. Simple, with no page size: the summary at the start, the
12
+ * pager at the end, flush.
13
+ */
14
+ const pagination = css({
15
+ display: "flex",
16
+ alignItems: "center",
17
+ justifyContent: "space-between",
18
+ gap: "layout.controlGap",
19
+ minHeight: "control.row",
20
+ paddingInline: "panel.default",
21
+ lineHeight: "tight",
22
+ "&[data-type=simple]": { paddingInline: "0" },
23
+ });
24
+ const paginationEnd = css({
25
+ display: "flex",
26
+ alignItems: "center",
27
+ gap: "layout.clusterGap",
28
+ marginInlineStart: "auto",
29
+ });
30
+ const paginationSummary = css({
31
+ color: "fg.secondary",
32
+ fontSize: "code",
33
+ fontWeight: "medium",
34
+ letterSpacing: "compact",
35
+ fontVariantNumeric: "tabular-nums",
36
+ });
37
+ // Simple: the summary and the pager are the bar's own ends.
38
+ const summaryAndPagerAsBarEnds = css({ display: "contents" });
39
+ const pager = css({ display: "flex", alignItems: "center", gap: "layout.tightGap" });
40
+ const pagerAlone = css({ marginInlineStart: "auto" });
41
+ export function Pagination({ page, pages, onPageChange, pageSizeSelect, summary, numbered = false, labels: ownLabels, className, ...props }) {
7
42
  const locale = useCiridaeLocale();
8
43
  const labels = { ...locale.labels.pagination, ...ownLabels };
9
- const visible = Array.from({ length: pages }, (_, index) => index + 1);
10
- return (_jsxs("nav", { "aria-label": labels.label, ...props, className: cx(css({ display: "flex", gap: "layout.tightGap" }), className), "data-ciridae-ui": "", children: [_jsx(Button, { variant: "ghost", disabled: pages === 0 || page <= 1, onClick: () => onPageChange(Math.max(1, page - 1)), children: labels.previous }), visible.map((value) => (_jsx(Button, { variant: value === page ? "primary" : "ghost", "aria-current": value === page ? "page" : undefined, onClick: () => onPageChange(value), children: locale.formatNumber(value) }, value))), _jsx(Button, { variant: "ghost", disabled: pages === 0 || page >= pages, onClick: () => onPageChange(Math.min(pages, page + 1)), children: labels.next })] }));
44
+ const Previous = locale.dir === "rtl" ? CaretRightIcon : CaretLeftIcon;
45
+ const Next = locale.dir === "rtl" ? CaretLeftIcon : CaretRightIcon;
46
+ return (_jsxs("nav", { "aria-label": labels.label, ...props, className: cx(pagination, className), "data-ciridae-ui": "", "data-type": pageSizeSelect ? "full" : "simple", children: [pageSizeSelect, _jsxs("div", { className: pageSizeSelect ? paginationEnd : summaryAndPagerAsBarEnds, children: [summary && _jsx("span", { className: paginationSummary, children: summary }), _jsxs("div", { className: cx(pager, !summary && pagerAlone), children: [_jsx(IconButton, { "aria-label": labels.previous, size: "medium", disabled: pages === 0 || page <= 1, onClick: () => onPageChange(Math.max(1, page - 1)), children: _jsx(Previous, { className: icon({ size: "small" }) }) }), numbered &&
47
+ Array.from({ length: pages }, (_, index) => index + 1).map((value) => (_jsx(Button, { variant: value === page ? "primary" : "ghost", "aria-current": value === page ? "page" : undefined, onClick: () => onPageChange(value), children: locale.formatNumber(value) }, value))), _jsx(IconButton, { "aria-label": labels.next, size: "medium", disabled: pages === 0 || page >= pages, onClick: () => onPageChange(Math.min(pages, page + 1)), children: _jsx(Next, { className: icon({ size: "small" }) }) })] })] })] }));
11
48
  }
12
49
  const emptyState = cva({
13
50
  base: { display: "grid", placeItems: "center", textAlign: "center" },
14
51
  variants: {
15
52
  variant: {
16
- // A page or panel with nothing in it yet: a dashed frame with room for a heading.
53
+ // A page or panel with nothing in it yet: no frame, with room around it.
17
54
  block: {
18
- minHeight: "emptyState.minimum",
19
- padding: "panel.roomy",
20
- border: "1px dashed token(colors.border.control)",
21
- borderRadius: "inset",
55
+ justifyItems: "center",
56
+ gap: "layout.clusterGap",
57
+ paddingBlock: "emptyState.pageBlock",
58
+ paddingInline: "panel.roomy",
22
59
  },
23
- // Inside a table or a card that frames it already: the icon in a ring, no frame of its own.
60
+ // Inside a table or a card that frames it already: tighter, its copy wrapping to the card.
24
61
  bare: {
25
62
  justifyItems: "center",
26
- gap: "layout.clusterGap",
27
- paddingBlock: "panel.roomy",
63
+ gap: "layout.contentGap",
64
+ paddingBlock: "emptyState.compactBlock",
28
65
  paddingInline: "panel.comfortable",
29
66
  },
30
67
  // A list or section with nothing to show: one muted line, no frame.
@@ -45,31 +82,59 @@ const emptyRing = css({
45
82
  height: "brand.logo",
46
83
  border: "1px solid token(colors.border.control)",
47
84
  borderRadius: "round",
48
- background: "bg.field",
85
+ // The field's grey well in light; in dark the deep grey a disabled field wears, not the white
86
+ // wash of a dark field, so the ring sits below the page rather than lit on it.
87
+ background: "bg.fieldDisabled",
88
+ _light: { background: "bg.field" },
49
89
  color: "fg.muted",
50
90
  fontSize: "icon.small",
51
91
  });
52
92
  const emptyCopy = css({ display: "grid", gap: "control.iconGap", maxWidth: "card.centered" });
93
+ // The title in the primary ink, whatever the table or card round it sets as its text colour.
94
+ const emptyTitle = css({ color: "fg.primary", lineHeight: "tight" });
53
95
  const emptyDescription = css({
54
96
  margin: "none",
55
97
  color: "fg.muted",
98
+ _light: { color: "fg.secondary" },
56
99
  fontSize: "code",
57
100
  fontWeight: "medium",
58
101
  letterSpacing: "compact",
59
- lineHeight: "body",
102
+ // A page's one line of help sits on the title's tight line; a card's wraps.
103
+ lineHeight: "tight",
104
+ "[data-variant=bare] &": { lineHeight: "body" },
60
105
  });
61
106
  const emptyActions = css({
62
107
  display: "flex",
63
108
  flexWrap: "wrap",
64
109
  justifyContent: "center",
65
110
  gap: "layout.controlGap",
111
+ paddingBlock: "layout.tightGap",
112
+ "[data-variant=bare] > &": { paddingBlock: "none" },
66
113
  });
67
- export function EmptyState({ icon, title, description, action, variant = "block", className, ...props }) {
114
+ export function EmptyState({ icon, title, description, action, secondaryAction, variant = "block", className, ...props }) {
68
115
  if (variant === "inline") {
69
116
  return (_jsxs("section", { ...props, "data-ciridae-ui": "", className: cx(emptyState({ variant }), className), children: [_jsxs("span", { className: css({ display: "inline-flex", alignItems: "center", gap: "control.iconGap" }), children: [icon, title] }), description && _jsx("span", { children: description }), action] }));
70
117
  }
71
- if (variant === "bare") {
72
- return (_jsxs("section", { ...props, "data-ciridae-ui": "", className: cx(emptyState({ variant }), className), children: [icon && (_jsx("span", { className: emptyRing, "aria-hidden": "true", children: icon })), _jsxs("div", { className: emptyCopy, children: [_jsx(Heading, { level: 2, size: "small", children: title }), description && _jsx("p", { className: emptyDescription, children: description })] }), action && _jsx("div", { className: emptyActions, children: action })] }));
73
- }
74
- return (_jsx("section", { ...props, "data-ciridae-ui": "", className: cx(emptyState({ variant }), className), children: _jsxs("div", { className: css({ display: "grid", justifyItems: "center", gap: "layout.contentGap" }), children: [icon, _jsx(Heading, { level: 2, children: title }), description && _jsx("p", { className: css({ margin: "none", color: "fg.muted" }), children: description }), action] }) }));
118
+ return (_jsxs("section", { ...props, "data-ciridae-ui": "", "data-variant": variant, className: cx(emptyState({ variant }), className), children: [icon && (_jsx("span", { className: emptyRing, "aria-hidden": "true", children: icon })), _jsxs("div", { className: emptyCopy, children: [_jsx(Heading, { level: 2, size: "small", className: emptyTitle, children: title }), description && _jsx("p", { className: emptyDescription, children: description })] }), (action || secondaryAction) && (_jsxs("div", { className: emptyActions, children: [secondaryAction, action] }))] }));
119
+ }
120
+ // A dashboard tile with nothing to count, worn like the stat tile it stands in for.
121
+ const emptyTile = css(cardSurface, {
122
+ display: "grid",
123
+ alignContent: "start",
124
+ justifyItems: "start",
125
+ gap: "layout.controlGap",
126
+ minHeight: "tile.minimum",
127
+ paddingBlock: "tile.block",
128
+ paddingInline: "tile.inline",
129
+ fontSize: "code",
130
+ fontWeight: "medium",
131
+ letterSpacing: "compact",
132
+ lineHeight: "tight",
133
+ });
134
+ const emptyTileLabel = css({ color: "fg.label" });
135
+ const emptyTileValue = css({ color: "fg.primary", fontSize: "body" });
136
+ const emptyTileNote = css({ color: "fg.subtle" });
137
+ /** A stat tile with no figure: its label, a dash in the figure's place, and why. */
138
+ export function EmptyTile({ label, note, className, ...props }) {
139
+ return (_jsxs("div", { ...props, className: cx(emptyTile, className), "data-ciridae-ui": "", children: [_jsx("span", { className: emptyTileLabel, children: label }), _jsx("span", { className: emptyTileValue, children: "\u2014" }), note && _jsx("span", { className: emptyTileNote, children: note })] }));
75
140
  }
@@ -45,12 +45,13 @@ const popup = css({
45
45
  maxWidth: "calc(100vw - token(spacing.layout.pageGap) * 2)",
46
46
  padding: "none",
47
47
  overflow: "hidden",
48
- border: "1px solid token(colors.border.control)",
48
+ border: "1px solid token(colors.border.divider)",
49
49
  borderRadius: "action",
50
- background: "bg.card",
50
+ background: "bg.popover",
51
+ backdropFilter: "blur(token(blurs.popover))",
51
52
  color: "fg.primary",
52
53
  textStyle: "body",
53
- boxShadow: "card",
54
+ boxShadow: "soft",
54
55
  outline: "none",
55
56
  });
56
57
  const header = css({
@@ -66,7 +67,7 @@ const caughtUp = css({ color: "fg.muted", fontSize: "caption", fontWeight: "medi
66
67
  const filters = css({
67
68
  paddingInline: "panel.comfortable",
68
69
  paddingBottom: "layout.tightGap",
69
- borderBottom: "1px solid token(colors.border.control)",
70
+ borderBottom: "1px solid token(colors.border.divider)",
70
71
  });
71
72
  const list = css({
72
73
  display: "grid",
@@ -103,5 +103,7 @@ export declare function Popover({ trigger, anchor, children, density, width, ali
103
103
  export type TooltipProps = Omit<ComponentProps<typeof BaseTooltip.Root>, "children"> & {
104
104
  children: ReactElement;
105
105
  content: ReactNode;
106
+ /** A count or shortcut after the content, quieter than it. */
107
+ meta?: ReactNode;
106
108
  };
107
- export declare function Tooltip({ children, content, ...rootProps }: TooltipProps): import("react").JSX.Element;
109
+ export declare function Tooltip({ children, content, meta, ...rootProps }: TooltipProps): import("react").JSX.Element;