@crewlethq/ui 0.39.0 → 0.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +47 -1
  2. package/dist/AppShell/AppShell.d.ts +13 -2
  3. package/dist/AppShell/index.js +1 -1
  4. package/dist/{AppShell-NDU4XMGF.css → AppShell-VE2MAV42.css} +71 -7
  5. package/dist/ArtifactBar/index.js +6 -5
  6. package/dist/AvatarPicker/index.js +10 -6
  7. package/dist/CatalogTile/index.js +1 -1
  8. package/dist/{CatalogTile-TGR2ZZVQ.css → CatalogTile-XL4XLFUH.css} +8 -4
  9. package/dist/CharacterPicker/index.js +2 -2
  10. package/dist/Charts/Histogram.d.ts +71 -0
  11. package/dist/Charts/index.d.ts +2 -0
  12. package/dist/Charts/index.js +1 -1
  13. package/dist/{Charts-MBJG72BS.css → Charts-TZ744HT6.css} +118 -0
  14. package/dist/DataGrid/DataGrid.d.ts +267 -0
  15. package/dist/DataGrid/index.d.ts +3 -0
  16. package/dist/DataGrid/index.js +7 -0
  17. package/dist/DataGrid-AQKPJD7W.css +482 -0
  18. package/dist/HuePicker/index.js +11 -4
  19. package/dist/LayersList/index.js +2 -2
  20. package/dist/LockedLabel/LockedLabel.d.ts +34 -0
  21. package/dist/LockedLabel/index.d.ts +3 -0
  22. package/dist/LockedLabel/index.js +3 -0
  23. package/dist/LockedLabel-VQEGEB7G.css +16 -0
  24. package/dist/MarkdownEditor/index.js +8 -5
  25. package/dist/MergeConflicts/index.js +11 -5
  26. package/dist/PageNote/PageNote.d.ts +14 -0
  27. package/dist/PageNote/index.d.ts +3 -0
  28. package/dist/PageNote/index.js +2 -0
  29. package/dist/PageNote-RHLXH3ML.css +12 -0
  30. package/dist/PropertyList/PropertyList.d.ts +88 -0
  31. package/dist/PropertyList/index.d.ts +3 -0
  32. package/dist/PropertyList/index.js +7 -0
  33. package/dist/PropertyList-SCYM66LL.css +96 -0
  34. package/dist/RelatedRow/RelatedRow.d.ts +27 -0
  35. package/dist/RelatedRow/index.d.ts +3 -0
  36. package/dist/RelatedRow/index.js +9 -0
  37. package/dist/RelatedRow-2FVS6QQA.css +6 -0
  38. package/dist/SectionFrame/SectionFrame.d.ts +76 -0
  39. package/dist/SectionFrame/index.d.ts +3 -0
  40. package/dist/SectionFrame/index.js +7 -0
  41. package/dist/SectionFrame-M62UUVFQ.css +164 -0
  42. package/dist/SegmentedControl/index.js +11 -4
  43. package/dist/SheetColumnEditor/index.js +6 -5
  44. package/dist/SheetGrid/index.js +2 -2
  45. package/dist/SidebarNav/SidebarNav.d.ts +35 -2
  46. package/dist/SidebarNav/index.js +2 -1
  47. package/dist/{SidebarNav-7SGUZYKK.css → SidebarNav-YI74SPDC.css} +10 -0
  48. package/dist/Tabs/Tabs.d.ts +55 -1
  49. package/dist/Tabs/fold.d.ts +54 -0
  50. package/dist/Tabs/index.d.ts +1 -1
  51. package/dist/Tabs/index.js +9 -2
  52. package/dist/Tabs/roving.d.ts +3 -3
  53. package/dist/{Tabs-WIF7457H.css → Tabs-S7F4RWAN.css} +77 -0
  54. package/dist/TagsInput/index.js +1 -1
  55. package/dist/ThemeGallery/index.js +2 -2
  56. package/dist/ThemeSwitcher/index.js +12 -5
  57. package/dist/VersionCompare/index.js +12 -5
  58. package/dist/{chunk-B5J23C73.js → chunk-2PEXZ7L4.js} +1 -1
  59. package/dist/{chunk-TZYFTC4M.js → chunk-2VOHUGUA.js} +1 -1
  60. package/dist/{chunk-2QZ2VPHR.js → chunk-4OK4DX3V.js} +2 -2
  61. package/dist/{chunk-ECGGQX2H.js → chunk-A44YHLCQ.js} +2 -2
  62. package/dist/{chunk-F4LPQA4E.js → chunk-BA32AVKS.js} +1 -1
  63. package/dist/chunk-CV4NES3P.js +9 -0
  64. package/dist/{chunk-PPHTIKZO.js → chunk-E4BOVKNJ.js} +1 -1
  65. package/dist/{chunk-W7PAIVAP.js → chunk-E7QLG3PG.js} +30 -9
  66. package/dist/{chunk-RKKO7QHH.js → chunk-EGMQURBR.js} +5 -4
  67. package/dist/{chunk-GMW4ZHST.js → chunk-EX22Q5SH.js} +2 -2
  68. package/dist/{chunk-F3WZ5AKM.js → chunk-GFYQKMQZ.js} +1 -1
  69. package/dist/{chunk-DYB66CNM.js → chunk-GR4YM7HT.js} +6 -3
  70. package/dist/{chunk-HG5VF3TZ.js → chunk-HMVQQEHW.js} +91 -2
  71. package/dist/chunk-INVSUFJK.js +71 -0
  72. package/dist/{chunk-4LRAJXHL.js → chunk-JSWOXRRE.js} +1 -1
  73. package/dist/{chunk-ZMDYQ2HH.js → chunk-KWWP3D7M.js} +1 -1
  74. package/dist/{chunk-46RL3E54.js → chunk-M6NQ4XGK.js} +1 -1
  75. package/dist/chunk-OHGH7O6T.js +19 -0
  76. package/dist/{chunk-4ISUIS6F.js → chunk-OOC7Q67K.js} +1 -1
  77. package/dist/{chunk-XNXK5MA4.js → chunk-PCT47E7O.js} +1 -1
  78. package/dist/{chunk-YEVG2X5R.js → chunk-SNQRGH4A.js} +1 -1
  79. package/dist/chunk-T6MXORP5.js +116 -0
  80. package/dist/chunk-X4IXXLWM.js +306 -0
  81. package/dist/chunk-XIDD7PJO.js +416 -0
  82. package/dist/chunk-ZUTUI27P.js +21 -0
  83. package/dist/generated/index.d.ts +6 -0
  84. package/dist/generated/index.js +62 -56
  85. package/dist/index.js +62 -56
  86. package/dist/styles.css +1087 -20
  87. package/package.json +3 -3
  88. package/dist/chunk-7SDUKCNW.js +0 -141
  89. package/dist/{chunk-JZPK4IXR.js → chunk-DKNBGL26.js} +1 -1
  90. /package/dist/{chunk-QRLIWCLX.js → chunk-S3QTFUJX.js} +0 -0
