@graphty/compact-mantine 0.8.18 → 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,48 +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.
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:
104
158
 
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 |
159
+ ```tsx
160
+ import { createCompactTheme } from "@graphty/compact-mantine";
110
161
 
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.
162
+ <MantineProvider theme={createCompactTheme({ highContrast: true })}>{children}</MantineProvider>;
163
+ ```
115
164
 
116
- ### Components the theme restyles
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`.
117
173
 
118
- Pass no `size` prop and these render compact. Pass a size token and they step
119
- along a compact scale: an input field is 20, 24, 30, 36 and 44px tall at `xs`,
120
- `sm` (the default), `md`, `lg` and `xl`.
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";
121
183
 
122
- | Group | Components |
123
- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
124
- | Inputs (14) | TextInput, NumberInput, Select, NativeSelect, Textarea, PasswordInput, Autocomplete, MultiSelect, TagsInput, PillsInput, FileInput, JsonInput, ColorInput, InputClearButton |
125
- | Buttons (3) | Button, ActionIcon, CloseButton |
126
- | Controls (6) | Switch, Checkbox, Radio, Slider, RangeSlider, SegmentedControl |
127
- | Display (7) | Badge, Text, Avatar, ThemeIcon, Indicator, Kbd, Pill |
128
- | Navigation (6) | Tabs, NavLink, Pagination, Stepper, Anchor, Burger |
129
- | Feedback (3) | Loader, Progress, RingProgress |
130
- | Overlays (4) | Menu, Tooltip, Popover, HoverCard |
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.
209
+
210
+ ### Components the theme restyles
211
+
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.
131
231
 
132
232
  ### Making it your own
133
233
 
134
- `compactTheme` is a full theme, already merged with Mantine's default. Merge
135
- your own on top of it:
234
+ Merge your own theme on top of the compact one:
136
235
 
137
236
  ```tsx
138
237
  import { createTheme, MantineProvider, mergeMantineTheme } from "@mantine/core";
@@ -142,7 +241,6 @@ const theme = mergeMantineTheme(
142
241
  compactTheme,
143
242
  createTheme({
144
243
  primaryColor: "teal",
145
- fontFamily: "Inter, sans-serif",
146
244
  }),
147
245
  );
148
246
 
