@crewlethq/ui 0.12.0 → 0.14.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 (157) hide show
  1. package/README.md +82 -5
  2. package/dist/AppShell/index.js +2 -1
  3. package/dist/Avatar/index.js +1 -1
  4. package/dist/{Avatar-QHYOFID3.css → Avatar-BPJ36QZR.css} +1 -1
  5. package/dist/AvatarPicker/index.js +5 -5
  6. package/dist/Breadcrumb/Breadcrumb.d.ts +68 -0
  7. package/dist/Breadcrumb/index.d.ts +3 -0
  8. package/dist/Breadcrumb/index.js +3 -0
  9. package/dist/Breadcrumb-YMWVM3SV.css +193 -0
  10. package/dist/ChoiceButtons/ChoiceButtons.d.ts +43 -0
  11. package/dist/ChoiceButtons/index.d.ts +3 -0
  12. package/dist/ChoiceButtons/index.js +3 -0
  13. package/dist/ChoiceButtons-DJ5EOGUA.css +127 -0
  14. package/dist/Combobox/index.js +2 -2
  15. package/dist/CommandPalette/CommandPalette.d.ts +48 -1
  16. package/dist/CommandPalette/index.js +3 -3
  17. package/dist/{CommandPalette-C5AMKGN2.css → CommandPalette-HBTNKHZQ.css} +55 -2
  18. package/dist/DataTable/index.js +4 -4
  19. package/dist/DataView/index.js +11 -11
  20. package/dist/DateTimePicker/index.js +2 -2
  21. package/dist/{DateTimePicker-4AZBTODT.css → DateTimePicker-NJ6XH2TE.css} +4 -3
  22. package/dist/Document/Document.d.ts +56 -0
  23. package/dist/Document/index.d.ts +3 -0
  24. package/dist/Document/index.js +2 -0
  25. package/dist/Document-PQGXJHB7.css +288 -0
  26. package/dist/EntityChip/index.js +1 -1
  27. package/dist/Highlight/Highlight.d.ts +45 -0
  28. package/dist/Highlight/index.d.ts +3 -0
  29. package/dist/Highlight/index.js +2 -0
  30. package/dist/Highlight-DXLV2R6L.css +29 -0
  31. package/dist/HuePicker/index.js +3 -3
  32. package/dist/ImageUpload/index.js +2 -2
  33. package/dist/{ImageUpload-L2GE4JUM.css → ImageUpload-NVGMN7DP.css} +9 -6
  34. package/dist/InboxList/InboxList.d.ts +116 -0
  35. package/dist/InboxList/index.d.ts +3 -0
  36. package/dist/InboxList/index.js +5 -0
  37. package/dist/InboxList-YMFWKGGS.css +252 -0
  38. package/dist/KeyChip/KeyChip.d.ts +21 -0
  39. package/dist/KeyChip/index.d.ts +3 -0
  40. package/dist/KeyChip/index.js +2 -0
  41. package/dist/KeyChip-4XJU4BBE.css +30 -0
  42. package/dist/Link/Link.d.ts +17 -2
  43. package/dist/Link/index.js +1 -1
  44. package/dist/{Link-6BEFNMN2.css → Link-WPMVLKLY.css} +36 -1
  45. package/dist/Listbox/index.d.ts +2 -0
  46. package/dist/Listbox/index.js +1 -1
  47. package/dist/Listbox/useHighlightRest.d.ts +35 -0
  48. package/dist/MarkdownEditor/MarkdownEditor.d.ts +93 -0
  49. package/dist/MarkdownEditor/format.d.ts +34 -0
  50. package/dist/MarkdownEditor/index.d.ts +5 -0
  51. package/dist/MarkdownEditor/index.js +12 -0
  52. package/dist/MarkdownEditor-ZLSHTOIZ.css +146 -0
  53. package/dist/Modal/Modal.d.ts +5 -1
  54. package/dist/Modal/index.js +1 -1
  55. package/dist/{Modal-BNT3TDV2.css → Modal-RQAYGG4L.css} +34 -1
  56. package/dist/OrgLabel/index.js +1 -1
  57. package/dist/OrgNode/index.js +1 -1
  58. package/dist/OrgTable/index.js +4 -4
  59. package/dist/Outline/Outline.d.ts +72 -0
  60. package/dist/Outline/index.d.ts +3 -0
  61. package/dist/Outline/index.js +2 -0
  62. package/dist/Outline-VCXKKU3E.css +78 -0
  63. package/dist/Popover/Popover.d.ts +21 -1
  64. package/dist/Popover/index.js +1 -1
  65. package/dist/ReadingPane/ReadingPane.d.ts +95 -0
  66. package/dist/ReadingPane/index.d.ts +3 -0
  67. package/dist/ReadingPane/index.js +4 -0
  68. package/dist/ReadingPane-5UOLPWY5.css +194 -0
  69. package/dist/SegmentedControl/SegmentedControl.d.ts +9 -1
  70. package/dist/SegmentedControl/index.js +3 -3
  71. package/dist/{SegmentedControl-76HODRBT.css → SegmentedControl-PKBLJVSA.css} +18 -0
  72. package/dist/Select/Select.d.ts +22 -0
  73. package/dist/Select/index.js +2 -2
  74. package/dist/{Select-4ANVLHUY.css → Select-2P7AWUE4.css} +56 -6
  75. package/dist/SidebarNav/index.js +2 -1
  76. package/dist/{SidebarNav-X5NLZNWA.css → SidebarNav-KF66MLOM.css} +4 -19
  77. package/dist/Switcher/Switcher.d.ts +151 -0
  78. package/dist/Switcher/index.d.ts +3 -0
  79. package/dist/Switcher/index.js +16 -0
  80. package/dist/Switcher-B44Q4WF4.css +346 -0
  81. package/dist/Tabs/Tabs.d.ts +7 -1
  82. package/dist/Tabs/index.js +1 -1
  83. package/dist/{Tabs-5I5Q5KKM.css → Tabs-ITJYJFMU.css} +49 -0
  84. package/dist/TagsInput/index.js +2 -2
  85. package/dist/ThemeGallery/ThemeGallery.d.ts +65 -0
  86. package/dist/ThemeGallery/index.d.ts +3 -0
  87. package/dist/ThemeGallery/index.js +6 -0
  88. package/dist/ThemeGallery-NITASULQ.css +93 -0
  89. package/dist/ThemeSwatch/ThemeSwatch.d.ts +71 -0
  90. package/dist/ThemeSwatch/index.d.ts +3 -0
  91. package/dist/ThemeSwatch/index.js +2 -0
  92. package/dist/ThemeSwatch-4DL457ZB.css +256 -0
  93. package/dist/ThemeSwitcher/ThemeSwitcher.d.ts +5 -1
  94. package/dist/ThemeSwitcher/index.js +4 -4
  95. package/dist/ThemeToggle/ThemeToggle.d.ts +1 -1
  96. package/dist/ThemeToggle/index.js +1 -1
  97. package/dist/TimeWindowPicker/index.js +4 -4
  98. package/dist/{TimeWindowPicker-GBF4JDFI.css → TimeWindowPicker-BZ5HHATU.css} +5 -4
  99. package/dist/Toaster/index.js +1 -1
  100. package/dist/{Toaster-NVDCEIP5.css → Toaster-VZKISP2S.css} +81 -87
  101. package/dist/Tree/index.d.ts +1 -1
  102. package/dist/Tree/index.js +1 -1
  103. package/dist/Tree/useTreeState.d.ts +37 -7
  104. package/dist/TreeCanvas/index.js +2 -2
  105. package/dist/TreeGrid/TreeGrid.d.ts +22 -2
  106. package/dist/TreeGrid/index.js +2 -2
  107. package/dist/{TreeGrid-NSW7VMOX.css → TreeGrid-FBX3MSJA.css} +19 -0
  108. package/dist/{chunk-EBBAAQDY.js → chunk-26PEPONN.js} +3 -1
  109. package/dist/{chunk-GOUKOXJK.js → chunk-3VVKJ7AZ.js} +3 -3
  110. package/dist/{chunk-HV6IZFBK.js → chunk-4FFHJBEN.js} +2 -2
  111. package/dist/{chunk-JHQUT4WJ.js → chunk-4LDTQWS7.js} +1 -1
  112. package/dist/{chunk-GXYNEDNA.js → chunk-4Y7WKXAW.js} +1 -1
  113. package/dist/{chunk-IINFVK3W.js → chunk-5IXMLWGW.js} +6 -6
  114. package/dist/{chunk-MCPKHYMC.js → chunk-6LXMLQUM.js} +17 -4
  115. package/dist/{chunk-3UK4VFSK.js → chunk-6NIZCQUM.js} +1 -1
  116. package/dist/{chunk-ORNOOMVY.js → chunk-6TSZCBID.js} +2 -2
  117. package/dist/{chunk-ZMJHK7DR.js → chunk-75WOCYGC.js} +2 -2
  118. package/dist/chunk-77IEHFY4.js +299 -0
  119. package/dist/chunk-A5YKNRG6.js +94 -0
  120. package/dist/{chunk-JCNUQ3US.js → chunk-AXNQCAFC.js} +2 -2
  121. package/dist/{chunk-UG5ZMAXW.js → chunk-CBZSSKH3.js} +5 -5
  122. package/dist/{chunk-LAQK4V3Z.js → chunk-DJMLHCLD.js} +1 -1
  123. package/dist/{chunk-6MU7HNB4.js → chunk-F73MC7EZ.js} +47 -2
  124. package/dist/{chunk-ZPSOF6VL.js → chunk-FBSEJO2C.js} +1 -1
  125. package/dist/chunk-FYTIJM2T.js +40 -0
  126. package/dist/chunk-H6VKCIWV.js +41 -0
  127. package/dist/{chunk-HLBRPES3.js → chunk-IFM63LAG.js} +3 -3
  128. package/dist/chunk-IG75YMUN.js +22 -0
  129. package/dist/{chunk-SEFMO2EV.js → chunk-IXUU5DLZ.js} +55 -6
  130. package/dist/{chunk-ZHOM65MI.js → chunk-JCO3SIST.js} +1 -1
  131. package/dist/chunk-KLIEPFEZ.js +95 -0
  132. package/dist/{chunk-7LD6P32I.js → chunk-L3CTFFLZ.js} +2 -18
  133. package/dist/chunk-MNFU2RNT.js +74 -0
  134. package/dist/{chunk-S3BCVWTU.js → chunk-MY374UUD.js} +49 -31
  135. package/dist/{chunk-K5Q2DK3P.js → chunk-NXDEPUVG.js} +1 -1
  136. package/dist/{chunk-PDUZO35I.js → chunk-O2ZDZZS7.js} +15 -6
  137. package/dist/{chunk-5WEVLIB6.js → chunk-RZ7RGECB.js} +1 -1
  138. package/dist/{chunk-WEIINZPA.js → chunk-SBRE7IML.js} +6 -2
  139. package/dist/{chunk-TEEJSTFG.js → chunk-TFMJH2E6.js} +1 -1
  140. package/dist/{chunk-IVFJD7WY.js → chunk-UCCC7YVZ.js} +3 -2
  141. package/dist/chunk-VWNC2L6W.js +110 -0
  142. package/dist/chunk-VYY56AMX.js +2 -0
  143. package/dist/chunk-WI6K5M5X.js +24 -0
  144. package/dist/{chunk-XV65NTIT.js → chunk-WYQOHFML.js} +94 -41
  145. package/dist/chunk-XYCADWZN.js +100 -0
  146. package/dist/chunk-Y6NR7QJ3.js +336 -0
  147. package/dist/chunk-YSJC7AZC.js +62 -0
  148. package/dist/chunk-ZAJHGDAW.js +93 -0
  149. package/dist/generated/index.d.ts +12 -0
  150. package/dist/generated/index.js +64 -51
  151. package/dist/index.js +64 -51
  152. package/dist/styles.css +2711 -402
  153. package/dist/utils/highlight.d.ts +17 -0
  154. package/dist/utils/useMediaMatch.d.ts +12 -0
  155. package/package.json +3 -3
  156. package/dist/chunk-UAQIUSSK.js +0 -2
  157. /package/dist/{chunk-74OVQWA5.js → chunk-6AR2T5Y4.js} +0 -0
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Moving a page to another subject of the same kind: one project for another,
3
+ * or for all of them.
4
+ *
5
+ * # One panel, three places
6
+ *
7
+ * ANCHORED under its button where the page names its subject, which is a
8
+ * breadcrumb's heading (`Breadcrumb`'s `current`): the reader changes the
9
+ * subject where the subject is named. CENTRED over the page when it is opened
10
+ * by a key from a page with no such button, and nothing behind it moves until
11
+ * a row is chosen. A SHEET from the bottom edge below the phone breakpoint,
12
+ * whichever of the two it would otherwise be, because a thumb reaches the
13
+ * bottom of a phone and a panel hung from a heading at the top of one leaves
14
+ * no room for the keyboard.
15
+ *
16
+ * # It is a combobox in a dialog
17
+ *
18
+ * Focus stays in the search field and the arrows move a highlight through the
19
+ * rows, which the field names with `aria-activedescendant`, so a screen reader
20
+ * hears each row as it is reached. The rows are options in ONE listbox: the
21
+ * pinned row, the rows a message offers, the groups, and the footer's actions,
22
+ * which sit in their own band below the scrolling list and are owned by the
23
+ * listbox (`aria-owns`) so the arrows walk into them without a second list.
24
+ *
25
+ * # Every row that goes somewhere is a link
26
+ *
27
+ * A row with an `href` is an anchor, so a middle click and a Command or Control
28
+ * click open it in a new tab as every link does, and Command or Control with
29
+ * Enter does the same from the keyboard. The panel stays open for all three:
30
+ * the reader asked for another tab, not to leave this one.
31
+ *
32
+ * # A held row stays, with its reason
33
+ *
34
+ * A row the reader cannot take right now (New project, for a viewer without
35
+ * the credential) is drawn with the reason at its end and passed over by the
36
+ * arrows. A row that vanished would be one the reader could not know exists.
37
+ *
38
+ * # Leaving it
39
+ *
40
+ * Escape closes it and puts focus back on its button. Tab from the anchored
41
+ * panel closes it and carries on to whatever follows the button, which is
42
+ * what Tab from the button would have reached; a centred panel or a sheet is a
43
+ * modal surface and keeps Tab inside. Taking a row puts focus back on the
44
+ * button, which is the page's heading, so the reader stays on the name of the
45
+ * place they just moved to.
46
+ *
47
+ * IT KNOWS NOTHING ABOUT PROJECTS. The caller searches, ranks and groups, and
48
+ * hands over rows and the words around them; this owns the surface, the keys,
49
+ * and how a screen reader hears it.
50
+ */
51
+ import { type ReactNode } from 'react';
52
+ /** Where the panel opens. Derived, never asked for: see the component doc. */
53
+ export type SwitcherPlacement = 'anchored' | 'centered' | 'sheet';
54
+ export interface SwitcherOption {
55
+ /** Stable across a re-query: it keys the row and names its option element. */
56
+ id: string;
57
+ /** The row's words. Plain text, so a search's letters can be drawn bold in it. */
58
+ label: string;
59
+ /** A project's key, drawn as a `KeyChip` in the lead column. */
60
+ keyChip?: string | undefined;
61
+ /** A glyph in the lead column, for a row that is not a project. Decorative. */
62
+ icon?: ReactNode;
63
+ /** A short figure at the row's end, in the quiet ink: a project's open count. */
64
+ meta?: ReactNode;
65
+ /**
66
+ * What a screen reader calls the row where its words alone do not say it:
67
+ * "Product and Engineering, FAIR, 4 open" rather than a name followed by a
68
+ * bare figure.
69
+ */
70
+ accessibleLabel?: string | undefined;
71
+ /**
72
+ * Where the row goes. A row with one is a link: a middle click, a Command or
73
+ * Control click, and Command or Control with Enter open it in a new tab.
74
+ */
75
+ href?: string | undefined;
76
+ /** The subject the page is on now: outlined in the accent, and `aria-current="page"`. */
77
+ current?: boolean | undefined;
78
+ /** Why the row cannot be taken right now. Drawn at its end; the arrows pass over it. */
79
+ held?: string | undefined;
80
+ /** Taking the row. Without one, a row with an `href` follows it. */
81
+ onSelect?: (() => void) | undefined;
82
+ /**
83
+ * Taking the row leaves the panel open: an action on the list itself, such
84
+ * as asking for it again after it did not load, where closing would take
85
+ * the answer away from the reader who asked for it.
86
+ */
87
+ keepsOpen?: boolean | undefined;
88
+ }
89
+ export interface SwitcherGroup {
90
+ id: string;
91
+ /** The heading over the group, and the name a screen reader gives it. */
92
+ label: string;
93
+ /** A figure at the heading's end: how many rows the group holds. */
94
+ count?: number | undefined;
95
+ options: SwitcherOption[];
96
+ }
97
+ /**
98
+ * A state the list is in, said in the panel: nothing matches, nothing loaded,
99
+ * nothing exists yet. Its actions are rows, where the arrows reach them.
100
+ */
101
+ export interface SwitcherMessage {
102
+ title: string;
103
+ body?: string | undefined;
104
+ actions?: SwitcherOption[] | undefined;
105
+ }
106
+ /** The button the panel opens from, drawn as the page's own name. */
107
+ export interface SwitcherTrigger {
108
+ /** The subject's name. */
109
+ label: string;
110
+ /** Its key, drawn as a `KeyChip` before the name. */
111
+ keyChip?: string | undefined;
112
+ /** Drawn after the name: a `Tag` saying the subject is archived. */
113
+ tag?: ReactNode;
114
+ /** The name in the mono face: an identifier still waiting for its name. */
115
+ mono?: boolean | undefined;
116
+ /** The tooltip: what the button does and the key that does it from anywhere. */
117
+ title?: string | undefined;
118
+ }
119
+ export interface SwitcherProps {
120
+ /** Names the panel, "Switch project"; a sheet draws it as its title. */
121
+ label: string;
122
+ open: boolean;
123
+ onOpenChange: (open: boolean) => void;
124
+ /**
125
+ * The button the panel hangs from, drawn where the switcher is rendered.
126
+ * Without one the panel opens centred over the page.
127
+ */
128
+ trigger?: SwitcherTrigger | undefined;
129
+ query: string;
130
+ onQueryChange: (query: string) => void;
131
+ /** The field's placeholder, and its name: "Find a project". */
132
+ placeholder?: string | undefined;
133
+ /** Names the list of rows: "Projects". */
134
+ listLabel?: string | undefined;
135
+ /** The row that is always first and never matched: "All projects". */
136
+ pinned?: SwitcherOption | undefined;
137
+ /** A line about the list itself, under the pinned row: "200 of 340 listed." */
138
+ note?: string | undefined;
139
+ message?: SwitcherMessage | undefined;
140
+ groups?: SwitcherGroup[] | undefined;
141
+ /** Actions in a band under the list that does not scroll: "Browse all projects". */
142
+ footer?: SwitcherOption[] | undefined;
143
+ /** Placeholder rows in place of the groups, while the rows are on their way. */
144
+ loading?: boolean | undefined;
145
+ /** What a screen reader is told while loading: "Loading projects". */
146
+ loadingLabel?: string | undefined;
147
+ /** Said politely whenever it changes: "3 projects". */
148
+ announcement?: string | undefined;
149
+ className?: string | undefined;
150
+ }
151
+ export declare function Switcher({ label, open, onOpenChange, trigger, className, ...panel }: SwitcherProps): import("react").JSX.Element;
@@ -0,0 +1,3 @@
1
+ import './Switcher.css';
2
+ export { Switcher } from './Switcher.js';
3
+ export type { SwitcherGroup, SwitcherMessage, SwitcherOption, SwitcherPlacement, SwitcherProps, SwitcherTrigger, } from './Switcher.js';
@@ -0,0 +1,16 @@
1
+ export { Switcher } from '../chunk-Y6NR7QJ3.js';
2
+ import '../chunk-W5J7R5EB.js';
3
+ import '../chunk-TFMJH2E6.js';
4
+ import '../chunk-F73MC7EZ.js';
5
+ import '../chunk-FTEJPHBI.js';
6
+ import '../chunk-FYTIJM2T.js';
7
+ import '../chunk-OG6CL3EB.js';
8
+ import '../chunk-PQADV6P6.js';
9
+ import '../chunk-RSAQOKH3.js';
10
+ import '../chunk-IG75YMUN.js';
11
+ import '../chunk-4FQT6ZAY.js';
12
+ import '../chunk-O2ZDZZS7.js';
13
+ import '../chunk-7RPGY3JE.js';
14
+ import '../chunk-R25RGPUB.js';
15
+ import '../chunk-BZFWDIV6.js';
16
+ import '../chunk-ODEQTPFM.js';
@@ -0,0 +1,346 @@
1
+ /*
2
+ * The switcher: a button that is the page's name, and a banded panel under it
3
+ * (a search band, a list that scrolls, and a footer that does not).
4
+ *
5
+ * The rows are the listbox register (`Listbox.css`), because a project in this
6
+ * list and a result in the command palette are read the same way, and the
7
+ * raised row that marks where the arrows are must mean the same in both. What
8
+ * this adds is the switcher's own: a lead column every row's key lines up in,
9
+ * the current subject outlined in the accent, and a held row that keeps its
10
+ * reason without a hover.
11
+ */
12
+
13
+ .crewlet-switcher {
14
+ /* The panel's own measures: a width that holds a key, a long name and a
15
+ count, and a height that holds eight projects with Recent above them
16
+ without scrolling. */
17
+ --crewlet-switcher-width: 360px;
18
+ --crewlet-switcher-max-height: 520px;
19
+ --crewlet-switcher-lead: 52px;
20
+ }
21
+
22
+ /* ── The button ─────────────────────────────────────────────────────────── */
23
+
24
+ /*
25
+ * THE PAGE'S NAME, WITH NO BOX AT REST. It is the heading of the page as much
26
+ * as it is a control, so it reads as text in the trail until the pointer
27
+ * reaches it; the fill appears under the pointer and stays while the panel is
28
+ * open, and the chevron turns. The name takes the cut, never the key or the
29
+ * chevron.
30
+ */
31
+ .crewlet-switcher-trigger {
32
+ display: inline-flex;
33
+ align-items: center;
34
+ gap: calc(var(--spacing-1) * 1.5);
35
+ min-width: 0;
36
+ max-width: 100%;
37
+ height: var(--size-row-sm);
38
+ padding: 0 var(--spacing-1) 0 calc(var(--spacing-1) * 1.5);
39
+ border: none;
40
+ border-radius: var(--radius-md);
41
+ background: transparent;
42
+ color: var(--color-text-primary);
43
+ font: inherit;
44
+ font-weight: var(--font-weight-medium);
45
+ cursor: pointer;
46
+ transition: background-color var(--motion-duration-fast) var(--motion-easing-default);
47
+ }
48
+
49
+ .crewlet-switcher-trigger:hover,
50
+ .crewlet-switcher-trigger[aria-expanded='true'] {
51
+ background: var(--color-surface-hover);
52
+ }
53
+
54
+ .crewlet-switcher-trigger:active {
55
+ background: var(--color-surface-pressed);
56
+ }
57
+
58
+ /* Inside the button's own edge, so the trail's scrollport never clips it. */
59
+ .crewlet-switcher-trigger:focus-visible {
60
+ outline: 2px solid var(--color-focus);
61
+ outline-offset: var(--size-focus-ring-inset-offset);
62
+ }
63
+
64
+ .crewlet-switcher-trigger__label {
65
+ min-width: 0;
66
+ overflow: hidden;
67
+ text-overflow: ellipsis;
68
+ white-space: nowrap;
69
+ }
70
+
71
+ .crewlet-switcher-trigger.is-mono .crewlet-switcher-trigger__label {
72
+ font-family: var(--font-family-mono);
73
+ font-size: var(--font-size-compact);
74
+ }
75
+
76
+ .crewlet-switcher-trigger .crewlet-key-chip,
77
+ .crewlet-switcher-trigger__tag {
78
+ display: inline-flex;
79
+ flex: none;
80
+ }
81
+
82
+ svg.crewlet-switcher-trigger__chevron {
83
+ flex: none;
84
+ color: var(--color-text-tertiary);
85
+ transition:
86
+ transform var(--motion-duration-fast) var(--motion-easing-default),
87
+ color var(--motion-duration-fast) var(--motion-easing-default);
88
+ }
89
+
90
+ .crewlet-switcher-trigger:hover svg.crewlet-switcher-trigger__chevron {
91
+ color: var(--color-text-secondary);
92
+ }
93
+
94
+ .crewlet-switcher-trigger[aria-expanded='true'] svg.crewlet-switcher-trigger__chevron {
95
+ color: var(--color-text-secondary);
96
+ transform: rotate(180deg);
97
+ }
98
+
99
+ /* ── The three surfaces ─────────────────────────────────────────────────── */
100
+
101
+ /*
102
+ * ANCHORED: the popover's own surface (the subtle rung, the default hairline,
103
+ * the large corner and the deepest shadow) at the switcher's size. The bands
104
+ * lay themselves out inside it, so the panel itself does not scroll.
105
+ */
106
+ .crewlet-popover.crewlet-switcher {
107
+ display: flex;
108
+ flex-direction: column;
109
+ width: var(--crewlet-switcher-width);
110
+ min-width: 0;
111
+ max-width: calc(100vw - var(--spacing-4));
112
+ max-height: min(var(--crewlet-switcher-max-height), calc(100dvh - var(--spacing-10) - var(--spacing-4)));
113
+ overflow: hidden;
114
+ }
115
+
116
+ /*
117
+ * CENTRED: the same panel as a dialog, a step lower than a dialog stands so
118
+ * it clears the page's bar, arriving at the popover's pace rather than a
119
+ * dialog's, because it is the same panel opened from somewhere else.
120
+ */
121
+ .crewlet-modal.crewlet-switcher--centered {
122
+ --crewlet-modal-width: var(--crewlet-switcher-width);
123
+
124
+ max-height: min(var(--crewlet-switcher-max-height), calc(100dvh - var(--spacing-10) - var(--spacing-6)));
125
+ margin-top: var(--spacing-2);
126
+ animation-duration: var(--motion-duration-base);
127
+ }
128
+
129
+ /* A SHEET carries a grab handle over its title, which says it came from the
130
+ edge it is pinned to. Decoration: the close control is what closes it. */
131
+ .crewlet-modal.crewlet-switcher--sheet::before {
132
+ content: '';
133
+ flex: none;
134
+ width: calc(var(--spacing-7) + var(--spacing-1));
135
+ height: var(--spacing-1);
136
+ margin: var(--spacing-2) auto 0;
137
+ border-radius: var(--radius-pill);
138
+ background: var(--color-border-strong);
139
+ }
140
+
141
+ .crewlet-switcher--sheet .crewlet-modal__header {
142
+ min-height: var(--size-control-lg);
143
+ padding: 0 var(--spacing-2) 0 var(--spacing-4);
144
+ }
145
+
146
+ /* A dialog's body scrolls its whole content; here the list alone scrolls. */
147
+ .crewlet-switcher .crewlet-modal__body {
148
+ display: flex;
149
+ flex-direction: column;
150
+ overflow: hidden;
151
+ }
152
+
153
+ .crewlet-switcher__panel {
154
+ display: flex;
155
+ flex: 1 1 auto;
156
+ flex-direction: column;
157
+ min-height: 0;
158
+ }
159
+
160
+ /* ── The search band ────────────────────────────────────────────────────── */
161
+
162
+ /*
163
+ * The field with no box of its own, as the command palette draws it: the band
164
+ * is the field. It is set at the trail's size rather than the palette's
165
+ * headline size, because the panel is a list read at a glance, and at the
166
+ * reading size on a phone, where a smaller field zooms the page when tapped.
167
+ */
168
+ .crewlet-input.crewlet-switcher__search {
169
+ flex-shrink: 0;
170
+ min-height: var(--size-control-lg);
171
+ padding-inline: var(--spacing-3) var(--spacing-2);
172
+ border: none;
173
+ border-bottom: 1px solid var(--color-border-default);
174
+ border-radius: 0;
175
+ background: transparent;
176
+ }
177
+
178
+ .crewlet-input.crewlet-switcher__search .crewlet-input__control {
179
+ font-size: var(--font-size-sm);
180
+ }
181
+
182
+ .crewlet-switcher--sheet .crewlet-input.crewlet-switcher__search .crewlet-input__control {
183
+ font-size: var(--font-size-md);
184
+ }
185
+
186
+ /* ── The list ───────────────────────────────────────────────────────────── */
187
+
188
+ .crewlet-switcher__scroller {
189
+ flex: 1 1 auto;
190
+ min-height: 0;
191
+ overflow-y: auto;
192
+ overscroll-behavior: contain;
193
+ padding: var(--spacing-1);
194
+ scrollbar-width: thin;
195
+ }
196
+
197
+ .crewlet-switcher__list {
198
+ display: flex;
199
+ flex-direction: column;
200
+ }
201
+
202
+ /*
203
+ * A ROW: a lead column, the name, and a figure at the end. Both classes are
204
+ * named so this ties with nothing in `Listbox.css`, whose rows are a step
205
+ * shorter and padded on every side.
206
+ */
207
+ .crewlet-listbox__option.crewlet-switcher__option {
208
+ gap: 0;
209
+ min-height: var(--size-row-md);
210
+ padding-block: 0;
211
+ font-weight: var(--font-weight-medium);
212
+ text-decoration: none;
213
+ }
214
+
215
+ .crewlet-switcher--sheet .crewlet-listbox__option.crewlet-switcher__option {
216
+ min-height: var(--size-control-lg);
217
+ font-size: var(--font-size-sm);
218
+ }
219
+
220
+ /*
221
+ * THE CURRENT SUBJECT, outlined by its own accent hairline inside the row: an
222
+ * outline rather than a shadow, so forced-colours mode keeps it. The row the
223
+ * arrows are on takes the raised fill as every listbox row does; both can land
224
+ * on one row, and it never gains a second border.
225
+ */
226
+ .crewlet-listbox__option.crewlet-switcher__option.is-current {
227
+ color: var(--color-text-primary);
228
+ outline: 1px solid var(--color-brand-accent);
229
+ outline-offset: -1px;
230
+ }
231
+
232
+ /* A HELD ROW answers no pointer: no fill under it, and the reason in the quiet
233
+ ink at its end says why. */
234
+ .crewlet-listbox__option.crewlet-switcher__option[aria-disabled='true'] {
235
+ background: transparent;
236
+ color: var(--color-text-tertiary);
237
+ }
238
+
239
+ /* Every row's key starts on one line, whatever the key's length. */
240
+ .crewlet-switcher__lead {
241
+ display: flex;
242
+ flex: none;
243
+ align-items: center;
244
+ width: var(--crewlet-switcher-lead);
245
+ color: var(--color-text-tertiary);
246
+ }
247
+
248
+ .crewlet-switcher__label {
249
+ flex: 1 1 auto;
250
+ min-width: 0;
251
+ overflow: hidden;
252
+ text-overflow: ellipsis;
253
+ white-space: nowrap;
254
+ }
255
+
256
+ /* The letters a search matched: the weight carries it, in the primary ink. */
257
+ b.crewlet-switcher__match {
258
+ font-weight: var(--font-weight-bold);
259
+ color: var(--color-text-primary);
260
+ }
261
+
262
+ .crewlet-switcher__meta {
263
+ display: inline-flex;
264
+ flex: none;
265
+ align-items: center;
266
+ gap: calc(var(--spacing-1) * 1.5);
267
+ margin-left: var(--spacing-3);
268
+ color: var(--color-text-tertiary);
269
+ font-size: var(--font-size-xs);
270
+ font-weight: var(--font-weight-regular);
271
+ font-variant-numeric: tabular-nums;
272
+ white-space: nowrap;
273
+ }
274
+
275
+ /* A group's heading carries its count at its end. */
276
+ .crewlet-switcher__heading {
277
+ display: flex;
278
+ align-items: baseline;
279
+ justify-content: space-between;
280
+ }
281
+
282
+ .crewlet-switcher__count {
283
+ font-variant-numeric: tabular-nums;
284
+ letter-spacing: normal;
285
+ }
286
+
287
+ /* A line about the list itself, on the inset rung: not a row, so not a row's
288
+ shape. */
289
+ .crewlet-switcher__note {
290
+ margin: calc(var(--spacing-1) * 1.5) var(--spacing-2) var(--spacing-1);
291
+ padding: var(--spacing-2) calc(var(--spacing-2) + var(--spacing-1) / 2);
292
+ border-radius: var(--radius-md);
293
+ background: var(--color-surface-inset);
294
+ color: var(--color-text-secondary);
295
+ font-size: var(--font-size-xs);
296
+ }
297
+
298
+ /* A state of the list, said in two lines, with its actions as rows under it. */
299
+ .crewlet-switcher__message {
300
+ display: grid;
301
+ gap: var(--spacing-1);
302
+ padding: var(--spacing-3) var(--spacing-3) var(--spacing-2);
303
+ }
304
+
305
+ .crewlet-switcher__message-title {
306
+ color: var(--color-text-primary);
307
+ font-size: var(--font-size-compact);
308
+ font-weight: var(--font-weight-semibold);
309
+ }
310
+
311
+ .crewlet-switcher__message-body {
312
+ color: var(--color-text-secondary);
313
+ font-size: var(--font-size-xs);
314
+ }
315
+
316
+ /* Placeholder rows the shape of the rows on their way. */
317
+ .crewlet-switcher__skeleton-row {
318
+ display: flex;
319
+ align-items: center;
320
+ height: var(--size-row-md);
321
+ padding: 0 var(--spacing-3);
322
+ }
323
+
324
+ .crewlet-switcher__skeleton-row > .crewlet-switcher__meta {
325
+ margin-left: auto;
326
+ }
327
+
328
+ /* ── The footer ─────────────────────────────────────────────────────────── */
329
+
330
+ .crewlet-switcher__footer {
331
+ display: flex;
332
+ flex: none;
333
+ flex-direction: column;
334
+ padding: var(--spacing-1);
335
+ border-top: 1px solid var(--color-border-default);
336
+ }
337
+
338
+ @media (prefers-reduced-motion: reduce) {
339
+ .crewlet-switcher-trigger {
340
+ transition: none;
341
+ }
342
+
343
+ svg.crewlet-switcher-trigger__chevron {
344
+ transition: none;
345
+ }
346
+ }
@@ -1,5 +1,11 @@
1
1
  import React, { type ReactNode } from 'react';