package/README.md CHANGED
@@ -52,7 +52,7 @@ import { Button } from '@crewlethq/ui/Button'; // 8 KB of CSS, not 182 KB
52
52
  | Hooks and seams | `useModalLayer`, `usePopupLayer`, `LayerHost`, `useListbox`, `useOptionKeys`, `useHighlightRest` and `HIGHLIGHT_REST_MS`, `useClipboard`, `useDownload`, `downloadText`, `downloadBlob`, `safeFilename`, `toCsv`, `elementToPng` and `ElementToPngError`, `useOutcome`, `useAnnouncer`, `useNow`, `formatRelative`, `HeadingLevelProvider`, `cx`, the node hues (`NODE_HUES`, `NODE_HUE_NAMES`, `isNodeHue`) |
53
53
  | Layout | `Container`, `Section`, `Card`, `TileGrid`, `Tabs`, `Accordion`, `Disclosure`, `Outline` (with `useActiveHeading` and `scrollToHeading`), `TreeGrid` |
54
54
  | Arranging a page | `ArrangeBoard`, `ArrangeGrid`, `ArrangeFrame` (with `useGridResize`), `ArrangeGroup`, `ArrangeList`, and the reorder hooks under them (`useReorder`, `useReorderLists`, `moveKey`) |
55
- | Application shell | `AppShell` with `AppShell.Rail`, `AppShell.Topbar`, `AppShell.AccountRow` and `useAppShell`, `AuthLayout`, `SidebarNav` and `NavItem`, `BrandLockup`, `SearchTrigger`, `PageHeader`, `Toolbar`, `Stack`, `Inline`, `Spacer`, `AutoGrid`, `ErrorBoundary`, `useShortcut`, `useFullscreen`, `useIdle`, `useMediaMatch` |
55
+ | Application shell | `AppShell` with `AppShell.Rail`, `SectionFrame` (with `SectionFrame.Nav` and `SectionFrame.Body`), `LockedLabel`, `AppShell.Topbar`, `AppShell.AccountRow` and `useAppShell`, `AuthLayout`, `SidebarNav` and `NavItem`, `BrandLockup`, `SearchTrigger`, `PageHeader`, `Toolbar`, `Stack`, `Inline`, `Spacer`, `AutoGrid`, `ErrorBoundary`, `useShortcut`, `useFullscreen`, `useIdle`, `useMediaMatch` |
56
56
  | Preferences | `ThemeToggle`, `ThemeSwitcher`, `DensitySwitcher`, `ThemeGallery`, `ThemeSwatch`, `useThemePreference`, `useDensityPreference`, `useSystemTheme`, `resolveTheme`, `applyStoredPreferences`, and the framework-free `applyTheme`, `applyPalette`, `applyDensity` with `THEME_PREFERENCES`, `DENSITY_PREFERENCES` and their guards |
