@apliteni/apliteni-ui 0.100.0 → 0.104.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apliteni/apliteni-ui",
3
- "version": "0.100.0",
3
+ "version": "0.104.0",
4
4
  "workspaces": [
5
5
  "react"
6
6
  ],
package/react/README.md CHANGED
@@ -32,7 +32,17 @@ import { DataTable, Modal, Button } from '@apliteni/apliteni-ui/react';
32
32
  Both stylesheets. The React one adds what React's own components need — it carries no tokens, no
33
33
  reset and none of the kit's control CSS, so it cannot stand in for the first import.
34
34
 
35
- Components: `Success`, `SuccessPanel`, `SuccessCheck`, `Checkbox`, `Switch`, `SearchField`, `EmptyState`, `DataTable`, `Pagination`, `StatBand`, `Modal`, `Confirm`, `Drawer`, `CommandPalette`, `Dropdown`, `BackLink`, `Tooltip`, `Snippet`, `Tabs`, `Button`, `Badge`, `Pill`, `StatusDot`, `Card`, `Callout`, `Icon`.
35
+ Components: `Success`, `SuccessPanel`, `SuccessCheck`, `Checkbox`, `Switch`, `SearchField`, `EmptyState`, `DataTable`, `Pagination`, `StatBand`, `Modal`, `Confirm`, `Drawer`, `CommandPalette`, `Dropdown`, `BackLink`, `Tooltip`, `Snippet`, `Segmented`, `Tabs`, `Button`, `Badge`, `Pill`, `StatusDot`, `Card`, `Callout`, `Icon`.
36
+
37
+ `Segmented` is a controlled choice toolbar: pass `options`, `value`, `onChange`,
38
+ and a `label` for its accessible name. `size="sm"` compacts it, `block` makes it
39
+ fill its container, and `appearance="underline"` draws the underline strip instead
40
+ of the pill track; omit all three for the standard pill. Options support
41
+ `ariaLabel` and `disabled`; `disabled` on the toolbar disables every choice. What
42
+ each presentation guarantees is in
43
+ docs/components.md#the-rest-of-the-react-surface.
44
+ Reach for it when a choice filters or switches a view in place; use `Tabs` when the
45
+ choice owns a panel, and links when it changes the page.
36
46
 
37
47
  `Pill` renders metadata with the existing pill spacing. Omit `variant` for neutral
38
48
  metadata or use `live` or `soon`. Its children accept React content.
@@ -36,10 +36,10 @@
36
36
  --tap-clear-y: 0px;
37
37
  }
