@crewlethq/ui 0.76.0 → 0.76.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -205,6 +205,12 @@ places:
205
205
  - A surface that pans or zooms dispatches `LAYER_REPOSITION_EVENT` on its host
206
206
  when the content beneath it moves, and every panel anchored inside follows.
207
207
 
208
+ ## Changes in 0.76.1
209
+
210
+ **`SetupWizard`'s row of steps on a phone has a name of its own.** While the row hides steps it takes the keyboard to scroll, as a group named by the new `labels.stepsRow` ("All steps, scrolls sideways"), rather than by `stepsLabel` again, which already names the navigation around it and the list inside it.
211
+
212
+ **The row's edge fade never dims a focus ring.** The row's ring is drawn by a frame around it (`crewlet-setup-wizard__steps-frame`), which the fade does not reach, and a step that takes focus is scrolled clear of the faded edges, by the fade's width (the row's `scroll-padding-inline`), in either direction.
213
+
208
214
  ## Changes in 0.76.0
209
215
 
210
216
  **`ChoiceCards`, choices a person compares.** One answer from a few, each a card of the same lines: a picture (`media`), the choice's name (`label`) and its `tag`; one line of `description`; and a few `facts`, one to a line, each a name and a value, with an optional `tone` drawn as a `StatusDot` beside words that say the state. Cards side by side share every line on a CSS subgrid, so their facts stand level; where subgrid is not supported each card is a plain column. A description row is held only when some card has one, and a card without one keeps it empty. `minCardWidth` is the narrowest a card stands (`md`, 20rem, the default, or `lg`, 24rem), which decides how many share a row. The radios are native, so the arrow keys move the choice; each is named by its choice and tag alone and described by the rest of the card. A card that cannot be chosen (`disabled`) stands on the page's ground with a quiet radio and a muted picture, and says why in `disabledReason`, on its last line or, with `disabledReasonAt`, in place of a fact's value, read with the fact's name. A chosen card lays the selected tint over its raised surface inside an edge in the accent's ink. Under forced colours the radio is the platform's own, the chosen card's edge the system's selection colour and an unavailable card the system's disabled ink. The types are `ChoiceCardsProps`, `ChoiceCardOption`, `ChoiceCardFact` and `ChoiceCardsMinWidth`.
@@ -6,6 +6,13 @@ export interface SetupWizardLabels {
6
6
  progress: (position: number, total: number) => string;
7
7
  /** The disclosure the aside folds into on a phone: "Preview". */
8
8
  asideToggle: string;
9
+ /**
10
+ * The phone's row of steps while it hides some and takes the keyboard to
11
+ * scroll: what that stop is, in words of its own. The navigation around it
12
+ * is already named by `stepsLabel`, and the list inside it too, so the row
13
+ * repeating that name was the same words three times over.
14
+ */
15
+ stepsRow?: string;
9
16
  }