57
57
  | Artifacts | `SandboxedFrame` (with `SandboxedFramePlaceholder` and `SandboxedFrameNotice`), `LinkInterstitial` and `openExternal`, `VersionTimeline`, `ShareDialog` with the role ladder (`ARTIFACT_ROLES`, `SHAREABLE_ROLES`, `roleAtLeast`), `PrincipalPicker`, `Principal` and `PrincipalAvatar`, `ArtifactCard`, `ArtifactKindGlyph` and `ArtifactPreviewGlyph` (the preview a card draws with no cover, for a cover with nothing to draw), `ArtifactBar` and `ArtifactVersionChip` (an open artifact's one slim bar), `ArtifactPanel` and `ArtifactWorkspace` (the panels it opens beside or over the artifact), `CanvasBoard` (a design's artboards and notes on a `Canvas`, with the visibility budget `liveBoards`), `CanvasToolbar` (a canvas's tool palette, with `toolForKey`), `LayersList` (the list of a canvas's items beside it, marked by the canvas's selection), `CanvasSchematic` (a design's cover, drawn from its boards' frames), `AccessDeniedCard`, `ArtifactIcon` (with `artifactIconGlyph` and `ARTIFACT_ICON_WORDS`), the native gallery covers (`DocCover`, `SheetCover`, `DashboardCover`), `MergeConflicts`, the dashboard pieces (`WidgetFrame`, `DatasetSource` and `DatasetSourceList`), and `FrameProtocol` (the wire between a host page and an embedded artifact, with no React in it), `ArtifactNameCell` and `ArtifactAccessLabel` (with `ArtifactAccessGlyph`) for a list of artifacts |
58
58
  | Sheets | `SheetGrid`, the cells on their own (`CheckboxCell`, `TextCell`, `LongTextCell`, `NumberCell`, `SelectCell`, `MultiSelectCell`, `PersonCell`, `StatusCell`, `DateCell`, `UrlCell`, `RatingCell`, and `SheetCell` for any type), `ChangeMark`, and `SheetModel` (every rule a sheet is drawn from, with no React in it: values, parsing, the keyboard model, TSV, the pending edits of D23) |
@@ -204,6 +204,52 @@ places:
204
204
  - A surface that pans or zooms dispatches `LAYER_REPOSITION_EVENT` on its host
205
205
  when the content beneath it moves, and every panel anchored inside follows.
206
206
 
207
+ ## Changes in 0.40.1
208
+
209
+ **The rail's foot stands on the compact step.** What a reader reaches for rarely (a settings link, a status row, who they are) takes less of the rail's height: the foot's rows are 26px (`--size-control-sm`, through `--crewlet-rail-foot-row`) where they were the nav's 30px, the foot's gap is 2px and its inset 4px where they were 4px and 8px, and the account row spends 2px around its name and detail where it spent 8px. A foot of Settings, an engine row and the account row is 101px tall where it was 133px. Only heights change: every row keeps its order, its columns, its glyphs and its words, the rows above the foot keep their 30px step, and every target still clears 24px, with the targets of neighbouring rows meeting across the gap.
210
+
211
+ ## Changes in 0.40.0
212
+
213
+ The engine dashboard's own lists, page parts and navigation pieces, moved into the kit so every application draws them from one place.
214
+
215
+ ### Lists and pages
216
+
217
+ **`DataGrid` draws a list as columns.** One track list on the grid, which every row adopts (`subgrid`), so a value always sits under the head that names it. The sort (`sort`, `onSortChange`, as `key` or `-key`) and the column set (`cols`, `onColsChange`, `key,key`, empty for the default set) are controlled, so an application keeps them wherever it keeps state (the engine keeps them in the address). A column gives way rather than starving its neighbours: `shrink` sizes it to its content up to a share of the grid, `floor` keeps the column that is the point of the list readable, and a grid that cannot fit drops columns by their `drop` rank rather than cutting them off, saying which in its foot ("Hidden to fit"). An `optional` column is off until the column set names it. A row links (`rowHref`, an overlay over the row, so a cell's own link or button still takes its own click) or activates (`onRowActivate`); `handleRef` steps and opens rows from an application's own keymap. Bands (`bands`) draw a grouped answer with each group's count, a sticky head sticks at `--crewlet-data-grid-sticky-top`, an empty grid draws `empty` as an `EmptyState`, and a grid inside a `Card` gives up its own edge. Below 640px each row is a labelled card, or one compact line with `phoneRows="compact"`. It is announced as a table, with `aria-sort` on its sortable column heads.
218
+
219
+ **`ColumnChooser` and `ChooserList`** are the checklist a reader picks what a view shows with. `ColumnChooser` takes a grid's own columns (`columnChoicesOf`) and writes the column set in declaration order; `ChooserList` is the plain checklist under it, for any set of facts a view shows (a card's fields). Both are one group named by their heading.
220
+
221
+ **`PropertyList` draws an object's facts in sections on one column**: every section's terms in one column (`--crewlet-description-list-term`, which `DescriptionList` reads too, so a page lines a list up with its key-value cards), the four kinds of absent value, a section with nothing filled in said once (`whenAllAbsent`), and one "set by" line under the last of the rows one change set (`setBy`, `change`). `headings` is `eyebrow` or `sentence`.
222
+
223
+ **`Tabs` take `overflow="menu"`**: what does not fit folds into a More menu (`moreLabel`, `moreMenuLabel`) instead of being cut, a tab with work in flight carries a pulsing mark and says so (`live`), and `note` is a sentence after the tabs that steps aside before any tab folds. Folded tabs stay measured and the arrow keys skip them. Link tabs spread `foldProps` on their link.
224
+
225
+ **`Histogram` draws where in a window things happened** (in `Charts`): one bar per bucket with its failed part inside the fill, a floor so a bucket with one event is still drawn, a date axis (`formatTick`) and the total said once (`summary`). With `onSelect` each bar is a button that narrows the window.
226
+
227
+ **`PageNote` is a screen's one quiet sentence** under its title, at the reading measure, with no word alone on its last line and no margin of its own. **`RelatedRow` is the one related place a page points at**: a quiet linked card under the page's content, its glyph and words on the card heads' columns, the whole row the link, in a `nav` named "Related".
228
+
229
+ **`NamedGlyph` draws a glyph from its name** (`@crewlethq/icons/glyphs/registry`, with `glyphFor`), for a place whose glyph is data, such as a list's empty state or a navigation entry.
230
+
231
+ ### Fixes
232
+
233
+ **Every catalogue tile's name stands on one line across a row** (`CatalogTile`). A tile without a status tag centred its name against its mark, 13px under its tagged neighbours; the name now starts at the top of the head whatever the tag.
234
+
235
+ ### Navigation
236
+
237
+ **`LockedLabel` draws a place the reader may not open**: its words with the key after them, the reason (`reason`, "Needs Admin access") as the pointer's title and read after the name, and nothing but the words where nothing is missing. A place is locked, never hidden. `mark={false}` keeps the reason and drops the key, for a place under a heading that already carries it.
238
+
239
+ **`SidebarNav.Item` takes `locked`, `title` and `onPrefetch`.** `locked` draws the row's words as a `LockedLabel`, and the row stays a link. `title` is the row's hint. `onPrefetch` is called when the pointer reaches the row and when it takes focus, on the kit's anchor and on a router's (`renderLink` is handed `title`, `onPointerEnter` and `onFocus`), so an application needs no wrapper of its own to start fetching where a row leads.
240
+
241
+ **A wholly locked group draws its key once** (`SidebarNav.Group` `locked`). The key and the reason go on the group's label, and its rows stay links in the quiet ink with no key each, still saying their own reason. A reader holding none of a column's access met a key on every row of it. A mixed group, and a run with no label to carry the key, keep it on each locked row.
242
+
243
+ **`SectionFrame` draws a workspace's sections as a column beside the screen** (`SectionFrame.Nav`, `SectionFrame.Body`). The column sticks at the view's height while the section scrolls and keeps the wheel at its ends; with `folded` (the application's decision, from its own arithmetic of what a section beside the column needs) it becomes one row naming the section the reader is on ("Settings Section: People"), which unfolds the list, folds again on a pick, and opens on an arrival the caller asks it to (`openOnArrival`). The frame takes the content column's padding back and gives it to the body, which takes the phone's 16px under 640px.
244
+
245
+ ### Application shell
246
+
247
+ **The rail has a field slot and a head that takes its width** (`AppShell.Rail`). `search` is drawn first in the rail's middle, on the nav's gutter with its glyph on the nav rows' glyph column; the head's first child takes the head's width, so a long name truncates. Both were an application's own wrapper and rule.
248
+
249
+ **The scroller is a size container** (`.crewlet-app-shell__main`, `container-type: size`), so a box inside a screen can be as tall as the view it scrolls in (`100cqb`), as `SectionFrame`'s column is.
250
+
251
+ **A fill screen keeps the reserved scrollbar gutter.** The top bar's end inset counts the gutter, so a fill screen that reserved none put the bar's controls a gutter further out than on its neighbour: with classic scrollbars they moved 11px between an org chart and its roster. An application that restated `scrollbar-gutter: auto` for a fill screen drops it.
252
+
207
253
  ## Changes in 0.39.0
208
254
 
209
255
  These come from a production polish of the engine dashboard's Settings › Limits, Company › Integrations and Company › Policies, and the same patterns everywhere else it draws them.
@@ -108,8 +108,19 @@ export declare namespace AppShell {
108
108
  var AccountRow: typeof import("./AccountRow.js").AccountRow;
109
109
  }
110
110
  export interface AppShellRailProps {
111
- /** The head of the rail, drawn at the top bar's own height. */
111
+ /**
112
+ * The head of the rail, drawn at the top bar's own height. Its first child
113
+ * takes the head's width, so a long name (a company's, in a lockup)
114
+ * truncates rather than wrapping.
115
+ */
112
116
  header?: ReactNode;
117
+ /**
118
+ * The rail's field, first in its scrolling middle: a `SearchTrigger` with
119
+ * `variant="rail"`. The slot stands it on the nav's own gutter with its
120
+ * glyph on the nav rows' glyph column, so the field needs no inset of its
121
+ * application's own.
122
+ */
123
+ search?: ReactNode;
113
124
  /**
114
125
  * The foot: what the engine is doing, the theme and density controls. An
115
126
  * `<AppShell.RailRow>` puts a line there on the rail's own insets, so its
@@ -120,7 +131,7 @@ export interface AppShellRailProps {
120
131
  children?: ReactNode;
121
132
  }
122
133
  /** The rail's three regions: a head, a scrolling middle, and a foot. */
123
- export declare function AppShellRail({ header, footer, className, children }: AppShellRailProps): import("react").JSX.Element;
134
+ export declare function AppShellRail({ header, search, footer, className, children }: AppShellRailProps): import("react").JSX.Element;
124
135
  /**
125
136
  * A figure at the end of a row in the foot, and what it counts.
126
137
  *
@@ -1,4 +1,4 @@
1
- export { AccountRow, AppShell, AppShellRail, AppShellRailRow, AppShellToggle, AppShellTopbar, useAppShell } from '../chunk-DYB66CNM.js';
1
+ export { AccountRow, AppShell, AppShellRail, AppShellRailRow, AppShellToggle, AppShellTopbar, useAppShell } from '../chunk-GR4YM7HT.js';
2
2
  import '../chunk-RTPQDNW3.js';
3
3
  import '../chunk-D6FEHY5G.js';
4
4
  import '../chunk-OMV5W53A.js';
@@ -130,6 +130,8 @@
130
130
  .crewlet-app-shell__rail-head {
131
131
  display: flex;
132
132
  align-items: center;
133
+ gap: var(--spacing-2);
134
+ min-width: 0;
133
135
  flex: none;
134
136
  box-sizing: border-box;
135
137
  min-height: calc(var(--size-shell-inset) + var(--crewlet-app-shell-topbar));
@@ -143,15 +145,48 @@
143
145
  scrollbar-color: var(--color-scrollbar) transparent;
144
146
  }
145
147
 
148
+ /*
149
+ * THE HEAD'S FIRST CHILD TAKES ITS WIDTH: a lockup alone on its line, whose
150
+ * name truncates at the rail's edge rather than wrapping or pushing the head
151
+ * wider. It was every application's own wrapper and rule.
152
+ */
153
+ .crewlet-app-shell__rail-head > :first-child {
154
+ flex: 1 1 auto;
155
+ min-width: 0;
156
+ }
157
+
158
+ /*
159
+ * THE RAIL'S FIELD ON THE NAV'S COLUMNS. It stands on the nav's gutter, so
160
+ * its right edge is where every row's background ends; and its glyph stands
161
+ * on the nav's glyph column: a row's glyph is the gutter and the row's pad
162
+ * in, the field's is the gutter, its own 1px edge and its pad
163
+ * (`SearchTrigger`'s two steps and a half), so the inset at its start makes
164
+ * up the difference from those measures.
165
+ */
166
+ .crewlet-app-shell__rail-search {
167
+ padding: 0 var(--size-nav-gutter) var(--spacing-3);
168
+ padding-inline-start: calc(
169
+ var(--size-nav-gutter) + var(--size-nav-row-pad) - (var(--spacing-2) + var(--spacing-1) / 2) - 1px
170
+ );
171
+ }
172
+
146
173
  /* The same gutter the nav takes, so what the foot says lines up with the rows
147
- above it rather than sitting further out. */
174
+ above it rather than sitting further out.
175
+
176
+ THE FOOT IS THE COMPACT STEP. It holds what a reader reaches for rarely (a
177
+ settings link, the engine's state, who they are), so its rows stand on the
178
+ small control step (`--crewlet-rail-foot-row`, 26px, never under the 24px
179
+ target) on a half-step gap and a one-step inset, where the nav above keeps
180
+ its 30px rows. Only heights change: every row keeps its order, columns,
181
+ glyphs and words. */
148
182
  .crewlet-app-shell__rail-foot {
149
- --crewlet-rail-foot-gap: var(--spacing-1);
183
+ --crewlet-rail-foot-gap: calc(var(--spacing-1) / 2);
184
+ --crewlet-rail-foot-row: var(--size-control-sm);
150
185
  flex: none;
151
186
  display: flex;
152
187
  flex-direction: column;
153
188
  gap: var(--crewlet-rail-foot-gap);
154
- padding: var(--spacing-2) var(--size-nav-gutter);
189
+ padding: var(--spacing-1) var(--size-nav-gutter);
155
190
  border-top: 1px solid var(--color-border-default);
156
191
  }
157
192
 
@@ -252,6 +287,14 @@
252
287
  min-width: 14px;
253
288
  }
254
289
 
290
+ /* The foot's own step (see `.crewlet-app-shell__rail-foot`), for its rows and
291
+ for a navigation dropped into it, whose rows keep the rail's columns. */
292
+ .crewlet-app-shell__rail-foot > .crewlet-app-shell__rail-row--action:not(.crewlet-app-shell__rail-row--block),
293
+ .crewlet-app-shell__rail-foot > .crewlet-btn,
294
+ .crewlet-app-shell__rail-foot .crewlet-nav-item__row {
295
+ min-height: var(--crewlet-rail-foot-row);
296
+ }
297
+
255
298
  /*
256
299
  * OPEN IS POINTED AT. The row a panel is open from keeps the hover's lift for
257
300
  * as long as the panel is open, drawn from `aria-expanded` so the picture and
@@ -488,6 +531,15 @@
488
531
  overflow-y: auto;
489
532
  overflow-x: hidden;
490
533
  scrollbar-gutter: stable;
534
+ /*
535
+ * A SIZE CONTAINER, so a box inside the screen can be as tall as the view
536
+ * it scrolls in (`100cqb`): a column that sticks beside the screen
537
+ * (`SectionFrame`), a record bounded by the window. The scroller's size is
538
+ * the shell's, never its content's, so containing it costs nothing. A box
539
+ * inside an inline-size container (a card, a stat row) reaches past it to
540
+ * this one for the block axis.
541
+ */
542
+ container-type: size;
491
543
  }
492
544
 
493
545
  /* No focus rule of its own: `main` takes focus only from the skip link, and a
@@ -505,12 +557,17 @@
505
557
  }
506
558
 
507
559
  /* A screen that draws to the bottom of the window: the scroller stops
508
- scrolling and the content column takes the height that is left. It
509
- reserves no scrollbar gutter either, since nothing in it scrolls: the
510
- reserved strip was a dead band down the screen's end edge. */
560
+ scrolling and the content column takes the height that is left.
561
+
562
+ IT KEEPS THE GUTTER RESERVED, like every other screen. The top bar's end
563
+ inset counts the scroller's gutter (`--crewlet-app-shell-gutter`, measured
564
+ by the shell), so a fill screen that reserved none put the bar's controls
565
+ one gutter further out than on the screen beside it: with classic
566
+ scrollbars, Add seat moved 11px between an org chart and its roster, two
567
+ tabs of one page. A strip at the end edge of a full-screen canvas is the
568
+ smaller cost than a bar that moves under the pointer. */
511
569
  .crewlet-app-shell--fill .crewlet-app-shell__main {
512
570
  overflow: hidden;
513
- scrollbar-gutter: auto;
514
571
  }
515
572
 
516
573
  .crewlet-app-shell--fill .crewlet-app-shell__content {
@@ -690,9 +747,16 @@
690
747
  * whatever size of badge a caller hands the row, it is centred where a glyph
691
748
  * is, and the row takes the nav row's pad and its glyph-to-word gap.
692
749
  */
750
+ .crewlet-app-shell__rail-foot .crewlet-account-row {
751
+ --crewlet-account-row-pad: var(--spacing-0);
752
+ }
753
+
693
754
  .crewlet-app-shell__rail-foot .crewlet-account-row__who {
694
755
  --crewlet-hit-area-block-start: calc(var(--crewlet-rail-foot-gap) / 2 + var(--crewlet-account-row-pad));
695
756
  gap: calc(var(--spacing-2) + var(--spacing-1) / 2);
757
+ /* On the foot's compact step: the two lines of name and detail with half a
758
+ step above and below them, which still clears the avatar. */
759
+ padding-block: calc(var(--spacing-1) / 2);
696
760
  padding-inline-start: var(--size-nav-row-pad);
697
761
  }
698
762
 
@@ -1,8 +1,9 @@
1
- export { ARTIFACT_BAR_LABELS, ArtifactBar, ArtifactVersionChip } from '../chunk-TZYFTC4M.js';
2
- import '../chunk-4ISUIS6F.js';
3
- import '../chunk-2QZ2VPHR.js';
4
- import '../chunk-7SDUKCNW.js';
5
- import '../chunk-RKKO7QHH.js';
1
+ export { ARTIFACT_BAR_LABELS, ArtifactBar, ArtifactVersionChip } from '../chunk-2VOHUGUA.js';
2
+ import '../chunk-OOC7Q67K.js';
3
+ import '../chunk-4OK4DX3V.js';
4
+ import '../chunk-X4IXXLWM.js';
5
+ import '../chunk-WIATRXEJ.js';
6
+ import '../chunk-EGMQURBR.js';
6
7
  import '../chunk-I33UIUZD.js';
7
8
  import '../chunk-ILDNJHRU.js';
8
9
  import '../chunk-CE2THTQR.js';
@@ -1,16 +1,20 @@
1
- export { AvatarPicker } from '../chunk-ECGGQX2H.js';
2
- import '../chunk-F4LPQA4E.js';
1
+ export { AvatarPicker } from '../chunk-A44YHLCQ.js';
2
+ import '../chunk-BA32AVKS.js';
3
3
  import '../chunk-KHJIEPZV.js';
4
4
  import '../chunk-AYKXMIOR.js';
5
- import '../chunk-YEVG2X5R.js';
6
- import '../chunk-2QZ2VPHR.js';
7
- import '../chunk-7SDUKCNW.js';
8
- import '../chunk-RKKO7QHH.js';
5
+ import '../chunk-SNQRGH4A.js';
6
+ import '../chunk-4OK4DX3V.js';
7
+ import '../chunk-X4IXXLWM.js';
8
+ import '../chunk-WIATRXEJ.js';
9
+ import '../chunk-EGMQURBR.js';
9
10
  import '../chunk-PZKHBB7D.js';
10
11
  import '../chunk-BHHIIK3M.js';
12
+ import '../chunk-D6FEHY5G.js';
11
13
  import '../chunk-OMV5W53A.js';
12
14
  import '../chunk-H5XTJ7GJ.js';
13
15
  import '../chunk-2YTOOH3A.js';
16
+ import '../chunk-OG6CL3EB.js';
17
+ import '../chunk-4FQT6ZAY.js';
14
18
  import '../chunk-NUZHC6MM.js';
15
19
  import '../chunk-BZFWDIV6.js';
16
20
  import '../chunk-ODEQTPFM.js';
@@ -1,3 +1,3 @@
1
- export { CatalogTile } from '../chunk-XNXK5MA4.js';
1
+ export { CatalogTile } from '../chunk-PCT47E7O.js';
2
2
  import '../chunk-PQADV6P6.js';
3
3
  import '../chunk-ODEQTPFM.js';
@@ -28,7 +28,7 @@
28
28
 
29
29
  .crewlet-catalog-tile__head {
30
30
  display: flex;
31
- align-items: center;
31
+ align-items: flex-start;
32
32
  gap: var(--spacing-3);
33
33
  min-width: 0;
34
34
  }
@@ -46,13 +46,17 @@
46
46
  color: var(--color-text-primary);
47
47
  }
48
48
 
49
- /* The name, and under it the state when there is one; centred on the mark
50
- either way, so a tile with no state is not a tile with a hole in it. */
49
+ /* The name, and under it the state when there is one. THE NAME STANDS ON ONE
50
+ LINE ACROSS A ROW whether or not a tile has a state: centred on the mark, a
51
+ name with a pill under it rose 13px above its stateless neighbour's, so the
52
+ names of one row stood at two heights. It starts one step below the mark's
53
+ top edge in every tile instead. */
51
54
  .crewlet-catalog-tile__title {
52
55
  display: flex;
53
56
  flex-direction: column;
54
57
  align-items: flex-start;
55
- justify-content: center;
58
+ justify-content: flex-start;
59
+ padding-top: var(--spacing-1);
56
60
  gap: var(--spacing-1);
57
61
  min-width: 0;
58
62
  }
@@ -1,5 +1,5 @@
1
1
  import '../chunk-3L5NHASN.js';
2
- export { CHARACTER_PICKER_COLUMNS, CharacterPicker } from '../chunk-YEVG2X5R.js';
3
- import '../chunk-RKKO7QHH.js';
2
+ export { CHARACTER_PICKER_COLUMNS, CharacterPicker } from '../chunk-SNQRGH4A.js';
3
+ import '../chunk-EGMQURBR.js';
4
4
  import '../chunk-PZKHBB7D.js';
5
5
  import '../chunk-ODEQTPFM.js';
@@ -0,0 +1,71 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface HistogramBar {
3
+ /** The bucket's start, epoch milliseconds. It is also the bar's key. */
4
+ t: number;
5
+ /** How many things fell in the bucket. */
6
+ v: number;
7
+ /**
8
+ * How many of `v` failed, where the caller has the split. Drawn as the
9
+ * danger-toned foot of the same fill, so the column's height is still the
10
+ * bucket's whole count. Absent draws the plain fill, because a split the
11
+ * data did not carry is not a split of zero.
12
+ */
13
+ part?: number | undefined;
14
+ }
15
+ export interface HistogramProps {
16
+ bars: HistogramBar[];
17
+ /** What the chart is, as the name of its group: "Turns that ended over the window". */
18
+ label: string;
19
+ /**
20
+ * The total and what it is a total of, read in place of the picture:
21
+ * "212 turns in this window". Required, and never summed here: the caller
22
+ * knows whether the bars cover the whole window or only the rows it holds,
23
+ * and a number whose scope is guessed is wrong on the second caller.
24
+ */
25
+ summary: string;
26
+ /** A bar's own sentence, for its tooltip and its accessible name. */
27
+ formatTitle: (bar: HistogramBar, index: number) => string;
28
+ /**
29
+ * A tick's label, which turns the axis on. Left out, the chart draws no
30
+ * dates, for a strip narrower than its own labels.
31
+ */
32
+ formatTick?: ((bar: HistogramBar, index: number) => string) | undefined;
33
+ /**
34
+ * Narrow the window to one bar. Given, every bar is a button; left out, the
35
+ * bars are a picture and take no tab stop.
36
+ */
37
+ onSelect?: ((bar: HistogramBar, index: number) => void) | undefined;
38
+ /** The plot's height in pixels. */
39
+ height?: number | undefined;
40
+ /** What the chart says in place of the bars when it has none. */
41
+ emptyLabel?: ReactNode;
42
+ className?: string | undefined;
43
+ }
44
+ /**
45
+ * Which bars get a label: the first, the last, and an even whole stride
46
+ * between, so a seven-day window reads every other day rather than gaps of
47
+ * two, one, two and one days (which reads as days missing from the data).
48
+ * The stride is the smallest whole number of bars that keeps the labels at or
49
+ * under five, preferring one that lands exactly on the last bar; where none
50
+ * does, a label nearer the last one than half a stride gives way to it.
51
+ */
52
+ export declare function histogramTicks(bars: number): number[];
53
+ /**
54
+ * A log's own time axis: where in a window things happened, which a page of
55
+ * rows cannot say.
56
+ *
57
+ * THE SLOT IS THE CONTROL AND THE FILL INSIDE IT IS THE BAR. The fill takes
58
+ * the column proportion `StackedColumns` uses, so one bucket and ninety draw
59
+ * with the same geometry, while the click target stays the whole slot.
60
+ *
61
+ * THE HEIGHT IS THE TRUE RATIO, with a floor in pixels rather than percent:
62
+ * a percentage floor grows with the panel and draws a bucket of 4 and one of
63
+ * 400 at the same height. An empty bucket stands one pixel under the smallest
64
+ * real count, because a zero-height element is invisible and an empty stretch
65
+ * would read as a gap in the chart rather than a quiet stretch in the data.
66
+ *
67
+ * A BUTTON WHEN IT DOES SOMETHING AND A DIV WHEN IT DOES NOT: a keyboard
68
+ * reader tabbing through eighty inert controls is worse served than one told
69
+ * there is nothing to operate.
70
+ */
71
+ export declare function Histogram({ bars, label, summary, formatTitle, formatTick, onSelect, height, emptyLabel, className, }: HistogramProps): import("react").JSX.Element;
@@ -8,6 +8,8 @@ export { StackedBar } from './StackedBar.js';
8
8
  export type { StackedBarProps, StackedSegment } from './StackedBar.js';
9
9
  export { ActivityStrip } from './ActivityStrip.js';
10
10
  export type { ActivityBucket, ActivityStripProps } from './ActivityStrip.js';
11
+ export { Histogram, histogramTicks } from './Histogram.js';
12
+ export type { HistogramBar, HistogramProps } from './Histogram.js';
11
13
  export { TimeSeries, Sparkline } from './TimeSeries.js';
12
14
  export type { Series, SeriesPoint, SparklineProps, TimeSeriesProps } from './TimeSeries.js';
13
15
  export { StackedColumns, niceScale } from './StackedColumns.js';
@@ -1,3 +1,3 @@
1
- export { ActivityStrip, BarList, ChartEmpty, ChartTooltip, DATA_COLORS, DATA_COLOR_OTHER, Legend, Sparkline, StackedBar, StackedColumns, TimeSeries, dataColor, niceScale, useChartHover } from '../chunk-HG5VF3TZ.js';
1
+ export { ActivityStrip, BarList, ChartEmpty, ChartTooltip, DATA_COLORS, DATA_COLOR_OTHER, Histogram, Legend, Sparkline, StackedBar, StackedColumns, TimeSeries, dataColor, histogramTicks, niceScale, useChartHover } from '../chunk-HMVQQEHW.js';
2
2
  import '../chunk-BZFWDIV6.js';
3
3
  import '../chunk-ODEQTPFM.js';
@@ -978,3 +978,121 @@
978
978
  transform: translateY(-50%);
979
979
  pointer-events: none;
980
980
  }