@@ -150,8 +248,8 @@ const theme = mergeMantineTheme(
150
248
  ```
151
249
 
152
250
  If you already build your theme from several overrides, take
153
- `compactThemeOverride` instead. It is the raw `createTheme()` result, suitable
154
- for `mergeThemeOverrides()`:
251
+ `compactThemeOverride` (or `createCompactTheme(options)`) instead. It is the raw
252
+ `createTheme()` result, suitable for `mergeThemeOverrides()`:
155
253
 
156
254
  ```tsx
157
255
  import { createTheme, mergeThemeOverrides } from "@mantine/core";
@@ -160,6 +258,11 @@ import { compactThemeOverride } from "@graphty/compact-mantine";
160
258
  const override = mergeThemeOverrides(compactThemeOverride, createTheme({ primaryColor: "teal" }));
161
259
  ```
162
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
+
163
266
  ### A compact region inside a normal-sized app
164
267
 
165
268
  `MantineProvider` nests, so you can keep your application at its usual size and
@@ -170,76 +273,105 @@ make one region dense -- which is the common case for a sidebar or an inspector.
170
273
  <TextInput label="Normal size" />
171
274
 
172
275
  <MantineProvider theme={compactTheme}>
173
- <aside style={{ width: 280 }}>
276
+ <aside style={{ width: 240 }}>
174
277
  <TextInput label="Compact" />
175
278
  </aside>
176
279
  </MantineProvider>
177
280
  </MantineProvider>
178
281
  ```
179
282
 
180
- Also exported: `compactColors` (the palette this theme adds) and
181
- `compactDarkColors` (its dark scale), if you want to reuse the colours
182
- 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.
183
286
 
184
287
  ## The components
185
288
 
186
- Grouped by what you are trying to do. Every component has a page in
187
- [Storybook](https://graphty.app/storybook/compact-mantine/) with runnable
188
- examples, and every prop is documented in your editor.
189
-
190
- ### Building a panel
191
-
192
- | Component | Reach for it when |
193
- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
194
- | `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. |
195
- | `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. |
196
- | `ControlSubGroup` | a handful of rarely-opened settings belong under a section you already have. Quieter than a section: no rule, a smaller chevron. |
197
- | `FieldRow` | one or two fields share a line. It owns the widths and the gaps, so a column of rows lines up. |
198
- | `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. |
199
- | `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. |
200
-
201
- ### Editing a value
202
-
203
- | Component | Reach for it when |
204
- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
205
- | `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. |
206
- | `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. |
207
- | `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. |
208
- | `ToggleRow` | a setting is a plain yes or no that no picture could stand for. |
209
- | `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. |
210
- | `RampRow` | a range is better drawn than described: a size wedge or a colour ramp with its two endpoints. |
211
- | `CompactColorInput` | a colour and its opacity, on one 24px line, with a picker in a pop-out. Needs a [`PopoutManager`](#floating-panels). |
212
- | `GradientEditor` | a multi-stop linear gradient: colours, positions, angle. Needs a `PopoutManager`. |
213
- | `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. |
214
- | `StyleSelect` | the same idea for a dropdown. |
215
- | `ToggleWithContent` | a feature is a yes or no that brings its own settings with it. Turning it off takes its settings off the screen. |
216
-
217
- ### Showing data
218
-
219
- | Component | Reach for it when |
220
- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
221
- | `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. |
222
- | `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. |
223
- | `RankChip` | a rank belongs beside a row: `#6`, rather than a sentence saying "rank 6 of 318". |
224
- | `MetricRow` | one reading has a percentile and a rank. Draws the name, a bar filled to the percentile, the number, and the chip. |
225
- | `HistogramRow` | a distribution would otherwise be spelled as the four numbers that summarise it. 64px tall. |
226
- | `SparklineRow` | a series is going somewhere and you want to see which way. 32px tall. |
227
- | `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. |
228
- | `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. |
229
- | `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`).
230
363
 
231
364
  ### Floating panels
232
365
 
233
- A pop-out is a panel that opens beside a control, can be dragged, and can
234
- 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)
235
368
 
236
- **Put one `PopoutManager` high in your tree, above anything that opens a
237
- pop-out.** It owns the shared floating layer: the stacking order, the portal the
238
- panels render into, and the dismissal rules they all obey. Without it, anything
239
- that opens a pop-out throws `usePopoutManagerContext must be used within a
240
- PopoutManager` on first render. `CompactColorInput` and `GradientEditor` use a
241
- pop-out internally, so they need one too. (`InfoCircle` is the exception: it
242
- 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.)
243
375
 
244
376
  ```tsx
245
377
  import { Popout, PopoutButton, PopoutManager, UiGlyph } from "@graphty/compact-mantine";
@@ -250,7 +382,7 @@ import { Popout, PopoutButton, PopoutManager, UiGlyph } from "@graphty/compact-m
250
382
  <Popout.Trigger>
251
383
  <PopoutButton icon={<UiGlyph name="gear" />} aria-label="Display settings" />
252
384
  </Popout.Trigger>
253
- <Popout.Panel width={280} header={{ variant: "title", title: "Display settings" }}>
385
+ <Popout.Panel width={240} header={{ variant: "title", title: "Display settings" }}>
254
386
  <Popout.Content>{/* anything */}</Popout.Content>
255
387
  </Popout.Panel>
256
388
  </Popout>
@@ -258,63 +390,145 @@ import { Popout, PopoutButton, PopoutManager, UiGlyph } from "@graphty/compact-m
258
390
  </PopoutManager>;
259
391
  ```
260
392
 
261
- | Component | What it is |
262
- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------- |
263
- | `PopoutManager` | The shared floating layer. Required, once, near the root. |
264
- | `Popout` | One pop-out: its trigger and its panel. Also namespaces `Popout.Trigger`, `Popout.Panel`, `Popout.Content` and `Popout.Anchor`. |
265
- | `Popout.Trigger` | Wraps the single element that opens the panel. Give it a real button. |
266
- | `Popout.Panel` | The panel itself: a width, an optional header or tab strip, and its content. |
267
- | `Popout.Anchor` | Wraps a sidebar so every panel opened inside it lines up with that sidebar's edge instead of with its own button. |
268
- | `PopoutButton` | An icon button that stays lit while its panel is open. |
269
- | `InfoCircle` | A circled "i" that reveals an explanation on hover, focus or tap. |
270
-
271
- The rules a `PopoutManager` enforces, so you do not have to: Escape closes the
272
- innermost panel; a click outside closes everything; opening a panel closes its
273
- siblings; closing a panel closes everything opened from it; focus returns to the
274
- trigger.
275
-
276
- Where a panel opens is set by two independent props on `Popout.Panel`:
277
- `anchorX` decides which edge it lines up with (`"panel"`, `"trigger"`,
278
- `"parent"`, or a ref of your own) and `anchorY` how far down it opens. The
279
- defaults are the arrangement most sidebars want: flush with the panel's edge,
280
- 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.
281
480
 
282
481
  ### Glyphs
283
482
 
284
- The premise of the row components is that a small drawing can replace a word,
285
- which only works if the drawings are a fixed, learnable set. Two components draw
286
- 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.
287
491
 
288
- - `FieldGlyph` -- the eight glyphs allowed inside a field's 16px slot, each with
289
- a hollow and a filled form. `FIELD_GLYPH_NAMES` lists them.
290
- - `UiGlyph` -- the fifteen shared marks the components draw elsewhere: chevrons,
291
- 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).
292
496
 
