diff --git a/AGENTS.md b/AGENTS.md index 1ac695e4..1e426e1b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -111,3 +111,6 @@ finding, and it lets somebody check you. | `templates/studio` | What a new studio is scaffolded from. Mirrored to its own repository on release. | | `docs/` | The published site. `docs/api.md` is **generated** — edit the JSDoc instead. | | `docs/internal/` | Findings, decisions and open threads. Written to survive the conversation they came from. | + +Asked where things stand, start with `docs/internal/next.md`, and raise its **Raise +these first** section before anything else. diff --git a/CHANGELOG.md b/CHANGELOG.md index a043f871..cd6a7313 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,49 @@ Every published package shares a version — `@single-studio/core`, `@single-studio/provider-supabase` and the four plugins go out as one release. The tag is checked against all six manifests, so they cannot drift apart. +## Unreleased + +### Added + +- **A `Panel` header that does more.** `actions` puts anything -- a reset, a swap, + a status light -- at the right of the title, or a function given `{ open }` that + returns it. They stay in the header, and work, while the panel is folded, and + drop under the title in a dock too narrow for both. `collapsible` makes the title + a button that folds the panel away; a collapsible panel starts folded on every + load, and `defaultOpen` starts one open. The fold belongs to the page, not the + show, and is not remembered. A panel with no title keeps its actions and gets a + chevron of its own to fold by; one with nothing for a header to hold has none. + Panels that ask for neither keep their layout; the title now sits in a + `.ss-panel-header` line. +- **`size="sm"` on `Confirm`, `ResetButton`, `SwapButton` and `Toggle`**, for a + button in a panel's header: smaller text and padding, off the Row's input line, + and never under the 24px pointer target WCAG 2.5.8 asks for, however its padding + is squeezed. It replaces overriding the line height away with + `min-h-0! px-2.5! py-1! text-xs!`, which had no floor at all. +- **`useCollapsed` and `Chevron`**, from `@single-studio/core/control`: what a + collapsible panel folds with, for folding anything else on the board the same + way -- a team card inside a panel, say. + +### Changed + +- **A `Stepper` and a `Stopwatch` fill their cell.** A stepper's number field + stopped at 4rem and a stopwatch's readout at its minimum, so on a board with + breakpoints both sat in the corner of their cell: a stepper left 170px of 330 + empty in a slim dock, and 121–201px in the demo below 1024px. The field and the + readout now grow between their buttons. `col-auto` still keeps one at its own + width -- a stepper is 160px, as before. +- **Panels are striped.** Every other panel on the board is a shade lighter, so a + long board reads as blocks rather than one slab. Counted among sibling panels: + a panel wrapped in an element of its own (`
`) stays the + darker shade. A `bg-*` class on a panel, or a studio's own rule, still wins. The + panel's background moved from a class on the element into the stylesheet to make + the count possible. +- **The board declares `color-scheme: dark`.** Chrome draws a time input's clock + icon, its picker, the list behind a ` + + + {/* No title: actions keep their line, and the fold is a chevron of its own. */} + + Untitled action + + } + > +

No title; starts open.

+ + {/* A stepper at its own width beside a field taking the rest: `col-auto` still + means 160px now that a stepper grows into any other cell. Measured, never + pressed or typed into. */} + + + + +
+ + {/* Every header button at size="sm", both ways a reset and a swap render, beside + the same reset at its own size on a Row. Mounted only when the e2e suite asks, + because checks long before it find the board's one swap and one reset by + class. Bound to paths nothing else reads, and only measured, never pressed. */} + + {}} /> + + + + + + {/* Squeezed the way a studio might, to fit one more in: the size keeps a + floor under it however its padding is cut. */} + + + ) : null + } + > + + {small ? ( + + + + ) : null} + + + {/* Nothing for a header to hold, so no header. */} + +

No title, no actions.