981
+
982
+ /* ─── Histogram, a log's own time axis ─────────────────────────── */
983
+
984
+ /*
985
+ * Time runs left to right on this axis in every writing direction, as it does
986
+ * on every chart, and the ticks are placed on physical sides to match.
987
+ */
988
+ .crewlet-histogram {
989
+ display: flex;
990
+ flex-direction: column;
991
+ min-width: 0;
992
+ direction: ltr;
993
+ }
994
+
995
+ /* The plot's bottom border is the axis line the ticks hang under. */
996
+ .crewlet-histogram__plot {
997
+ display: flex;
998
+ align-items: flex-end;
999
+ gap: 1px;
1000
+ width: 100%;
1001
+ height: var(--crewlet-histogram-height, 96px);
1002
+ padding: var(--spacing-1) 0;
1003
+ border-bottom: 1px solid var(--color-border-hover);
1004
+ }
1005
+
1006
+ /* The slot is the control, and the whole of it is the target. */
1007
+ .crewlet-histogram__bar {
1008
+ all: unset;
1009
+ flex: 1 1 0;
1010
+ min-width: 2px;
1011
+ height: 100%;
1012
+ display: flex;
1013
+ align-items: flex-end;
1014
+ justify-content: center;
1015
+ }
1016
+
1017
+ button.crewlet-histogram__bar {
1018
+ cursor: pointer;
1019
+ }
1020
+
1021
+ /* A slot draws no edge, so its ring stands off it. `all: unset` takes the
1022
+ baseline ring away; this states it again at a weight the reset cannot
1023
+ out-rank. */
1024
+ .crewlet-histogram__bar:focus-visible {
1025
+ outline: 2px solid var(--color-focus);
1026
+ outline-offset: 2px;
1027
+ }
1028
+
1029
+ /*
1030
+ * A count is data, so it is drawn in the data ramp; the accent is kept for the
1031
+ * bar the reader is pointing at, the selection a click is about to make. The
1032
+ * fill stacks from its foot, so the failed share sits at its bottom. Floors in
1033
+ * pixels: an empty bucket at two, the smallest real count at three.
1034
+ */
1035
+ .crewlet-histogram__fill {
1036
+ display: flex;
1037
+ flex-direction: column;
1038
+ justify-content: flex-end;
1039
+ overflow: hidden;
1040
+ width: 63%;
1041
+ min-width: 2px;
1042
+ max-width: var(--spacing-7);
1043
+ height: var(--crewlet-histogram-fill, 0%);
1044
+ min-height: 3px;
1045
+ background: var(--color-data-1);
1046
+ border-radius: var(--radius-sm) var(--radius-sm) 0 0;
1047
+ transition: background var(--motion-duration-fast) var(--motion-easing-default);
1048
+ }
1049
+
1050
+ .crewlet-histogram__part {
1051
+ display: block;
1052
+ width: 100%;
1053
+ height: var(--crewlet-histogram-part, 0%);
1054
+ background: var(--color-feedback-danger);
1055
+ }
1056
+
1057
+ button.crewlet-histogram__bar:hover > .crewlet-histogram__fill,
1058
+ button.crewlet-histogram__bar:focus-visible > .crewlet-histogram__fill {
1059
+ background: var(--color-brand-accent);
1060
+ }
1061
+
1062
+ /* An empty bucket is a visible floor in the tone an axis is ruled in:
1063
+ present, and plainly not a quantity. */
1064
+ .crewlet-histogram__bar[data-empty] > .crewlet-histogram__fill {
1065
+ min-height: 2px;
1066
+ background: var(--color-border-strong);
1067
+ }
1068
+
1069
+ button.crewlet-histogram__bar[data-empty]:hover > .crewlet-histogram__fill,
1070
+ button.crewlet-histogram__bar[data-empty]:focus-visible > .crewlet-histogram__fill {
1071
+ background: var(--color-border-strong-hover);
1072
+ }
1073
+
1074
+ /*
1075
+ * The dates under the bars. Absolutely placed, so a tick sits over the bucket
1076
+ * it names; the row states its own height because absolutely placed children
1077
+ * contribute none.
1078
+ */
1079
+ .crewlet-histogram__axis {
1080
+ position: relative;
1081
+ height: calc(var(--font-size-2xs) * var(--font-line-height-normal));
1082
+ margin-top: 2px;
1083
+ }
1084
+
1085
+ .crewlet-histogram__tick {
1086
+ position: absolute;
1087
+ top: 0;
1088
+ font-size: var(--font-size-2xs);
1089
+ color: var(--color-text-tertiary);
1090
+ font-variant-numeric: tabular-nums;
1091
+ white-space: nowrap;
1092
+ }
1093
+
1094
+ @media (prefers-reduced-motion: reduce) {
1095
+ .crewlet-histogram__fill {
1096
+ transition: none;
1097
+ }
1098
+ }