@weasel-js/labkit 1.4.4 → 1.5.1

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 (278) hide show
  1. package/README.md +54 -6
  2. package/dist/_dts/{CanvasStackContext-kTEZvEgE.d.ts → CanvasStackContext-CJNMS6mz.d.ts} +1 -1
  3. package/dist/_dts/{PrefsForm.d-DDHFANkc.d.ts → PrefsForm.d-DPy9I8It.d.ts} +4 -0
  4. package/dist/_dts/Select.d-Gwr8fLpK.d.ts +453 -0
  5. package/dist/_dts/{frac-z7ker2Vx.d.ts → frac-B4okC2lX.d.ts} +291 -31
  6. package/dist/_dts/{index-Blv9uQzu.d.ts → index-BNdz-tNd.d.ts} +105 -17
  7. package/dist/_dts/index-CSJoj4LC.d.ts +248 -0
  8. package/dist/_dts/{types-B9_zrHmb.d.ts → types-DzoNY1pO.d.ts} +91 -4
  9. package/dist/_dts/usePanZoom-Pzf9f2jh.d.ts +160 -0
  10. package/dist/_dts/useTrialState-uLlmaccn.d.ts +212 -0
  11. package/dist/canvas/index.d.ts +18 -129
  12. package/dist/canvas/index.js +4 -2
  13. package/dist/chrome/index.d.ts +5 -93
  14. package/dist/chrome/index.js +10 -6
  15. package/dist/chunk-7PFH3CNM.js +150 -0
  16. package/dist/chunk-7PFH3CNM.js.map +1 -0
  17. package/dist/{chunk-I6JCVE24.js → chunk-CEIUBPPW.js} +143 -17
  18. package/dist/chunk-CEIUBPPW.js.map +1 -0
  19. package/dist/chunk-EH7UEQV7.js +590 -0
  20. package/dist/chunk-EH7UEQV7.js.map +1 -0
  21. package/dist/{chunk-MOM3GOVY.js → chunk-EHGVROVV.js} +125 -27
  22. package/dist/chunk-EHGVROVV.js.map +1 -0
  23. package/dist/{chunk-TJ7QY3OC.js → chunk-ERFMC6WU.js} +160 -78
  24. package/dist/chunk-ERFMC6WU.js.map +1 -0
  25. package/dist/{chunk-CTRKTLYZ.js → chunk-H6ZAOWNE.js} +11 -11
  26. package/dist/chunk-H6ZAOWNE.js.map +1 -0
  27. package/dist/{chunk-64ZCN3DA.js → chunk-IHUVQ3TX.js} +250 -160
  28. package/dist/chunk-IHUVQ3TX.js.map +1 -0
  29. package/dist/{chunk-FQMJUVHJ.js → chunk-IJJUK4FU.js} +53 -46
  30. package/dist/chunk-IJJUK4FU.js.map +1 -0
  31. package/dist/chunk-IXTQI7JX.js +737 -0
  32. package/dist/chunk-IXTQI7JX.js.map +1 -0
  33. package/dist/{chunk-BKFVHKJH.js → chunk-MDVTK5U3.js} +4 -4
  34. package/dist/{chunk-BKFVHKJH.js.map → chunk-MDVTK5U3.js.map} +1 -1
  35. package/dist/chunk-PHK4W562.js +72 -0
  36. package/dist/chunk-PHK4W562.js.map +1 -0
  37. package/dist/chunk-PJ7WEQBJ.js +18 -0
  38. package/dist/chunk-PJ7WEQBJ.js.map +1 -0
  39. package/dist/chunk-RWNDDKOH.js +682 -0
  40. package/dist/chunk-RWNDDKOH.js.map +1 -0
  41. package/dist/chunk-T3EC2DXD.js +12628 -0
  42. package/dist/chunk-T3EC2DXD.js.map +1 -0
  43. package/dist/{chunk-SMHP6XZ4.js → chunk-UN4U4P63.js} +10 -8
  44. package/dist/chunk-UN4U4P63.js.map +1 -0
  45. package/dist/chunk-WIG6XHJ7.js +35 -0
  46. package/dist/chunk-WIG6XHJ7.js.map +1 -0
  47. package/dist/chunk-Z76KEZAX.js +15 -0
  48. package/dist/chunk-Z76KEZAX.js.map +1 -0
  49. package/dist/chunk-ZDHY47EV.js +363 -0
  50. package/dist/chunk-ZDHY47EV.js.map +1 -0
  51. package/dist/config/index.d.ts +276 -0
  52. package/dist/config/index.js +8 -0
  53. package/dist/config/index.js.map +1 -0
  54. package/dist/controls/index.d.ts +3 -4
  55. package/dist/controls/index.js +4 -3
  56. package/dist/dragdrop/index.d.ts +3 -5
  57. package/dist/index.d.ts +266 -413
  58. package/dist/index.js +1775 -2251
  59. package/dist/index.js.map +1 -1
  60. package/dist/layers/index.d.ts +4 -6
  61. package/dist/layers/index.js +2 -2
  62. package/dist/loupe/index.d.ts +69 -20
  63. package/dist/loupe/index.js +2 -2
  64. package/dist/passthrough/weasel-ui.d.ts +175 -309
  65. package/dist/passthrough/weasel-ui.js +1 -1
  66. package/dist/primitives/index.d.ts +55 -6
  67. package/dist/primitives/index.js +6 -4
  68. package/dist/state/index.d.ts +13 -8
  69. package/dist/state/index.js +18 -13
  70. package/dist/state/index.js.map +1 -1
  71. package/dist/styles.css +223 -76
  72. package/dist/surface/index.d.ts +44 -7
  73. package/dist/surface/index.js +2 -1
  74. package/dist/ui/layers/index.js +1 -1
  75. package/dist/undo/index.d.ts +3 -5
  76. package/package.json +15 -8
  77. package/src/annotations/AnnotationOverlay.tsx +56 -5
  78. package/src/annotations/Annotations.less +4 -0
  79. package/src/annotations/Annotations.overlay.test.tsx +105 -6
  80. package/src/annotations/drawOne.test.ts +1 -1
  81. package/src/annotations/drawOne.ts +2 -2
  82. package/src/annotations/paint.test.ts +2 -2
  83. package/src/annotations/paint.ts +1 -1
  84. package/src/annotations/preload.ts +6 -0
  85. package/src/annotations/store.test.ts +18 -0
  86. package/src/annotations/store.ts +5 -4
  87. package/src/annotations/svgNodes.test.ts +2 -2
  88. package/src/annotations/svgNodes.ts +1 -0
  89. package/src/annotations/toolMap.test.ts +9 -5
  90. package/src/annotations/toolMap.ts +6 -3
  91. package/src/annotations/types.ts +6 -3
  92. package/src/canvas/CameraWheelContext.ts +12 -0
  93. package/src/canvas/CanvasStack.stories.tsx +1 -1
  94. package/src/canvas/CanvasStack.test.tsx +25 -0
  95. package/src/canvas/CanvasStack.tsx +31 -4
  96. package/src/canvas/Stage.less +18 -0
  97. package/src/canvas/Stage.test.tsx +186 -0
  98. package/src/canvas/Stage.tsx +134 -0
  99. package/src/canvas/camera.test.ts +17 -2
  100. package/src/canvas/camera.ts +9 -9
  101. package/src/canvas/index.ts +4 -0
  102. package/src/canvas/usePanZoom.test.ts +28 -0
  103. package/src/canvas/usePanZoom.ts +21 -9
  104. package/src/chrome/ChromeRegions.stories.tsx +1 -4
  105. package/src/chrome/LabChrome.tsx +130 -0
  106. package/src/chrome/builtins.test.ts +1 -0
  107. package/src/chrome/builtins.tsx +21 -31
  108. package/src/chrome/index.ts +16 -0
  109. package/src/chrome/labRegions.test.tsx +208 -0
  110. package/src/chrome/labTypes.ts +50 -0
  111. package/src/chrome/merge.ts +7 -9
  112. package/src/chrome/regions/PaletteRegion.test.tsx +6 -4
  113. package/src/chrome/regions/PaletteRegion.tsx +16 -9
  114. package/src/chrome/regions/SidebarRegion.stories.tsx +44 -0
  115. package/src/chrome/regions/SidebarRegion.tsx +25 -13
  116. package/src/chrome/regions/StatusRegion.tsx +15 -8
  117. package/src/chrome/regions/ToolbarRegion.tsx +34 -16
  118. package/src/chrome/types.ts +50 -20
  119. package/src/config/auto.test.ts +12 -0
  120. package/src/config/auto.ts +15 -0
  121. package/src/config/autoConfig.test.ts +107 -0
  122. package/src/config/autoConfig.ts +153 -0
  123. package/src/config/builder.test.ts +42 -0
  124. package/src/config/builder.ts +68 -17
  125. package/src/config/declarationEmit.fixture.ts +11 -0
  126. package/src/config/declarationEmit.test.ts +51 -0
  127. package/src/config/entry.test.ts +46 -0
  128. package/src/config/index.ts +16 -1
  129. package/src/config/nodeClasses.test.ts +18 -0
  130. package/src/config/path.ts +7 -2
  131. package/src/config/resolve.test.ts +25 -0
  132. package/src/config/resolve.ts +17 -1
  133. package/src/config/types.ts +34 -2
  134. package/src/config/useResolvedConfig.ts +26 -0
  135. package/src/controls/AutoControls.stories.tsx +88 -0
  136. package/src/controls/ControlPanel.stories.tsx +42 -1
  137. package/src/controls/ControlPanel.test.tsx +530 -4
  138. package/src/controls/ControlPanel.tsx +562 -82
  139. package/src/fake-indexeddb-auto.d.ts +5 -0
  140. package/src/index.test.ts +7 -0
  141. package/src/index.ts +66 -5
  142. package/src/instrument/serializers.test.ts +22 -0
  143. package/src/instrument/serializers.ts +40 -1
  144. package/src/instrument/types.ts +25 -1
  145. package/src/lab/Lab.chrome.test.tsx +16 -0
  146. package/src/lab/Lab.less +4 -13
  147. package/src/lab/Lab.persist.test.tsx +147 -0
  148. package/src/lab/Lab.stories.tsx +1 -3
  149. package/src/lab/Lab.surface.test.tsx +71 -3
  150. package/src/lab/Lab.test.tsx +168 -16
  151. package/src/lab/Lab.tsx +434 -129
  152. package/src/lab/LabContext.ts +8 -0
  153. package/src/lab/LabFit.stories.less +18 -0
  154. package/src/lab/LabFit.stories.tsx +219 -0
  155. package/src/lab/LabFullChrome.stories.tsx +1 -4
  156. package/src/lab/LabHeader.test.tsx +40 -9
  157. package/src/lab/LabHeader.tsx +7 -14
  158. package/src/lab/LabPalette.tsx +13 -26
  159. package/src/lab/LabRoot.test.tsx +56 -0
  160. package/src/lab/LabRoot.tsx +47 -0
  161. package/src/lab/LabShell.less +73 -15
  162. package/src/lab/LabShell.stories.tsx +50 -1
  163. package/src/lab/LabShell.tsx +18 -27
  164. package/src/lab/LabSwitcher.less +18 -11
  165. package/src/lab/LabSwitcher.test.tsx +14 -9
  166. package/src/lab/LabSwitcher.tsx +8 -4
  167. package/src/lab/Workspace.less +1 -1
  168. package/src/lab/Workspace.stories.tsx +1 -1
  169. package/src/lab/Workspace.surface.test.tsx +1 -0
  170. package/src/lab/fitCheck.test.ts +57 -0
  171. package/src/lab/fitCheck.ts +125 -0
  172. package/src/lab/index.ts +2 -0
  173. package/src/layers/LayerList.stories.tsx +1 -1
  174. package/src/loupe/AGENTS.md +17 -5
  175. package/src/loupe/Loupe.less +1 -1
  176. package/src/loupe/LoupeGestures.tsx +67 -0
  177. package/src/loupe/TrialLoupe.tsx +43 -28
  178. package/src/loupe/canvasLens.ts +1 -1
  179. package/src/loupe/index.ts +4 -0
  180. package/src/loupe/loupeActions.ts +98 -0
  181. package/src/loupe/types.ts +1 -1
  182. package/src/loupe/useLoupe.test.tsx +60 -3
  183. package/src/loupe/useLoupe.ts +29 -49
  184. package/src/passthrough/weasel-ui.ts +9 -0
  185. package/src/primitives/FloatingPanel.stories.tsx +14 -2
  186. package/src/primitives/FloatingPanel.test.tsx +47 -16
  187. package/src/primitives/FloatingPanel.tsx +11 -25
  188. package/src/primitives/FpsMeter.stories.tsx +1 -1
  189. package/src/primitives/JobProgress.stories.tsx +1 -1
  190. package/src/primitives/Legend.stories.tsx +1 -1
  191. package/src/primitives/ScaleIndicator.stories.tsx +1 -1
  192. package/src/primitives/Sidebar.less +7 -0
  193. package/src/primitives/Sidebar.stories.tsx +1 -1
  194. package/src/primitives/Split.less +14 -0
  195. package/src/primitives/Split.test.tsx +133 -0
  196. package/src/primitives/Split.tsx +175 -0
  197. package/src/primitives/StatusBar.stories.tsx +1 -1
  198. package/src/primitives/Toolbar.stories.tsx +1 -1
  199. package/src/primitives/ZoomControl.tsx +5 -2
  200. package/src/primitives/index.ts +2 -0
  201. package/src/specimen/Specimen.less +95 -0
  202. package/src/specimen/Specimen.stories.tsx +13 -0
  203. package/src/specimen/Specimen.test.tsx +13 -0
  204. package/src/specimen/Specimen.tsx +888 -0
  205. package/src/state/Persistence.stories.tsx +75 -0
  206. package/src/state/Persistence.tsx +33 -0
  207. package/src/state/SingletonExperiment.test.tsx +47 -53
  208. package/src/state/SingletonExperiment.tsx +24 -13
  209. package/src/state/adapterContract.ts +116 -0
  210. package/src/state/adapters.test.ts +113 -61
  211. package/src/state/adapters.ts +328 -69
  212. package/src/state/document.test.ts +71 -96
  213. package/src/state/document.ts +66 -52
  214. package/src/state/helpers.test.ts +1 -1
  215. package/src/state/index.ts +8 -0
  216. package/src/state/labRecords.test.ts +105 -0
  217. package/src/state/labRecords.ts +160 -0
  218. package/src/state/openLabStore.test.ts +658 -0
  219. package/src/state/openLabStore.ts +403 -0
  220. package/src/state/records.test.ts +234 -0
  221. package/src/state/records.ts +219 -0
  222. package/src/state/store.test.ts +169 -635
  223. package/src/state/store.ts +146 -163
  224. package/src/state/toolSlot.test.ts +1 -1
  225. package/src/state/types.ts +65 -17
  226. package/src/state/useOpenOnce.ts +68 -0
  227. package/src/state/usePersistedState.test.tsx +125 -0
  228. package/src/state/usePersistedState.ts +66 -0
  229. package/src/state/useTrialState.test.tsx +87 -3
  230. package/src/state/useTrialState.ts +9 -1
  231. package/src/state/view.test.ts +49 -15
  232. package/src/state/view.ts +18 -1
  233. package/src/styles.less +3 -0
  234. package/src/surface/AGENTS.md +39 -7
  235. package/src/surface/SurfaceContext.ts +26 -3
  236. package/src/surface/index.ts +2 -0
  237. package/src/surface/useSurfaceTile.test.tsx +1 -0
  238. package/src/surface/useSurfaceTile.ts +6 -5
  239. package/src/surface/useTiledSurface.test.tsx +231 -1
  240. package/src/surface/useTiledSurface.ts +76 -15
  241. package/src/theme/Interstellar.stories.tsx +9 -9
  242. package/src/theme/interstellar.test.ts +17 -6
  243. package/src/theme/interstellar.theme.json +180 -0
  244. package/src/theme/interstellar.ts +8 -5
  245. package/src/tools/labTool.ts +11 -0
  246. package/src/trial/Trial.annotations.persist.test.tsx +100 -58
  247. package/src/trial/Trial.annotations.test.tsx +59 -7
  248. package/src/trial/Trial.canvas.test.tsx +32 -0
  249. package/src/trial/Trial.config.test.tsx +75 -2
  250. package/src/trial/Trial.job.test.tsx +3 -3
  251. package/src/trial/Trial.less +5 -26
  252. package/src/trial/Trial.stories.tsx +5 -3
  253. package/src/trial/Trial.test.tsx +31 -4
  254. package/src/trial/Trial.trialId.test.tsx +23 -1
  255. package/src/trial/Trial.tsx +139 -52
  256. package/src/trial/TrialBody.tsx +17 -141
  257. package/src/trial/TrialChrome.tsx +24 -7
  258. package/src/trial/trialOps.test.ts +101 -1
  259. package/src/trial/trialOps.ts +67 -7
  260. package/dist/_dts/types-BP2OCcpg.d.ts +0 -155
  261. package/dist/_dts/types-x92Kfeme.d.ts +0 -62
  262. package/dist/_dts/useTrialState-D6Vb3T-g.d.ts +0 -99
  263. package/dist/chunk-2P6PP5N4.js +0 -553
  264. package/dist/chunk-2P6PP5N4.js.map +0 -1
  265. package/dist/chunk-64ZCN3DA.js.map +0 -1
  266. package/dist/chunk-CTRKTLYZ.js.map +0 -1
  267. package/dist/chunk-E44SU6XS.js +0 -50
  268. package/dist/chunk-E44SU6XS.js.map +0 -1
  269. package/dist/chunk-FQMJUVHJ.js.map +0 -1
  270. package/dist/chunk-I6JCVE24.js.map +0 -1
  271. package/dist/chunk-MOM3GOVY.js.map +0 -1
  272. package/dist/chunk-SMHP6XZ4.js.map +0 -1
  273. package/dist/chunk-TJ7QY3OC.js.map +0 -1
  274. package/dist/chunk-U3IYHIAE.js +0 -343
  275. package/dist/chunk-U3IYHIAE.js.map +0 -1
  276. package/dist/chunk-W3ECWC2K.js +0 -7335
  277. package/dist/chunk-W3ECWC2K.js.map +0 -1
  278. package/src/theme/interstellar.tokens.json +0 -61