2
- export type TabsVariant = 'pill' | 'underline';
2
+ /**
3
+ * How the row is drawn. `pill` is a segmented well with a raised chip for the
4
+ * one that is on; `underline` is a strip with a sliding bar, for a screen's
5
+ * sections; `chip` is a row of quiet chips with no well, the one that is on
6
+ * outlined in the accent, for the lenses beside an object's name in a bar.
7
+ */
8
+ export type TabsVariant = 'pill' | 'underline' | 'chip';
3
9
  export type TabsSize = 'sm' | 'md';
4
10
  export interface TabsItem {
5
11
  /** Stable identity: the value reported, and half of the tab's id. */
@@ -1,4 +1,4 @@
1
- export { TabPanel, Tabs, tabId } from '../chunk-JHQUT4WJ.js';
1
+ export { TabPanel, Tabs, tabId } from '../chunk-4LDTQWS7.js';
2
2
  import '../chunk-CBOICT64.js';
3
3
  import '../chunk-BHHIIK3M.js';
4
4
  import '../chunk-BZFWDIV6.js';
@@ -300,6 +300,55 @@
300
300
  outline-offset: 0;
301
301
  }
302
302
 
303
+ /* ─── Chip (a lens row) ───────────────────────────────────────── */
304
+ /*
305
+ * QUIET CHIPS WITH NO WELL: the lenses of an object or a scope, set beside its
306
+ * name in a page's bar. A well round them was a second box beside the name the
307
+ * lenses are about, and a raised chip inside it a second highlight. Here the
308
+ * chip that is on is drawn by its own border in the accent and nothing else,
309
+ * which is the kit's one way of saying "this one is chosen".
310
+ */
311
+ .crewlet-tabs--chip {
312
+ display: inline-flex;
313
+ align-items: center;
314
+ gap: calc(var(--spacing-1) / 2);
315
+ }
316
+
317
+ .crewlet-tabs--chip .crewlet-tabs__tab {
318
+ min-height: var(--size-control-sm);
319
+ padding: 0 calc(var(--spacing-2) + var(--spacing-1) / 2);
320
+ border-radius: var(--radius-md);
321
+ color: var(--color-text-secondary);
322
+ font-size: var(--font-size-xs);
323
+ }
324
+
325
+ .crewlet-tabs--chip .crewlet-tabs__tab:hover:not(.is-disabled):not(.is-active) {
326
+ background: var(--color-surface-hover);
327
+ }
328
+
329
+ /* A COUNT IS A QUIET FIGURE here, not a pill: a pill inside a chip is a box
330
+ inside a box, beside a name that already has its own. */
331
+ .crewlet-tabs--chip .crewlet-count {
332
+ min-width: 0;
333
+ padding: 0;
334
+ background: none;
335
+ }
336
+
337
+ /* An outline rather than a border, so a chip turning on does not move its
338
+ neighbours by a pixel, and forced-colors mode keeps it. */
339
+ .crewlet-tabs--chip .crewlet-tabs__tab.is-active {
340
+ color: var(--color-text-primary);
341
+ outline: 1px solid var(--color-brand-accent);
342
+ outline-offset: -1px;
343
+ }
344
+
345
+ /* THE RING TAKES THE BORDER'S PLACE: declared after it and of the same
346
+ weight, so a focused chip that is on draws one edge, the ring. */
347
+ .crewlet-tabs--chip .crewlet-tabs__tab:focus-visible {
348
+ outline: 2px solid var(--color-focus);
349
+ outline-offset: -2px;
350
+ }
351
+
303
352
  .crewlet-tabs__tab.is-disabled,