293
- Both take a `name` and an optional `size`, draw in `currentColor`, and are
294
- hidden from screen readers, because the control around them carries the name.
295
- `FIELD_LETTERS` is the small set of capital letters a field may show in place of
296
- a drawing when a concept has no picture.
497
+ ### Themed Mantine components
297
498
 
298
- See every drawing at once in the **Glyphs** section of
299
- [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).
300
505
 
301
506
  ## A worked example
302
507
 
303
- 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`.
304
510
 
305
511
  ```tsx
512
+ import { SegmentedControl, VisuallyHidden } from "@mantine/core";
306
513
  import {
307
514
  AdvancedButton,
308
515
  ControlSection,
309
516
  FieldGlyph,
310
517
  FieldRow,
311
- IconGroupRow,
518
+ PANEL_GRID,
312
519
  PanelField,
313
520
  RampRow,
314
521
  ToggleRow,
315
522
  ToggleRowGroup,
523
+ TrailingSlot,
316
524
  } from "@graphty/compact-mantine";
317
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
+
318
532
  function NodeSizeSection({ openAdvanced }: { openAdvanced: () => void }) {
319
533
  return (
320
534
  <ControlSection
@@ -332,17 +546,32 @@ function NodeSizeSection({ openAdvanced }: { openAdvanced: () => void }) {
332
546
  {/* The mapping between them, drawn at row height. */}
333
547
  <RampRow label="Size range" min="1.0" max="4.0" variant="size" scale="sqrt" />
334
548
 
335
- {/* Three options whose difference can be drawn. */}
336
- <IconGroupRow
337
- label="Scale"
338
- hybrid
339
- defaultValue="sqrt"
340
- options={[
341
- { value: "sqrt", label: "Square root", icon: <FieldGlyph name="scaleSqrt" /> },
342
- { value: "linear", label: "Linear", icon: <FieldGlyph name="scaleLinear" /> },
343
- { value: "log", label: "Logarithmic", icon: <FieldGlyph name="scaleLog" /> },
344
- ]}
345
- />
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>
346
575
 
347
576
  {/* Booleans with no picture, packed tighter than the other rows. */}
348
577
  <ToggleRowGroup label="Node decorations">
@@ -374,12 +603,12 @@ user setting is recommended; nothing in the library requires it.
374
603
 
375
604
  ## The panel grid
376
605
 
377
- The row components are laid out for a 280px column, and they all measure
378
- 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:
379
608
 
380
609
  ```
381
- 16 + 108 + 8 + 108 + 8 + 24 + 8 = 280
382
- pad field gut field gap trail pad
610
+ 16 + 88 + 8 + 88 + 8 + 24 + 8 = 240
611
+ pad field gut field gap trail pad
383
612
  ```
384
613
 
385
614
  `PANEL_GRID` names every number in it -- `WIDTH`, `FIELD`, `BODY`,
@@ -387,11 +616,11 @@ pad field gut field gap trail pad
387
616
  match without retyping them. It is also published on the theme as
388
617
  `theme.other.panelGrid`.
389
618
 
390
- `PANEL_INK` is the matching colour map: one entry per role a row paints
391
- (`VALUE`, `CHROME`, `SURFACE`, `ACCENT`, `BORDER`, `SELECTED`, `DISABLED` and
392
- so on), each one a Mantine CSS variable rather than a fixed colour. Paint your
393
- own rows from it and they follow the light scheme, the dark scheme and your
394
- 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.
395
624
 
396
625
  ```tsx
397
626
  import { PANEL_GRID, PANEL_INK } from "@graphty/compact-mantine";
@@ -399,7 +628,7 @@ import { PANEL_GRID, PANEL_INK } from "@graphty/compact-mantine";
399
628
  <div style={{ height: PANEL_GRID.ROW_PITCH, color: PANEL_INK.CHROME }}>Custom row</div>;
400
629
  ```
401
630
 
402
- 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
403
632
  are there so that the components agree with each other and with anything you
404
633
  write beside them.
405
634
 
@@ -434,16 +663,16 @@ modifier keys, call `preventDefault()`, or find the element that was activated:
434
663
  />
435
664
  ```
436
665
 
437
- Rows whose selection behaviour depends on how they were activated -- `DataRow`,
666
+ Rows whose selection behavior depends on how they were activated -- `DataRow`,
438
667
  `MetricRow`, `ActionRow` and `DataTable`'s rows -- get a second argument saying
439
668
  so, rather than making you sniff the event:
440
669
 
441
670
  ```tsx
442
671
  <DataRow
443
- label="Mr_Whiskers"
444
- value={12}
672
+ name="Mr_Whiskers"
673
+ value="12"
445
674
  onClick={(event, meta) => {
446
- select(id, { add: meta.source === "pointer" && event.shiftKey });
675
+ openNode(id, { inNewPanel: meta.source === "pointer" && event.shiftKey });
447
676
  }}
448
677
  />
449
678
  ```
@@ -582,11 +811,20 @@ as a set of props you must remember:
582
811
 
583
812
  - **Every control is a real control.** Fields are inputs, toggles are checkboxes
584
813
  or switches, segmented options are radios, the advanced button is a button, so
585
- keyboard behaviour, focus order and disabled semantics come from the platform.
814
+ keyboard behavior, focus order and disabled semantics come from the platform.
586
815
  - **Keyboard focus is always visible**, and pointer focus is not, so a dense
587
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.
588
826
  - **State is announced, not only drawn.** Expanded, checked, selected, disabled,
589
- 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.
590
828
  - **Charts are one named image with a hidden table behind them**, so a screen
591
829
  reader announces the chart and can then read every value, rather than
592
830
  announcing a run of anonymous bars. Give every chart a `label`.
@@ -597,7 +835,7 @@ as a set of props you must remember:
597
835
  reader set themselves.
598
836
  - **Hover-revealed controls also appear on focus**, stay in the document, and
599
837
  stay in the tab order.
600
- - **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
601
839
  changed says so in its accessible name; a section with non-default values does
602
840
  the same.
603
841
 
@@ -629,32 +867,104 @@ unknown string to them), `DataTableColumn<TRow>` describes a table column, and
629
867
 
630
868
  ## Which one should I use?
631
869
 
632
- Some components overlap, and a few sit beside a Mantine component that looks
633
- as though it would do. This table names the one to reach for, and what it is
634
- being chosen over where that choice is not obvious.
635
-
636
- | If you have | Use | Instead of |
637
- | ----------------------------------------------------------------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
638
- | 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 |
639
- | A gear that opens advanced settings | `AdvancedButton`, with `changed` | -- |
640
- | A group of controls that folds away | `ControlSection` | -- |
641
- | A group of controls that must not fold, or whose rule has to bleed to the edges of a padded container | `ControlGroup` | -- |
642
- | A checkbox on its own line | `ToggleRow` inside a `ToggleRowGroup` | -- |
643
- | A checkbox that reveals the settings it turns on | `ToggleWithContent` | -- |
644
- | A dropdown in a panel row | `PanelField` with `kind="select"` | -- |
645
- | A dropdown with a default the reader can override | `StyleSelect` | -- |
646
- | 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 |
647
- | 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 |
648
- | An explanation bubble | `InfoCircle` | a Mantine `Popover`, which does not share this library's dismissal rules |
649
-
650
- A lone boolean is not a row: put it in the trailing slot of the row it modifies,
651
- or make it one tile of an `IconGroupRow`. `ToggleRowGroup` warns in development
652
- 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 |
653
908
 
654
909
  Every component puts its own name, in kebab case, on a root `data-testid` --
655
910
  `advanced-button`, `metric-row`, `popout-panel` -- so a test can find any of
656
911
  them the same way.
657
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
+
658
968
  ## Contributing
659
969
 
660
970
  The package lives in the [graphty monorepo](https://github.com/graphty-org/graphty-monorepo)
@@ -671,8 +981,27 @@ npm run build-storybook # what CI builds
671
981
  ```
672
982
 
673
983
  Storybook is the development environment: every component has a page, and a
674
- change to a component shows up there without a rebuild. New components need a
675
- 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
676
1005
  into both the published type declarations and Storybook's tables, so they are
677
1006
  read by people who will never see this repository.
678
1007
 
@@ -705,4 +1034,5 @@ Bugs and questions: <https://github.com/graphty-org/graphty-monorepo/issues>.
705
1034
 
706
1035
  ## License
707
1036
 
708
- MIT
1037
+ MIT. The bundled Inter typeface is licensed under the SIL Open Font License 1.1
1038
+ (`dist/fonts/LICENSE-Inter.txt`).