@adecore/ui 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 (318) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -0
  3. package/dist/AccentSwatches.d.ts +15 -0
  4. package/dist/AccentSwatches.js +21 -0
  5. package/dist/Banner.d.ts +12 -0
  6. package/dist/Banner.js +18 -0
  7. package/dist/Button.d.ts +9 -0
  8. package/dist/Button.js +28 -0
  9. package/dist/ButtonGroup.d.ts +3 -0
  10. package/dist/ButtonGroup.js +11 -0
  11. package/dist/Checkbox.d.ts +11 -0
  12. package/dist/Checkbox.js +9 -0
  13. package/dist/ChoiceCards.d.ts +23 -0
  14. package/dist/ChoiceCards.js +21 -0
  15. package/dist/CloseButton.d.ts +15 -0
  16. package/dist/CloseButton.js +11 -0
  17. package/dist/ColorSwatch.d.ts +14 -0
  18. package/dist/ColorSwatch.js +28 -0
  19. package/dist/ColumnResizeHandle.d.ts +9 -0
  20. package/dist/ColumnResizeHandle.js +12 -0
  21. package/dist/DisabledReason.d.ts +6 -0
  22. package/dist/DisabledReason.js +8 -0
  23. package/dist/DockShell.d.ts +9 -0
  24. package/dist/DockShell.js +49 -0
  25. package/dist/EmptyState.d.ts +16 -0
  26. package/dist/EmptyState.js +11 -0
  27. package/dist/ErrorBoundary.d.ts +24 -0
  28. package/dist/ErrorBoundary.js +47 -0
  29. package/dist/Field.d.ts +17 -0
  30. package/dist/Field.js +48 -0
  31. package/dist/FileIcon.d.ts +8 -0
  32. package/dist/FileIcon.js +15 -0
  33. package/dist/Icon.d.ts +9 -0
  34. package/dist/Icon.js +12 -0
  35. package/dist/IconButton.d.ts +23 -0
  36. package/dist/IconButton.js +25 -0
  37. package/dist/IconPicker.d.ts +17 -0
  38. package/dist/IconPicker.js +93 -0
  39. package/dist/Input.d.ts +14 -0
  40. package/dist/Input.js +25 -0
  41. package/dist/Kbd.d.ts +23 -0
  42. package/dist/Kbd.js +20 -0
  43. package/dist/ListRow.d.ts +10 -0
  44. package/dist/ListRow.js +20 -0
  45. package/dist/Meter.d.ts +12 -0
  46. package/dist/Meter.js +7 -0
  47. package/dist/PanelEmpty.d.ts +16 -0
  48. package/dist/PanelEmpty.js +7 -0
  49. package/dist/PanelHeader.d.ts +6 -0
  50. package/dist/PanelHeader.js +20 -0
  51. package/dist/Pill.d.ts +27 -0
  52. package/dist/Pill.js +25 -0
  53. package/dist/ProjectSwitcher.d.ts +30 -0
  54. package/dist/ProjectSwitcher.js +18 -0
  55. package/dist/PromptDialog.d.ts +40 -0
  56. package/dist/PromptDialog.js +47 -0
  57. package/dist/SectionLabel.d.ts +3 -0
  58. package/dist/SectionLabel.js +16 -0
  59. package/dist/SegmentBar.d.ts +18 -0
  60. package/dist/SegmentBar.js +44 -0
  61. package/dist/Segmented.d.ts +17 -0
  62. package/dist/Segmented.js +7 -0
  63. package/dist/Select.d.ts +27 -0
  64. package/dist/Select.js +34 -0
  65. package/dist/Separator.d.ts +7 -0
  66. package/dist/Separator.js +8 -0
  67. package/dist/ShortcutHints.d.ts +1 -0
  68. package/dist/ShortcutHints.js +94 -0
  69. package/dist/Skeleton.d.ts +6 -0
  70. package/dist/Skeleton.js +6 -0
  71. package/dist/SlidingColumn.d.ts +19 -0
  72. package/dist/SlidingColumn.js +41 -0
  73. package/dist/Spinner.d.ts +6 -0
  74. package/dist/Spinner.js +7 -0
  75. package/dist/Stepper.d.ts +14 -0
  76. package/dist/Stepper.js +18 -0
  77. package/dist/Surface.d.ts +3 -0
  78. package/dist/Surface.js +16 -0
  79. package/dist/Switch.d.ts +10 -0
  80. package/dist/Switch.js +7 -0
  81. package/dist/TextMenu.d.ts +5 -0
  82. package/dist/TextMenu.js +31 -0
  83. package/dist/Tile.d.ts +11 -0
  84. package/dist/Tile.js +10 -0
  85. package/dist/Toasts.d.ts +9 -0
  86. package/dist/Toasts.js +58 -0
  87. package/dist/Tooltip.d.ts +16 -0
  88. package/dist/Tooltip.js +21 -0
  89. package/dist/UIProvider.d.ts +9 -0
  90. package/dist/UIProvider.js +26 -0
  91. package/dist/Waveform.d.ts +16 -0
  92. package/dist/Waveform.js +123 -0
  93. package/dist/Wipe.d.ts +12 -0
  94. package/dist/Wipe.js +47 -0
  95. package/dist/ZoomControls.d.ts +30 -0
  96. package/dist/ZoomControls.js +16 -0
  97. package/dist/class-name.d.ts +2 -0
  98. package/dist/class-name.js +3 -0
  99. package/dist/clipboard.d.ts +2 -0
  100. package/dist/clipboard.js +16 -0
  101. package/dist/context-menu/index.parts.d.ts +1 -0
  102. package/dist/context-menu/index.parts.js +2 -0
  103. package/dist/dialog/index.parts.d.ts +1 -0
  104. package/dist/dialog/index.parts.js +1 -0
  105. package/dist/dialog/parts.d.ts +36 -0
  106. package/dist/dialog/parts.js +55 -0
  107. package/dist/dialog-layer.d.ts +1 -0
  108. package/dist/dialog-layer.js +27 -0
  109. package/dist/error-boundary.d.ts +4 -0
  110. package/dist/error-boundary.js +34 -0
  111. package/dist/error-message.d.ts +1 -0
  112. package/dist/error-message.js +4 -0
  113. package/dist/field-context.d.ts +7 -0
  114. package/dist/field-context.js +4 -0
  115. package/dist/file-icon.d.ts +4 -0
  116. package/dist/file-icon.js +46 -0
  117. package/dist/floating.d.ts +5 -0
  118. package/dist/floating.js +6 -0
  119. package/dist/format/datetime.d.ts +11 -0
  120. package/dist/format/datetime.js +50 -0
  121. package/dist/format/duration.d.ts +5 -0
  122. package/dist/format/duration.js +76 -0
  123. package/dist/format/index.d.ts +6 -0
  124. package/dist/format/index.js +7 -0
  125. package/dist/format/locale.d.ts +17 -0
  126. package/dist/format/locale.js +105 -0
  127. package/dist/format/number.d.ts +8 -0
  128. package/dist/format/number.js +72 -0
  129. package/dist/format/regions.d.ts +6 -0
  130. package/dist/format/regions.js +15 -0
  131. package/dist/format/time-zone.d.ts +1 -0
  132. package/dist/format/time-zone.js +2 -0
  133. package/dist/icon-button-size.d.ts +3 -0
  134. package/dist/icon-button-size.js +3 -0
  135. package/dist/icon-picker.d.ts +23 -0
  136. package/dist/icon-picker.js +80 -0
  137. package/dist/index.d.ts +74 -0
  138. package/dist/index.js +74 -0
  139. package/dist/lazy.d.ts +12 -0
  140. package/dist/lazy.js +104 -0
  141. package/dist/locales/en.json +59 -0
  142. package/dist/locales/nl.json +59 -0
  143. package/dist/locales.d.ts +4 -0
  144. package/dist/locales.js +17 -0
  145. package/dist/menu/index.parts.d.ts +1 -0
  146. package/dist/menu/index.parts.js +1 -0
  147. package/dist/menu/parts.d.ts +67 -0
  148. package/dist/menu/parts.js +113 -0
  149. package/dist/merge-refs.d.ts +2 -0
  150. package/dist/merge-refs.js +11 -0
  151. package/dist/modality.d.ts +1 -0
  152. package/dist/modality.js +29 -0
  153. package/dist/platform.d.ts +2 -0
  154. package/dist/platform.js +9 -0
  155. package/dist/popover/index.parts.d.ts +1 -0
  156. package/dist/popover/index.parts.js +1 -0
  157. package/dist/popover/parts.d.ts +26 -0
  158. package/dist/popover/parts.js +28 -0
  159. package/dist/prefetch.d.ts +21 -0
  160. package/dist/prefetch.js +99 -0
  161. package/dist/preview-card/index.parts.d.ts +1 -0
  162. package/dist/preview-card/index.parts.js +1 -0
  163. package/dist/project-switcher-row.d.ts +6 -0
  164. package/dist/project-switcher-row.js +16 -0
  165. package/dist/selection.d.ts +2 -0
  166. package/dist/selection.js +22 -0
  167. package/dist/seti-icons.d.ts +5 -0
  168. package/dist/seti-icons.js +445 -0
  169. package/dist/settings/ConfirmDialog.d.ts +9 -0
  170. package/dist/settings/ConfirmDialog.js +9 -0
  171. package/dist/settings/DetailHeader.d.ts +10 -0
  172. package/dist/settings/DetailHeader.js +6 -0
  173. package/dist/settings/MasterDetail.d.ts +24 -0
  174. package/dist/settings/MasterDetail.js +16 -0
  175. package/dist/settings/SettingsDialog.d.ts +46 -0
  176. package/dist/settings/SettingsDialog.js +96 -0
  177. package/dist/settings/SettingsRow.d.ts +22 -0
  178. package/dist/settings/SettingsRow.js +30 -0
  179. package/dist/settings/SettingsSection.d.ts +14 -0
  180. package/dist/settings/SettingsSection.js +8 -0
  181. package/dist/settings/index.d.ts +7 -0
  182. package/dist/settings/index.js +8 -0
  183. package/dist/settings/target.d.ts +7 -0
  184. package/dist/settings/target.js +3 -0
  185. package/dist/shortcut-hints.d.ts +38 -0
  186. package/dist/shortcut-hints.js +81 -0
  187. package/dist/shortcut.d.ts +32 -0
  188. package/dist/shortcut.js +137 -0
  189. package/dist/testing/dedupe.d.ts +1 -0
  190. package/dist/testing/dedupe.js +73 -0
  191. package/dist/testing/fake-source.d.ts +8 -0
  192. package/dist/testing/fake-source.js +15 -0
  193. package/dist/testing/index.d.ts +1 -0
  194. package/dist/testing/index.js +2 -0
  195. package/dist/theme.css +562 -0
  196. package/dist/toast-store.d.ts +38 -0
  197. package/dist/toast-store.js +61 -0
  198. package/dist/useAsyncAction.d.ts +8 -0
  199. package/dist/useAsyncAction.js +23 -0
  200. package/dist/useColumnResize.d.ts +14 -0
  201. package/dist/useColumnResize.js +49 -0
  202. package/dist/useContentSize.d.ts +5 -0
  203. package/dist/useContentSize.js +21 -0
  204. package/dist/useMeasuredWidth.d.ts +1 -0
  205. package/dist/useMeasuredWidth.js +6 -0
  206. package/dist/useNow.d.ts +3 -0
  207. package/dist/useNow.js +41 -0
  208. package/dist/wipe-split.d.ts +7 -0
  209. package/dist/wipe-split.js +20 -0
  210. package/dist/zoom.d.ts +3 -0
  211. package/dist/zoom.js +6 -0
  212. package/package.json +92 -0
  213. package/src/AccentSwatches.tsx +73 -0
  214. package/src/Banner.tsx +43 -0
  215. package/src/Button.tsx +55 -0
  216. package/src/ButtonGroup.tsx +14 -0
  217. package/src/Checkbox.tsx +44 -0
  218. package/src/ChoiceCards.tsx +108 -0
  219. package/src/CloseButton.tsx +21 -0
  220. package/src/ColorSwatch.tsx +41 -0
  221. package/src/ColumnResizeHandle.tsx +23 -0
  222. package/src/DisabledReason.tsx +23 -0
  223. package/src/DockShell.tsx +76 -0
  224. package/src/EmptyState.tsx +48 -0
  225. package/src/ErrorBoundary.tsx +116 -0
  226. package/src/Field.tsx +116 -0
  227. package/src/FileIcon.tsx +27 -0
  228. package/src/Icon.tsx +22 -0
  229. package/src/IconButton.tsx +84 -0
  230. package/src/IconPicker.tsx +257 -0
  231. package/src/Input.tsx +78 -0
  232. package/src/Kbd.tsx +67 -0
  233. package/src/ListRow.tsx +26 -0
  234. package/src/Meter.tsx +41 -0
  235. package/src/PanelEmpty.tsx +37 -0
  236. package/src/PanelHeader.tsx +31 -0
  237. package/src/Pill.tsx +61 -0
  238. package/src/ProjectSwitcher.tsx +127 -0
  239. package/src/PromptDialog.tsx +168 -0
  240. package/src/SectionLabel.tsx +20 -0
  241. package/src/SegmentBar.tsx +128 -0
  242. package/src/Segmented.tsx +51 -0
  243. package/src/Select.tsx +135 -0
  244. package/src/Separator.tsx +15 -0
  245. package/src/ShortcutHints.tsx +118 -0
  246. package/src/Skeleton.tsx +13 -0
  247. package/src/SlidingColumn.tsx +91 -0
  248. package/src/Spinner.tsx +32 -0
  249. package/src/Stepper.tsx +67 -0
  250. package/src/Surface.tsx +20 -0
  251. package/src/Switch.tsx +32 -0
  252. package/src/TextMenu.tsx +59 -0
  253. package/src/Tile.tsx +49 -0
  254. package/src/Toasts.tsx +151 -0
  255. package/src/Tooltip.tsx +70 -0
  256. package/src/UIProvider.tsx +41 -0
  257. package/src/Waveform.tsx +199 -0
  258. package/src/Wipe.tsx +99 -0
  259. package/src/ZoomControls.tsx +99 -0
  260. package/src/class-name.ts +8 -0
  261. package/src/clipboard.ts +17 -0
  262. package/src/context-menu/index.parts.ts +21 -0
  263. package/src/dialog/index.parts.ts +10 -0
  264. package/src/dialog/parts.tsx +112 -0
  265. package/src/dialog-layer.ts +31 -0
  266. package/src/error-boundary.ts +40 -0
  267. package/src/error-message.ts +5 -0
  268. package/src/field-context.ts +12 -0
  269. package/src/file-icon.ts +52 -0
  270. package/src/floating.ts +9 -0
  271. package/src/format/datetime.ts +63 -0
  272. package/src/format/duration.ts +83 -0
  273. package/src/format/index.ts +20 -0
  274. package/src/format/locale.ts +153 -0
  275. package/src/format/number.ts +87 -0
  276. package/src/format/regions.ts +20 -0
  277. package/src/format/time-zone.ts +2 -0
  278. package/src/icon-button-size.ts +6 -0
  279. package/src/icon-picker.ts +112 -0
  280. package/src/index.ts +103 -0
  281. package/src/lazy.tsx +124 -0
  282. package/src/locales/en.json +59 -0
  283. package/src/locales/nl.json +59 -0
  284. package/src/locales.ts +21 -0
  285. package/src/menu/index.parts.ts +20 -0
  286. package/src/menu/parts.tsx +296 -0
  287. package/src/merge-refs.ts +14 -0
  288. package/src/modality.ts +37 -0
  289. package/src/platform.ts +10 -0
  290. package/src/popover/index.parts.ts +8 -0
  291. package/src/popover/parts.tsx +119 -0
  292. package/src/prefetch.ts +113 -0
  293. package/src/preview-card/index.parts.ts +1 -0
  294. package/src/project-switcher-row.tsx +51 -0
  295. package/src/selection.ts +24 -0
  296. package/src/seti-icons.ts +451 -0
  297. package/src/settings/ConfirmDialog.tsx +30 -0
  298. package/src/settings/DetailHeader.tsx +25 -0
  299. package/src/settings/MasterDetail.tsx +67 -0
  300. package/src/settings/SettingsDialog.tsx +342 -0
  301. package/src/settings/SettingsRow.tsx +89 -0
  302. package/src/settings/SettingsSection.tsx +46 -0
  303. package/src/settings/index.ts +16 -0
  304. package/src/settings/target.ts +12 -0
  305. package/src/shortcut-hints.ts +117 -0
  306. package/src/shortcut.ts +170 -0
  307. package/src/testing/dedupe.ts +93 -0
  308. package/src/testing/fake-source.ts +21 -0
  309. package/src/testing/index.ts +3 -0
  310. package/src/theme.css +562 -0
  311. package/src/toast-store.ts +120 -0
  312. package/src/useAsyncAction.ts +32 -0
  313. package/src/useColumnResize.ts +75 -0
  314. package/src/useContentSize.ts +27 -0
  315. package/src/useMeasuredWidth.ts +7 -0
  316. package/src/useNow.ts +49 -0
  317. package/src/wipe-split.ts +24 -0
  318. package/src/zoom.ts +9 -0