304
353
  .crewlet-tabs__tab:disabled {
305
354
  opacity: 0.55;
@@ -1,7 +1,7 @@
1
- export { TagsInput } from '../chunk-ZHOM65MI.js';
1
+ export { TagsInput } from '../chunk-JCO3SIST.js';
2
2
  import '../chunk-LOECRMHA.js';
3
3
  import '../chunk-WIATRXEJ.js';
4
- import '../chunk-6MU7HNB4.js';
4
+ import '../chunk-F73MC7EZ.js';
5
5
  import '../chunk-RSAQOKH3.js';
6
6
  import '../chunk-4FQT6ZAY.js';
7
7
  import '../chunk-7RPGY3JE.js';
@@ -0,0 +1,65 @@
1
+ import { type HTMLAttributes } from 'react';
2
+ import { type ThemeId } from '@crewlethq/tokens';
3
+ import { type ThemeSwatchPair } from '../ThemeSwatch/index.js';
4
+ /** A theme as the gallery offers it: the fields of a `themeRegistry` entry it reads. */
5
+ export interface ThemeGalleryTheme<T extends string = string> {
6
+ id: T;
7
+ name: string;
8
+ /** A few words on what sets it apart, said with its name. */
9
+ tag: string;
10
+ /** The id of the group it is listed under. */
11
+ group: string;
12
+ swatch: ThemeSwatchPair;
13
+ }
14
+ /** A heading the themes are listed under, as `themeGroups` carries it. */
15
+ export interface ThemeGalleryGroup {
16
+ id: string;
17
+ name: string;
18
+ }
19
+ export interface ThemeGalleryProps<T extends string = ThemeId> extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onChange'> {
20
+ /** The group's name, said on entering it. "Theme" by default. */
21
+ label?: string | undefined;
22
+ /** The theme in use. */
23
+ value: T;
24
+ /** The reader chose a theme: a click, Space or Enter, or an arrow key. */
25
+ onValueChange: (id: T) => void;
26
+ /**
27
+ * The theme the pointer has come to rest on, for a preview beside the
28
+ * gallery, and `null` when it leaves: told once the pointer has rested on a
29
+ * card for `HIGHLIGHT_REST_MS`, so crossing the grid does not repaint the
30
+ * preview at every card it passes.
31
+ */
32
+ onPreview?: ((id: T | null) => void) | undefined;
33
+ /** The themes offered. Every theme `@crewlethq/tokens` ships, by default. */
34
+ themes?: readonly ThemeGalleryTheme<T>[] | undefined;
35
+ /** The headings they are listed under, in order. The registry's groups, by default. */
36
+ groups?: readonly ThemeGalleryGroup[] | undefined;
37
+ /** How many cards a row holds. Three by default. */
38
+ columns?: number | undefined;
39
+ /** Nothing can be chosen, and the gallery says so. */
40
+ disabled?: boolean | undefined;
41
+ }
42
+ /**
43
+ * The choice of a theme: every theme, each drawn as its two palettes, listed
44
+ * under its group.
45
+ *
46
+ * A RADIO GROUP, because a theme is a setting and exactly one is in use: the
47
+ * gallery is one tab stop, and the arrows move through it and choose as they
48
+ * move, as the platform's own radio group does. They walk the cards in
49
+ * reading order, across the groups, because the groups break the rows and an
50
+ * arrow that jumped a row would land somewhere the reader is not looking.
51
+ *
52
+ * THE CHOSEN CARD IS SAID WITH ITS OWN BORDER, in the accent: one line round
53
+ * the card, and the line means "this one". Focus draws its ring over that
54
+ * border rather than outside it, so a focused card is never two outlines.
55
+ *
56
+ * POINTING PREVIEWS, CHOOSING APPLIES. `onPreview` is told the card the
57
+ * pointer rests on, for a pane beside the gallery to show it in, so a reader
58
+ * crossing the grid with the pointer does not repaint the whole window at
59
+ * every card. The keys choose as they move, which is what applies.
60
+ *
61
+ * A CARD IS NAMED BY THE THEME'S NAME AND ITS TAG ("Abyss, Vivid violet"),
62
+ * and described by its group's heading; the drawing is decoration, because
63
+ * the name is printed under it.
64
+ */
65
+ export declare function ThemeGallery<T extends string = ThemeId>({ label, value, onValueChange, onPreview, themes, groups, columns, disabled, className, style, ...rest }: ThemeGalleryProps<T>): import("react").JSX.Element;
@@ -0,0 +1,3 @@
1
+ import './ThemeGallery.css';
2
+ export { ThemeGallery } from './ThemeGallery.js';
3
+ export type { ThemeGalleryGroup, ThemeGalleryProps, ThemeGalleryTheme } from './ThemeGallery.js';
@@ -0,0 +1,6 @@
1
+ export { ThemeGallery } from '../chunk-KLIEPFEZ.js';
2
+ import '../chunk-MNFU2RNT.js';
3
+ import '../chunk-F73MC7EZ.js';
4
+ import '../chunk-CBOICT64.js';
5
+ import '../chunk-R25RGPUB.js';
6
+ import '../chunk-ODEQTPFM.js';