@graphty/compact-mantine 0.8.19 → 0.9.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.
package/README.md CHANGED
@@ -6,17 +6,21 @@
6
6
 
7
7
  Compact components and a compact theme for [Mantine 8](https://mantine.dev), for
8
8
  interfaces where screen space is the scarce resource: property panels,
9
- inspectors, sidebars, editors and dashboards.
9
+ inspectors, sidebars, editors and dashboards. The look is measured from the
10
+ Figma editor's own interface, in its light and its dark theme: the same 24px
11
+ controls, the same 11px Inter, the same colors, radii, shadows and timing.
10
12
 
11
13
  It gives you two things, and you can take either one on its own:
12
14
 
13
- 1. **A theme.** Drop `compactTheme` into your `MantineProvider` and 41 Mantine
14
- components render at a dense size -- 24px controls, 11px text, tighter
15
- spacing -- with no `size` prop anywhere in your code.
16
- 2. **A component library.** About thirty components built for a narrow column:
17
- rows that put a value and its label on one 32px line, charts that fit in the
18
- height of a line of text, a virtualized data table, and a floating-panel
19
- system for the settings that do not fit.
15
+ 1. **A theme.** Drop `compactTheme` into your `MantineProvider` and the Mantine
16
+ components render dense and Figma-accurate -- 24px controls, 11px text,
17
+ tighter spacing -- with no `size` prop anywhere in your code. The theme ships
18
+ its own typeface (Inter) and stylesheet, so there is nothing else to set up.
19
+ 2. **A component library.** Components built for a narrow column and an editor
20
+ around it: rows that put a value and its label on one 32px line, charts that
21
+ fit in the height of a line of text, a virtualized data table, a
22
+ floating-panel system, and the editor shell -- a floating toolbar, a
23
+ navigation rail, a quick actions palette and a keyboard shortcuts sheet.
20
24
 
21
25
  Everything is typed, translatable, keyboard-operable, screen-reader-tested,
22
26
  works in light and dark schemes, and works in right-to-left languages.
@@ -29,11 +33,18 @@ works in light and dark schemes, and works in right-to-left languages.
29
33
  - [Quick start](#quick-start)
30
34
  - [The compact theme](#the-compact-theme)
31
35
  - [The components](#the-components)
32
- - [Building a panel](#building-a-panel)
33
- - [Editing a value](#editing-a-value)
34
- - [Showing data](#showing-data)
36
+ - [Panels and rows](#panels-and-rows)
37
+ - [Inputs](#inputs)
38
+ - [Selection](#selection)
39
+ - [Color](#color)
40
+ - [Actions](#actions)
35
41
  - [Floating panels](#floating-panels)
42
+ - [Other overlays](#other-overlays)
43
+ - [Lists and trees](#lists-and-trees)
44
+ - [Data display](#data-display)
45
+ - [The editor shell](#the-editor-shell)
36
46
  - [Glyphs](#glyphs)
47
+ - [Themed Mantine components](#themed-mantine-components)
37
48
  - [A worked example](#a-worked-example)
38
49
  - [The panel grid](#the-panel-grid)
39
50
  - [Handling events](#handling-events)
@@ -43,6 +54,8 @@ works in light and dark schemes, and works in right-to-left languages.
43
54
  - [Accessibility](#accessibility)
44
55
  - [TypeScript](#typescript)
45
56
  - [Which one should I use?](#which-one-should-i-use)
57
+ - [Use these instead of Mantine's](#use-these-instead-of-mantines)
58
+ - [Breaking changes in the Figma release](#breaking-changes-in-the-figma-release)
46
59
  - [Contributing](#contributing)
47
60
  - [License](#license)
48
61
 
@@ -76,8 +89,8 @@ export function App() {
76
89
  <TextInput label="Project name" />
77
90
  <Button>Save</Button>
78
91
 
79
- {/* This package's own components, for a 280px panel. */}
80
- <div style={{ width: 280 }}>
92
+ {/* This package's own components, for a 240px panel. */}
93
+ <div style={{ width: 240 }}>
81
94
  <ControlSection label="Node size">
82
95
  <FieldRow>
83
96
  <PanelField label="Smallest" glyph="sizeSmallest" defaultValue="1.0" />
@@ -91,56 +104,134 @@ export function App() {
91
104
  }
92
105
  ```
93
106
 
94
- That is the whole setup. Two further providers are optional and are introduced
95
- where they matter: [`PopoutManager`](#floating-panels) if you use pop-outs, and
96
- [`LabelsProvider`](#internationalization) if you translate the library's
107
+ That is the whole setup: the theme puts its stylesheet (the Inter typeface, the
108
+ color tokens and the component rules) on the page the first time a themed
109
+ component renders. Three further pieces are optional and are introduced where
110
+ they matter: [`Tooltip.Group`](#tooltips-wrap-your-app-once) around your app for
111
+ Figma's tooltip timing, [`PopoutManager`](#floating-panels) if you use pop-outs,
112
+ and [`LabelsProvider`](#internationalization) if you translate the library's
97
113
  strings.
98
114
 
99
115
  ## The compact theme
100
116
 
101
- `compactTheme` is a complete Mantine theme. It changes three global tokens and
102
- sets default props and styles on 41 Mantine components, so that a component you
103
- already know renders small without being told to.
104
-
105
- | Token | Values |
106
- | ----------- | ------------------------------------------- |
107
- | `fontSizes` | xs 10px, sm 11px, md 13px, lg 14px, xl 16px |
108
- | `spacing` | xs 4px, sm 6px, md 8px, lg 12px, xl 16px |
109
- | `radius` | xs 2px, sm 4px, md 6px, lg 8px, xl 12px |
110
-
111
- Inputs default to a 24px height and an 11px face, and are drawn without a
112
- border at rest so that a column of them reads as a list of values rather than a
113
- grid of boxes. Keyboard focus still paints a visible ring; a mouse click does
114
- not, so the surface stays quiet under the pointer.
115
-
116
- The accent -- a filled `Button`, a checked box -- passes WCAG AA in both
117
- schemes: the theme sets `primaryShade: {light: 8, dark: 5}` and
118
- `autoContrast: true`, so the fill stands at least 3:1 off the panel and the text
119
- on it at least 4.5:1 (white in the light scheme, black in the dark one). Set
120
- your own `primaryColor` or `primaryShade` and you get yours instead. The one
121
- exception is a checked `Switch`: Mantine always paints its thumb white, so its
122
- track stays the darker light-scheme shade in both schemes.
117
+ `compactTheme` is a complete Mantine theme, already merged with Mantine's
118
+ default, that draws every component the way the Figma editor draws its own.
119
+ Light and dark are Mantine's own color scheme (`defaultColorScheme`,
120
+ `forceColorScheme`, `useMantineColorScheme`); one theme serves both, and the dark
121
+ scheme uses Figma's neutral grays (#2c2c2c panels, #383838 fields).
122
+
123
+ | Token | Values |
124
+ | --------------------------- | ------------------------------------------------------- |
125
+ | `fontSizes` / `lineHeights` | xs 9/14, sm 11/16, md 13/22, lg 15/25, xl 24/32 |
126
+ | `spacing` | xs 4px, sm 8px, md 8px, lg 12px, xl 16px |
127
+ | `radius` | xs 2px, sm 5px, md 5px, lg 13px, xl 13px |
128
+ | `shadows` | xs to xl map onto Figma's five elevations |
129
+ | `primaryColor` | `brand`: #0d99ff for filled surfaces, #007be5 for links |
130
+
131
+ Every color is a CSS custom property named `--cm-*` (for example `--cm-bg`,
132
+ `--cm-text-secondary`, `--cm-bg-brand`), written with the CSS `light-dark()`
133
+ function, so it resolves from the color scheme of the element that uses it. A
134
+ subtree that has to render dark inside a light app -- a menu, a tooltip, the
135
+ shortcuts sheet -- sets `color-scheme: dark` and every token inside it follows.
136
+ Read the tokens in your own CSS; `PANEL_INK` (see [the panel grid](#the-panel-grid))
137
+ names the ones a panel row uses.
138
+
139
+ Controls are 24px tall with an 11px face. Fields are drawn filled and without a
140
+ border at rest, outlined on hover and ringed on focus. The focus ring is Figma's:
141
+ 1px, keyboard focus only (number and text fields ring on any focus), inside a
142
+ field and outside a button. Menus, list boxes and tooltips are dark in both
143
+ schemes. Overlays open and close in one frame, with no fade.
144
+
145
+ ### The bundled typeface
146
+
147
+ The theme sets Inter (the variable font, latin subset) and ships it inside the
148
+ package, inlined into the stylesheet, so Figma's 450, 550 and 600 weights render
149
+ exactly with no font setup and no network request. Inter is licensed under the
150
+ SIL Open Font License 1.1; the license text is in `dist/fonts/LICENSE-Inter.txt`.
151
+ To use another face, set `fontFamily` in a theme merged over this one.
152
+
153
+ ### `createCompactTheme` and the WCAG AA option
154
+
155
+ `compactTheme` is exact Figma. Figma's secondary text, field boundaries and
156
+ checkbox edges fall short of WCAG 2.2 AA contrast, so the theme has an option
157
+ that raises them:
123
158
 
124
- ### Components the theme restyles
159
+ ```tsx
160
+ import { createCompactTheme } from "@graphty/compact-mantine";
161
+
162
+ <MantineProvider theme={createCompactTheme({ highContrast: true })}>{children}</MantineProvider>;
163
+ ```
164
+
165
+ `highContrast` changes color tokens only -- nothing moves. It darkens secondary
166
+ text and icons (50% to 55% black, 4.7:1), raises checkbox, switch and field
167
+ boundaries to 3:1 (fields gain a 1px inside edge), gives the selected segment of
168
+ a segmented control a 3:1 edge, and darkens placeholder text, the focus ring,
169
+ links and the brand, danger and success fills to the next darker color in
170
+ Figma's own palette. Dividers stay as Figma draws them. `createCompactTheme()`
171
+ with no options is `compactThemeOverride`, and the options it was built with are
172
+ published on `theme.other.compact`.
173
+
174
+ ### Tooltips: wrap your app once
175
+
176
+ Tooltips follow Figma's timing: the first one opens after 1000ms, and while one
177
+ is showing, or for 300ms after it closes, the next one opens at once. That
178
+ hand-off needs Mantine's `Tooltip.Group` around everything that has tooltips,
179
+ and a theme cannot add a provider, so wrap your app once:
180
+
181
+ ```tsx
182
+ import { MantineProvider, Tooltip } from "@mantine/core";
183
+
184
+ <MantineProvider theme={compactTheme}>
185
+ <Tooltip.Group>
186
+ <App />
187
+ </Tooltip.Group>
188
+ </MantineProvider>;
189
+ ```
190
+
191
+ The theme gives `Tooltip.Group` the right delays, so it needs no props. Do not
192
+ nest a second group inside: tooltips inside it would stop sharing the warmth.
193
+ No component in this package nests one, so the warm hand-off crosses the whole
194
+ shell: from a toolbar tooltip to a rail button, to the help button, and back.
195
+ The price is that inside a group Mantine ignores a tooltip's own `openDelay`
196
+ and `closeDelay`. Figma opens rail tooltips after 500ms and shows and hides the
197
+ help button's at once; `RailButton` and `HelpButton` keep those timings when
198
+ there is no group, and take the group's 1000ms / 300ms when cold inside one.
199
+ Tooltips keep their `aria-describedby` link, so screen readers still hear them.
200
+
201
+ ### Stylesheet, SSR and shadow roots
202
+
203
+ The stylesheet is injected into `document.head` when the theme is created
204
+ (and again, if something removed it, when a themed component renders). For server rendering, put `compactGlobalCss()` (or
205
+ `compactGlobalCss({ highContrast: true })`) in a `<style>` in your document head.
206
+ For a shadow root, call `ensureCompactStyles()` or append `compactGlobalCss()` to
207
+ the root yourself. Mantine's own `@mantine/core/styles.css` is still required,
208
+ imported before this package's stylesheet as in the quick start.
125
209
 
126
- Pass no `size` prop and these render compact. Pass a size token and they step
127
- along a compact scale: an input field is 20, 24, 30, 36 and 44px tall at `xs`,
128
- `sm` (the default), `md`, `lg` and `xl`.
210
+ ### Components the theme restyles
129
211
 
130
- | Group | Components |
131
- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
132
- | Inputs (14) | TextInput, NumberInput, Select, NativeSelect, Textarea, PasswordInput, Autocomplete, MultiSelect, TagsInput, PillsInput, FileInput, JsonInput, ColorInput, InputClearButton |
133
- | Buttons (3) | Button, ActionIcon, CloseButton |
134
- | Controls (6) | Switch, Checkbox, Radio, Slider, RangeSlider, SegmentedControl |
135
- | Display (7) | Badge, Text, Avatar, ThemeIcon, Indicator, Kbd, Pill |
136
- | Navigation (6) | Tabs, NavLink, Pagination, Stepper, Anchor, Burger |
137
- | Feedback (3) | Loader, Progress, RingProgress |
138
- | Overlays (4) | Menu, Tooltip, Popover, HoverCard |
212
+ Pass no `size` prop and these render compact. Pass `size="md"` or `size="lg"`
213
+ and you get larger sizes back.
214
+
215
+ | Group | Components |
216
+ | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
217
+ | Inputs | TextInput, NumberInput, Select, NativeSelect, Textarea, PasswordInput, Autocomplete, MultiSelect, TagsInput, PillsInput, FileInput, JsonInput, InputClearButton |
218
+ | Buttons | Button, ActionIcon, CloseButton |
219
+ | Controls | Switch, Checkbox, Radio, Slider, RangeSlider, SegmentedControl |
220
+ | Color | ColorSwatch, HueSlider, AlphaSlider |
221
+ | Display | Badge, Text, Avatar, Avatar.Group, ThemeIcon, Indicator, Kbd, Pill |
222
+ | Navigation | Tabs, NavLink, Pagination, Stepper, Anchor, Burger |
223
+ | Feedback | Loader, Progress, RingProgress, Notification |
224
+ | Overlays | Menu, Tooltip, Tooltip.Group, Popover, HoverCard, Modal, ScrollArea |
225
+
226
+ A few defaults are worth knowing: `Badge` defaults to the outlined look
227
+ (`variant="filled"` and `"light"` are the brand looks); `Kbd` is Figma's dark key
228
+ cap in both schemes (`size="md"` for the large cap, `variant="inline"` for a
229
+ light cap in running text, `mod={{ active: true }}` to light it); `Select`
230
+ defaults to the outlined trigger; `Tabs` defaults to pills.
139
231
 
140
232
  ### Making it your own
141
233
 
142
- `compactTheme` is a full theme, already merged with Mantine's default. Merge
143
- your own on top of it:
234
+ Merge your own theme on top of the compact one:
144
235
 
145
236
  ```tsx
146
237
  import { createTheme, MantineProvider, mergeMantineTheme } from "@mantine/core";
@@ -150,7 +241,6 @@ const theme = mergeMantineTheme(
150
241
  compactTheme,
151
242
  createTheme({
152
243
  primaryColor: "teal",
153
- fontFamily: "Inter, sans-serif",
154
244
  }),
155
245
  );
156
246
 
@@ -158,8 +248,8 @@ const theme = mergeMantineTheme(
158
248
  ```
159
249
 
160
250
  If you already build your theme from several overrides, take
161
- `compactThemeOverride` instead. It is the raw `createTheme()` result, suitable
162
- for `mergeThemeOverrides()`:
251
+ `compactThemeOverride` (or `createCompactTheme(options)`) instead. It is the raw
252
+ `createTheme()` result, suitable for `mergeThemeOverrides()`:
163
253
 
164
254
  ```tsx
165
255
  import { createTheme, mergeThemeOverrides } from "@mantine/core";
@@ -168,6 +258,11 @@ import { compactThemeOverride } from "@graphty/compact-mantine";
168
258
  const override = mergeThemeOverrides(compactThemeOverride, createTheme({ primaryColor: "teal" }));
169
259
  ```
170
260
 
261
+ Merge component extensions with care: `mergeThemeOverrides` replaces a
262
+ component's `vars` or `styles` function rather than composing it, so an override
263
+ of a component this theme already extends discards the compact treatment. Adjust
264
+ such a component through its props, `classNames` or the `--cm-*` tokens instead.
265
+
171
266
  ### A compact region inside a normal-sized app
172
267
 
173
268
  `MantineProvider` nests, so you can keep your application at its usual size and
@@ -178,76 +273,105 @@ make one region dense -- which is the common case for a sidebar or an inspector.
178
273
  <TextInput label="Normal size" />
179
274
 
180
275
  <MantineProvider theme={compactTheme}>
181
- <aside style={{ width: 280 }}>
276
+ <aside style={{ width: 240 }}>
182
277
  <TextInput label="Compact" />
183
278
  </aside>
184
279
  </MantineProvider>
185
280
  </MantineProvider>
186
281
  ```
187
282
 
188
- Also exported: `compactColors` (the palette this theme adds) and
189
- `compactDarkColors` (its dark scale), if you want to reuse the colours
190
- elsewhere.
283
+ Also exported: `compactColors` (the palettes this theme adds), `compactDarkColors`
284
+ (its neutral dark scale) and `compactBrandColors` (the brand scale), if you want
285
+ to reuse the colors elsewhere.
191
286
 
192
287
  ## The components
193
288
 
194
- Grouped by what you are trying to do. Every component has a page in
195
- [Storybook](https://graphty.app/storybook/compact-mantine/) with runnable
196
- examples, and every prop is documented in your editor.
197
-
198
- ### Building a panel
199
-
200
- | Component | Reach for it when |
201
- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
202
- | `ControlSection` | a run of controls needs a name, a rule above it and a chevron that folds it away. The workhorse container for a property panel. It can show a dot when something inside it is non-default, an empty state with a single "+", an explanation bubble, and buttons of its own in the header. |
203
- | `ControlGroup` | the same, but it must never fold, or its rule has to bleed out to the edges of a padded container such as a pop-out. |
204
- | `ControlSubGroup` | a handful of rarely-opened settings belong under a section you already have. Quieter than a section: no rule, a smaller chevron. |
205
- | `FieldRow` | one or two fields share a line. It owns the widths and the gaps, so a column of rows lines up. |
206
- | `TrailingSlot` | you are laying out a row by hand and need the fixed 24px slot every row ends with, so that rows with a trailing control end level with rows without one. |
207
- | `AdvancedButton` | a row or a section has settings most people never change. The gear opens them in a pop-out, and marks itself when something behind it is no longer default. |
208
-
209
- ### Editing a value
210
-
211
- | Component | Reach for it when |
212
- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
213
- | `PanelField` | a value is typed, picked from a list or dragged. A real text box, number box or select, 24px tall, whose caption is a 16px drawing inside the box instead of a word above it. |
214
- | `CompoundRow` | two or three values are one thing seen several ways -- a colour and its opacity, a width and its unit -- and must read as one control. |
215
- | `IconGroupRow` | two to six mutually exclusive options whose difference can be drawn: node shapes, edge routing, scale curves. A `SegmentedControl` underneath, so arrow keys work. |
216
- | `ToggleRow` | a setting is a plain yes or no that no picture could stand for. |
217
- | `ToggleRowGroup` | you have two or more of those. It packs them at a 24px pitch and warns you in development if you give it only one. |
218
- | `RampRow` | a range is better drawn than described: a size wedge or a colour ramp with its two endpoints. |
219
- | `CompactColorInput` | a colour and its opacity, on one 24px line, with a picker in a pop-out. Needs a [`PopoutManager`](#floating-panels). |
220
- | `GradientEditor` | a multi-stop linear gradient: colours, positions, angle. Needs a `PopoutManager`. |
221
- | `StyleNumberInput` | a number that has a sensible default, and you want the panel to show at a glance whether the reader has overridden it. `undefined` means "not set" and shows the default in italics with no reset button. |
222
- | `StyleSelect` | the same idea for a dropdown. |
223
- | `ToggleWithContent` | a feature is a yes or no that brings its own settings with it. Turning it off takes its settings off the screen. |
224
-
225
- ### Showing data
226
-
227
- | Component | Reach for it when |
228
- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
229
- | `DataRow` | the string is the reader's own -- an id, a node label, a filename -- with a number beside it. The one row here that keeps a text label, because data cannot be drawn. Selectable, double-clickable, and it can carry a trailing control. |
230
- | `DataRowHeader` | a run of data rows needs a caption, so the rows below can drop the unit word they would otherwise repeat. Give it `onSortChange` and it becomes a sort control. |
231
- | `RankChip` | a rank belongs beside a row: `#6`, rather than a sentence saying "rank 6 of 318". |
232
- | `MetricRow` | one reading has a percentile and a rank. Draws the name, a bar filled to the percentile, the number, and the chip. |
233
- | `HistogramRow` | a distribution would otherwise be spelled as the four numbers that summarise it. 64px tall. |
234
- | `SparklineRow` | a series is going somewhere and you want to see which way. 32px tall. |
235
- | `ProseBlock` | the panel has to say something in words: a plain-language reading, a caveat about how a result falls short, or a record of the last run. |
236
- | `ActionRow` | a row reports a state and offers verbs. The state is always visible; the verbs appear on hover, on focus, and always on a touch screen. |
237
- | `DataTable` | you have columns rather than rows: thousands of them, sortable, searchable, selectable, with only the visible rows in the document. |
289
+ Every component has one page in
290
+ [Storybook](https://graphty.app/storybook/compact-mantine/): what it is for and when to use it
291
+ instead of its nearest alternative, an example whose props you can edit, every state in light
292
+ and dark side by side, the keyboard, and the key measurements. Every prop is also documented in
293
+ your editor. The groups below match the Storybook sidebar, and each group has an overview page.
294
+
295
+ ### Panels and rows
296
+
297
+ The containers a property panel is built from, and the row layout every other component agrees
298
+ with. [Overview](https://graphty.app/storybook/compact-mantine/?path=/docs/components-panels-and-rows-overview--docs)
299
+
300
+ | Component | Reach for it when |
301
+ | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
302
+ | [`ControlSection`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-panels-and-rows-controlsection--docs) | a run of controls needs a name, a rule and a chevron that folds it away. The workhorse container for a property panel. It can show a dot when something inside it is non-default, an empty state with a single "+", an explanation bubble, and buttons of its own in the header. |
303
+ | [`ControlGroup`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-panels-and-rows-controlgroup--docs) | the same, but it must never fold, or it sits in a padded container such as a pop-out. |
304
+ | [`ControlSubGroup`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-panels-and-rows-controlsubgroup--docs) | a handful of rarely-opened settings belong under a section you already have. Quieter than a section. |
305
+ | [`FieldRow`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-panels-and-rows-fieldrow--docs) | one or two fields share a line. It owns the widths and the gaps, so a column of rows lines up. |
306
+ | [`CompoundRow`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-panels-and-rows-compoundrow--docs) | two or three values are one thing seen several ways -- a color and its opacity, a width and its unit -- and must read as one control. |
307
+ | [`TrailingSlot`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-panels-and-rows-trailingslot--docs) | you are laying out a row by hand and need the fixed 24px slot every row ends with. |
308
+ | [`ResizeHandle`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-panels-and-rows-resizehandle--docs) | a panel's edge can be dragged: pointer, arrow keys, or a double-click back to the default size. |
309
+
310
+ ### Inputs
311
+
312
+ Values the reader types or picks. [Overview](https://graphty.app/storybook/compact-mantine/?path=/docs/components-inputs-overview--docs)
313
+
314
+ | Component | Reach for it when |
315
+ | ------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
316
+ | [`PanelField`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-inputs-panelfield--docs) | a value is typed, picked from a list or dragged in a panel row. A 24px text box, number box or select whose caption is a 16px drawing inside the box instead of a word above it. |
317
+ | [`Select` and `StyleSelect`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-inputs-select--docs) | one of a fixed list. Mantine's `Select` is themed as Figma's outlined trigger with a dark list; `StyleSelect` adds a reset, for a choice where `undefined` means "inherit the default". |
318
+ | [`StyleNumberInput`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-inputs-stylenumberinput--docs) | a number with a sensible default, where the panel should show at a glance whether the reader has overridden it. |
319
+ | [`SearchInput`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-inputs-searchinput--docs) | a filter or find box: a leading magnifier, a clear button and Escape to clear. |
320
+ | [`ComboInput`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-inputs-comboinput--docs) | a value you can type, with a chevron that opens a list of presets over the field (font size, gap, export scale). |
321
+ | [`VariablePill`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-inputs-variablepill--docs) | a value bound to a named variable, drawn as a pill inside a field, with a Detach button. |
322
+
323
+ ### Selection
324
+
325
+ Yes or no, and one of a few. [Overview](https://graphty.app/storybook/compact-mantine/?path=/docs/components-selection-overview--docs)
326
+
327
+ | Component | Reach for it when |
328
+ | ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
329
+ | [`ToggleRow`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-selection-togglerow--docs) | a setting is a plain yes or no that no picture could stand for. |
330
+ | [`ToggleRowGroup`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-selection-togglerowgroup--docs) | you have two or more of those. It packs them and warns in development if you give it only one. |
331
+ | [`ToggleWithContent`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-selection-togglewithcontent--docs) | a yes or no brings its own settings with it; turning it off takes them off the screen. |
332
+ | [`AlignmentMatrix`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-selection-alignmentmatrix--docs) | the 3 x 3 alignment grid: nine radios with two-dimensional arrow keys. |
333
+
334
+ Two to six options whose difference can be drawn are a themed Mantine `SegmentedControl` with a
335
+ glyph and a visually hidden word in each segment; its
336
+ [Storybook page](https://graphty.app/storybook/compact-mantine/?path=/docs/themed-mantine-selection-segmentedcontrol--docs) shows the recipe in a panel
337
+ row. (`IconGroupRow` did this until 0.9.)
338
+
339
+ ### Color
340
+
341
+ [Overview](https://graphty.app/storybook/compact-mantine/?path=/docs/components-color-overview--docs)
342
+
343
+ | Component | Reach for it when |
344
+ | ------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
345
+ | [`CompactColorInput`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-color-compactcolorinput--docs) | a color and its opacity, on one 24px line, with Figma's picker in a pop-out. Needs a [`PopoutManager`](#floating-panels). |
346
+ | [`ColorPickerPanel`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-color-colorpickerpanel--docs) | you want Figma's picker itself on a surface of your own: paint type, saturation field, hue and alpha, eyedropper, swatches. |
347
+ | [`GradientEditor`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-color-gradienteditor--docs) | a multi-stop linear gradient: a bar with stop handles, one picker for the selected stop, positions and angle. Alone, or in `ColorPickerPanel`'s `gradient` slot. |
348
+ | [`RampRow`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-color-ramprow--docs) | a range is better drawn than described: a size wedge or a color ramp with its two endpoints. |
349
+
350
+ ### Actions
351
+
352
+ [Overview](https://graphty.app/storybook/compact-mantine/?path=/docs/components-actions-overview--docs)
353
+
354
+ | Component | Reach for it when |
355
+ | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
356
+ | [`ToggleIconButton`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-actions-toggleiconbutton--docs) | an icon button stays pressed (`aria-pressed`): a lock, a visibility eye. Drag across a column of them to set them all. |
357
+ | [`SplitButton`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-actions-splitbutton--docs) | a main action and a chevron that opens a menu of related choices. |
358
+ | [`AdvancedButton`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-actions-advancedbutton--docs) | a row or a section has settings most people never change. It marks itself when something behind it is no longer default. |
359
+
360
+ Variants the theme adds to Mantine buttons (pass them as `variant`): `Button` `danger`,
361
+ `danger-outline`, `inverse`, `success`; `ActionIcon` `joined` (inside a themed
362
+ `ActionIcon.Group`).
238
363
 
239
364
  ### Floating panels
240
365
 
241
- A pop-out is a panel that opens beside a control, can be dragged, and can
242
- contain pop-outs of its own.
366
+ A pop-out is a panel that opens beside a control, can be dragged, and can contain pop-outs of its
367
+ own. [Overview](https://graphty.app/storybook/compact-mantine/?path=/docs/components-overlays-overview--docs)
243
368
 
244
- **Put one `PopoutManager` high in your tree, above anything that opens a
245
- pop-out.** It owns the shared floating layer: the stacking order, the portal the
246
- panels render into, and the dismissal rules they all obey. Without it, anything
247
- that opens a pop-out throws `usePopoutManagerContext must be used within a
248
- PopoutManager` on first render. `CompactColorInput` and `GradientEditor` use a
249
- pop-out internally, so they need one too. (`InfoCircle` is the exception: it
250
- supplies its own if there is none.)
369
+ **Put one `PopoutManager` high in your tree, above anything that opens a pop-out.** It owns the
370
+ shared floating layer: the stacking order, the portal the panels render into, and the dismissal
371
+ rules they all obey. Without it, anything that opens a pop-out throws
372
+ `usePopoutManagerContext must be used within a PopoutManager` on first render.
373
+ `CompactColorInput` uses a pop-out internally, so it needs one too. (`InfoCircle` is the
374
+ exception: it supplies its own if there is none.)
251
375
 
252
376
  ```tsx
253
377
  import { Popout, PopoutButton, PopoutManager, UiGlyph } from "@graphty/compact-mantine";
@@ -258,7 +382,7 @@ import { Popout, PopoutButton, PopoutManager, UiGlyph } from "@graphty/compact-m
258
382
  <Popout.Trigger>
259
383
  <PopoutButton icon={<UiGlyph name="gear" />} aria-label="Display settings" />
260
384
  </Popout.Trigger>
261
- <Popout.Panel width={280} header={{ variant: "title", title: "Display settings" }}>
385
+ <Popout.Panel width={240} header={{ variant: "title", title: "Display settings" }}>
262
386
  <Popout.Content>{/* anything */}</Popout.Content>
263
387
  </Popout.Panel>
264
388
  </Popout>
@@ -266,63 +390,145 @@ import { Popout, PopoutButton, PopoutManager, UiGlyph } from "@graphty/compact-m
266
390
  </PopoutManager>;
267
391
  ```
268
392
 
269
- | Component | What it is |
270
- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------- |
271
- | `PopoutManager` | The shared floating layer. Required, once, near the root. |
272
- | `Popout` | One pop-out: its trigger and its panel. Also namespaces `Popout.Trigger`, `Popout.Panel`, `Popout.Content` and `Popout.Anchor`. |
273
- | `Popout.Trigger` | Wraps the single element that opens the panel. Give it a real button. |
274
- | `Popout.Panel` | The panel itself: a width, an optional header or tab strip, and its content. |
275
- | `Popout.Anchor` | Wraps a sidebar so every panel opened inside it lines up with that sidebar's edge instead of with its own button. |
276
- | `PopoutButton` | An icon button that stays lit while its panel is open. |
277
- | `InfoCircle` | A circled "i" that reveals an explanation on hover, focus or tap. |
278
-
279
- The rules a `PopoutManager` enforces, so you do not have to: Escape closes the
280
- innermost panel; a click outside closes everything; opening a panel closes its
281
- siblings; closing a panel closes everything opened from it; focus returns to the
282
- trigger.
283
-
284
- Where a panel opens is set by two independent props on `Popout.Panel`:
285
- `anchorX` decides which edge it lines up with (`"panel"`, `"trigger"`,
286
- `"parent"`, or a ref of your own) and `anchorY` how far down it opens. The
287
- defaults are the arrangement most sidebars want: flush with the panel's edge,
288
- level with the row that opened it.
393
+ | Component | What it is |
394
+ | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
395
+ | `PopoutManager` | The shared floating layer. Required, once, near the root. |
396
+ | [`Popout`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-overlays-popout--docs) | One pop-out: `Popout.Trigger` wraps the element that opens it, `Popout.Panel` is the panel (a width, an optional header or tab strip, its content), and `Popout.Anchor` wraps a sidebar so every panel opened inside it lines up with that sidebar's edge. |
397
+ | [`PopoutButton`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-overlays-popoutbutton--docs) | An icon button that stays lit while its panel is open. |
398
+ | [`InfoCircle`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-overlays-infocircle--docs) | A circled "i" that reveals an explanation on hover, focus or tap. |
399
+ | `usePopoutManager` | A hook returning `closeAll()` and `hasOpenPopouts()`, for closing every pop-out from a route a click-outside does not cover: a keyboard shortcut, a command palette row, a full-page sheet. |
400
+ | `PopoutRegion`, `usePopoutRegion` | `PopoutRegion id="..."` names a part of your shell (a panel, an inspector); `usePopoutRegion()` reads that name from anywhere inside it, including a pop-out opened there. It does not change which pop-outs may be open together. |
401
+
402
+ The rules a `PopoutManager` enforces, so you do not have to: Escape closes the innermost panel; a
403
+ click outside closes everything; only one root pop-out is open at a time; closing a panel closes
404
+ everything opened from it; focus returns to the trigger. Where a panel opens is set by two
405
+ props on `Popout.Panel`: `anchorX` decides which edge it lines up with (`"panel"`, `"trigger"`,
406
+ `"parent"`, or a ref of your own) and `anchorY` how far down it opens.
407
+
408
+ ### Other overlays
409
+
410
+ | Component | What it is |
411
+ | --------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
412
+ | [`ContextMenu`, `MenuCheckItem`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-overlays-menu--docs) | A dark menu opened at the pointer by a right-click, or from the keyboard with Shift+F10 or the ContextMenu key; and a checkable row for any Mantine `Menu`. |
413
+ | [`TooltipShortcut`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-overlays-tooltip--docs) | A tooltip label with its keyboard shortcut after it. |
414
+ | [`ModalFooter`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-overlays-modal--docs) | The footer row of a Mantine `Modal`: its action buttons, end-aligned. |
415
+ | [`Toast`, `ToastProvider`, `useToast`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-overlays-toast--docs) | Figma's dark toast: put one `ToastProvider` near the root and call `useToast()` to show one. |
416
+
417
+ ### Lists and trees
418
+
419
+ Objects the reader selects, renames or moves. [Overview](https://graphty.app/storybook/compact-mantine/?path=/docs/components-lists-and-trees-overview--docs)
420
+
421
+ | Component | Reach for it when |
422
+ | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
423
+ | [`Tree`, `TreeItem`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-lists-and-trees-tree--docs) | a list of objects, nested or flat: a real ARIA tree with arrow keys, multiple selection, F2 to rename, drag to reorder, and Alt+ArrowUp / Alt+ArrowDown to move an item when you pass `onMove`. With no `children` on any item it is a flat reorderable list. The tree never edits your data: `moveTreeItem(items, move)` and `renameTreeItem(items, id, name)` apply what `onMove` and `onRename` report and return a new list. |
424
+ | [`PageList`, `PageRow`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-lists-and-trees-pagelist--docs) | flat pages with dividers, above a layer tree. |
425
+ | [`InlineRename`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-lists-and-trees-inlinerename--docs) | the in-place rename field a tree or page row turns into, for a row of your own. |
426
+ | [`ResultRow`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-lists-and-trees-resultrow--docs) | one row of find results. |
427
+
428
+ ### Data display
429
+
430
+ Readings: things the reader looks at rather than changes. [Overview](https://graphty.app/storybook/compact-mantine/?path=/docs/components-data-display-overview--docs)
431
+
432
+ | Component | Reach for it when |
433
+ | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
434
+ | [`DataRow`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-data-display-datarow--docs) | a name and a reading on one line -- a statistic, a fact, a ranking. `selected` marks the current item of a ranking; `onClick` opens what the reading is about. For objects the reader selects, renames or moves, use `Tree` or `PageList`. |
435
+ | [`DataRowHeader`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-data-display-datarowheader--docs) | a run of data rows needs a caption. Give it `onSortChange` and it becomes a sort control. |
436
+ | [`RankChip`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-data-display-rankchip--docs) | a rank belongs beside a row: `#6`, rather than "rank 6 of 318". |
437
+ | [`MetricRow`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-data-display-metricrow--docs) | one reading has a percentile and a rank: the name, a bar filled to the percentile, the number and the chip. |
438
+ | [`HistogramRow`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-data-display-histogramrow--docs) | a distribution would otherwise be spelled as the numbers that summarize it. 64px tall. |
439
+ | [`SparklineRow`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-data-display-sparklinerow--docs) | a series is going somewhere and you want to see which way. 32px tall. |
440
+ | [`ProseBlock`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-data-display-proseblock--docs) | the panel has to say something in words: a reading, a caveat, a record of the last run. |
441
+ | [`ActionRow`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-data-display-actionrow--docs) | a row reports a state and offers verbs beside it. |
442
+ | [`DataTable`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-data-display-datatable--docs) | you have columns rather than rows: thousands of them, sortable, searchable, selectable, virtualized. |
443
+
444
+ ### The editor shell
445
+
446
+ The chrome around an editor's canvas, as Figma draws it. Each one is a real toolbar, dialog or
447
+ tab list: one Tab stop, arrow keys inside, names for screen readers.
448
+ [Overview](https://graphty.app/storybook/compact-mantine/?path=/docs/components-app-shell-overview--docs)
449
+
450
+ | Component | What it is |
451
+ | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
452
+ | [`Toolbar`, `ToolButton`, `ToolGroup`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-app-shell-toolbar--docs) | The floating bottom toolbar (48 tall, 13px corners, `floating` pins it bottom center), its 32 x 32 tools (`label`, `icon`, `shortcut`, `selected`), and a tool with a flyout of related tools whose face shows the last one picked. |
453
+ | [`SecondaryToolbar`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-app-shell-secondarytoolbar--docs) | The contextual 40-tall bar above the toolbar while a mode is active. |
454
+ | [`NavRail`, `RailButton`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-app-shell-navrail--docs) | The 56-wide navigation rail at the window's edge, and its destinations: a 32 x 32 pill over a 9px caption. |
455
+ | [`HelpButton`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-app-shell-helpbutton--docs) | The round floating help button; its children are the items of the menu it opens. |
456
+ | [`ShortcutSheet`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-app-shell-shortcutsheet--docs) | The keyboard shortcuts sheet docked at the window's foot: tabs of shortcut columns with dark key caps. |
457
+ | [`QuickActions`](https://graphty.app/storybook/compact-mantine/?path=/docs/components-app-shell-quickactions--docs) | The quick actions palette: a search field over sectioned rows. Typing filters, arrows move, Enter runs, Escape closes. |
458
+
459
+ ```tsx
460
+ import { SegmentedControl } from "@mantine/core";
461
+ import { useState } from "react";
462
+ import { Toolbar, ToolButton, ToolGroup } from "@graphty/compact-mantine";
463
+
464
+ function EditorToolbar() {
465
+ const [tool, setTool] = useState("move");
466
+ return (
467
+ <Toolbar aria-label="Editor" floating>
468
+ <ToolGroup label="Move tools" tools={moveTools} activeTool={tool} onToolChange={setTool} />
469
+ <ToolGroup label="Shape tools" tools={shapeTools} activeTool={tool} onToolChange={setTool} />
470
+ <ToolButton label="Actions" icon={<ActionsGlyph />} shortcut="Ctrl+K" />
471
+ <Toolbar.Divider />
472
+ <SegmentedControl variant="toolbar" data={modes} />
473
+ </Toolbar>
474
+ );
475
+ }
476
+ ```
477
+
478
+ The shell brings no glyphs of its own for your tools: pass your own icons, drawn to sit in a 24px
479
+ box.
289
480
 
290
481
  ### Glyphs
291
482
 
292
- The premise of the row components is that a small drawing can replace a word,
293
- which only works if the drawings are a fixed, learnable set. Two components draw
294
- them:
483
+ The premise of the row components is that a small drawing can replace a word, which only works
484
+ if the drawings are a fixed, learnable set. Two components draw them:
485
+
486
+ - `FieldGlyph` -- the eight glyphs allowed inside a field's slot, each with a hollow and a filled
487
+ form. `FIELD_GLYPH_NAMES` lists them.
488
+ - `UiGlyph` -- the shared marks the components draw elsewhere: chevrons and carets, settings, close,
489
+ plus, check, warning, the alignment and transform verbs, and the layer shapes, each drawn with a
490
+ 1px stroke at any size. `UI_GLYPH_NAMES` lists them.
295
491
 
296
- - `FieldGlyph` -- the eight glyphs allowed inside a field's 16px slot, each with
297
- a hollow and a filled form. `FIELD_GLYPH_NAMES` lists them.
298
- - `UiGlyph` -- the fifteen shared marks the components draw elsewhere: chevrons,
299
- a gear, a close, a plus, a check, a warning. `UI_GLYPH_NAMES` lists them.
492
+ Both take a `name` and an optional `size`, draw in `currentColor`, and are hidden from screen
493
+ readers, because the control around them carries the name. `FIELD_LETTERS` is the small set of
494
+ capital letters a field may show when a concept has no picture. See every drawing on the
495
+ [Glyphs page](https://graphty.app/storybook/compact-mantine/?path=/docs/foundations-glyphs--docs).
300
496
 
301
- Both take a `name` and an optional `size`, draw in `currentColor`, and are
302
- hidden from screen readers, because the control around them carries the name.
303
- `FIELD_LETTERS` is the small set of capital letters a field may show in place of
304
- a drawing when a concept has no picture.
497
+ ### Themed Mantine components
305
498
 
306
- See every drawing at once in the **Glyphs** section of
307
- [Storybook](https://graphty.app/storybook/compact-mantine/).
499
+ The Mantine components the theme restyles but does not wrap have their own section in Storybook,
500
+ [Themed Mantine](https://graphty.app/storybook/compact-mantine/?path=/docs/themed-mantine-actions-button--docs), grouped as actions, inputs,
501
+ selection, navigation, feedback and surfaces. Their props are Mantine's. Looks the theme adds through
502
+ `variant`: `Checkbox` `neutral` (Figma's gray panel checkbox; the default is the blue dialog one),
503
+ `SegmentedControl` `toolbar` (the sliding mode switch) and `loose`, `Anchor` `secondary`, and
504
+ `Tabs` `default` (the underline tabs; pills are the default).
308
505
 
309
506
  ## A worked example
310
507
 
311
- A complete section of a property panel, using one component from each group.
508
+ A complete section of a property panel. The scale is a themed Mantine `SegmentedControl` whose
509
+ segments are drawings with a visually hidden word, the recipe that replaced `IconGroupRow`.
312
510
 
313
511
  ```tsx
512
+ import { SegmentedControl, VisuallyHidden } from "@mantine/core";
314
513
  import {
315
514
  AdvancedButton,
316
515
  ControlSection,
317
516
  FieldGlyph,
318
517
  FieldRow,
319
- IconGroupRow,
518
+ PANEL_GRID,
320
519
  PanelField,
321
520
  RampRow,
322
521
  ToggleRow,
323
522
  ToggleRowGroup,
523
+ TrailingSlot,
324
524
  } from "@graphty/compact-mantine";
325
525
 
526
+ const SCALES = [
527
+ { value: "sqrt", word: "Square root", glyph: "scaleSqrt" },
528
+ { value: "linear", word: "Linear", glyph: "scaleLinear" },
529
+ { value: "log", word: "Logarithmic", glyph: "scaleLog" },
530
+ ] as const;
531
+
326
532
  function NodeSizeSection({ openAdvanced }: { openAdvanced: () => void }) {
327
533
  return (
328
534
  <ControlSection
@@ -340,17 +546,32 @@ function NodeSizeSection({ openAdvanced }: { openAdvanced: () => void }) {
340
546
  {/* The mapping between them, drawn at row height. */}
341
547
  <RampRow label="Size range" min="1.0" max="4.0" variant="size" scale="sqrt" />
342
548
 
343
- {/* Three options whose difference can be drawn. */}
344
- <IconGroupRow
345
- label="Scale"
346
- hybrid
347
- defaultValue="sqrt"
348
- options={[
349
- { value: "sqrt", label: "Square root", icon: <FieldGlyph name="scaleSqrt" /> },
350
- { value: "linear", label: "Linear", icon: <FieldGlyph name="scaleLinear" /> },
351
- { value: "log", label: "Logarithmic", icon: <FieldGlyph name="scaleLog" /> },
352
- ]}
353
- />
549
+ {/* Three options whose difference can be drawn: a segmented control of pictures. */}
550
+ <div
551
+ style={{
552
+ display: "flex",
553
+ alignItems: "center",
554
+ gap: PANEL_GRID.TRAIL_GAP,
555
+ height: PANEL_GRID.ROW_PITCH,
556
+ }}
557
+ >
558
+ <SegmentedControl
559
+ fullWidth
560
+ aria-label="Scale"
561
+ defaultValue="sqrt"
562
+ data={SCALES.map((s) => ({
563
+ value: s.value,
564
+ label: (
565
+ <>
566
+ <FieldGlyph name={s.glyph} />
567
+ <VisuallyHidden>{s.word}</VisuallyHidden>
568
+ </>
569
+ ),
570
+ }))}
571
+ style={{ flex: "1 1 auto", minWidth: 0 }}
572
+ />
573
+ <TrailingSlot />
574
+ </div>
354
575
 
355
576
  {/* Booleans with no picture, packed tighter than the other rows. */}
356
577
  <ToggleRowGroup label="Node decorations">
@@ -382,12 +603,12 @@ user setting is recommended; nothing in the library requires it.
382
603
 
383
604
  ## The panel grid
384
605
 
385
- The row components are laid out for a 280px column, and they all measure
386
- themselves from one exported object so that a column of them lines up:
606
+ The row components are laid out for Figma's 240px panel column, and they all
607
+ measure themselves from one exported object so that a column of them lines up:
387
608
 
388
609
  ```
389
- 16 + 108 + 8 + 108 + 8 + 24 + 8 = 280
390
- pad field gut field gap trail pad
610
+ 16 + 88 + 8 + 88 + 8 + 24 + 8 = 240
611
+ pad field gut field gap trail pad
391
612
  ```
392
613
 
393
614
  `PANEL_GRID` names every number in it -- `WIDTH`, `FIELD`, `BODY`,
@@ -395,11 +616,11 @@ pad field gut field gap trail pad
395
616
  match without retyping them. It is also published on the theme as
396
617
  `theme.other.panelGrid`.
397
618
 
398
- `PANEL_INK` is the matching colour map: one entry per role a row paints
399
- (`VALUE`, `CHROME`, `SURFACE`, `ACCENT`, `BORDER`, `SELECTED`, `DISABLED` and
400
- so on), each one a Mantine CSS variable rather than a fixed colour. Paint your
401
- own rows from it and they follow the light scheme, the dark scheme and your
402
- primary colour for free.
619
+ `PANEL_INK` is the matching color map: one entry per role a row paints
620
+ (`VALUE`, `CHROME`, `SURFACE`, `ACCENT`, `BORDER`, `SELECTED`, `HOVER`,
621
+ `FOCUS`, `DISABLED` and so on), each one a `--cm-*` token rather than a fixed
622
+ color. Paint your own rows from it and they follow the light scheme, the dark
623
+ scheme and the AA option for free.
403
624
 
404
625
  ```tsx
405
626
  import { PANEL_GRID, PANEL_INK } from "@graphty/compact-mantine";
@@ -407,7 +628,7 @@ import { PANEL_GRID, PANEL_INK } from "@graphty/compact-mantine";
407
628
  <div style={{ height: PANEL_GRID.ROW_PITCH, color: PANEL_INK.CHROME }}>Custom row</div>;
408
629
  ```
409
630
 
410
- You do not have to use a 280px column. Nothing enforces the width; the numbers
631
+ You do not have to use a 240px column. Nothing enforces the width; the numbers
411
632
  are there so that the components agree with each other and with anything you
412
633
  write beside them.
413
634
 
@@ -442,16 +663,16 @@ modifier keys, call `preventDefault()`, or find the element that was activated:
442
663
  />
443
664
  ```
444
665
 
445
- Rows whose selection behaviour depends on how they were activated -- `DataRow`,
666
+ Rows whose selection behavior depends on how they were activated -- `DataRow`,
446
667
  `MetricRow`, `ActionRow` and `DataTable`'s rows -- get a second argument saying
447
668
  so, rather than making you sniff the event:
448
669
 
449
670
  ```tsx
450
671
  <DataRow
451
- label="Mr_Whiskers"
452
- value={12}
672
+ name="Mr_Whiskers"
673
+ value="12"
453
674
  onClick={(event, meta) => {
454
- select(id, { add: meta.source === "pointer" && event.shiftKey });
675
+ openNode(id, { inNewPanel: meta.source === "pointer" && event.shiftKey });
455
676
  }}
456
677
  />
457
678
  ```
@@ -590,11 +811,20 @@ as a set of props you must remember:
590
811
 
591
812
  - **Every control is a real control.** Fields are inputs, toggles are checkboxes
592
813
  or switches, segmented options are radios, the advanced button is a button, so
593
- keyboard behaviour, focus order and disabled semantics come from the platform.
814
+ keyboard behavior, focus order and disabled semantics come from the platform.
594
815
  - **Keyboard focus is always visible**, and pointer focus is not, so a dense
595
816
  surface stays quiet under the mouse without giving up the focus indicator.
817
+ - **Exact Figma by default, AA on request.** Figma's own contrast falls short
818
+ of AA in a few places (secondary text, field and checkbox edges); pass
819
+ `createCompactTheme({ highContrast: true })` to raise exactly those. See
820
+ [the WCAG AA option](#createcompacttheme-and-the-wcag-aa-option).
821
+ - **Accessibility kept beyond Figma.** Tooltips stay linked with
822
+ `aria-describedby`; the toolbar, the rail and the shortcuts sheet are real
823
+ toolbars and tab lists with arrow-key movement; the quick actions palette is a
824
+ named dialog with a combobox and a list box; the shortcuts sheet closes with
825
+ Escape.
596
826
  - **State is announced, not only drawn.** Expanded, checked, selected, disabled,
597
- busy and current are all exposed as ARIA in addition to colour.
827
+ busy and current are all exposed as ARIA in addition to color.
598
828
  - **Charts are one named image with a hidden table behind them**, so a screen
599
829
  reader announces the chart and can then read every value, rather than
600
830
  announcing a run of anonymous bars. Give every chart a `label`.
@@ -605,7 +835,7 @@ as a set of props you must remember:
605
835
  reader set themselves.
606
836
  - **Hover-revealed controls also appear on focus**, stay in the document, and
607
837
  stay in the tab order.
608
- - **Colour is never the only signal.** An advanced button whose settings have
838
+ - **Color is never the only signal.** An advanced button whose settings have
609
839
  changed says so in its accessible name; a section with non-default values does
610
840
  the same.
611
841
 
@@ -637,32 +867,104 @@ unknown string to them), `DataTableColumn<TRow>` describes a table column, and
637
867
 
638
868
  ## Which one should I use?
639
869
 
640
- Some components overlap, and a few sit beside a Mantine component that looks
641
- as though it would do. This table names the one to reach for, and what it is
642
- being chosen over where that choice is not obvious.
643
-
644
- | If you have | Use | Instead of |
645
- | ----------------------------------------------------------------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
646
- | A label and a reading on one line | `DataRow`, with the reading already formatted | -- it draws `value` verbatim and gives the pair no accessible name, so run a number through `useNumberFormatter().format(n)` yourself, and name the pair yourself where a reader has to hear the two together |
647
- | A gear that opens advanced settings | `AdvancedButton`, with `changed` | -- |
648
- | A group of controls that folds away | `ControlSection` | -- |
649
- | A group of controls that must not fold, or whose rule has to bleed to the edges of a padded container | `ControlGroup` | -- |
650
- | A checkbox on its own line | `ToggleRow` inside a `ToggleRowGroup` | -- |
651
- | A checkbox that reveals the settings it turns on | `ToggleWithContent` | -- |
652
- | A dropdown in a panel row | `PanelField` with `kind="select"` | -- |
653
- | A dropdown with a default the reader can override | `StyleSelect` | -- |
654
- | Two to six drawable options in a panel row | `IconGroupRow` | a bare Mantine `SegmentedControl`, which `IconGroupRow` is built on and adds the panel grid, the glyphs and the `showLabels` preference to |
655
- | An icon button that opens a pop-out | `PopoutButton`, inside `Popout.Trigger` | `AdvancedButton`, which is for a row's or a section's advanced settings and does not light up while a panel is open |
656
- | An explanation bubble | `InfoCircle` | a Mantine `Popover`, which does not share this library's dismissal rules |
657
-
658
- A lone boolean is not a row: put it in the trailing slot of the row it modifies,
659
- or make it one tile of an `IconGroupRow`. `ToggleRowGroup` warns in development
660
- when given only one child, for exactly this reason.
870
+ Some components overlap, and a few sit beside a Mantine component that looks as though it would
871
+ do. This table names the one to reach for, and what it is being chosen over where that choice is
872
+ not obvious. Storybook's
873
+ [Choosing a component](https://graphty.app/storybook/compact-mantine/?path=/docs/introduction-choosing-a-component--docs)
874
+ page has the full list, group by group.
875
+
876
+ | If you have | Use | Instead of |
877
+ | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
878
+ | A name and a reading on one line | `DataRow`, with the reading already formatted by `useNumberFormatter()` | `Tree` or `PageList`, which are for objects the reader selects, renames or moves |
879
+ | A list of objects to select, rename or reorder | `Tree` (nested, or flat when no item has `children`) or `PageList` | `DataRow`, which lost its listbox props in 0.9 |
880
+ | A group of controls that folds away | `ControlSection` | -- |
881
+ | A group of controls that must not fold, or sits in a padded container | `ControlGroup` | -- |
882
+ | Two or more checkboxes on their own lines | `ToggleRow`s inside a `ToggleRowGroup` | Mantine `Checkbox` with a label, which is sized for a form |
883
+ | A checkbox that reveals the settings it turns on | `ToggleWithContent` | -- |
884
+ | A dropdown in a panel row | `PanelField` with `kind="select"` | -- |
885
+ | A dropdown with a default the reader can override | `StyleSelect` | a `Select` with a hand-made reset button |
886
+ | A value typed freely, with presets | `ComboInput` | `Select` (fixed choices) or `Autocomplete` (suggestions, no chevron) |
887
+ | Two to six drawable options in a panel row | Mantine `SegmentedControl` with a glyph and a `VisuallyHidden` word per segment, beside a `TrailingSlot` | `IconGroupRow`, removed in 0.9 |
888
+ | An icon button that stays pressed | `ToggleIconButton` | `ActionIcon`, which is an action, not an on/off state |
889
+ | An icon button that opens a pop-out | `PopoutButton`, inside `Popout.Trigger` | `AdvancedButton`, which is for a row's or a section's advanced settings and does not light up while a panel is open |
890
+ | A floating panel that can be dragged and nest | `Popout` | Mantine `Popover`: one-off, not draggable, no nesting |
891
+ | An explanation bubble | `InfoCircle` | a `Tooltip`, which closes when the pointer leaves |
892
+ | A color with its opacity | `CompactColorInput` | Mantine `ColorInput`, which has no opacity field and no Figma picker |
893
+
894
+ A lone boolean is not a row: put it in the trailing slot of the row it modifies, or make it one
895
+ segment of a `SegmentedControl`. `ToggleRowGroup` warns in development when given only one child,
896
+ for exactly this reason.
897
+
898
+ ### Use these instead of Mantine's
899
+
900
+ Where this package has its own component for a job a stock Mantine component also does, the
901
+ theme leaves the Mantine one unstyled, so it renders exactly as Mantine documents it. Use these
902
+ instead to get the compact, Figma-matched look:
903
+
904
+ | Mantine component | Use instead |
905
+ | ----------------- | ---------------------------------------------------------------------------- |
906
+ | `ColorInput` | `CompactColorInput` |
907
+ | `ColorPicker` | `CompactColorInput`, or `ColorPickerPanel` for the picker on its own surface |
661
908
 
662
909
  Every component puts its own name, in kebab case, on a root `data-testid` --
663
910
  `advanced-button`, `metric-row`, `popout-panel` -- so a test can find any of
664
911
  them the same way.
665
912
 
913
+ ## Breaking changes in the Figma release
914
+
915
+ compact-mantine is 0.x, and 0.9.0 redraws every component to match the Figma editor. The full
916
+ list, with every changed value, is [CHANGELOG-figma.md](./CHANGELOG-figma.md); Storybook's
917
+ [Upgrading to 0.9](https://graphty.app/storybook/compact-mantine/?path=/docs/introduction-upgrading-to-0-9--docs)
918
+ page has the migrations.
919
+
920
+ **Removed exports and props (code that stops compiling):**
921
+
922
+ 1. **`IconGroupRow`**, `IconGroupRowProps` and `IconGroupOption` are removed. Use the themed
923
+ Mantine `SegmentedControl` directly: a glyph and a `VisuallyHidden` word per segment, the row's
924
+ trailing control in a `TrailingSlot` (see [the worked example](#a-worked-example)).
925
+ 2. **`DataRow`** keeps only `name`, `value`, `icon`, `selected`, `onClick`, `onFocus`, `onBlur`
926
+ and `trailing`. `role`, the `DataRowRole` type, `tabIndex`, `onDoubleClick` and
927
+ `onContextMenu` are removed; a selected row is always `aria-current`. A list the reader
928
+ selects, renames or reorders is a `Tree` or a `PageList`; a find result is a `ResultRow`.
929
+
930
+ **Behavior changes:**
931
+
932
+ 3. **`GradientEditor`** edits stop colors in one picker under the gradient bar, which follows the
933
+ selected stop; stop rows no longer open a pop-out, so it no longer needs a `PopoutManager`.
934
+ 4. **`Tree`**: with `onMove`, Alt+ArrowUp / Alt+ArrowDown move the focused item among its
935
+ siblings (additive).
936
+ 5. **Motion and timing.** Overlays have no transitions, and a tooltip opens after 1000ms (it was
937
+ immediate). Wrap your app in `Tooltip.Group` for the warm hand-off.
938
+ 6. **Pop-outs.** Only one root pop-out is open on the page at a time; `PopoutRegion` no longer
939
+ keeps one open per region. `POPOUT_GAP` 8 -> 0, `POPOUT_NESTED_GAP` 4 -> 0.
940
+ 7. **Select and Autocomplete** have `role="combobox"`; `Select` defaults to the outlined trigger
941
+ with a dark list opening over it. `Tabs` default to `variant="pills"`.
942
+ 8. **Escape in a color field** reverts the typed value and stops at the field.
943
+
944
+ **Values and looks:**
945
+
946
+ 9. **Panel grid.** `PANEL_GRID.WIDTH` 280 -> 240, `FIELD` 108 -> 88, and most other members
947
+ change value. Code that hard-codes a 280px panel must follow `PANEL_GRID.WIDTH`.
948
+ 10. **Theme scales.** `spacing.sm` 6 -> 8; `radius.sm` / `md` 4 / 6 -> 5, `lg` / `xl` 8 / 12 ->
949
+ 13; `fontSizes.xs` 10 -> 9, `lg` 14 -> 15, `xl` 16 -> 24; `lineHeights` are px; `shadows` are
950
+ Figma's elevations.
951
+ 11. **Colors.** `colors.dark` is a neutral ramp, and `primaryColor` is the new `brand` palette
952
+ (`color="blue"` now gets Mantine's blue). `PANEL_INK.SELECTED` / `ON_SELECTED` are Figma's
953
+ pale selected ground and brand glyph; every `PANEL_INK` value is a `--cm-*` token.
954
+ 12. **Focus.** `focusRing` is `"never"` and the ring is the theme's own 1px ring.
955
+ 13. **Typeface.** The body font is the bundled Inter at 11/16 weight 450; `<Text size="sm">` and
956
+ `size="xs"` render at 450 with Figma's letter-spacing.
957
+ 14. **Rows and sections.** `ControlSection`'s header is 40 tall with its rule below; `DataRow` is
958
+ 32 tall with an 11px name; `DataTable` rows and header are 40; `FieldRow` puts captions above
959
+ by default (`labelPosition="inline"` restores beside).
960
+ 15. **Display and overlays.** `Badge` defaults to the outlined look, `Kbd` is a dark key cap in
961
+ both schemes, `Modal` has no backdrop by default, `Loader` at `sm` is 16px, and
962
+ `PopoutButton` defaults to `sm` and shows its open state as the ghost button's
963
+ `aria-expanded` look.
964
+ 16. **Mantine's `ColorInput` and `ColorPicker` are no longer themed.** They render as plain
965
+ Mantine. Use `CompactColorInput` (see
966
+ [Use these instead of Mantine's](#use-these-instead-of-mantines)).
967
+
666
968
  ## Contributing
667
969
 
668
970
  The package lives in the [graphty monorepo](https://github.com/graphty-org/graphty-monorepo)
@@ -679,8 +981,27 @@ npm run build-storybook # what CI builds
679
981
  ```
680
982
 
681
983
  Storybook is the development environment: every component has a page, and a
682
- change to a component shows up there without a rebuild. New components need a
683
- story, a test and complete prop documentation -- the prop comments are compiled
984
+ change to a component shows up there without a rebuild.
985
+
986
+ Stories live under `stories/`, one folder per sidebar section: `introduction/` (MDX pages),
987
+ `foundations/`, `components/<group>/` (`panels`, `inputs`, `selection`, `color`, `actions`,
988
+ `overlays`, `lists`, `data`, `shell`, each with an `Overview.mdx`), `patterns/`, and
989
+ `mantine/<group>/` for the themed Mantine components. `stories/helpers/` holds the shared story
990
+ helpers. A component has exactly one story file, titled `Components/<Group>/<Name>`, laid out
991
+ like every other:
992
+
993
+ - The JSDoc above `const meta` is the page's description: one sentence on what the component is
994
+ for, then "When to use it" (naming its nearest alternative), "Usage" (a code snippet),
995
+ "Keyboard and accessibility" and "Measurements" (taken from `design/figma-spec.md`, which the
996
+ published page does not cite).
997
+ - `Default` is the first story and is driven by `args`, so the Controls table edits it.
998
+ - `States` is the second, with `parameters: BOTH_SCHEMES` from `stories/helpers/schemes.ts`, so
999
+ the page shows every state in light and dark side by side. Chromatic still captures every story
1000
+ once per scheme.
1001
+ - Feature stories follow, then `Keyboard`, `Translated` and `RightToLeft`. Every story has a
1002
+ one-sentence JSDoc comment, and a story whose job is to test behavior keeps a `play` function.
1003
+
1004
+ New components need a story, a test and complete prop documentation -- the prop comments are compiled
684
1005
  into both the published type declarations and Storybook's tables, so they are
685
1006
  read by people who will never see this repository.
686
1007
 
@@ -713,4 +1034,5 @@ Bugs and questions: <https://github.com/graphty-org/graphty-monorepo/issues>.
713
1034
 
714
1035
  ## License
715
1036
 
716
- MIT
1037
+ MIT. The bundled Inter typeface is licensed under the SIL Open Font License 1.1
1038
+ (`dist/fonts/LICENSE-Inter.txt`).