@@ -0,0 +1,888 @@
1
+ import type { FillStyle, GradientFill, Track } from '@weasel-js/core';
2
+ import {
3
+ ActionsBar,
4
+ Badge,
5
+ type Band,
6
+ BandEditor,
7
+ Button,
8
+ Callout,
9
+ Checkbox,
10
+ CheckboxRow,
11
+ ColorField,
12
+ ColorRow,
13
+ ComboBox,
14
+ type ControlPoint,
15
+ CurveEditor,
16
+ createToastQueue,
17
+ DataGrid,
18
+ DetentSlider,
19
+ Dialog,
20
+ Disclosure,
21
+ GradientEditor,
22
+ InlineRange,
23
+ Input,
24
+ ItemList,
25
+ KeyCap,
26
+ KeySequence,
27
+ LayerStack,
28
+ type LayerStackItem,
29
+ MenuButton,
30
+ NumberField,
31
+ NumberRow,
32
+ OptionsBar,
33
+ PaintInput,
34
+ Plot2D,
35
+ PointPlotter,
36
+ Powerline,
37
+ type PrefGroup,
38
+ PrefsForm,
39
+ PropertyGroup,
40
+ PropertyList,
41
+ PropertyPanel,
42
+ Radio,
43
+ RadioGroup,
44
+ RangeSlider,
45
+ ResizeHandle,
46
+ Select,
47
+ SelectRow,
48
+ SidebarPanel,
49
+ Slider,
50
+ SliderRow,
51
+ StatusBarItem,
52
+ StatusBarSpacer,
53
+ Switch,
54
+ Tab,
55
+ TabList,
56
+ TabPanel,
57
+ Tabs,
58
+ TextRow,
59
+ type Thumb,
60
+ Timeline,
61
+ ToastRegion,
62
+ ToggleBar,
63
+ ToggleRow,
64
+ ToolButton,
65
+ ToolGroup,
66
+ ToolOptionsBar,
67
+ Tooltip,
68
+ TooltipTrigger,
69
+ Sidebar as UiSidebar,
70
+ StatusBar as UiStatusBar,
71
+ } from '@weasel-js/ui';
72
+ import { type CSSProperties, type ReactNode, useId, useMemo, useRef, useState } from 'react';
73
+ import { Focusable } from 'react-aria-components';
74
+ import type { JobHandle } from '../job/types';
75
+ import { FloatingPanel } from '../primitives/FloatingPanel';
76
+ import { JobProgress } from '../primitives/JobProgress';
77
+ import { Legend, type LegendEntry } from '../primitives/Legend';
78
+ import { ScaleIndicator } from '../primitives/ScaleIndicator';
79
+ import { Sidebar } from '../primitives/Sidebar';
80
+ import { Split } from '../primitives/Split';
81
+ import { StatusBar } from '../primitives/StatusBar';
82
+ import { Toolbar } from '../primitives/Toolbar';
83
+ import { ZoomControl } from '../primitives/ZoomControl';
84
+
85
+ /** The specimen's sections, in page order. */
86
+ export const SPECIMEN_SECTIONS = [
87
+ 'Buttons & toggles',
88
+ 'Fields & pickers',
89
+ 'Panels & property rows',
90
+ 'Overlays & feedback',
91
+ 'Lists & data',
92
+ 'Tool chrome',
93
+ 'Editors & plots',
94
+ 'Lab chrome',
95
+ ] as const;
96
+
97
+ type SectionTitle = (typeof SPECIMEN_SECTIONS)[number];
98
+
99
+ function Section({ title, children }: { title: SectionTitle; children: ReactNode }) {
100
+ const id = useId();
101
+ return (
102
+ <section className="lk-specimen__section" aria-labelledby={id}>
103
+ <h2 id={id} className="lk-specimen__heading">
104
+ {title}
105
+ </h2>
106
+ <div className="lk-specimen__grid">{children}</div>
107
+ </section>
108
+ );
109
+ }
110
+
111
+ function Cell({ label, wide, children }: { label: string; wide?: boolean; children: ReactNode }) {
112
+ return (
113
+ <div className={wide ? 'lk-specimen__cell lk-specimen__cell--wide' : 'lk-specimen__cell'}>
114
+ <span className="lk-specimen__label">{label}</span>
115
+ {children}
116
+ </div>
117
+ );
118
+ }
119
+
120
+ function Dot() {
121
+ return (
122
+ <svg viewBox="0 0 16 16" width={16} height={16} aria-hidden="true">
123
+ <circle cx={8} cy={8} r={4} fill="currentColor" />
124
+ </svg>
125
+ );
126
+ }
127
+
128
+ const LEGEND: LegendEntry[] = [
129
+ { key: 'contour', label: 'contour', color: 'var(--wzl-fg-muted)' },
130
+ { key: 'floor', label: 'floor', color: 'var(--wzl-fg-subtle)', mark: 'dash' },
131
+ { key: 'authored', label: 'authored', color: 'var(--wzl-success)', mark: 'dot' },
132
+ { key: 'replaced', label: 'replaced', color: 'var(--wzl-danger)', mark: 'band' },
133
+ ];
134
+
135
+ const PREFS: PrefGroup = {
136
+ name: 'Preferences',
137
+ children: {
138
+ canvas: {
139
+ name: 'Canvas',
140
+ children: {
141
+ showGrid: {
142
+ kind: 'boolean',
143
+ name: 'Show grid',
144
+ description: 'Draw a grid behind the scene.',
145
+ default: true,
146
+ },
147
+ zoomStep: {
148
+ kind: 'number',
149
+ name: 'Zoom step',
150
+ description: 'Percent the zoom controls move per click.',
151
+ default: 10,
152
+ min: 1,
153
+ max: 50,
154
+ },
155
+ format: {
156
+ kind: 'enum',
157
+ name: 'Format',
158
+ description: 'File type for exported images.',
159
+ default: 'svg',
160
+ options: [
161
+ { value: 'svg', label: 'SVG' },
162
+ { value: 'png', label: 'PNG' },
163
+ ],
164
+ },
165
+ },
166
+ },
167
+ },
168
+ };
169
+
170
+ const JOB: JobHandle = {
171
+ status: 'running',
172
+ done: 34,
173
+ total: 120,
174
+ failures: [],
175
+ error: null,
176
+ start: () => {},
177
+ cancel: () => {},
178
+ };
179
+
180
+ function setAt(
181
+ values: Record<string, unknown>,
182
+ path: string,
183
+ value: unknown,
184
+ ): Record<string, unknown> {
185
+ const [head, ...rest] = path.split('.');
186
+ if (head === undefined) return values;
187
+ if (rest.length === 0) return { ...values, [head]: value };
188
+ const inner = (values[head] as Record<string, unknown> | undefined) ?? {};
189
+ return { ...values, [head]: setAt(inner, rest.join('.'), value) };
190
+ }
191
+
192
+ function ButtonsAndToggles() {
193
+ const [snap, setSnap] = useState(true);
194
+ const [wifi, setWifi] = useState(false);
195
+ const [align, setAlign] = useState<string | null>('center');
196
+ const [bold, setBold] = useState(true);
197
+ const [italic, setItalic] = useState(false);
198
+ const [open, setOpen] = useState(true);
199
+ return (
200
+ <Section title="Buttons & toggles">
201
+ <Cell label="Button">
202
+ <div className="lk-specimen__row">
203
+ <Button variant="primary">Save</Button>
204
+ <Button variant="secondary">Cancel</Button>
205
+ <Button variant="ghost">Ghost</Button>
206
+ <Button variant="secondary" disabled>
207
+ Disabled
208
+ </Button>
209
+ <Button variant="secondary" iconOnly ariaLabel="Add">
210
+ <Dot />
211
+ </Button>
212
+ </div>
213
+ </Cell>
214
+ <Cell label="Badge">
215
+ <div className="lk-specimen__row">
216
+ <Badge shape="pill" tone="accent" variant="outline" size="sm">
217
+ ACCENT
218
+ </Badge>
219
+ <Badge tone="info" variant="subtle">
220
+ INFO
221
+ </Badge>
222
+ <Badge tone="warn" variant="solid">
223
+ WARN
224
+ </Badge>
225
+ <Badge tone="danger" variant="solid">
226
+ DANGER
227
+ </Badge>
228
+ <Badge tone="muted">MUTED</Badge>
229
+ </div>
230
+ </Cell>
231
+ <Cell label="Checkbox, Switch">
232
+ <div className="lk-specimen__row">
233
+ <Checkbox isSelected={snap} onChange={setSnap}>
234
+ Snap to grid
235
+ </Checkbox>
236
+ <Checkbox isIndeterminate>Mixed</Checkbox>
237
+ <Switch isSelected={wifi} onChange={setWifi}>
238
+ Wifi
239
+ </Switch>
240
+ </div>
241
+ </Cell>
242
+ <Cell label="RadioGroup">
243
+ <RadioGroup label="Snap mode" orientation="horizontal" defaultValue="grid">
244
+ <Radio value="off">Off</Radio>
245
+ <Radio value="grid">Grid</Radio>
246
+ <Radio value="pixel">Pixel</Radio>
247
+ </RadioGroup>
248
+ </Cell>
249
+ <Cell label="ToggleBar">
250
+ <ToggleBar
251
+ ariaLabel="Align"
252
+ value={align}
253
+ onChange={setAlign}
254
+ items={[
255
+ { value: 'left', label: 'Left' },
256
+ { value: 'center', label: 'Center' },
257
+ { value: 'right', label: 'Right' },
258
+ ]}
259
+ />
260
+ <ToggleBar
261
+ ariaLabel="Align, flat"
262
+ variant="flat"
263
+ value={align}
264
+ onChange={setAlign}
265
+ items={[
266
+ { value: 'left', label: 'Left' },
267
+ { value: 'center', label: 'Center' },
268
+ { value: 'right', label: 'Right' },
269
+ ]}
270
+ />
271
+ </Cell>
272
+ <Cell label="OptionsBar, ActionsBar">
273
+ <div className="lk-specimen__row">
274
+ <OptionsBar
275
+ ariaLabel="Text style"
276
+ items={[
277
+ { value: 'b', label: 'B', selected: bold, onChange: setBold },
278
+ { value: 'i', label: 'I', selected: italic, onChange: setItalic },
279
+ ]}
280
+ />
281
+ <ActionsBar
282
+ items={[
283
+ { value: 'undo', label: 'Undo', onAction: () => {} },
284
+ { value: 'redo', label: 'Redo', onAction: () => {} },
285
+ ]}
286
+ />
287
+ </div>
288
+ </Cell>
289
+ <Cell label="MenuButton, Disclosure">
290
+ <div className="lk-specimen__row">
291
+ <MenuButton
292
+ label="Add trial…"
293
+ onAction={() => {}}
294
+ items={[
295
+ { value: 'sine', label: 'Sine wave' },
296
+ { value: 'annotate', label: 'Annotate' },
297
+ ]}
298
+ />
299
+ <Disclosure open={open} onToggle={() => setOpen((o) => !o)} label="Shapes" />
300
+ <Disclosure open={false} onToggle={() => {}} label="Down" direction="down" />
301
+ </div>
302
+ </Cell>
303
+ <Cell label="Tabs">
304
+ <Tabs>
305
+ <TabList aria-label="Sections">
306
+ <Tab id="props">Props</Tab>
307
+ <Tab id="events">Events</Tab>
308
+ <Tab id="snapshot">Snapshot</Tab>
309
+ </TabList>
310
+ <TabPanel id="props">Props panel.</TabPanel>
311
+ <TabPanel id="events">Events panel.</TabPanel>
312
+ <TabPanel id="snapshot">Snapshot panel.</TabPanel>
313
+ </Tabs>
314
+ </Cell>
315
+ <Cell label="Keycaps">
316
+ <div className="lk-specimen__row">
317
+ <KeySequence keys={[{ label: '⌘' }, { label: '⇧' }, { label: 'K' }]} separator="+" />
318
+ <KeyCap label="K" />
319
+ <KeyCap label="⌥" variant="minimal" />
320
+ </div>
321
+ </Cell>
322
+ </Section>
323
+ );
324
+ }
325
+
326
+ function FieldsAndPickers() {
327
+ const [thumbs, setThumbs] = useState<Thumb[]>([{ value: 0.25 }, { value: 0.75 }]);
328
+ const [rate, setRate] = useState(1);
329
+ const [width, setWidth] = useState(6);
330
+ const [color, setColor] = useState('#b08adbff');
331
+ const [gradient, setGradient] = useState<GradientFill>({
332
+ fill: 'linear-gradient',
333
+ from: { x: 0.1, y: 0.1 },
334
+ to: { x: 0.9, y: 0.9 },
335
+ stops: [
336
+ { offset: 0, color: '#0fb5a8' },
337
+ { offset: 1, color: '#f4c43c' },
338
+ ],
339
+ });
340
+ const [paint, setPaint] = useState<FillStyle | null>({ fill: 'solid', color: '#b08adb' });
341
+ return (
342
+ <Section title="Fields & pickers">
343
+ <Cell label="Input">
344
+ <Input label="Name" placeholder="e.g. Pico" />
345
+ <Input
346
+ label="Size"
347
+ leadingAdornment={<span>W</span>}
348
+ trailingAdornment={<span>px</span>}
349
+ defaultValue="120"
350
+ />
351
+ <Input label="Required" isInvalid errorMessage="Name is required." />
352
+ </Cell>
353
+ <Cell label="NumberField, Select, ComboBox">
354
+ <NumberField label="Opacity" minValue={0} maxValue={100} step={5} defaultValue={80} />
355
+ <Select
356
+ label="Color"
357
+ defaultSelectedKey="g"
358
+ options={[
359
+ { value: 'r', label: 'Red' },
360
+ { value: 'g', label: 'Green' },
361
+ { value: 'b', label: 'Blue' },
362
+ ]}
363
+ />
364
+ <ComboBox
365
+ label="Filter"
366
+ placeholder="Type to filter…"
367
+ options={[
368
+ { value: 'r', label: 'Red' },
369
+ { value: 'g', label: 'Green' },
370
+ ]}
371
+ />
372
+ </Cell>
373
+ <Cell label="RangeSlider, Slider">
374
+ <RangeSlider label="Range" defaultValue={[20, 80]} minValue={0} maxValue={100} />
375
+ <RangeSlider
376
+ label="Opacity"
377
+ defaultValue={75}
378
+ minValue={0}
379
+ maxValue={100}
380
+ formatOutput={(v) => `${v}%`}
381
+ />
382
+ <Slider
383
+ ariaLabel="Pair"
384
+ min={0}
385
+ max={1}
386
+ step={0.01}
387
+ constraint="ordered"
388
+ thumbs={thumbs}
389
+ onInput={setThumbs}
390
+ readoutPlacement="inline-after"
391
+ />
392
+ </Cell>
393
+ <Cell label="DetentSlider, InlineRange">
394
+ <DetentSlider
395
+ items={[0.25, 0.5, 1, 2, 4]}
396
+ value={rate}
397
+ onChange={setRate}
398
+ formatLabel={(r) => `${r}×`}
399
+ labels="all"
400
+ ariaLabel="Playback rate"
401
+ />
402
+ <InlineRange
403
+ aria-label="Width"
404
+ min={0}
405
+ max={20}
406
+ value={width}
407
+ onChange={(event) => setWidth(Number((event.target as HTMLInputElement).value))}
408
+ />
409
+ </Cell>
410
+ <Cell label="ColorField, GradientEditor">
411
+ <ColorField value={color} alpha onInput={setColor} onChange={setColor} aria-label="Color" />
412
+ <GradientEditor value={gradient} onInput={setGradient} onChange={setGradient} kindSwitch />
413
+ </Cell>
414
+ <Cell label="PaintInput">
415
+ <PaintInput
416
+ value={paint}
417
+ onInput={setPaint}
418
+ onChange={setPaint}
419
+ allowNone
420
+ aria-label="Fill"
421
+ />
422
+ </Cell>
423
+ </Section>
424
+ );
425
+ }
426
+
427
+ function PanelsAndRows() {
428
+ const [opacity, setOpacity] = useState(0.8);
429
+ const [radius, setRadius] = useState(12);
430
+ const [fill, setFill] = useState('#5841b8');
431
+ const [alpha, setAlpha] = useState(1);
432
+ const [stroke, setStroke] = useState('#2ec27e');
433
+ const [count, setCount] = useState(8);
434
+ const [cap, setCap] = useState('round');
435
+ const [side, setSide] = useState('center');
436
+ const [label, setLabel] = useState('Badge');
437
+ const [visible, setVisible] = useState(true);
438
+ const [prefs, setPrefs] = useState<Record<string, unknown>>({});
439
+ const [layers, setLayers] = useState<LayerStackItem[]>([
440
+ { id: 'base', label: 'Base coat' },
441
+ { id: 'wash', label: 'Wash' },
442
+ ]);
443
+ const [panelCollapsed, setPanelCollapsed] = useState(false);
444
+ return (
445
+ <Section title="Panels & property rows">
446
+ <Cell label="PropertyPanel">
447
+ <PropertyPanel title="Shape">
448
+ <PropertyList>
449
+ <SliderRow
450
+ label="Opacity"
451
+ value={opacity}
452
+ min={0}
453
+ max={1}
454
+ step={0.01}
455
+ onChange={setOpacity}
456
+ />
457
+ <SliderRow
458
+ label="Radius"
459
+ value={radius}
460
+ min={0}
461
+ max={64}
462
+ unit="px"
463
+ onChange={setRadius}
464
+ />
465
+ <ColorRow
466
+ label="Fill"
467
+ value={fill}
468
+ onChange={setFill}
469
+ alpha={alpha}
470
+ onAlphaChange={setAlpha}
471
+ />
472
+ <ColorRow label="Stroke" value={stroke} onChange={setStroke} />
473
+ <NumberRow label="Count" value={count} min={0} max={100} step={1} onChange={setCount} />
474
+ <SelectRow
475
+ label="Line cap"
476
+ value={cap}
477
+ onChange={setCap}
478
+ options={[
479
+ { value: 'butt', label: 'Butt' },
480
+ { value: 'round', label: 'Round' },
481
+ ]}
482
+ />
483
+ <ToggleRow
484
+ label="Align"
485
+ value={side}
486
+ onChange={setSide}
487
+ options={[
488
+ { value: 'left', label: 'L' },
489
+ { value: 'center', label: 'C' },
490
+ { value: 'right', label: 'R' },
491
+ ]}
492
+ />
493
+ <TextRow label="Label" value={label} onChange={setLabel} />
494
+ <CheckboxRow label="Visible" value={visible} onChange={setVisible} />
495
+ <PropertyGroup title="Bevel" defaultCollapsed>
496
+ <SliderRow label="Rings" value={32} min={4} max={96} step={1} onChange={() => {}} />
497
+ </PropertyGroup>
498
+ </PropertyList>
499
+ </PropertyPanel>
500
+ </Cell>
501
+ <Cell label="PrefsForm">
502
+ <PrefsForm
503
+ schema={PREFS}
504
+ values={prefs}
505
+ onChange={(path, value) => setPrefs((prev) => setAt(prev, path, value))}
506
+ />
507
+ </Cell>
508
+ <Cell label="LayerStack">
509
+ <LayerStack
510
+ items={layers}
511
+ onReorder={(ids) =>
512
+ setLayers(ids.flatMap((id) => layers.filter((item) => item.id === id)))
513
+ }
514
+ renderBody={(item) => <div>settings for {item.label}</div>}
515
+ />
516
+ </Cell>
517
+ <Cell label="Sidebar, SidebarPanel">
518
+ <div className="lk-specimen__box">
519
+ <UiSidebar side="right" ariaLabel="Specimen sidebar">
520
+ <SidebarPanel
521
+ title="Selection"
522
+ collapsed={panelCollapsed}
523
+ onToggleCollapse={() => setPanelCollapsed((v) => !v)}
524
+ onHide={() => {}}
525
+ >
526
+ <div className="lk-specimen__pad">2 items selected</div>
527
+ </SidebarPanel>
528
+ </UiSidebar>
529
+ </div>
530
+ </Cell>
531
+ </Section>
532
+ );
533
+ }
534
+
535
+ function OverlaysAndFeedback() {
536
+ const calloutAnchor = useRef<HTMLButtonElement>(null);
537
+ const [calloutOpen, setCalloutOpen] = useState(false);
538
+ const [dialogOpen, setDialogOpen] = useState(false);
539
+ const toasts = useMemo(() => createToastQueue(), []);
540
+ return (
541
+ <Section title="Overlays & feedback">
542
+ <Cell label="Tooltip, Callout, Dialog, Toast">
543
+ <div className="lk-specimen__row">
544
+ <TooltipTrigger>
545
+ <Focusable>
546
+ <button type="button">Hover me</button>
547
+ </Focusable>
548
+ <Tooltip placement="bottom">Duplicates the selected layer</Tooltip>
549
+ </TooltipTrigger>
550
+ <button type="button" ref={calloutAnchor} onClick={() => setCalloutOpen(true)}>
551
+ Callout
552
+ </button>
553
+ <Callout
554
+ triggerRef={calloutAnchor}
555
+ isOpen={calloutOpen}
556
+ onOpenChange={setCalloutOpen}
557
+ tone="info"
558
+ title="Info"
559
+ >
560
+ Neutral guidance.
561
+ </Callout>
562
+ <Button onClick={() => setDialogOpen(true)}>Dialog</Button>
563
+ <Dialog
564
+ isOpen={dialogOpen}
565
+ onOpenChange={setDialogOpen}
566
+ title="Preferences"
567
+ footer={
568
+ <Button variant="primary" onClick={() => setDialogOpen(false)}>
569
+ OK
570
+ </Button>
571
+ }
572
+ >
573
+ <p>Dialog body.</p>
574
+ </Dialog>
575
+ <Button
576
+ onClick={() =>
577
+ toasts.add('success', 'Saved', { description: 'All changes stored.', ttlMs: null })
578
+ }
579
+ >
580
+ Toast
581
+ </Button>
582
+ <ToastRegion queue={toasts} placement="bottom-right" />
583
+ </div>
584
+ </Cell>
585
+ <Cell label="Powerline">
586
+ <Powerline
587
+ variant="solid"
588
+ size="sm"
589
+ segments={[
590
+ { text: 'main', tone: 'accent', endCap: 'chevron' },
591
+ { text: '✓ 12', tone: 'info', endCap: 'slant' },
592
+ { text: '~/proj', tone: 'muted' },
593
+ ]}
594
+ />
595
+ </Cell>
596
+ <Cell label="StatusBar">
597
+ <UiStatusBar ariaLabel="Editor status">
598
+ <StatusBarItem>tool: select</StatusBarItem>
599
+ <StatusBarItem>sel: 3</StatusBarItem>
600
+ <StatusBarSpacer />
601
+ <StatusBarItem muted>0.7.0</StatusBarItem>
602
+ </UiStatusBar>
603
+ </Cell>
604
+ </Section>
605
+ );
606
+ }
607
+
608
+ function ListsAndData() {
609
+ return (
610
+ <Section title="Lists & data">
611
+ <Cell label="ItemList">
612
+ <ItemList
613
+ rows={[
614
+ { id: 'a', label: 'Draw rect' },
615
+ { id: 'b', label: 'Fill', selected: true },
616
+ { id: 'c', label: 'Move', muted: true },
617
+ ]}
618
+ empty="Nothing"
619
+ />
620
+ </Cell>
621
+ <Cell label="DataGrid">
622
+ <DataGrid
623
+ rows={[
624
+ { id: '1', name: 'accent', count: 3 },
625
+ { id: '2', name: 'info', count: 1 },
626
+ { id: '3', name: 'danger', count: 2 },
627
+ ]}
628
+ columns={[
629
+ { id: 'name', header: 'name', accessor: (r) => r.name, sortable: true },
630
+ { id: 'count', header: 'count', accessor: (r) => r.count, sortable: true },
631
+ ]}
632
+ defaultSort={{ columnId: 'name', direction: 'asc' }}
633
+ />
634
+ </Cell>
635
+ </Section>
636
+ );
637
+ }
638
+
639
+ function ToolChrome() {
640
+ const [tool, setTool] = useState('select');
641
+ const [paneWidth, setPaneWidth] = useState(160);
642
+ return (
643
+ <Section title="Tool chrome">
644
+ <Cell label="ToolGroup, ToolButton">
645
+ <ToolGroup orientation="horizontal" ariaLabel="Tools">
646
+ {['select', 'rect', 'ellipse'].map((id) => (
647
+ <ToolButton
648
+ key={id}
649
+ icon={<Dot />}
650
+ label={id}
651
+ shortcut={id.slice(0, 1).toUpperCase()}
652
+ active={tool === id}
653
+ tabbable={tool === id}
654
+ onClick={() => setTool(id)}
655
+ />
656
+ ))}
657
+ </ToolGroup>
658
+ </Cell>
659
+ <Cell label="ToolOptionsBar">
660
+ <ToolOptionsBar label="Text">
661
+ <Button size="sm" variant="ghost" iconOnly ariaLabel="Bold">
662
+ B
663
+ </Button>
664
+ <Button size="sm" variant="ghost" iconOnly ariaLabel="Italic">
665
+ I
666
+ </Button>
667
+ </ToolOptionsBar>
668
+ </Cell>
669
+ <Cell label="ResizeHandle">
670
+ <div className="lk-specimen__box">
671
+ <div className="lk-specimen__box-main">content</div>
672
+ <ResizeHandle
673
+ value={paneWidth}
674
+ min={100}
675
+ max={220}
676
+ invert
677
+ onInput={setPaneWidth}
678
+ ariaLabel="Resize"
679
+ />
680
+ <div
681
+ className="lk-specimen__pane"
682
+ style={{ '--lk-specimen-pane-w': `${paneWidth}px` } as CSSProperties}
683
+ >
684
+ {paneWidth}px
685
+ </div>
686
+ </div>
687
+ </Cell>
688
+ </Section>
689
+ );
690
+ }
691
+
692
+ function EditorsAndPlots() {
693
+ const [curve, setCurve] = useState<ControlPoint[]>([
694
+ { x: 0, y: 0 },
695
+ { x: 0.3, y: 0.1 },
696
+ { x: 0.7, y: 0.9 },
697
+ { x: 1, y: 1 },
698
+ ]);
699
+ const [points, setPoints] = useState<ControlPoint[]>([
700
+ { x: 0.2, y: 0.3 },
701
+ { x: 0.5, y: 0.6 },
702
+ { x: 0.8, y: 0.2 },
703
+ ]);
704
+ const [bands, setBands] = useState<Band<{ name: string }>[]>([
705
+ { from: 1 / 64, data: { name: 'Radial' } },
706
+ { from: 1 / 12, data: { name: 'Name plate' } },
707
+ ]);
708
+ const [band, setBand] = useState<number | null>(null);
709
+ const [tracks, setTracks] = useState<Track[]>(
710
+ () =>
711
+ [
712
+ {
713
+ kind: 'sampled',
714
+ label: 'x',
715
+ keys: [
716
+ { t: 0, value: 0 },
717
+ { t: 800, value: 120, easing: 'easeOutCubic' },
718
+ { t: 1600, value: 40 },
719
+ ],
720
+ onTick: () => {},
721
+ },
722
+ {
723
+ kind: 'event',
724
+ label: 'footstep',
725
+ events: [
726
+ { t: 300, fire: () => {} },
727
+ { t: 900, fire: () => {} },
728
+ ],
729
+ },
730
+ ] as Track[],
731
+ );
732
+ const [playhead, setPlayhead] = useState(0);
733
+ return (
734
+ <Section title="Editors & plots">
735
+ <Cell label="Plot2D">
736
+ <Plot2D
737
+ width={260}
738
+ height={130}
739
+ grid={{ divisions: 3 }}
740
+ axes={{}}
741
+ xRange={[0, 2000]}
742
+ yRange={[-0.35, 1.2]}
743
+ />
744
+ </Cell>
745
+ <Cell label="CurveEditor">
746
+ <CurveEditor
747
+ value={curve}
748
+ onInput={setCurve}
749
+ domain="1d"
750
+ interpolation="catmull-rom"
751
+ endpoints="pinned-both"
752
+ addPointMode="click-curve"
753
+ axes={{}}
754
+ fill={{ side: 'below' }}
755
+ width={260}
756
+ height={130}
757
+ />
758
+ </Cell>
759
+ <Cell label="PointPlotter">
760
+ <PointPlotter
761
+ value={points}
762
+ onInput={setPoints}
763
+ xRange={[0, 1]}
764
+ yRange={[0, 1]}
765
+ grid={{ divisions: 3 }}
766
+ axes={{}}
767
+ addPointMode="click-empty"
768
+ width={260}
769
+ height={130}
770
+ />
771
+ </Cell>
772
+ <Cell label="BandEditor" wide>
773
+ <BandEditor<{ name: string }>
774
+ min={1 / 64}
775
+ max={1 / 2}
776
+ scale="log"
777
+ snap
778
+ value={bands}
779
+ onChange={setBands}
780
+ selectedIndex={band}
781
+ onSelect={setBand}
782
+ renderBand={(b) => b.data.name}
783
+ ticks={[1 / 24, 1 / 12, 1 / 6].map((at) => ({ at, label: `1/${Math.round(1 / at)}` }))}
784
+ />
785
+ </Cell>
786
+ <Cell label="Timeline" wide>
787
+ <div className="lk-specimen__timeline">
788
+ <Timeline
789
+ tracks={tracks}
790
+ duration={2000}
791
+ playhead={playhead}
792
+ onChange={setTracks}
793
+ onScrub={setPlayhead}
794
+ />
795
+ </div>
796
+ </Cell>
797
+ </Section>
798
+ );
799
+ }
800
+
801
+ function LabChrome() {
802
+ const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
803
+ const [zoom, setZoom] = useState(1);
804
+ return (
805
+ <Section title="Lab chrome">
806
+ <Cell label="Toolbar" wide>
807
+ <Toolbar aria-label="Trial actions">
808
+ <Toolbar.Title>My Trial</Toolbar.Title>
809
+ <Toolbar.Group>
810
+ <Toolbar.Button onClick={() => {}}>Undo</Toolbar.Button>
811
+ <Toolbar.Button onClick={() => {}} disabled>
812
+ Redo
813
+ </Toolbar.Button>
814
+ </Toolbar.Group>
815
+ <Toolbar.Group end>
816
+ <Toolbar.Button onClick={() => {}}>Save</Toolbar.Button>
817
+ </Toolbar.Group>
818
+ </Toolbar>
819
+ </Cell>
820
+ <Cell label="StatusBar">
821
+ <StatusBar>
822
+ <StatusBar.Section>Items: 12</StatusBar.Section>
823
+ <StatusBar.Section>Zoom: 100%</StatusBar.Section>
824
+ </StatusBar>
825
+ </Cell>
826
+ <Cell label="Sidebar">
827
+ <div className="lk-specimen__box">
828
+ <div className="lk-specimen__box-main">main</div>
829
+ <Sidebar
830
+ title="Controls"
831
+ collapsed={sidebarCollapsed}
832
+ onToggle={() => setSidebarCollapsed((v) => !v)}
833
+ >
834
+ <p>One slider</p>
835
+ </Sidebar>
836
+ </div>
837
+ </Cell>
838
+ <Cell label="Legend, JobProgress">
839
+ <Legend entries={LEGEND} />
840
+ <JobProgress job={JOB} />
841
+ </Cell>
842
+ <Cell label="ScaleIndicator, ZoomControl">
843
+ <ScaleIndicator zoom={zoom} unit="ft" pixelsPerUnit={50} />
844
+ <ZoomControl zoom={zoom} onZoomChange={setZoom} min={0.1} max={8} />
845
+ </Cell>
846
+ <Cell label="FloatingPanel">
847
+ <div className="lk-specimen__floating-host">
848
+ <FloatingPanel anchor="top-right">
849
+ <Legend entries={LEGEND.slice(0, 2)} />
850
+ </FloatingPanel>
851
+ </div>
852
+ </Cell>
853
+ <Cell label="Split" wide>
854
+ <div className="lk-specimen__box">
855
+ <Split
856
+ sidebar={<div className="lk-specimen__pad">sidebar</div>}
857
+ defaultWidth={160}
858
+ minWidth={100}
859
+ maxWidth={260}
860
+ >
861
+ <div className="lk-specimen__pad">content</div>
862
+ </Split>
863
+ </div>
864
+ </Cell>
865
+ </Section>
866
+ );
867
+ }
868
+
869
+ /**
870
+ * One page of the kit's controls and chrome, each in a small working state: a
871
+ * preview surface for a theme, where every token has something on screen that
872
+ * reads it. Popovers, dialogs and toasts portal into the page itself, so they
873
+ * take whatever theme the page is under.
874
+ */
875
+ export function Specimen({ className }: { className?: string }) {
876
+ return (
877
+ <div className={className ? `lk-specimen ${className}` : 'lk-specimen'} data-wzl-portal-host="">
878
+ <ButtonsAndToggles />
879
+ <FieldsAndPickers />
880
+ <PanelsAndRows />
881
+ <OverlaysAndFeedback />
882
+ <ListsAndData />
883
+ <ToolChrome />
884
+ <EditorsAndPlots />
885
+ <LabChrome />
886
+ </div>
887
+ );
888
+ }