10
17
  export interface SetupWizardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'onSelect'> {
11
18
  /** What is being set up: "Set up your brand". The page's heading. */
@@ -1,4 +1,4 @@
1
- export { SetupWizard } from '../chunk-X5FKORDM.js';
1
+ export { SetupWizard } from '../chunk-5EFLA6BW.js';
2
2
  import '../chunk-RJWO2PC5.js';
3
3
  import '../chunk-FPDIHEM4.js';
4
4
  import '../chunk-CYQBDZ6M.js';
@@ -309,14 +309,27 @@
309
309
  * THE SCROLLER SAYS IT SCROLLS: with its bar hidden, an edge that hides
310
310
  * steps fades out, at whichever side has steps beyond it, so a row cut at
311
311
  * the screen's edge reads as more to come rather than as the last step.
312
+ *
313
+ * THE FADE NEVER DIMS A FOCUS RING. A mask fades everything the element
314
+ * paints, its own outline included, so the scroller's ring is drawn by the
315
+ * frame around it, which is not masked. And a pill that takes focus is
316
+ * scrolled clear of the faded edges, so its ring is never in the fade
317
+ * either: `scroll-padding-inline` is the fade's own width, which the wizard
318
+ * reads to place it (SetupWizard.tsx, `clearOfFade`).
312
319
  */
313
- .crewlet-setup-wizard__steps {
320
+ .crewlet-setup-wizard__steps-frame {
314
321
  --crewlet-setup-wizard-fade: var(--spacing-6);
322
+
323
+ min-width: 0;
324
+ }
325
+
326
+ .crewlet-setup-wizard__steps {
315
327
  --crewlet-setup-wizard-fade-left: 0px;
316
328
  --crewlet-setup-wizard-fade-right: 0px;
317
329
 
318
330
  min-width: 0;
319
331
  overflow-x: auto;
332
+ scroll-padding-inline: var(--crewlet-setup-wizard-fade);
320
333
  overscroll-behavior-x: contain;
321
334
  scrollbar-width: none;
322
335
  -webkit-mask-image: linear-gradient(
@@ -347,8 +360,20 @@
347
360
  --crewlet-setup-wizard-fade-right: var(--crewlet-setup-wizard-fade);
348
361
  }
349
362
 
350
- /* A row that overflows takes the keyboard, so it can be scrolled without a pointer; it draws no edge, so the ring sits inside it. */
363
+ /*
364
+ * A row that overflows takes the keyboard, so it can be scrolled without a
365
+ * pointer. Its ring is the frame's, unmasked, inside the frame's edge, and
366
+ * the frame's ring is the one that carries the focus in a forced-colours
367
+ * mode too. The scroller's own outline is transparent only to turn off the
368
+ * browser's default ring, which would sit under the mask, dimmed, beside
369
+ * the frame's (transparent rather than none, as the kit's focus check asks
370
+ * of every focus style).
371
+ */
351
372
  .crewlet-setup-wizard__steps:focus-visible {
373
+ outline: 2px solid transparent;
374
+ }
375
+
376
+ .crewlet-setup-wizard__steps-frame:has(> .crewlet-setup-wizard__steps:focus-visible) {
352
377
  outline: 2px solid var(--color-focus);
353
378
  outline-offset: var(--size-focus-ring-inset-offset);
354
379
  }
@@ -4,14 +4,15 @@ import { Disclosure } from './chunk-222VILSP.js';
4
4
  import { useHeadingLevel, nextHeadingLevel, headingTag, HeadingLevelProvider } from './chunk-PQADV6P6.js';
5
5
  import { useMediaMatch } from './chunk-RTPQDNW3.js';
6
6
  import { cx } from './chunk-ODEQTPFM.js';
7
- import './SetupWizard-2SUMQEWU.css';
7
+ import './SetupWizard-Y2PFPBCZ.css';
8
8
  import { useId, useRef, useState, useEffect, useCallback, createElement } from 'react';
9
9
  import { breakpoint } from '@crewlethq/tokens';
10
10
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
11
11
 
12
12
  var LABELS = {
13
13
  progress: (position, total) => `Step ${position} of ${total}`,
14
- asideToggle: "Preview"
14
+ asideToggle: "Preview",
15
+ stepsRow: "All steps, scrolls sideways"
15
16
  };
16
17
  var PHONE = `(width < ${breakpoint.phone})`;
17
18
  var STILL_ROW = { overflows: false, left: false, right: false };
@@ -24,6 +25,15 @@ function stepRowOf(scroller) {
24
25
  const right = Math.max(...boxes.map((r) => r.right)) > box.right + 0.5;
25
26
  return { overflows: scroller.scrollWidth > scroller.clientWidth + 0.5 || left || right, left, right };
26
27
  }
28
+ function clearOfFade(scroller, step) {
29
+ const fade = Number.parseFloat(getComputedStyle(scroller).scrollPaddingInlineStart) || 0;
30
+ const box = scroller.getBoundingClientRect();
31
+ const drawn = step.getBoundingClientRect();
32
+ const pastRight = drawn.right - (box.right - fade);
33
+ const pastLeft = box.left + fade - drawn.left;
34
+ if (pastRight > 0) scroller.scrollLeft += pastRight;
35
+ else if (pastLeft > 0) scroller.scrollLeft -= pastLeft;
36
+ }
27
37
  function SetupWizard({
28
38
  title,
29
39
  headingLevel,
@@ -147,8 +157,10 @@ function SetupWizard({
147
157
  phone ? (
148
158
  // A ROW THAT OVERFLOWS IS A SCROLLER THE KEYBOARD CAN REACH: it
149
159
  // takes focus and a name only while it hides steps, since a pill
150
- // that only labels its step takes no focus of its own.
151
- /* @__PURE__ */ jsx(
160
+ // that only labels its step takes no focus of its own. Its focus
161
+ // ring is drawn by the frame around it, which the edge fade does
162
+ // not reach (SetupWizard.css).
163
+ /* @__PURE__ */ jsx("div", { className: "crewlet-setup-wizard__steps-frame", children: /* @__PURE__ */ jsx(
152
164
  "div",
153
165
  {
154
166
  ref: stepsRef,
@@ -156,10 +168,16 @@ function SetupWizard({
156
168
  "data-fade-left": row.left ? "" : void 0,
157
169
  "data-fade-right": row.right ? "" : void 0,
158
170
  onScroll: measure,
159
- ...row.overflows ? { tabIndex: 0, role: "group", "aria-label": stepsLabel } : {},
171
+ onFocus: (event) => {
172
+ const step2 = event.target.closest("li");
173
+ if (event.target === event.currentTarget || !step2 || !event.currentTarget.contains(step2)) return;
174
+ clearOfFade(event.currentTarget, step2);
175
+ measure();
176
+ },
177
+ ...row.overflows ? { tabIndex: 0, role: "group", "aria-label": text.stepsRow } : {},
160
178
  children: stepper
161
179
  }
162
- )
180
+ ) })
163
181
  ) : stepper
164
182
  ] }),
165
183
  /* @__PURE__ */ jsxs("section", { className: "crewlet-setup-wizard__content", "aria-labelledby": headingId, children: [
@@ -31,13 +31,11 @@ export { TagGroup } from '../chunk-UEOEBOBR.js';
31
31
  export { matchesShortcut, useShortcut } from '../chunk-AYHIF6DH.js';
32
32
  import '../chunk-73GUXYYE.js';
33
33
  export { SLIDE_COMPARE_LABELS, SlideCompare } from '../chunk-DV5L2GRY.js';
34
- export { SectionFrame, SectionFrameBody, SectionFrameNav } from '../chunk-G4CGDUCP.js';
35
- export { NavGroup, NavItem, SidebarNav } from '../chunk-WCDPFTJZ.js';
36
34
  export { SegmentedMeter, segmentedMeterLabel } from '../chunk-4AK52VHB.js';
37
35
  export { SelectionBar } from '../chunk-L4HHWG4V.js';
38
36
  export { SEND_BUTTON_LABELS, SendButton } from '../chunk-T2GC7UAP.js';
39
37
  export { SettingsTable, SettingsTableRow } from '../chunk-WL4TC4YZ.js';
40
- export { SetupWizard } from '../chunk-X5FKORDM.js';
38
+ export { SetupWizard } from '../chunk-5EFLA6BW.js';
41
39
  export { ARTIFACT_ROLES, SHAREABLE_ROLES, ShareDialog, roleAtLeast } from '../chunk-Q2T4L6ML.js';
42
40
  export { Select } from '../chunk-VK45YJLH.js';
43
41
  export { ReviewList } from '../chunk-NKBKQEKS.js';
@@ -48,6 +46,8 @@ import '../chunk-7CSTEH5I.js';
48
46
  export { RUN_STATES, RunChip } from '../chunk-GPFYITOF.js';
49
47
  export { FRAME_HELLO_TIMEOUT_MS, FRAME_LOAD_GRACE_MS, SandboxedFrame, SandboxedFrameNotice, SandboxedFramePlaceholder } from '../chunk-DGSK4LLS.js';
50
48
  export { SearchTrigger } from '../chunk-ERNT3UTB.js';
49
+ export { SectionFrame, SectionFrameBody, SectionFrameNav } from '../chunk-G4CGDUCP.js';
50
+ export { NavGroup, NavItem, SidebarNav } from '../chunk-WCDPFTJZ.js';
51
51
  export { ReadingPane, ReadingPaneByline, ReadingPaneLead, ReadingPaneOutcome, ReadingPaneQuote, ReadingPaneReasoning, ReadingPaneSection, ReadingPaneTitle } from '../chunk-Q7N2ATMC.js';
52
52
  export { RecordChip, RecordChipList } from '../chunk-BFM5G2Z6.js';
53
53
  export { REFUSAL_CARD_LABELS, RefusalCard } from '../chunk-UYF2B2TH.js';
@@ -232,6 +232,7 @@ export { ARTIFACT_KINDS, ArtifactKindGlyph } from '../chunk-RSKW25Y5.js';
232
232
  export { ACCESS_DENIED_REASONS, AccessDeniedCard } from '../chunk-QTMUKRTN.js';
233
233
  export { EmptyState } from '../chunk-PPUR46MK.js';
234
234
  export { Accordion, AccordionItem } from '../chunk-5NJMSYDZ.js';
235
+ export { ADD_PILL_MS, AddPill } from '../chunk-CVKANYHD.js';
235
236
  export { AGENT_HANDOFF_LABELS, AgentHandoffPanel } from '../chunk-57AKYFML.js';
236
237
  export { Timeline, TimelineItem } from '../chunk-4MJN2KN6.js';
237
238
  export { Section } from '../chunk-WFCWPXJN.js';
@@ -245,7 +246,6 @@ export { CHARACTER_COUNT_LABELS, CHARACTER_COUNT_NEAR, CharacterCount, character
245
246
  export { DisclosureAnatomy } from '../chunk-IVTQWOXV.js';
246
247
  export { Count } from '../chunk-MLDTLZAO.js';
247
248
  export { HeadingLevelProvider, headingTag, nextHeadingLevel, useHeadingLevel } from '../chunk-PQADV6P6.js';
248
- export { ADD_PILL_MS, AddPill } from '../chunk-CVKANYHD.js';
249
249
  export { Announcer, announce, useAnnouncer } from '../chunk-2G355KDJ.js';
250
250
  export { APP_SHELL_ASIDE_WIDTH, AccountRow, AppShell, AppShellRail, AppShellRailRow, AppShellRailStatus, AppShellToggle, AppShellTopbar, readStoredWidth, useAppShell, useScreenBelow, useScreenWidth, useStoredWidth } from '../chunk-UY3FWQWG.js';
251
251
  export { useMediaMatch } from '../chunk-RTPQDNW3.js';
package/dist/index.js CHANGED
@@ -31,13 +31,11 @@ export { TagGroup } from './chunk-UEOEBOBR.js';
31
31
  export { matchesShortcut, useShortcut } from './chunk-AYHIF6DH.js';
32
32
  import './chunk-73GUXYYE.js';
33
33
  export { SLIDE_COMPARE_LABELS, SlideCompare } from './chunk-DV5L2GRY.js';
34
- export { SectionFrame, SectionFrameBody, SectionFrameNav } from './chunk-G4CGDUCP.js';
35
- export { NavGroup, NavItem, SidebarNav } from './chunk-WCDPFTJZ.js';
36
34
  export { SegmentedMeter, segmentedMeterLabel } from './chunk-4AK52VHB.js';
37
35
  export { SelectionBar } from './chunk-L4HHWG4V.js';
38
36
  export { SEND_BUTTON_LABELS, SendButton } from './chunk-T2GC7UAP.js';
39
37
  export { SettingsTable, SettingsTableRow } from './chunk-WL4TC4YZ.js';
40
- export { SetupWizard } from './chunk-X5FKORDM.js';
38
+ export { SetupWizard } from './chunk-5EFLA6BW.js';
41
39
  export { ARTIFACT_ROLES, SHAREABLE_ROLES, ShareDialog, roleAtLeast } from './chunk-Q2T4L6ML.js';
42
40
  export { Select } from './chunk-VK45YJLH.js';
43
41
  export { ReviewList } from './chunk-NKBKQEKS.js';
@@ -48,6 +46,8 @@ import './chunk-7CSTEH5I.js';
48
46
  export { RUN_STATES, RunChip } from './chunk-GPFYITOF.js';
49
47
  export { FRAME_HELLO_TIMEOUT_MS, FRAME_LOAD_GRACE_MS, SandboxedFrame, SandboxedFrameNotice, SandboxedFramePlaceholder } from './chunk-DGSK4LLS.js';
50
48
  export { SearchTrigger } from './chunk-ERNT3UTB.js';
49
+ export { SectionFrame, SectionFrameBody, SectionFrameNav } from './chunk-G4CGDUCP.js';
50
+ export { NavGroup, NavItem, SidebarNav } from './chunk-WCDPFTJZ.js';
51
51
  export { ReadingPane, ReadingPaneByline, ReadingPaneLead, ReadingPaneOutcome, ReadingPaneQuote, ReadingPaneReasoning, ReadingPaneSection, ReadingPaneTitle } from './chunk-Q7N2ATMC.js';
52
52
  export { RecordChip, RecordChipList } from './chunk-BFM5G2Z6.js';
53
53
  export { REFUSAL_CARD_LABELS, RefusalCard } from './chunk-UYF2B2TH.js';
@@ -232,6 +232,7 @@ export { ARTIFACT_KINDS, ArtifactKindGlyph } from './chunk-RSKW25Y5.js';
232
232
  export { ACCESS_DENIED_REASONS, AccessDeniedCard } from './chunk-QTMUKRTN.js';
233
233
  export { EmptyState } from './chunk-PPUR46MK.js';
234
234
  export { Accordion, AccordionItem } from './chunk-5NJMSYDZ.js';
235
+ export { ADD_PILL_MS, AddPill } from './chunk-CVKANYHD.js';
235
236
  export { AGENT_HANDOFF_LABELS, AgentHandoffPanel } from './chunk-57AKYFML.js';
236
237
  export { Timeline, TimelineItem } from './chunk-4MJN2KN6.js';
237
238
  export { Section } from './chunk-WFCWPXJN.js';
@@ -245,7 +246,6 @@ export { CHARACTER_COUNT_LABELS, CHARACTER_COUNT_NEAR, CharacterCount, character
245
246
  export { DisclosureAnatomy } from './chunk-IVTQWOXV.js';
246
247
  export { Count } from './chunk-MLDTLZAO.js';
247
248
  export { HeadingLevelProvider, headingTag, nextHeadingLevel, useHeadingLevel } from './chunk-PQADV6P6.js';
248
- export { ADD_PILL_MS, AddPill } from './chunk-CVKANYHD.js';
249
249
  export { Announcer, announce, useAnnouncer } from './chunk-2G355KDJ.js';
250
250
  export { APP_SHELL_ASIDE_WIDTH, AccountRow, AppShell, AppShellRail, AppShellRailRow, AppShellRailStatus, AppShellToggle, AppShellTopbar, readStoredWidth, useAppShell, useScreenBelow, useScreenWidth, useStoredWidth } from './chunk-UY3FWQWG.js';
251
251
  export { useMediaMatch } from './chunk-RTPQDNW3.js';
package/dist/styles.css CHANGED
@@ -31431,7 +31431,7 @@ svg.crewlet-select__chevron {
31431
31431
  }
31432
31432
 
31433
31433
 
31434
- /* SetupWizard-2SUMQEWU.css */
31434
+ /* SetupWizard-Y2PFPBCZ.css */
31435
31435
  /*
31436
31436
  * SetupWizard: a setup walked step by step on a page of its own
31437
31437
  * (SetupWizard.tsx).
@@ -31743,14 +31743,27 @@ svg.crewlet-select__chevron {
31743
31743
  * THE SCROLLER SAYS IT SCROLLS: with its bar hidden, an edge that hides
31744
31744
  * steps fades out, at whichever side has steps beyond it, so a row cut at
31745
31745
  * the screen's edge reads as more to come rather than as the last step.
31746
+ *
31747
+ * THE FADE NEVER DIMS A FOCUS RING. A mask fades everything the element
31748
+ * paints, its own outline included, so the scroller's ring is drawn by the
31749
+ * frame around it, which is not masked. And a pill that takes focus is
31750
+ * scrolled clear of the faded edges, so its ring is never in the fade
31751
+ * either: `scroll-padding-inline` is the fade's own width, which the wizard
31752
+ * reads to place it (SetupWizard.tsx, `clearOfFade`).
31746
31753
  */
31747
- .crewlet-setup-wizard__steps {
31754
+ .crewlet-setup-wizard__steps-frame {
31748
31755
  --crewlet-setup-wizard-fade: var(--spacing-6);
31756
+
31757
+ min-width: 0;
31758
+ }
31759
+
31760
+ .crewlet-setup-wizard__steps {
31749
31761
  --crewlet-setup-wizard-fade-left: 0px;
31750
31762
  --crewlet-setup-wizard-fade-right: 0px;
31751
31763
 
31752
31764
  min-width: 0;
31753
31765
  overflow-x: auto;
31766
+ scroll-padding-inline: var(--crewlet-setup-wizard-fade);
31754
31767
  overscroll-behavior-x: contain;
31755
31768
  scrollbar-width: none;
31756
31769
  -webkit-mask-image: linear-gradient(
@@ -31781,8 +31794,20 @@ svg.crewlet-select__chevron {
31781
31794
  --crewlet-setup-wizard-fade-right: var(--crewlet-setup-wizard-fade);
31782
31795
  }
31783
31796
 
31784
- /* A row that overflows takes the keyboard, so it can be scrolled without a pointer; it draws no edge, so the ring sits inside it. */
31797
+ /*
31798
+ * A row that overflows takes the keyboard, so it can be scrolled without a
31799
+ * pointer. Its ring is the frame's, unmasked, inside the frame's edge, and
31800
+ * the frame's ring is the one that carries the focus in a forced-colours
31801
+ * mode too. The scroller's own outline is transparent only to turn off the
31802
+ * browser's default ring, which would sit under the mask, dimmed, beside
31803
+ * the frame's (transparent rather than none, as the kit's focus check asks
31804
+ * of every focus style).
31805
+ */
31785
31806
  .crewlet-setup-wizard__steps:focus-visible {
31807
+ outline: 2px solid transparent;
31808
+ }
31809
+
31810
+ .crewlet-setup-wizard__steps-frame:has(> .crewlet-setup-wizard__steps:focus-visible) {
31786
31811
  outline: 2px solid var(--color-focus);
31787
31812
  outline-offset: var(--size-focus-ring-inset-offset);
31788
31813
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewlethq/ui",
3
- "version": "0.76.0",
3
+ "version": "0.76.1",
4
4
  "description": "Crewlet shared React components built on @crewlethq/tokens and @crewlethq/icons.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -42,8 +42,8 @@
42
42
  "clean": "rm -rf dist src/generated"
43
43
  },
44
44
  "dependencies": {
45
- "@crewlethq/icons": "0.76.0",
46
- "@crewlethq/tokens": "0.76.0",
45
+ "@crewlethq/icons": "0.76.1",
46
+ "@crewlethq/tokens": "0.76.1",
47
47
  "prosemirror-commands": "1.7.2",
48
48
  "prosemirror-gapcursor": "1.4.1",
49
49
  "prosemirror-history": "1.5.1",