package/dist/Field.js ADDED
@@ -0,0 +1,48 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useId } from 'react';
3
+ import clsx from 'clsx';
4
+ import { useRender } from '@base-ui-components/react/use-render';
5
+ import { FieldContext } from './field-context.js';
6
+ import { SectionLabel } from './SectionLabel.js';
7
+ const HINT = 'text-xs text-text-muted';
8
+ const ERROR = 'text-xs text-status-error';
9
+ // Beside the control the label reads at the size of what is typed, on the middle of the control's first line however tall it grows.
10
+ const LABEL_BESIDE = 'flex h-8 items-center self-start text-sm text-text-muted';
11
+ /* The line under a field that says what goes in it. */
12
+ export function FieldHint({ render, className, ref, ...props }) {
13
+ return useRender({
14
+ render,
15
+ ref,
16
+ defaultTagName: 'p',
17
+ props: { ...props, className: clsx('mt-1', HINT, className) }
18
+ });
19
+ }
20
+ /* What went wrong in a dialog or a form. An alert, so a screen reader hears it arrive. */
21
+ export function FormError({ render, className, ref, ...props }) {
22
+ return useRender({
23
+ render,
24
+ ref,
25
+ defaultTagName: 'p',
26
+ props: { role: 'alert', ...props, className: clsx(ERROR, className) }
27
+ });
28
+ }
29
+ /* A group's label names it through `aria-labelledby`; a control's points at the control. */
30
+ function FieldLabel({ id, group, horizontal, children }) {
31
+ if (horizontal) {
32
+ return group ? (_jsx("span", { id: `${id}-label`, className: LABEL_BESIDE, children: children })) : (_jsx("label", { htmlFor: `${id}-control`, className: LABEL_BESIDE, children: children }));
33
+ }
34
+ return group ? (_jsx(SectionLabel, { id: `${id}-label`, children: children })) : (_jsx(SectionLabel, { render: _jsx("label", { htmlFor: `${id}-control` }), children: children }));
35
+ }
36
+ /*
37
+ * A labelled control, or a labelled group, with an optional hint and error under it, stacked 6px apart.
38
+ * Beside each other the label column is 112px, so the controls of every field in a form line up.
39
+ */
40
+ export function Field({ label, hint, error, group = false, orientation = 'vertical', className, ref, children }) {
41
+ const horizontal = orientation === 'horizontal';
42
+ const id = useId();
43
+ const hasLabel = label !== undefined;
44
+ const hasHint = hint !== undefined && hint !== null && hint !== '';
45
+ const hasError = error !== undefined && error !== null && error !== '';
46
+ const describedBy = [hasHint ? `${id}-hint` : null, hasError ? `${id}-error` : null].filter((part) => part !== null).join(' ') || undefined;
47
+ return (_jsxs("div", { ref: ref, role: group ? 'group' : undefined, "aria-labelledby": group && hasLabel ? `${id}-label` : undefined, "aria-describedby": group ? describedBy : undefined, className: clsx(horizontal ? 'grid grid-cols-[112px_minmax(0,1fr)] gap-x-3 gap-y-1.5' : 'flex flex-col gap-1.5', className), children: [hasLabel && (_jsx(FieldLabel, { id: id, group: group, horizontal: horizontal, children: label })), _jsx(FieldContext, { value: group ? null : { id: `${id}-control`, describedBy, invalid: hasError }, children: horizontal ? _jsx("div", { className: "col-start-2 min-w-0", children: children }) : children }), hasHint && (_jsx("p", { id: `${id}-hint`, className: clsx(HINT, horizontal && 'col-start-2'), children: hint })), hasError && (_jsx("p", { id: `${id}-error`, role: "alert", className: clsx(ERROR, 'break-words', horizontal && 'col-start-2'), children: error }))] }));
48
+ }
@@ -0,0 +1,8 @@
1
+ import { type Ref } from 'react';
2
+ export interface FileIconProps {
3
+ path: string;
4
+ size?: number;
5
+ className?: string;
6
+ ref?: Ref<SVGSVGElement>;
7
+ }
8
+ export declare function FileIcon({ path, size, className, ref }: FileIconProps): import("react").JSX.Element;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useInsertionEffect } from 'react';
3
+ import clsx from 'clsx';
4
+ import { fileIconFor, mountFileIconSprite } from './file-icon.js';
5
+ /*
6
+ * The icon a file gets in a file tree, drawn anywhere else the same file's name shows up. It is the
7
+ * one place the library steps outside Lucide. The glyphs are Seti's, and so are their colors, because
8
+ * a TypeScript blue or a Vue green is the mark of the file type, not a theme choice. Decorative like
9
+ * every other icon here, so the name beside it does the reading.
10
+ */
11
+ export function FileIcon({ path, size = 16, className, ref }) {
12
+ // Before the browser paints, so a `<use>` never points at a symbol that is not in the document yet.
13
+ useInsertionEffect(mountFileIconSprite, []);
14
+ return (_jsx("svg", { ref: ref, className: clsx('file-icon', className), width: size, height: size, viewBox: "0 0 16 16", "aria-hidden": true, children: _jsx("use", { href: `#${fileIconFor(path)}` }) }));
15
+ }
package/dist/Icon.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import type { Ref } from 'react';
2
+ import type { LucideIcon } from 'lucide-react';
3
+ export interface IconProps {
4
+ icon: LucideIcon;
5
+ size?: number;
6
+ className?: string;
7
+ ref?: Ref<SVGSVGElement>;
8
+ }
9
+ export declare function Icon({ icon: Glyph, size, className, ref }: IconProps): import("react").JSX.Element;
package/dist/Icon.js ADDED
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import clsx from 'clsx';
3
+ // The whole client draws at this weight. 2 is Lucide's default, but at 12 and 14 pixels next to
4
+ // text of the same size a hair thinner keeps the glyph from outweighing the words beside it.
5
+ const STROKE_WIDTH = 1.75;
6
+ /* One Lucide icon, sized in pixels. Icons here are decorative. The name a screen reader reads
7
+ sits on the button or the label next to it, so the glyph stays hidden. */
8
+ export function Icon({ icon: Glyph, size = 16, className, ref }) {
9
+ // An inline svg sits on the text baseline, which drops a square glyph below the words next to
10
+ // it; the middle of the box against the middle of the text is what reads as aligned.
11
+ return _jsx(Glyph, { ref: ref, size: size, strokeWidth: STROKE_WIDTH, className: clsx('align-middle', className), "aria-hidden": true });
12
+ }
@@ -0,0 +1,23 @@
1
+ import type { ComponentProps, ReactNode } from 'react';
2
+ import type { LucideIcon } from 'lucide-react';
3
+ import { useRender } from '@base-ui-components/react/use-render';
4
+ import type { Shortcut } from './shortcut.ts';
5
+ import { type TooltipSide } from './Tooltip.tsx';
6
+ import { type IconButtonSize } from './icon-button-size.ts';
7
+ export type { IconButtonSize };
8
+ export interface IconButtonProps extends Omit<ComponentProps<'button'>, 'children'> {
9
+ icon?: LucideIcon;
10
+ label: string;
11
+ tooltip?: ReactNode | false;
12
+ kbd?: Shortcut | string;
13
+ tooltipSide?: TooltipSide;
14
+ size?: IconButtonSize;
15
+ active?: boolean;
16
+ busy?: boolean;
17
+ /** @deprecated Turns the icon; use `busy`, which draws a `Spinner` in its place. */
18
+ spin?: boolean;
19
+ iconClassName?: string;
20
+ children?: ReactNode;
21
+ render?: useRender.RenderProp;
22
+ }
23
+ export declare function IconButton({ icon, label, tooltip, kbd, tooltipSide, size, active, busy, spin, iconClassName, render, className, ref, children, ...props }: IconButtonProps): import("react").JSX.Element;
@@ -0,0 +1,25 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import clsx from 'clsx';
3
+ import { useRender } from '@base-ui-components/react/use-render';
4
+ import { Icon } from './Icon.js';
5
+ import { Spinner } from './Spinner.js';
6
+ import { Tooltip } from './Tooltip.js';
7
+ import { ICON_BUTTON_CLASS, ICON_BUTTON_ICON_SIZE } from './icon-button-size.js';
8
+ /* Every button that is an icon and no word. A square, and the one place the icon inside it is sized. */
9
+ export function IconButton({ icon, label, tooltip, kbd, tooltipSide, size = 'md', active = false, busy = false, spin = false, iconClassName, render, className, ref, children, ...props }) {
10
+ const button = useRender({
11
+ render: render ?? _jsx("button", { type: "button" }),
12
+ ref,
13
+ props: {
14
+ 'aria-label': label,
15
+ 'data-active': active ? 'true' : undefined,
16
+ ...props,
17
+ className: clsx('icon-btn', ICON_BUTTON_CLASS[size], className),
18
+ children: (_jsxs(_Fragment, { children: [busy ? (_jsx(Spinner, { size: ICON_BUTTON_ICON_SIZE[size], className: iconClassName })) : (icon !== undefined && _jsx(Icon, { icon: icon, size: ICON_BUTTON_ICON_SIZE[size], className: clsx(spin && 'animate-spin', iconClassName) })), children] }))
19
+ }
20
+ });
21
+ if (tooltip === false) {
22
+ return button;
23
+ }
24
+ return (_jsx(Tooltip, { label: tooltip ?? label, kbd: kbd, side: tooltipSide, children: button }));
25
+ }
@@ -0,0 +1,17 @@
1
+ import { type Ref } from 'react';
2
+ import { type IconPickerIcons } from './icon-picker.ts';
3
+ export type { IconPickerGroup } from './icon-picker.ts';
4
+ export interface IconPickerProps {
5
+ icons: IconPickerIcons;
6
+ value: string | null;
7
+ onValueChange(name: string): void;
8
+ keywords?: Readonly<Record<string, readonly string[]>>;
9
+ searchable?: boolean;
10
+ rows?: number;
11
+ disabled?: boolean;
12
+ onClear?: () => void;
13
+ label?: string;
14
+ className?: string;
15
+ ref?: Ref<HTMLDivElement>;
16
+ }
17
+ export declare function IconPicker({ icons, value, onValueChange, keywords, searchable, rows, disabled, onClear, label, className, ref }: IconPickerProps): import("react").JSX.Element;
@@ -0,0 +1,93 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useId, useLayoutEffect, useMemo, useRef, useState } from 'react';
3
+ import clsx from 'clsx';
4
+ import { Ban, Search, X } from 'lucide-react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { EmptyState } from './EmptyState.js';
7
+ import { formatNumber } from './format/number.js';
8
+ import { Icon } from './Icon.js';
9
+ import { IconButton } from './IconButton.js';
10
+ import { cellForKey, escapeClearsSearch, filterSections, isGrouped, sectionsOf, tabStopOf } from './icon-picker.js';
11
+ import { SectionLabel } from './SectionLabel.js';
12
+ import { Tooltip } from './Tooltip.js';
13
+ /* A row of cells is 28 high with a gap of 4. Under a heading of 28 the last row keeps 8 below it; without headings the grid has 6 above as well. */
14
+ const ROW_HEIGHT = 32;
15
+ const HEADED_EXTRA = 32;
16
+ const BARE_EXTRA = 10;
17
+ /* A heading is stuck while its group runs past the top of the scroll area, and only then draws a line under it. */
18
+ const markStuckHeadings = (area) => {
19
+ const top = area.scrollTop;
20
+ for (const heading of area.querySelectorAll('[data-icon-heading]')) {
21
+ const group = heading.parentElement;
22
+ const stuck = group !== null && top > 0 && group.offsetTop <= top && group.offsetTop + group.offsetHeight > top;
23
+ heading.toggleAttribute('data-stuck', stuck);
24
+ }
25
+ };
26
+ /*
27
+ * The mark a thing wears, one of a set of Lucide icons, as a grid under a label. With a search or a
28
+ * height in rows, the search and the grid share one frame. The grid is one tab stop: arrows move the
29
+ * focus and only Enter, Space or a click chooses, since a caller may save every choice at once.
30
+ */
31
+ export function IconPicker({ icons, value, onValueChange, keywords, searchable = isGrouped(icons), rows, disabled = false, onClear, label, className, ref }) {
32
+ const { t } = useTranslation('ui');
33
+ const id = useId();
34
+ const area = useRef(null);
35
+ const input = useRef(null);
36
+ const [query, setQuery] = useState('');
37
+ const [focused, setFocused] = useState(null);
38
+ const headed = isGrouped(icons);
39
+ const framed = searchable || rows !== undefined;
40
+ const sections = useMemo(() => sectionsOf(icons), [icons]);
41
+ const shown = useMemo(() => filterSections(sections, query, keywords), [sections, query, keywords]);
42
+ const tabStop = tabStopOf(shown.flatMap((section) => section.icons.map(([name]) => name)), focused, value);
43
+ const name = label ?? t('iconPicker.label');
44
+ // Opens with the chosen icon in the middle of the area: a person changing an icon often starts from the current one.
45
+ useLayoutEffect(() => {
46
+ const element = area.current;
47
+ const chosen = element?.querySelector('[aria-checked="true"]');
48
+ if (!element || !chosen || element.scrollHeight <= element.clientHeight) {
49
+ return;
50
+ }
51
+ const heading = element.querySelector('[data-icon-heading]')?.offsetHeight ?? 0;
52
+ element.scrollTop = Math.round(chosen.offsetTop - heading - (element.clientHeight - heading - chosen.offsetHeight) / 2);
53
+ }, []);
54
+ const search = (next) => {
55
+ setQuery(next);
56
+ if (area.current) {
57
+ area.current.scrollTop = 0;
58
+ }
59
+ };
60
+ const moveFocus = (event) => {
61
+ if (event.metaKey || event.ctrlKey || event.altKey) {
62
+ return;
63
+ }
64
+ const cells = [...event.currentTarget.querySelectorAll('[role="radio"]')];
65
+ const from = cells.indexOf(event.target);
66
+ if (from === -1) {
67
+ return;
68
+ }
69
+ const to = cellForKey(event.key, cells.map((cell) => cell.getBoundingClientRect()), from);
70
+ if (to === null) {
71
+ return;
72
+ }
73
+ event.preventDefault();
74
+ cells[to]?.focus();
75
+ };
76
+ const grid = (_jsx("div", { ref: area, id: `${id}-grid`, role: "radiogroup", "aria-label": name, className: clsx(framed && 'relative overflow-y-auto overscroll-contain', framed && (headed ? 'scroll-pt-7' : 'px-1.5 pt-1.5 pb-2')), style: rows === undefined ? undefined : { height: rows * ROW_HEIGHT + (headed ? HEADED_EXTRA : BARE_EXTRA) }, onScroll: (event) => markStuckHeadings(event.currentTarget), onKeyDown: moveFocus, children: shown.length === 0 && query.trim() !== '' ? (_jsx(EmptyState, { className: "h-full", children: t('iconPicker.noMatch', { query: query.trim() }) })) : (shown.map((section, index) => {
77
+ const cells = (_jsx("div", { className: clsx('grid grid-cols-[repeat(auto-fill,minmax(28px,1fr))] gap-1', framed && section.label !== null && 'px-1.5 pb-2'), children: section.icons.map(([icon, glyph]) => (_jsx(Tooltip, { label: icon, name: true, children: _jsx("button", { type: "button", role: "radio", "aria-checked": value === icon, tabIndex: icon === tabStop ? 0 : -1, disabled: disabled, className: clsx('flex h-7 items-center justify-center rounded-md hover:bg-surface-hover disabled:opacity-50', value === icon ? 'bg-surface-active text-text' : 'text-text-muted'), onFocus: () => setFocused(icon), onClick: () => onValueChange(icon), children: _jsx(Icon, { icon: glyph, size: 16 }) }) }, icon))) }, section.id));
78
+ if (section.label === null) {
79
+ return cells;
80
+ }
81
+ const headingId = `${id}-group-${index}`;
82
+ return (_jsxs("div", { role: "group", "aria-labelledby": headingId, children: [_jsxs(SectionLabel, { render: _jsx("div", {}), "data-icon-heading": "", className: clsx('flex h-7 items-center gap-2', framed ? 'sticky top-0 z-10 bg-surface px-2.5 data-stuck:shadow-[0_1px_0_var(--border)]' : 'mt-1'), children: [_jsx("span", { id: headingId, className: "truncate", children: section.label }), _jsx("span", { className: "ms-auto shrink-0", children: formatNumber(section.icons.length) })] }), cells] }, section.id));
83
+ })) }));
84
+ return (_jsxs("div", { ref: ref, className: className, children: [_jsxs(SectionLabel, { render: _jsx("div", {}), className: "mb-1.5 flex items-center gap-2", children: [name, onClear && (_jsxs(_Fragment, { children: [_jsx("span", { className: "grow" }), _jsx(IconButton, { icon: Ban, size: "sm", label: t('iconPicker.none'), className: "-my-1", disabled: disabled || value === null, onClick: onClear })] }))] }), framed ? (_jsxs("div", { className: "field h-auto overflow-hidden p-0 has-[input:focus-visible]:outline-2 has-[input:focus-visible]:-outline-offset-1 has-[input:focus-visible]:outline-accent", children: [searchable && (_jsxs("div", { className: "flex h-8 items-center gap-2 border-b border-border pr-1 pl-2.5", children: [_jsx(Icon, { icon: Search, size: 14, className: "shrink-0 text-text-faint" }), _jsx("input", { ref: input, type: "text", value: query, placeholder: t('iconPicker.search'), "aria-label": t('iconPicker.search'), "aria-controls": `${id}-grid`, spellCheck: false, autoComplete: "off", disabled: disabled, className: "h-full min-w-0 flex-1 bg-transparent outline-none placeholder:text-text-faint", onChange: (event) => search(event.target.value), onKeyDown: (event) => {
85
+ if (escapeClearsSearch(event.key, query)) {
86
+ event.stopPropagation();
87
+ search('');
88
+ }
89
+ } }), query && (_jsx(IconButton, { icon: X, size: "sm", label: t('iconPicker.clear'), tooltip: false, onClick: () => {
90
+ search('');
91
+ input.current?.focus();
92
+ } }))] })), grid] })) : (grid)] }));
93
+ }
@@ -0,0 +1,14 @@
1
+ import type { ComponentProps } from 'react';
2
+ import type { LucideIcon } from 'lucide-react';
3
+ export interface InputProps extends Omit<ComponentProps<'input'>, 'size'> {
4
+ size?: 'md' | 'sm';
5
+ mono?: boolean;
6
+ icon?: LucideIcon;
7
+ }
8
+ export declare function Input({ size, mono, icon, className, id, ...props }: InputProps): import("react").JSX.Element;
9
+ export interface TextAreaProps extends ComponentProps<'textarea'> {
10
+ size?: 'md' | 'sm';
11
+ resize?: 'none' | 'vertical';
12
+ mono?: boolean;
13
+ }
14
+ export declare function TextArea({ size, resize, mono, className, id, ...props }: TextAreaProps): import("react").JSX.Element;
package/dist/Input.js ADDED
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import clsx from 'clsx';
3
+ import { useFieldControl } from './field-context.js';
4
+ import { Icon } from './Icon.js';
5
+ /* The one text input: one border and one focus ring at the one height or the compact one. */
6
+ export function Input({ size = 'md', mono = false, icon, className, id, ...props }) {
7
+ const field = useFieldControl();
8
+ const small = size === 'sm';
9
+ const control = {
10
+ id: id ?? field?.id,
11
+ 'aria-describedby': field?.describedBy,
12
+ 'aria-invalid': field?.invalid || undefined,
13
+ ...props
14
+ };
15
+ if (icon === undefined) {
16
+ return _jsx("input", { ...control, className: clsx('field', small && 'field-sm', mono && 'font-mono text-code', className) });
17
+ }
18
+ // The input fills the whole box and the icon lets clicks through, so a click on the icon still lands in the input.
19
+ return (_jsxs("span", { className: clsx('field relative flex p-0', small && 'field-sm', className), children: [_jsx(Icon, { icon: icon, size: small ? 12 : 14, className: clsx('pointer-events-none absolute top-1/2 -translate-y-1/2 text-text-faint', small ? 'left-2' : 'left-2.5') }), _jsx("input", { ...control, className: clsx('min-w-0 flex-1 bg-transparent outline-none placeholder:text-text-faint', small ? 'pr-2 pl-7' : 'pr-2.5 pl-8', mono && 'font-mono text-code') })] }));
20
+ }
21
+ /* A field of a few lines: a commit message, the body of a pull request, a note. At least 64px tall; `rows` makes it taller. */
22
+ export function TextArea({ size = 'md', resize = 'none', mono = false, className, id, ...props }) {
23
+ const field = useFieldControl();
24
+ return (_jsx("textarea", { id: id ?? field?.id, "aria-describedby": field?.describedBy, "aria-invalid": field?.invalid || undefined, ...props, className: clsx('field h-auto min-h-16 py-1.5', size === 'sm' && 'field-sm', resize === 'vertical' ? 'resize-y' : 'resize-none', mono && 'font-mono text-code', className) }));
25
+ }
package/dist/Kbd.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import type { ReactNode, Ref } from 'react';
2
+ import { type Shortcut } from './shortcut.ts';
3
+ export interface KbdProps {
4
+ shortcut?: Shortcut;
5
+ children?: ReactNode;
6
+ variant?: 'menu' | 'inline';
7
+ className?: string;
8
+ ref?: Ref<HTMLElement>;
9
+ }
10
+ export declare function Kbd({ shortcut, children, variant, className, ref }: KbdProps): import("react").JSX.Element;
11
+ export interface KeyCapProps {
12
+ children: ReactNode;
13
+ className?: string;
14
+ ref?: Ref<HTMLElement>;
15
+ }
16
+ export declare function KeyCap({ children, className, ref }: KeyCapProps): import("react").JSX.Element;
17
+ export interface KeysProps {
18
+ shortcut: Shortcut;
19
+ then?: string;
20
+ className?: string;
21
+ ref?: Ref<HTMLSpanElement>;
22
+ }
23
+ export declare function Keys({ shortcut, then, className, ref }: KeysProps): import("react").JSX.Element;
package/dist/Kbd.js ADDED
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import clsx from 'clsx';
3
+ import { isApplePlatform } from './platform.js';
4
+ import { formatShortcut, shortcutParts } from './shortcut.js';
5
+ /* A shortcut chip on the surface it sits on: in a tooltip, in a palette's footer, in a line of help. */
6
+ const INLINE_KBD = 'rounded-sm bg-surface-sunken px-[5px] py-px font-sans text-xs/[inherit] text-text-muted';
7
+ /* One key, drawn as a cap. A settings page prints a shortcut key by key, so a gesture can stand beside it. */
8
+ const KEY_CAP = 'inline-grid h-6 min-w-6 place-items-center rounded-md border border-b-2 border-border-strong bg-surface-hover bg-clip-padding px-1.5 font-sans text-xs text-text';
9
+ /* A shortcut as this platform prints it: `⌘K` on macOS, `Ctrl+K` elsewhere. */
10
+ export function Kbd({ shortcut, children, variant = 'menu', className, ref }) {
11
+ return (_jsx("kbd", { ref: ref, className: clsx(variant === 'inline' && INLINE_KBD, className), children: shortcut !== undefined ? formatShortcut(shortcut, isApplePlatform()) : children }));
12
+ }
13
+ export function KeyCap({ children, className, ref }) {
14
+ return (_jsx("kbd", { ref: ref, className: clsx(KEY_CAP, className), children: children }));
15
+ }
16
+ /* A shortcut as one cap per key, `⌘` and `K`, plus the pointer gesture it goes with. */
17
+ export function Keys({ shortcut, then, className, ref }) {
18
+ const parts = [...shortcutParts(shortcut, isApplePlatform()), ...(then ? [then] : [])];
19
+ return (_jsx("span", { ref: ref, className: clsx('flex items-center gap-1', className), children: parts.map((part, index) => (_jsx(KeyCap, { children: part }, `${part}-${index}`))) }));
20
+ }
@@ -0,0 +1,10 @@
1
+ import { useRender } from '@base-ui-components/react/use-render';
2
+ declare const VARIANT: {
3
+ readonly flat: 'flex h-7 items-center';
4
+ readonly inset: 'flex h-8 items-center rounded-md px-2';
5
+ };
6
+ export type ListRowProps = useRender.ComponentProps<'div'> & {
7
+ variant: keyof typeof VARIANT;
8
+ };
9
+ export declare function ListRow({ variant, render, className, ref, ...props }: ListRowProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
10
+ export {};
@@ -0,0 +1,20 @@
1
+ import clsx from 'clsx';
2
+ import { useRender } from '@base-ui-components/react/use-render';
3
+ const VARIANT = {
4
+ /* A row of a list that reads as a table and fills the width of its panel: commits, processes,
5
+ branches. Square, so the rows stack into one block. It carries no padding; the caller pads 12px
6
+ beside text and 4px beside a button. */
7
+ flat: 'flex h-7 items-center',
8
+ /* A row of a list a person finds their way through: a sidebar, devices, recent files. Inset from
9
+ the edge and rounded, so each row reads as a place of its own. */
10
+ inset: 'flex h-8 items-center rounded-md px-2'
11
+ };
12
+ /* The height and the shape of one row of a list. What it does (a button, a link, a menu trigger) comes from `render`. */
13
+ export function ListRow({ variant, render, className, ref, ...props }) {
14
+ return useRender({
15
+ render,
16
+ ref,
17
+ defaultTagName: 'div',
18
+ props: { ...props, className: clsx(VARIANT[variant], className) }
19
+ });
20
+ }
@@ -0,0 +1,12 @@
1
+ import type { Ref } from 'react';
2
+ export interface MeterProps {
3
+ value: number | null;
4
+ min?: number;
5
+ max?: number;
6
+ marks?: readonly number[];
7
+ label: string;
8
+ valueText?: string;
9
+ className?: string;
10
+ ref?: Ref<HTMLDivElement>;
11
+ }
12
+ export declare function Meter({ value, min, max, marks, label, valueText, className, ref }: MeterProps): import("react").JSX.Element;
package/dist/Meter.js ADDED
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import clsx from 'clsx';
3
+ /* A level against a scale: how full it is, and where a target or a limit sits on the same scale. */
4
+ export function Meter({ value, min = 0, max = 100, marks = [], label, valueText, className, ref }) {
5
+ const fractionOf = (level) => (max > min ? Math.min(1, Math.max(0, (level - min) / (max - min))) : 0);
6
+ return (_jsxs("div", { ref: ref, role: "meter", "aria-label": label, "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": value === null ? min : Math.min(max, Math.max(min, value)), "aria-valuetext": valueText, className: clsx('relative h-1.5 rounded-full bg-surface-sunken', className), children: [value !== null && _jsx("span", { className: "absolute inset-y-0 left-0 rounded-full bg-accent", style: { width: `${fractionOf(value) * 100}%` } }), marks.map((mark) => (_jsx("span", { className: "absolute -inset-y-0.5 w-px bg-text-muted", style: { left: `${fractionOf(mark) * 100}%` } }, mark)))] }));
7
+ }
@@ -0,0 +1,16 @@
1
+ import type { ReactNode, Ref } from 'react';
2
+ import type { LucideIcon } from 'lucide-react';
3
+ export interface PanelEmptyProps {
4
+ header?: ReactNode;
5
+ icon?: LucideIcon;
6
+ busy?: boolean;
7
+ /** @deprecated Turns the icon; use `busy`, which draws a `Spinner` in its place. */
8
+ spin?: boolean;
9
+ action?: ReactNode;
10
+ sunken?: boolean;
11
+ fill?: 'grow' | 'full';
12
+ children: ReactNode;
13
+ className?: string;
14
+ ref?: Ref<HTMLDivElement>;
15
+ }
16
+ export declare function PanelEmpty({ header, icon, busy, spin, action, sunken, fill, children, className, ref }: PanelEmptyProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import clsx from 'clsx';
3
+ import { EmptyState } from './EmptyState.js';
4
+ /* What a panel or a card shows while it holds nothing: a sentence, one icon, at most one button. */
5
+ export function PanelEmpty({ header, icon, busy = false, spin = false, action, sunken = false, fill = 'grow', children, className, ref }) {
6
+ return (_jsxs("div", { ref: ref, className: clsx('grid min-h-0 place-items-center', fill === 'full' ? 'h-full' : 'grow', sunken && 'bg-surface-sunken', className), children: [header, _jsx(EmptyState, { icon: icon, busy: busy, spin: spin, action: action, children: children })] }));
7
+ }
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from 'react';
2
+ import { useRender } from '@base-ui-components/react/use-render';
3
+ export type PanelHeaderProps = useRender.ComponentProps<'header'> & {
4
+ title?: ReactNode;
5
+ };
6
+ export declare function PanelHeader({ title, render, className, ref, children, ...props }: PanelHeaderProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
@@ -0,0 +1,20 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import clsx from 'clsx';
3
+ import { useRender } from '@base-ui-components/react/use-render';
4
+ import { SectionLabel } from './SectionLabel.js';
5
+ /*
6
+ * The bar across the top of a side panel, as tall as a 48px toolbar beside it: the panel's name,
7
+ * its own controls, and a close button last.
8
+ */
9
+ export function PanelHeader({ title, render, className, ref, children, ...props }) {
10
+ return useRender({
11
+ render,
12
+ ref,
13
+ defaultTagName: 'header',
14
+ props: {
15
+ ...props,
16
+ className: clsx('flex h-12 shrink-0 items-center gap-2 border-b border-border pr-2 pl-3', className),
17
+ children: (_jsxs(_Fragment, { children: [title !== undefined && _jsx(SectionLabel, { className: "shrink-0", children: title }), children] }))
18
+ }
19
+ });
20
+ }
package/dist/Pill.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ import type { ReactNode, Ref } from 'react';
2
+ declare const TONES: {
3
+ muted: string;
4
+ raised: string;
5
+ idle: string;
6
+ needsYou: string;
7
+ error: string;
8
+ accent: string;
9
+ };
10
+ declare const SHAPES: {
11
+ pill: string;
12
+ tag: string;
13
+ };
14
+ export interface PillProps {
15
+ icon?: ReactNode;
16
+ children: ReactNode;
17
+ tone?: keyof typeof TONES;
18
+ shape?: keyof typeof SHAPES;
19
+ mono?: boolean;
20
+ onClick?: () => void;
21
+ pressed?: boolean;
22
+ disabled?: boolean;
23
+ className?: string;
24
+ ref?: Ref<HTMLElement>;
25
+ }
26
+ export declare function Pill({ icon, children, tone, shape, mono, onClick, pressed, disabled, className, ref }: PillProps): import("react").JSX.Element;
27
+ export {};
package/dist/Pill.js ADDED
@@ -0,0 +1,25 @@
1
+ import { jsxs as _jsxs } from "react/jsx-runtime";
2
+ import clsx from 'clsx';
3
+ /* What the label says about the thing it names, drawn as its ground. */
4
+ const TONES = {
5
+ muted: 'bg-surface-sunken text-text-muted',
6
+ /* Lifted off a sunken header, for a label that should stand out as a control. */
7
+ raised: 'bg-surface-active text-text-muted',
8
+ idle: 'bg-status-idle/15 text-status-idle',
9
+ needsYou: 'bg-status-needs-you/15 text-status-needs-you',
10
+ error: 'bg-status-error/15 text-status-error',
11
+ accent: 'bg-accent-soft text-accent'
12
+ };
13
+ /* A pill rides in a header or a row; a tag sits at the end of a line of prose, tighter and bolder. */
14
+ const SHAPES = {
15
+ pill: 'rounded-full px-2 py-0.5',
16
+ tag: 'rounded-md px-1.5 py-0.5 font-medium'
17
+ };
18
+ /* The small rounded label in a header, a sidebar row or a settings line: a count, a branch, a status. */
19
+ export function Pill({ icon, children, tone = 'muted', shape = 'pill', mono = false, onClick, pressed, disabled, className, ref }) {
20
+ const shared = clsx('inline-flex shrink-0 items-center gap-1 text-xs', SHAPES[shape], TONES[tone], mono && 'font-mono', className);
21
+ if (!onClick) {
22
+ return (_jsxs("span", { ref: ref, className: shared, children: [icon, children] }));
23
+ }
24
+ return (_jsxs("button", { ref: ref, type: "button", "aria-pressed": pressed, disabled: disabled, className: clsx(shared, 'enabled:hover:text-text disabled:opacity-50 aria-pressed:bg-surface-active aria-pressed:text-text'), onClick: onClick, children: [icon, children] }));
25
+ }
@@ -0,0 +1,30 @@
1
+ import type { ComponentProps, MouseEvent, ReactNode, Ref } from 'react';
2
+ import * as Menu from './menu/index.parts.ts';
3
+ export interface ProjectSwitcherItem {
4
+ id: string;
5
+ name: string;
6
+ icon?: ReactNode;
7
+ description?: ReactNode;
8
+ hint?: ReactNode;
9
+ disabled?: boolean;
10
+ muted?: boolean;
11
+ actions?: ReactNode;
12
+ }
13
+ export interface ProjectSwitcherProps<Item extends ProjectSwitcherItem = ProjectSwitcherItem> {
14
+ current: ProjectSwitcherItem | null;
15
+ projects: readonly Item[];
16
+ recentProjects?: readonly Item[];
17
+ onSelect(project: Item, event: MouseEvent<HTMLElement>): void;
18
+ leading?: ReactNode;
19
+ before?: ReactNode;
20
+ children?: ReactNode;
21
+ label?: string;
22
+ disabled?: boolean;
23
+ open?: boolean;
24
+ defaultOpen?: boolean;
25
+ onOpenChange?: ComponentProps<typeof Menu.Root>['onOpenChange'];
26
+ className?: string;
27
+ ref?: Ref<HTMLButtonElement>;
28
+ }
29
+ export declare function ProjectSwitcher<Item extends ProjectSwitcherItem>({ current, projects, recentProjects, onSelect, leading, before, children, label, disabled, open, defaultOpen, onOpenChange, className, ref }: ProjectSwitcherProps<Item>): import("react").JSX.Element;
30
+ export declare function ProjectSwitcherGroups<Item extends ProjectSwitcherItem>({ projects, recentProjects, before, children, onSelect }: Pick<ProjectSwitcherProps<Item>, 'projects' | 'recentProjects' | 'before' | 'children' | 'onSelect'>): import("react").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import clsx from 'clsx';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { ChevronDown, History } from 'lucide-react';
5
+ import { Icon } from './Icon.js';
6
+ import * as Menu from './menu/index.parts.js';
7
+ import { ProjectSwitcherRow } from './project-switcher-row.js';
8
+ export function ProjectSwitcher({ current, projects, recentProjects, onSelect, leading, before, children, label, disabled, open, defaultOpen, onOpenChange, className, ref }) {
9
+ const { t } = useTranslation('ui');
10
+ return (_jsxs(Menu.Root, { open: open, defaultOpen: defaultOpen, onOpenChange: onOpenChange, children: [_jsxs(Menu.Trigger, { ref: ref, disabled: disabled, "aria-label": label ?? current?.name ?? t('projectSwitcher.label'), className: clsx('project-switcher-trigger', className), children: [leading, current?.icon, _jsx("span", { className: "truncate text-sm font-medium text-text", children: current?.name ?? t('projectSwitcher.label') }), _jsx(Icon, { icon: ChevronDown, size: 14, className: "shrink-0 text-text-muted" })] }), _jsx(Menu.Popup, { className: "min-w-60", children: _jsx(ProjectSwitcherGroups, { projects: projects, recentProjects: recentProjects, before: before, onSelect: onSelect, children: children }) })] }));
11
+ }
12
+ /* The rows of the popup, apart from it so they render without an open menu. */
13
+ export function ProjectSwitcherGroups({ projects, recentProjects = [], before, children, onSelect }) {
14
+ const { t } = useTranslation('ui');
15
+ // The theme hides a separator at the top of a menu or against another, so a group that draws
16
+ // nothing leaves no line behind.
17
+ return (_jsxs(_Fragment, { children: [projects.map((project) => (_jsx(ProjectSwitcherRow, { project: project, onSelect: onSelect }, project.id))), before && (_jsxs(_Fragment, { children: [_jsx(Menu.Separator, {}), before] })), (recentProjects.length > 0 || children) && (_jsxs(_Fragment, { children: [_jsx(Menu.Separator, {}), recentProjects.length > 0 && (_jsxs(Menu.SubmenuRoot, { children: [_jsxs(Menu.SubmenuTrigger, { children: [_jsx(Icon, { icon: History, size: 14 }), " ", t('projectSwitcher.recent')] }), _jsx(Menu.Popup, { className: "min-w-60", children: recentProjects.map((project) => (_jsx(ProjectSwitcherRow, { project: project, onSelect: onSelect }, project.id))) })] })), children] }))] }));
18
+ }
@@ -0,0 +1,40 @@
1
+ import { type ReactNode, type Ref } from 'react';
2
+ import type { LucideIcon } from 'lucide-react';
3
+ export interface PromptDialogProps {
4
+ open: boolean;
5
+ title: string;
6
+ titleIcon?: LucideIcon;
7
+ description?: ReactNode;
8
+ field?: {
9
+ label?: string;
10
+ ariaLabel?: string;
11
+ initial?: string;
12
+ placeholder?: string;
13
+ mono?: boolean;
14
+ maxLength?: number;
15
+ };
16
+ area?: {
17
+ label: string;
18
+ initial?: string;
19
+ placeholder?: string;
20
+ };
21
+ confirmLabel: string;
22
+ confirmBusyLabel?: string;
23
+ confirmIcon?: LucideIcon;
24
+ danger?: boolean;
25
+ busy?: boolean;
26
+ allowEmpty?: boolean;
27
+ confirmDisabled?: boolean;
28
+ secondary?: {
29
+ label: string;
30
+ onClick(): void;
31
+ };
32
+ nested?: boolean;
33
+ children?: ReactNode;
34
+ onConfirm(value: string, body: string): void | Promise<unknown>;
35
+ fallbackMessage?: string;
36
+ onOpenChange(open: boolean): void;
37
+ className?: string;
38
+ ref?: Ref<HTMLDivElement>;
39
+ }
40
+ export declare function PromptDialog({ open, title, titleIcon, description, field, area, confirmLabel, confirmBusyLabel, confirmIcon, danger, busy, allowEmpty, confirmDisabled, secondary, nested, children, onConfirm, fallbackMessage, onOpenChange, className, ref }: PromptDialogProps): import("react").JSX.Element;