+
+ + ) +} + export default function Control() { const mutate = useVelcroMutate() @@ -172,6 +289,8 @@ export default function Control() { + + {/* The manager, inline. The same component opens as a modal from any picker. */} diff --git a/demo/src/control/Control.jsx b/demo/src/control/Control.jsx index a2f03b19..128b198c 100644 --- a/demo/src/control/Control.jsx +++ b/demo/src/control/Control.jsx @@ -11,6 +11,9 @@ import Twitch from './panels/Twitch' * scoreboard is touched every round, the lower thirds a few times an hour, the * static card at the start and the breaks, the clock once, and Twitch looks after * itself. Panels live in ./panels, one per file. + * + * Every panel folds, and all but the scoreboard start folded: the board opens on the + * one panel a round needs and a list of headers for the rest. */ export default function Control() { return ( diff --git a/demo/src/control/panels/Clock.jsx b/demo/src/control/panels/Clock.jsx index 57b93a0f..0b6803ac 100644 --- a/demo/src/control/panels/Clock.jsx +++ b/demo/src/control/panels/Clock.jsx @@ -9,7 +9,7 @@ import { Cycle, Panel, Row, Toggle } from '@single-studio/core/control' */ export default function Clock() { return ( - + {/* The switch at its own width beside the format, from 640px. */} diff --git a/demo/src/control/panels/LowerThirds.jsx b/demo/src/control/panels/LowerThirds.jsx index 345bca52..3b6b17de 100644 --- a/demo/src/control/panels/LowerThirds.jsx +++ b/demo/src/control/panels/LowerThirds.jsx @@ -29,7 +29,7 @@ function Third({ which, label, name, tag }) { */ export default function LowerThirds() { return ( - + {/* Below 1024px each strap is two or three lines, and the two run together without a line between them. */} diff --git a/demo/src/control/panels/Scoreboard.jsx b/demo/src/control/panels/Scoreboard.jsx index 08a8e2a9..b81dee7e 100644 --- a/demo/src/control/panels/Scoreboard.jsx +++ b/demo/src/control/panels/Scoreboard.jsx @@ -36,7 +36,21 @@ export default function Scoreboard() { const mutate = useVelcroMutate() return ( - + // Open on load, where every other panel starts folded: it is the one touched every + // round. The swap is in the header, so it can be pressed with the panel folded. + + } + > {/* Below 1024px a side is four or two lines of controls, and two sides of identical controls run together without a line between them. From there @@ -45,17 +59,8 @@ export default function Scoreboard() {
- {/* The round takes the line and the swap only the room its words need, from 640px. */} - - {/* A field, not a stepper: empty, its placeholder says what the scoreboard shows. */} - - {/* One button, every path a side owns: half a swap is a scoreboard that lies. */} - - + {/* A field, not a stepper: empty, its placeholder says what the scoreboard shows. */} + + + + ) +} +``` + + + +### Chevron + +--- + +`import { Chevron } from '@single-studio/core/control'` + +The fold's chevron: pointing down when open, turned a quarter when folded, as a `Panel`'s is. Decoration -- the button it sits in says what it does, with `aria-expanded` saying which way it is. + +```jsx + +``` + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `className` | `string` | | Added to the component's own classes. | +| `collapsed` | `boolean` | | Folded: turned a quarter to point at what it belongs to. | + ### Break @@ -664,6 +732,7 @@ A destructive button that asks first, without a dialog: one click arms it, a sec | `disabled` | `boolean` | | Nothing happens on click. | | `label` | `string` | | What the button says when idle. | | `onConfirm` | `() => void` | Yes | Called on the second click. | +| `size` | `'md' \| 'sm'` | | `"sm"` for a Panel's header: smaller, off the input line, never under a 24px target. Defaults to `"md"`. | | `tone` | `'danger' \| 'warn' \| 'go' \| 'primary' \| 'quiet'` | | What the button means. Defaults to `"danger"`. | diff --git a/docs/getting-started.md b/docs/getting-started.md index 51940ec3..cdcb55ec 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -279,10 +279,29 @@ instead, over `--ss-fade`; anything beyond that is a rule of your own on | `Stopwatch` | `timers.` | Counts up. Start, pause, reset. | | `Leaderboard` | `variables.` | One delimited string; paste view and table view. Staged. | | `SaveButton` | — | Commits every staged edit. Hosts the save and discard shortcuts. | -| `Panel` | — | Titled group. One row per child. | +| `Panel` | — | Titled group, one row per child. `actions` on the title line; `collapsible`. | | `Row` | — | Side by side when there is room, stacked in a dock. Widths in twelfths. | | `Break` | — | Deprecated: draws nothing. Use `Row`. | +A `Panel`'s `actions` go at the right of its title, and work while it is folded: +anything at all, or a function given `{ open }` that returns it. `collapsible` +makes the title fold the panel away. A collapsible panel starts folded on every +load, so a fresh board is a short list of headers; `defaultOpen` starts one open. +The fold is that page's alone, never the show's, so folding a panel on your board +folds nobody else's. A button in the header takes `size="sm"` — `Confirm`, +`ResetButton`, `SwapButton` and `Toggle` all have it — and `useCollapsed` and +`Chevron` fold anything else, such as a card inside a panel, the same way. + +Every other panel is a shade lighter, so a long board reads as blocks. The stripes +count sibling panels, so render panels directly or in a fragment rather than each +inside a `
` of its own. + +```jsx +}> + + +``` + ## Working with other people A studio is one operator by default and needs nothing. To bring in others, press @@ -830,6 +849,11 @@ With a breakpoint a class holds from there up; without one, at every width, so `row-cols-2` keeps buttons in pairs even in a slim dock. A line that does not fill has no hole at the end: the last control on it takes the room. +Every control fills the cell it is given, a `Stepper` and a `Stopwatch` included: +the number field and the readout grow between their buttons. To keep one at its own +width and give the rest of the line to its neighbours, that is what `col-auto` is +for — a stepper is 160px. + Side by side, controls line up along their inputs. Every control is a label, then one input line of the same height, then anything extra — colour presets, a warning — below, where it cannot push its neighbours out of line. A switch or button beside a diff --git a/docs/internal/next.md b/docs/internal/next.md index a523cf9b..9e3b4fac 100644 --- a/docs/internal/next.md +++ b/docs/internal/next.md @@ -2,6 +2,106 @@ Left here rather than in a chat, so it survives. +## Raise these first + +Asked for at the top of any "where are we at": the owner wants reminding of these +before anything else. + +- **The Panel work and `color-scheme: dark` -- built, not released.** `actions`, + `collapsible`, `defaultOpen`, `size="sm"` on the header buttons, `useCollapsed` + and `Chevron`, striped panels and a dark scheme on the board are in core, under + Unreleased in the changelog, as are steppers and stopwatches that fill their + cell and a demo whose panels fold. Next: release them, then move + SS-Mercy-Riftbound-2v2 off its own `Panel.jsx`, `collapse.jsx`, `FillStepper.jsx`, + `HEADER_BUTTON` and the two rules in its `index.css`. See [the section below](#panel-header-actions-and-folding). +- **`Sequence` in a real OBS.** 0.12.0 shipped it tested only in Chromium. The check + that matters: a browser source with _Refresh browser when scene becomes active_ + ticked starts the sequence again from its first item every time the scene is + taken. + +## Panel: header actions, and folding + +From the SS-Mercy-Riftbound-2v2 studio, which built all of it locally +(`src/control/collapse.jsx` and its own `Panel.jsx`) because core's `Panel` has +nothing beside its title. What it does, and what it learned, so core can take it +over rather than rediscover it. + +**Behaviour.** + +- The title is a button -- chevron and title -- that folds the panel body away. +- **Everything starts folded, on every load**, panels and the cards inside them + alike. A fresh board is a short list of headers, and the operator opens what the + next segment needs. +- The fold is component state and nothing else. Not the show, which would fold the + panel on every operator's board; and not `localStorage`, so that a reload is + always the same board. +- Header `actions` (Reset, Swap, Seed…) stay in the header and keep working while + folded. +- A folded team card keeps a one-line summary -- "A1 · Comet Crew · 2–0 · 6 pts" -- + so a folded group still reads at a glance. Folding hides the controls, not the + state of the show. + +**How the studio built it.** + +- `useCollapsed(initial = true)` returns `[collapsed, toggle]`: a plain `useState`, + no keys and no storage. `false` starts one open. +- `` points down when open and turns −90° when folded. +- Markup is `

`, with + `id={bodyId}` from `useId` on the body. The button goes inside the heading: a + heading inside a button is invalid HTML. +- The wrapper copies core's markup -- `.ss-panel`, `.ss-panel-body`, same classes + and spacing -- so board CSS aimed at those still applies. The header's bottom + margin goes while folded. + +**What bit.** + +- The studio's notes say `.ss-panel-body { display: flex }` beats the `hidden` + attribute, so it sets Tailwind's `hidden` class as well. That does not reproduce + in core: Tailwind's preflight, which every studio imports, has + `[hidden] { display: none !important }`. A `[hidden]` rule written into `base.css` + for it changed nothing -- with it removed, the folded body still measured 0px tall + -- so core relies on the attribute alone. +- A grid of panels or cards needs `items-start`, or a folded one stretches to its + neighbours' height. +- A folded summary reads saved values (`useVelcroValue`), not the staged draft, so + it changes when the operator saves, not as they type. + +**The shape asked for in core.** + +```jsx +} collapsible defaultOpen={false}> +``` + +- `collapsible` turns folding on; `defaultOpen` starts one open. Folded is the + default. +- `useCollapsed` and `Chevron` exported from `@single-studio/core/control`, so a + studio folds its own cards the same way. +- It pairs with the other asks from the same studio: + - `actions` on `Panel`, rendered right-aligned on the title line; + - `size="sm"` on `Confirm`, `ResetButton`, `SwapButton` and `Toggle`, so a + header button needs no overrides -- today it takes + `min-h-0! px-2.5! py-1! text-xs!`. None of the four has a size yet; + - `color-scheme: dark` on the board, above. + +**What core has now** (on the branch, not released). `actions`, as anything or as +a function given `{ open }`; `collapsible`, off by default, so existing boards are +unchanged; `defaultOpen`. Settled on the way: + +- No title, but actions: the header holds just the actions. No title and + collapsible: a chevron of its own, named "Fold panel" or "Open panel". Neither, + and no actions: no header at all. +- In a slim dock the actions take a line of their own under the title, still at the + right, and the title keeps one line. Measured at 380px and 1280px. +- Folding hides the body without unmounting it, so a draft typed into a folded + panel survives. + +**Since then**, also in core: `size="sm"` on `Confirm`, `ResetButton`, +`SwapButton` and `Toggle`, with a 24px floor that holds when a studio squeezes the +padding; `useCollapsed` and `Chevron` exported from `@single-studio/core/control`; +every other panel a shade lighter, by default; and `color-scheme: dark` on +`.ss-control`. The stripes count sibling panels, so a panel wrapped in a `
` of +its own stays the darker shade -- written into the docs rather than worked around. + ## Rocket League - **A test suite with the real game.** `dev/replay.mjs` proves the plugin and diff --git a/packages/core/src/components/common/Icon.jsx b/packages/core/src/components/common/Icon.jsx index 056ac10b..99ab5823 100644 --- a/packages/core/src/components/common/Icon.jsx +++ b/packages/core/src/components/common/Icon.jsx @@ -109,6 +109,8 @@ const shapes = { ), + /* Pointing down: open. A folded Panel turns it a quarter to point at its title. */ + chevron: , /* Two figures: one nearer, one behind. Collaboration, without a metaphor. */ people: ( <> diff --git a/packages/core/src/components/control/Collapse.jsx b/packages/core/src/components/control/Collapse.jsx new file mode 100644 index 00000000..d0750732 --- /dev/null +++ b/packages/core/src/components/control/Collapse.jsx @@ -0,0 +1,58 @@ +import { useState } from 'react' + +import { cx } from '../../toolkits/cx' +import { Icon } from '../common/Icon' + +/** + * Whether something on the board is folded away, and a function that flips it: + * `const [collapsed, toggle] = useCollapsed()`. Folded to begin with; + * `useCollapsed(false)` starts it open. What a collapsible `Panel` folds with, for + * folding anything else the same way -- a team card inside a panel, a row of + * advanced settings. + * + * Starts folded unless told otherwise, on every load, as a panel does. Component + * state and nothing else: not the show, which would fold the same thing on every + * operator's board, and not browser storage, so a reload is always the same board. + * + * @example + * function TeamCard({ team }) { + * const [collapsed, toggle] = useCollapsed() + * const body = useId() + * + * return ( + *
+ * + * + *
+ * ) + * } + * + * @param {boolean} [initial] - Start folded. Defaults to `true`; `false` starts it open. + * @returns {[boolean, () => void]} Whether it is folded, and a function that folds or opens it. + */ +export function useCollapsed(initial = true) { + const [collapsed, setCollapsed] = useState(Boolean(initial)) + + return [collapsed, () => setCollapsed((was) => !was)] +} + +/** + * @typedef {object} ChevronProps + * @property {boolean} [collapsed] - Folded: turned a quarter to point at what it belongs to. + * @property {string} [className] - Added to the component's own classes. + */ +/** + * The fold's chevron: pointing down when open, turned a quarter when folded, as a + * `Panel`'s is. Decoration -- the button it sits in says what it does, with + * `aria-expanded` saying which way it is. + * + * @example + * + * + * @param {ChevronProps & import("react").SVGAttributes} props + */ +export function Chevron({ collapsed = false, className, ...rest }) { + return +} diff --git a/packages/core/src/components/control/Confirm.jsx b/packages/core/src/components/control/Confirm.jsx index 93ff3f6e..b175e21f 100644 --- a/packages/core/src/components/control/Confirm.jsx +++ b/packages/core/src/components/control/Confirm.jsx @@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react' import { cx } from '../../toolkits/cx' import { ARMED_TONE, toneClass } from '../../toolkits/tone' +import { SMALL } from './Label' const ARMED = 4000 @@ -12,6 +13,7 @@ const ARMED = 4000 * @property {string} [ask] - What it says once armed. Defaults to `"Click to confirm"`. * @property {'danger'|'warn'|'go'|'primary'|'quiet'} [tone] - What the button means. Defaults to `"danger"`. * @property {boolean} [disabled] - Nothing happens on click. + * @property {'md'|'sm'} [size] - `"sm"` for a Panel's header: smaller, off the input line, never under a 24px target. Defaults to `"md"`. * @property {import("react").ReactNode} [children] - Replaces `label` when idle. * @property {string} [className] - Added to the component's own classes. */ @@ -77,7 +79,7 @@ const ASK = 'Click to confirm' * * @param {ConfirmProps & import("react").ButtonHTMLAttributes} props */ -export function Confirm({ onConfirm, label, ask = ASK, tone = 'danger', disabled, className, children, ...rest }) { +export function Confirm({ onConfirm, label, ask = ASK, tone = 'danger', size = 'md', disabled, className, children, ...rest }) { const [armed, setArmed] = useState(false) const timer = useRef(null) @@ -118,7 +120,8 @@ export function Confirm({ onConfirm, label, ask = ASK, tone = 'danger', disabled disabled={disabled} data-armed={armed ? '' : undefined} className={cx( - 'ss-confirm rounded-md px-3 py-1.5 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-40', + 'ss-confirm rounded-md font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-40', + size === 'sm' ? SMALL : 'px-3 py-1.5 text-sm', armed ? ARMED_TONE : looks, className, )} diff --git a/packages/core/src/components/control/Label.jsx b/packages/core/src/components/control/Label.jsx index b8ec16c4..219f3cb9 100644 --- a/packages/core/src/components/control/Label.jsx +++ b/packages/core/src/components/control/Label.jsx @@ -15,6 +15,17 @@ import { useRowLabel } from './Row' export const LINE = 'h-[var(--ss-input-h,2.5rem)]' export const MIN_LINE = 'min-h-[var(--ss-input-h,2.5rem)]' +/** + * A button sized for a Panel's header rather than for a Row: `size="sm"` on + * Confirm, ResetButton, SwapButton and Toggle. + * + * Off the input line on purpose -- nothing in a header lines up with a field -- but + * never under 24px tall, the smallest pointer target WCAG 2.5.8 allows. Before + * this, a studio got a header-sized button by overriding the line height away with + * `min-h-0! px-2.5! py-1! text-xs!`, and `min-h-0` has no floor at all. + */ +export const SMALL = 'min-h-6 px-2.5 py-1 text-xs' + /** * A control's label line. One component so every control's line is the same height: * a Row lines its controls up by assuming it, and a label one pixel taller than its diff --git a/packages/core/src/components/control/Panel.jsx b/packages/core/src/components/control/Panel.jsx index cd8846f8..6c9b7573 100644 --- a/packages/core/src/components/control/Panel.jsx +++ b/packages/core/src/components/control/Panel.jsx @@ -1,8 +1,14 @@ +import { useId } from 'react' + import { cx } from '../../toolkits/cx' +import { Chevron, useCollapsed } from './Collapse' /** * @typedef {object} PanelProps * @property {string} [title] - Heading for the group. + * @property {import("react").ReactNode | ((state: { open: boolean }) => import("react").ReactNode)} [actions] - Anything at all, right-aligned on the title line: a reset, a swap, a status light. Or a function that returns it, given whether the panel is open. + * @property {boolean} [collapsible] - The title folds the panel away and opens it again. Defaults to `false`. + * @property {boolean} [defaultOpen] - A collapsible panel starts folded on every load; `true` starts this one open. Defaults to `false`. * @property {import("react").ReactNode} [children] - Controls, one row each. Put several in a `Row` to share a line. * @property {string} [className] - Added to the component's own classes. */ @@ -16,8 +22,32 @@ import { cx } from '../../toolkits/cx' * at every width. To put controls side by side when there is room, put them in a * `Row`, which stacks them again when there is not. * + * **`actions`** go on the title line, at the right: anything, not only buttons. They + * stay in the header, and keep working, while the panel is folded. In a dock too + * narrow for both, they drop under the title rather than squeezing it. + * + * **`collapsible`** makes the title a button that folds the body away. Folded is + * where every collapsible panel starts, on every load: a fresh board is a short + * list of headers, and the operator opens what the next segment needs. The fold is + * this page's alone -- not the show's, which would fold the panel on every + * operator's board, and not remembered, so a reload is always the same board. + * Folding hides the controls without unmounting them, so a draft typed into a + * folded panel is still there when it opens. + * + * Without a `title`, `actions` or `collapsible`, there is no header at all. Actions + * without a title still get their line; a collapsible panel without one gets a + * chevron on its own to fold it by. + * + * A button in `actions` takes `size="sm"` -- Confirm, ResetButton, SwapButton and + * Toggle all have it -- to sit in the header rather than on a Row's input line. + * `useCollapsed` and `Chevron` fold anything else on the board the same way. + * + * Every other panel is a shade lighter, so a long board reads as blocks. The stripes + * count sibling panels: render panels directly, or in a fragment, rather than each in + * an element of its own. + * * The panel's own box is `.ss-panel-body` in the stylesheet if you want to take it - * further. + * further; the title line is `.ss-panel-header`, the actions `.ss-panel-actions`. * * @example * @@ -28,13 +58,71 @@ import { cx } from '../../toolkits/cx' * * * + * @example + * // Folded until the operator opens it, with its reset reachable either way + * }> + * + * + * + * + * @example + * // Actions that change with the fold: the swap only while the names are in view + * open && }> + * … + * + * * @param {PanelProps & import("react").HTMLAttributes} props */ -export function Panel({ title, children, className, ...rest }) { +export function Panel({ title, actions, collapsible = false, defaultOpen = false, children, className, ...rest }) { + const [collapsed, toggle] = useCollapsed(!defaultOpen) + const body = useId() + + // Read every render rather than stored, so a panel that stops being collapsible + // is simply open, whatever it was folded to. + const folded = collapsible && collapsed + const tools = typeof actions === 'function' ? actions({ open: !folded }) : actions + const hasTools = tools !== undefined && tools !== null && tools !== false && tools !== '' + + // The button goes inside the heading, never the other way round: a heading inside + // a button is invalid HTML, and a screen reader's list of headings loses it. The + // negative margin and padding give it a bigger target without moving the title. + const fold = (label) => ( + + ) + + const heading = title ? ( +

{collapsible ? fold(title) : title}

+ ) : collapsible ? ( + {fold(null)} + ) : null + return ( -
- {title ?

{title}

: null} -
{children}
+
+ {heading || hasTools ? ( + // Wraps rather than shrinking the title: in a slim dock the actions take the + // next line, still at the right, and the title keeps its words. +
+ {heading} + {hasTools ?
{tools}
: null} +
+ ) : null} +
) } diff --git a/packages/core/src/components/control/ResetButton.jsx b/packages/core/src/components/control/ResetButton.jsx index 2820566d..2e3db481 100644 --- a/packages/core/src/components/control/ResetButton.jsx +++ b/packages/core/src/components/control/ResetButton.jsx @@ -1,7 +1,7 @@ import { useVelcroMutate } from '../../hooks/useVelcroMutate' import { qualify } from '../../toolkits/address' import { cx } from '../../toolkits/cx' -import { MIN_LINE } from './Label' +import { MIN_LINE, SMALL } from './Label' import { Confirm } from './Confirm' /** Where this component's values live. Not a prop: a studio never needs another. */ @@ -14,6 +14,7 @@ const NAMESPACE = 'variables' * @property {string} [label] - Names what gets cleared: the button reads "Reset `