38
38
  @media (max-width: 560px) and (pointer: coarse) {
39
- :where(.ui-seg button, .ui-tabs__tab, .ui-dropdown__trigger, .ui-toast__action, .ui-drawer__close, .ui-snippet__copy, .ui-footer__social, .ui-datepicker__opt) {
39
+ :where(.ui-seg button, .ui-tabs__tab, .ui-dropdown__trigger, .ui-toast__action, .ui-drawer__close, .ui-snippet__copy, .ui-footer__social, .ui-app__search, .toggle, .ui-datepicker__opt) {
40
40
  position: relative;
41
41
  }
42
- :where(.ui-btn, .ui-seg button, .ui-tabs__tab, .ui-dropdown__trigger, .ui-toast__action, .ui-drawer__close, .ui-snippet__copy, .ui-footer__social, .ui-switch, .ui-datepicker__opt)::after {
42
+ :where(.ui-btn, .ui-seg button, .ui-tabs__tab, .ui-dropdown__trigger, .ui-toast__action, .ui-drawer__close, .ui-snippet__copy, .ui-footer__social, .ui-switch, .ui-datepicker__opt, .ui-app__search, .toggle)::after {
43
43
  content: "";
44
44
  position: absolute;
45
45
  left: 50%;
@@ -133,6 +133,11 @@
133
133
  --tap-clear-x: var(--tap-gap);
134
134
  --tap-clear-y: var(--tap-gap);
135
135
  }
136
+ .ui-app__bar,
137
+ .topbar__in {
138
+ --tap-clear-x: var(--space-3, 12px);
139
+ --tap-clear-y: var(--space-3, 12px);
140
+ }
136
141
  }
137
142
 
138
143
  /* ../src/styles/tooltip.css */
@@ -872,12 +877,6 @@
872
877
  .ui-react-app .ui-app__bar {
873
878
  padding-inline: var(--space-4);
874
879
  }
875
- .ui-react-app .ui-app__search {
876
- min-width: 0;
877
- }
878
- .ui-react-app .ui-app__search kbd {
879
- display: none;
880
- }
881
880
  .ui-react-app .ui-app__bar > .ui-app__brand {
882
881
  display: none;
883
882
  }
@@ -545,9 +545,11 @@ type SegmentedProps = {
545
545
  onChange: (value: string) => void;
546
546
  label: string;
547
547
  appearance?: 'pill' | 'underline';
548
+ size?: 'sm';
549
+ block?: boolean;
548
550
  disabled?: boolean;
549
551
  };
550
- declare function Segmented({ options, value, onChange, label, appearance, disabled }: SegmentedProps): react.JSX.Element;
552
+ declare function Segmented({ options, value, onChange, label, appearance, size, block, disabled }: SegmentedProps): react.JSX.Element;
551
553
 
552
554
  type Filter = {
553
555
  id: string;
@@ -776,6 +778,10 @@ type AppShellProps = {
776
778
  lede?: string;
777
779
  actions?: ReactNode;
778
780
  bandControl?: ReactNode;
781
+ /** The band trigger's words. Below 560px the shell draws `searchShortLabel` instead;
782
+ * both stay the button's name, which does not change with the viewport. */
783
+ searchLabel?: string;
784
+ searchShortLabel?: string;
779
785
  account: {
780
786
  name: string;
781
787
  email: string;
@@ -787,7 +793,7 @@ type AppShellProps = {
787
793
  /** Spread linkProps onto the router link, including children and onClick. */
788
794
  renderLink?: (section: AppShellSection, linkProps: ComponentPropsWithoutRef<'a'>) => ReactNode;
789
795
  };
790
- declare function AppShell({ sections, pathname, title, children, word, brand, brandHref, width, back, lede, actions, bandControl, account, onSignOut, defaultCollapsed, palette, renderLink }: AppShellProps): react.JSX.Element;
796
+ declare function AppShell({ sections, pathname, title, children, word, brand, brandHref, width, back, lede, actions, bandControl, account, onSignOut, searchLabel, searchShortLabel, defaultCollapsed, palette, renderLink }: AppShellProps): react.JSX.Element;
791
797
 
792
798
  type EmptyStateVariant = 'first-run' | 'no-matches' | 'not-found' | 'not-yet-built';
793
799
  type EmptyStateProps = {
@@ -2519,10 +2519,10 @@ function RowIdentity({ symbol, name, logo, href }) {
2519
2519
  // src/Segmented.tsx
2520
2520
  import { segmentedNextIndex } from "@apliteni/apliteni-ui";
2521
2521
  import { jsx as jsx23 } from "react/jsx-runtime";
2522
- function Segmented({ options, value, onChange, label, appearance = "pill", disabled = false }) {
2522
+ function Segmented({ options, value, onChange, label, appearance = "pill", size, block = false, disabled = false }) {
2523
2523
  const enabled = options.filter((o) => !o.disabled);
2524
2524
  const tabValue = enabled.some((o) => o.value === value) ? value : enabled[0]?.value;
2525
- return /* @__PURE__ */ jsx23("div", { className: `ui-seg${appearance === "underline" ? " ui-seg--underline" : ""}`, role: "toolbar", "aria-label": label, children: options.map((option) => /* @__PURE__ */ jsx23(
2525
+ return /* @__PURE__ */ jsx23("div", { className: `ui-seg${size === "sm" ? " ui-seg--sm" : ""}${block ? " ui-seg--block" : ""}${appearance === "underline" ? " ui-seg--underline" : ""}`, role: "toolbar", "aria-label": label, children: options.map((option) => /* @__PURE__ */ jsx23(
2526
2526
  "button",
2527
2527
  {
2528
2528
  type: "button",
@@ -3407,6 +3407,8 @@ function AppShell({
3407
3407
  bandControl,
3408
3408
  account,
3409
3409
  onSignOut,
3410
+ searchLabel = "Search or run a command\u2026",
3411
+ searchShortLabel = "Search",
3410
3412
  defaultCollapsed = false,
3411
3413
  palette,
3412
3414
  renderLink
@@ -3514,7 +3516,8 @@ function AppShell({
3514
3516
  !navigable && lockup,
3515
3517
  /* @__PURE__ */ jsxs29("button", { type: "button", className: "ui-app__search", "aria-haspopup": "dialog", onClick: () => setSearch(true), children: [
3516
3518
  /* @__PURE__ */ jsx34("span", { className: "ui-app__search-ic", children: /* @__PURE__ */ jsx34(Icon, { name: "search" }) }),
3517
- /* @__PURE__ */ jsx34("span", { className: "ui-app__search-txt", children: "Search or run a command\u2026" }),
3519
+ /* @__PURE__ */ jsx34("span", { className: "ui-app__search-txt", children: searchLabel }),
3520
+ /* @__PURE__ */ jsx34("span", { className: "ui-app__search-short", "aria-hidden": "true", children: searchShortLabel }),
3518
3521
  /* @__PURE__ */ jsx34("kbd", { className: "ui-cmdk__key", children: paletteHotkey() })
3519
3522
  ] }),
3520
3523
  /* @__PURE__ */ jsx34("span", { className: "ui-app__bar-gap" }),
@@ -91,12 +91,17 @@ const toWidth = (v) => (v === 'wide' ? 'wide' : 'centered');
91
91
 
92
92
  // `search: 'palette-id'`, or the same id in `{ palette }` with a placeholder beside
93
93
  // it. No palette to open, no field — the argument `signOutHref` takes.
94
- // why: docs/components.md#the-page-shell
94
+ // `shortLabel` is the word the band draws below 560px, where the sentence used to
95
+ // truncate mid-word. why: docs/components.md#the-page-shell
95
96
  const toSearch = (v) => {
96
97
  const given = typeof v === 'string' ? { palette: v } : (isRecord(v) && !Array.isArray(v) ? v : null);
97
98
  const palette = given && typeof given.palette === 'string' ? given.palette.trim() : '';
98
99
  if (!palette) return null;
99
- return { palette, placeholder: str(given.placeholder) || 'Search or run a command…' };
100
+ return {
101
+ palette,
102
+ placeholder: str(given.placeholder) || 'Search or run a command…',
103
+ shortLabel: str(given.shortLabel) || 'Search',
104
+ };
100
105
  };
101
106
 
102
107
  // `maxWidth` lands inside a style attribute, so a length is all this accepts — a number
@@ -236,10 +241,13 @@ function readerBlock({ name, email }, signOutHref, { band = false } = {}) {
236
241
  // A field to look at, a button to press. `data-cmdk-open` and the cap's class are the
237
242
  // palette's own, and the cap is NOT aria-hidden: it is half the button's name.
238
243
  // why: docs/components.md#the-page-shell
239
- const searchField = ({ palette, placeholder }) =>
244
+ // The short word repeats the sentence, so it is `aria-hidden`: the sentence and the
245
+ // cap are the name at every width, and layout.css clips rather than drops them.
246
+ const searchField = ({ palette, placeholder, shortLabel }) =>
240
247
  `<button type="button" class="ui-app__search" data-cmdk-open="${esc(palette)}" aria-haspopup="dialog">`
241
248
  + `<span class="ui-app__search-ic" aria-hidden="true">${icon('search')}</span>`
242
249
  + `<span class="ui-app__search-txt">${esc(placeholder)}</span>`
250
+ + `<span class="ui-app__search-short" aria-hidden="true">${esc(shortLabel)}</span>`
243
251
  + `<kbd class="ui-cmdk__key" data-palette-hotkey>${esc(paletteHotkey())}</kbd>`
244
252
  + `</button>`;
245
253
 
@@ -119,7 +119,7 @@
119
119
  resting step. */
120
120
  /* Re-tuned at #295, which took the light rail down a step with the page. Below 720px
121
121
  the glyph is the whole row, so it is held at text grade rather than at 1.4.11's bar —
122
- stories/apps/shell-states.test.js:1597 `a resting glyph is dimmer but still legible`. */
122
+ stories/apps/shell-states.test.js:1704 `a resting glyph is dimmer but still legible`. */
123
123
  .ui-app__rail .ui-nav__ic svg { opacity: 0.66; }
124
124
 
125
125
  /* A destructive row of the rail is a navigation row and rests like one; the danger
@@ -404,11 +404,26 @@
404
404
  .ui-app__search-txt { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
405
405
  /* The other half of the name, so it never gives way. */
406
406
  .ui-app__search kbd { flex: none; }
407
+ /* The one word the phone band draws instead of the sentence. Above 560px the
408
+ sentence fits and this is a second copy of it, so it is not drawn.
409
+ why: docs/components.md#the-page-shell */
410
+ .ui-app__search-short { display: none; }
407
411
 
408
412
  /* The mark alone — no room for two lines on a 52px row. No rule over it, since the
409
413
  band's hairline is under the whole of it, and no column, since this is not the rail.
410
414
  why: docs/components.md#the-page-shell */
411
415
  .ui-app__user--bar { flex: none; padding-top: 0; border-top: 0; }
416
+ /* The switch is a 34px square and stays one: the trigger above is the thing on this
417
+ band that gives way, and a flex item with the default shrink does not wait its
418
+ turn. Measured at 390 with a control beside the trigger, the React band's switch
419
+ drew 19x34 and the strategy topbar's drew 28.47x34 — a square glyph plate squashed,
420
+ and a tap zone the sheet could only clamp to 29 or 39.
421
+ Pinning the strategy topbar's half of this rule does not make its row narrower: it
422
+ moves a 5.53px shortfall onto the account block's trailing gutter instead, because
423
+ nothing before it in that row has width to spare. topbar.css's --space-2 comment
424
+ names that choice and the gutter it leaves.
425
+ why: docs/components.md#the-page-shell */
426
+ .ui-app__bar .toggle, .topbar__in .toggle { flex: none; }
412
427
  .ui-app__user--bar .ui-app__av { margin-inline-start: 0; }
413
428
  /* Crumb, then the title block. At --space-2 the crumb sat 8px off the h1 and
414
429
  the h1 8px off its subtitle, so all three ranks read as one. */
@@ -518,6 +533,31 @@
518
533
  .ui-app__main .ui-card__row { flex-wrap: wrap; gap: var(--space-3); }
519
534
  }
520
535
 
536
+ /* -- The band's search at one column --------------------------------------- */
537
+ /* The band holds the trigger, the reader's mark and whatever the page puts between
538
+ them, so the sentence broke mid-word — "Search or run a co…". The short word takes
539
+ its place. Neither the sentence nor the cap is `display: none`: both are inside
540
+ the button's accessible name — the cap by Artur's call on #308 — so they are
541
+ clipped the way `.ui-sr` in loading.css clips, and the name holds at every width.
542
+ why: docs/components.md#the-page-shell */
543
+ @media (max-width: 560px) {
544
+ .ui-app__search-txt,
545
+ .ui-app__search kbd {
546
+ position: absolute;
547
+ width: 1px;
548
+ height: 1px;
549
+ margin: -1px;
550
+ padding: 0;
551
+ overflow: hidden;
552
+ clip-path: inset(50%);
553
+ white-space: nowrap;
554
+ border: 0;
555
+ }
556
+ /* It gives way before the band does: at a large font scale the word ellipsises
557
+ rather than pushing the reader's mark off the row. */
558
+ .ui-app__search-short { display: block; flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
559
+ }
560
+
521
561
  /* -- Auth card (sign-in / consent) — centered, glowing --------------------- */
522
562
  .ui-auth {
523
563
  min-height: 100vh;
@@ -114,6 +114,7 @@
114
114
  box. #527
115
115
  why: guidelines/layout-and-density.md, the spacing scale */
116
116
  .ui-seg--underline button { position: relative; border-radius: var(--radius-sm); padding: var(--space-3); min-height: var(--tap-min, 44px); white-space: normal; overflow-wrap: anywhere; font-weight: var(--weight-medium); }
117
+ .ui-seg--underline.ui-seg--sm button { padding: var(--space-3); min-height: var(--tap-min, 44px); }
117
118
  /* The chosen tab is its label and one bar under it: no plate, no hairline, no
118
119
  upright rail — those read as a list-row grip. #527 r31. The `outline: 0` that
119
120
  stood here cancelled the accent outline the pill rule gave every chosen button;
@@ -56,6 +56,8 @@
56
56
  .ui-drawer__close,
57
57
  .ui-snippet__copy,
58
58
  .ui-footer__social,
59
+ .ui-app__search,
60
+ .toggle,
59
61
  /* Already relative in react/src/DatePicker.css — restated because this
60
62
  gate reads the kit's own sheets and cannot see that one, and a carrier
61
63
  whose containing block it cannot find is a layer that measures against
@@ -75,7 +77,9 @@
75
77
  .ui-snippet__copy,
76
78
  .ui-footer__social,
77
79
  .ui-switch,
78
- .ui-datepicker__opt
80
+ .ui-datepicker__opt,
81
+ .ui-app__search,
82
+ .toggle
79
83
  )::after {
80
84
  content: "";
81
85
  position: absolute;
@@ -215,4 +219,24 @@
215
219
  --tap-clear-x: var(--tap-gap);
216
220
  --tap-clear-y: var(--tap-gap);
217
221
  }
222
+
223
+ /* Two 52px rows, each holding a control the kit draws under the floor: the
224
+ shell band's palette trigger, 37.05 tall once the phone band swaps its
225
+ sentence for the short word, and the theme switch at 34 square on both.
226
+ Neither row opens anything — the room is already in it, which is why these
227
+ two declare a clearance and no gap. Across, 6px a side against the band's
228
+ own 12px gap and the topbar's 8 below 560px, where this layer exists — not
229
+ the topbar's 14 above that step, where it does not. 6px a side crosses the
230
+ topbar's 4px midline by 1px, which does not mis-tap: neither neighbour on
231
+ that row carries a layer of its own. Down, the row's leftover height is 7.4px
232
+ above and below the trigger and 9 around the switch, where a 44px layer
233
+ needs 3.5 and 5. The rows keep their height, their gaps and their paint.
234
+ #486
235
+
236
+ --space-3 rather than --tap-gap, which is what every row above declares:
237
+ 20 claims 10px a side and the trigger has 7.4. The clamp would still stop
238
+ at 44 today and be a number that is not true, which is what the browser
239
+ half fails a container for. */
240
+ .ui-app__bar,
241
+ .topbar__in { --tap-clear-x: var(--space-3, 12px); --tap-clear-y: var(--space-3, 12px); }
218
242
  }
@@ -246,12 +246,31 @@
246
246
  /* One column, and the band is at its tightest. The row is already full at 390,
247
247
  and at the gap above this one the account avatar stood from 365 to 397 and was
248
248
  cut at the screen edge — the one control on this screen that opens the account
249
- area. Packed to --space-2 the band needs 352 of the 362 it has there, so the
250
- avatar is whole with the band's own gutter beside it. 320 is still short of the
251
- room these controls want, which stays #558.
249
+ area. Packed to --space-2 and a flat 14px trailing gutter the band needs 367.53
250
+ of the 362px it has there, a 5.53px shortfall, because layout.css now pins the
251
+ theme switch to a full 34px square so it keeps the tap floor it was given.
252
+ The wordmark, the Deck/Text switch and the version label are each already at
253
+ the width their own text needs, and the switch must not shrink again, so the
254
+ account block's trailing gutter is what gives — but a FLAT 14px gutter has no
255
+ fallback below the width it was sized for: at 375 it has already given every
256
+ one of its 14px and the row is still 6.53px short, so the avatar was cut and
257
+ the page gained 7px of horizontal scroll (#486 review round 2), worse again
258
+ at 320.
259
+ Both carry a share now, continuously, rather than the gutter alone holding a
260
+ fixed budget: the gutter still narrows first, from 14px at 390 down to 0 by
261
+ 381.53px of available row width, and once it is spent the inter-control gap
262
+ takes the rest, from --space-2 down to its own floor of --space-1. 381.53px is
263
+ this row's own fixed width minus its gutter's full 14px — not a general
264
+ formula, a number that belongs to this composition's own wordmark, switch and
265
+ version label. The avatar is whole and the page carries no horizontal scroll
266
+ down to 375px. 320 still falls 41.53px short with both all the way down,
267
+ which stays #558; it is 5px better than main's own 46.53px there, not solved.
252
268
  why: docs/foundations.md#breakpoints */
253
269
  @media (max-width: 560px) {
254
- .topbar__in { gap: var(--space-2); }
270
+ .topbar__in {
271
+ gap: clamp(var(--space-1), calc(var(--space-2) - (381.53px - 100vw) / 5), var(--space-2));
272
+ padding-right: clamp(0px, calc(100vw - 381.53px), 14px);
273
+ }
255
274
  }
256
275
 
257
276
  /* Account — avatar + dropdown. Gated: hidden until the app confirms a session