@eifi1/ui-kit 0.32.0 → 0.32.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/dist/account/delete-account-setting.d.ts +1 -1
- package/dist/admin/admin-action-confirm.d.ts +1 -1
- package/dist/admin/admin-action-log.d.ts +1 -1
- package/dist/admin/admin-action-log.js +4 -2
- package/dist/admin/admin-action-log.js.map +1 -1
- package/dist/admin/invitations-panel.d.ts +1 -1
- package/dist/admin/reviewer-scope-editor.d.ts +1 -1
- package/dist/admin/role-select.d.ts +1 -1
- package/dist/admin/transfer-ownership-dialog.d.ts +1 -1
- package/dist/admin/user-roster.d.ts +1 -1
- package/dist/auth/register-form.d.ts +1 -1
- package/dist/billing/billing-banner.d.ts +1 -1
- package/dist/billing/billing-labels.d.ts +1 -1
- package/dist/billing/billing-labels.js +3 -1
- package/dist/billing/billing-labels.js.map +1 -1
- package/dist/billing/billing-lock.d.ts +1 -1
- package/dist/billing/billing-standing.d.ts +1 -1
- package/dist/billing/plan-card.d.ts +1 -1
- package/dist/billing/plan-card.js +1 -1
- package/dist/billing/plan-card.js.map +1 -1
- package/dist/billing/plan-limit-notice.d.ts +1 -1
- package/dist/billing/subscription-actions.d.ts +1 -1
- package/dist/billing/subscription-status-chip.d.ts +1 -1
- package/dist/chart.d.ts +2 -2
- package/dist/chart.js.map +1 -1
- package/dist/components/alert-banner.js +1 -1
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +1 -1
- package/dist/components/appearance-labels.d.ts +6 -0
- package/dist/components/appearance-labels.js +2 -1
- package/dist/components/appearance-labels.js.map +1 -1
- package/dist/components/button-group.d.ts +1 -1
- package/dist/components/button-group.js +7 -1
- package/dist/components/button-group.js.map +1 -1
- package/dist/components/calculator.d.ts +1 -1
- package/dist/components/chip.js +7 -2
- package/dist/components/chip.js.map +1 -1
- package/dist/components/choice-card.js +1 -1
- package/dist/components/choice-card.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +1 -1
- package/dist/components/copy-button.d.ts +1 -1
- package/dist/components/danger-confirm.d.ts +1 -1
- package/dist/components/data-table-pagination.d.ts +2 -1
- package/dist/components/data-table-pagination.js +15 -6
- package/dist/components/data-table-pagination.js.map +1 -1
- package/dist/components/data-table.js +2 -2
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.js +11 -1
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/facing-pair.d.ts +1 -1
- package/dist/components/file-button.d.ts +1 -1
- package/dist/components/file-dropzone.d.ts +1 -1
- package/dist/components/form-actions.d.ts +1 -1
- package/dist/components/iban-input.d.ts +1 -1
- package/dist/components/inline-edit-field.js +4 -1
- package/dist/components/inline-edit-field.js.map +1 -1
- package/dist/components/language-select.d.ts +1 -1
- package/dist/components/legal-page.d.ts +1 -1
- package/dist/components/legal.d.ts +1 -1
- package/dist/components/money-field.d.ts +1 -1
- package/dist/components/month-picker.js +41 -34
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/number-field.d.ts +1 -1
- package/dist/components/number-input.d.ts +1 -1
- package/dist/components/numpad-sheet.d.ts +1 -1
- package/dist/components/phone-input.d.ts +1 -1
- package/dist/components/phone-input.js +2 -2
- package/dist/components/phone-input.js.map +1 -1
- package/dist/components/row-actions.d.ts +1 -1
- package/dist/components/row-actions.js +103 -37
- package/dist/components/row-actions.js.map +1 -1
- package/dist/components/series-chart.d.ts +1 -1
- package/dist/components/settings-fields.d.ts +7 -3
- package/dist/components/settings-fields.js +2 -1
- package/dist/components/settings-fields.js.map +1 -1
- package/dist/components/share-card.d.ts +1 -1
- package/dist/components/share-card.js +6 -2
- package/dist/components/share-card.js.map +1 -1
- package/dist/components/status-dot.js +9 -2
- package/dist/components/status-dot.js.map +1 -1
- package/dist/components/swipeable-row.js +8 -1
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/text-link.d.ts +1 -1
- package/dist/components/time-input.d.ts +1 -1
- package/dist/components/toggle-group.d.ts +9 -2
- package/dist/components/toggle-group.js +3 -3
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/translation-review-editor.js +1 -1
- package/dist/components/translation-review-editor.js.map +1 -1
- package/dist/components/translation-review.js +2 -2
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/ui.d.ts +1 -1
- package/dist/components/ui.js +5 -5
- package/dist/components/ui.js.map +1 -1
- package/dist/components/wizard-stepper.js +20 -14
- package/dist/components/wizard-stepper.js.map +1 -1
- package/dist/{delete-account-setting-BvdQdbv3.d.ts → delete-account-setting-FLyJf-kZ.d.ts} +43 -8
- package/dist/feedback/feedback-attachment.d.ts +1 -1
- package/dist/feedback/feedback-context.d.ts +1 -1
- package/dist/feedback/feedback-crash.d.ts +1 -1
- package/dist/feedback/feedback-dialog.d.ts +1 -1
- package/dist/feedback/feedback-inbox.d.ts +1 -1
- package/dist/feedback/feedback-labels.d.ts +1 -1
- package/dist/feedback/feedback-menu.d.ts +1 -1
- package/dist/feedback/feedback-record.d.ts +1 -1
- package/dist/feedback/feedback-row-detail.d.ts +1 -1
- package/dist/feedback/feedback-status-undo.d.ts +1 -1
- package/dist/feedback/feedback-submit.d.ts +1 -1
- package/dist/feedback/feedback-swipe.d.ts +1 -1
- package/dist/feedback/feedback-table.d.ts +1 -1
- package/dist/feedback/feedback-thread.d.ts +1 -1
- package/dist/feedback.d.ts +1 -1
- package/dist/hooks/use-breakpoint.d.ts +19 -8
- package/dist/hooks/use-breakpoint.js +1 -1
- package/dist/hooks/use-breakpoint.js.map +1 -1
- package/dist/hooks/use-file-drop.d.ts +1 -1
- package/dist/i18n/defaults.d.ts +1 -1
- package/dist/i18n/german.d.ts +1 -1
- package/dist/i18n/german.js +7 -3
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +1 -1
- package/dist/i18n/languages.d.ts +1 -1
- package/dist/i18n/locales/de-CH.d.ts +1 -1
- package/dist/i18n/locales/en.d.ts +1 -1
- package/dist/i18n/locales/es.d.ts +1 -1
- package/dist/i18n/locales/es.js +7 -3
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +1 -1
- package/dist/i18n/locales/fr.js +7 -3
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +1 -1
- package/dist/i18n/locales/hu.js +7 -3
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +1 -1
- package/dist/i18n/locales/it.js +7 -3
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +1 -1
- package/dist/i18n/locales/zh.js +7 -3
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/landing/landing-actions.d.ts +1 -1
- package/dist/landing/landing-sections.d.ts +1 -1
- package/dist/landing/landing-sections.js +7 -3
- package/dist/landing/landing-sections.js.map +1 -1
- package/dist/landing/public-header.d.ts +1 -1
- package/dist/landing/routing.d.ts +1 -1
- package/dist/rhf/fields.d.ts +1 -1
- package/dist/rhf/form.d.ts +1 -1
- package/dist/rhf.d.ts +1 -1
- package/dist/settings/settings-section.d.ts +1 -1
- package/dist/settings/use-account-appearance.d.ts +1 -1
- package/dist/settings/use-account-appearance.js.map +1 -1
- package/dist/shell/app-shell.d.ts +1 -1
- package/dist/shell/top-bar-brand.d.ts +1 -1
- package/dist/shell.d.ts +1 -1
- package/dist/theme/contrast.d.ts +3 -2
- package/dist/theme/contrast.js.map +1 -1
- package/dist/theme/text-size.d.ts +19 -3
- package/dist/theme/text-size.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +1 -1
- package/dist/wizard/wizard-summary.js +1 -1
- package/dist/wizard/wizard-summary.js.map +1 -1
- package/dist/wizard.d.ts +1 -1
- package/package.json +1 -1
- package/src/admin/admin-action-log.tsx +6 -1
- package/src/billing/billing-labels.ts +16 -0
- package/src/billing/plan-card.tsx +1 -1
- package/src/chart.ts +2 -0
- package/src/components/alert-banner.tsx +5 -1
- package/src/components/appearance-labels.ts +7 -0
- package/src/components/button-group.tsx +21 -0
- package/src/components/chip.tsx +34 -6
- package/src/components/choice-card.tsx +1 -1
- package/src/components/data-table-pagination.tsx +60 -31
- package/src/components/data-table.tsx +8 -4
- package/src/components/date-picker.tsx +32 -1
- package/src/components/inline-edit-field.tsx +20 -1
- package/src/components/month-picker.tsx +6 -1
- package/src/components/phone-input.tsx +7 -2
- package/src/components/row-actions.tsx +155 -49
- package/src/components/settings-fields.tsx +8 -3
- package/src/components/share-card.tsx +21 -2
- package/src/components/status-dot.tsx +25 -2
- package/src/components/swipeable-row.tsx +8 -1
- package/src/components/toggle-group.tsx +26 -12
- package/src/components/translation-review-editor.tsx +1 -1
- package/src/components/translation-review.tsx +5 -4
- package/src/components/ui.tsx +22 -8
- package/src/components/wizard-stepper.tsx +5 -1
- package/src/hooks/use-breakpoint.ts +20 -9
- package/src/i18n/german.ts +4 -0
- package/src/i18n/locales/es.ts +4 -0
- package/src/i18n/locales/fr.ts +4 -0
- package/src/i18n/locales/hu.ts +4 -0
- package/src/i18n/locales/it.ts +4 -0
- package/src/i18n/locales/zh.ts +4 -0
- package/src/index.ts +4 -2
- package/src/landing/landing-sections.tsx +7 -3
- package/src/settings/use-account-appearance.ts +1 -1
- package/src/theme/contrast.ts +3 -2
- package/src/theme/text-size.ts +19 -3
- package/src/wizard/wizard-summary.tsx +4 -2
- package/tokens.css +15 -7
|
@@ -35,7 +35,7 @@ import { useBodyScrollLock } from "../hooks/use-body-scroll-lock";
|
|
|
35
35
|
import { useOverlayHistory } from "../hooks/use-overlay-history";
|
|
36
36
|
import { FullBleedDialog } from "./full-bleed-dialog";
|
|
37
37
|
import { Popover } from "./popover";
|
|
38
|
-
import {
|
|
38
|
+
import { usePhoneLayout } from "../hooks/use-breakpoint";
|
|
39
39
|
import { useAnnounce } from "../hooks/use-announce";
|
|
40
40
|
import { DEFAULT_DATA_TABLE_LABELS, resolveDataTableLabels, type DataTableLabels } from "./data-table-labels";
|
|
41
41
|
import { useKitLabelOverrides, useKitLocale } from "../i18n/kit-labels";
|
|
@@ -1655,9 +1655,13 @@ export function DataTable<T>({
|
|
|
1655
1655
|
: labels.columnsCount(visibleCount, totalCount);
|
|
1656
1656
|
// Match the `md:` breakpoint at the text size in force: we render either the table or
|
|
1657
1657
|
// the card list — never both — so we don't double up DOM nodes that screen readers and
|
|
1658
|
-
// integration tests would have to disambiguate.
|
|
1659
|
-
// (
|
|
1660
|
-
|
|
1658
|
+
// integration tests would have to disambiguate. `usePhoneLayout()` itself, not its twin
|
|
1659
|
+
// `useBreakpoint("md")`: the same query an app's own phone checks ask (keksdose's
|
|
1660
|
+
// sticky editor footer), so the card list and the full-screen row dialog switch with
|
|
1661
|
+
// them and never a frame apart (§10.4 "usePhoneLayout() is the single answer"; 0.32.1,
|
|
1662
|
+
// keksdose's 0.32 report, where `md` and `max-md` both missed at 767.5 px). Without
|
|
1663
|
+
// `matchMedia` (SSR, jsdom) it is the table, as before.
|
|
1664
|
+
const isMdUp = !usePhoneLayout();
|
|
1661
1665
|
// `edgeFade` (0.25) on the desktop scroller; the phone list never scrolls sideways.
|
|
1662
1666
|
const desktopScroller = useRef<HTMLDivElement | null>(null);
|
|
1663
1667
|
const edgeFadeProps = useEdgeFade(desktopScroller, edgeFade && isMdUp);
|
|
@@ -206,6 +206,16 @@ function CustomTrigger({
|
|
|
206
206
|
return render(attrs, valueId);
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
/**
|
|
210
|
+
* The trigger's value wraps instead of truncating at Large and Extra large (0.32.1, the
|
|
211
|
+
* 360 px Extra-large sweep; docs/text-size-harmonization.md §4 "nothing truncates"). A
|
|
212
|
+
* range ("01/09/2026 – 30/09/2026") is 312 px at 150 %, and on a 360 px phone it read
|
|
213
|
+
* "01/09/2026 – 30/…" — its end, the half a reader needs, was the half cut off. It breaks
|
|
214
|
+
* at its spaces; a single date breaks only where it cannot fit at all. A `large:` class,
|
|
215
|
+
* so Normal keeps its one-line field.
|
|
216
|
+
*/
|
|
217
|
+
const VALUE_WRAP_AT_LARGE = "large:whitespace-normal large:break-words";
|
|
218
|
+
|
|
209
219
|
/**
|
|
210
220
|
* The field-shaped button that opens the calendar.
|
|
211
221
|
*
|
|
@@ -320,7 +330,7 @@ function DateFieldTrigger({
|
|
|
320
330
|
|
|
321
331
|
return (
|
|
322
332
|
<button {...attrs}>
|
|
323
|
-
<span id={valueId} className={cn("truncate", !hasValue && "text-[var(--text-placeholder)]")}>
|
|
333
|
+
<span id={valueId} className={cn("truncate", VALUE_WRAP_AT_LARGE, !hasValue && "text-[var(--text-placeholder)]")}>
|
|
324
334
|
{/* `|| " "` (a NON-BREAKING space) — triggerText is "" when there is no value and no
|
|
325
335
|
placeholder was passed. An empty span has no line box, so the
|
|
326
336
|
trigger collapses to its padding and sits shorter than every
|
|
@@ -558,6 +568,23 @@ function DateField({
|
|
|
558
568
|
);
|
|
559
569
|
}
|
|
560
570
|
|
|
571
|
+
/**
|
|
572
|
+
* The ‹ date › (today) row on a phone at Large and Extra large (0.32.1, the 360 px
|
|
573
|
+
* Extra-large sweep; docs/text-size-harmonization.md §4): the field takes the first line
|
|
574
|
+
* on its own, whole and with all four corners, and the buttons share the line under it
|
|
575
|
+
* as one joined strip. Three 2.5rem buttons are 180 px at 150 %, and on a 360 px phone
|
|
576
|
+
* they left the field 84 px — "Book… 0" for "Booking date 07/09/2026" — where a 360 px
|
|
577
|
+
* row would hold the date only by cutting it. `large:max-sm:`, the scaled phone width,
|
|
578
|
+
* so a tablet or a desktop at Large keeps the one row, and Normal is unchanged.
|
|
579
|
+
*/
|
|
580
|
+
const STEP_ROW_STACK_AT_LARGE = cn(
|
|
581
|
+
"large:max-sm:flex-wrap large:max-sm:gap-y-2",
|
|
582
|
+
"large:max-sm:[&>[data-slot=date-step-field]]:order-first large:max-sm:[&>[data-slot=date-step-field]]:basis-full",
|
|
583
|
+
"large:max-sm:[&>[data-slot=date-step-field]]:ms-0 large:max-sm:[&_[role=combobox]]:rounded-md",
|
|
584
|
+
// The buttons (each in its tooltip's wrapper) share the line under the field.
|
|
585
|
+
"large:max-sm:[&>span]:flex-1 large:max-sm:[&>span>button]:w-full",
|
|
586
|
+
);
|
|
587
|
+
|
|
561
588
|
/** One ‹ / › day-step button, sized to sit flush beside the field. */
|
|
562
589
|
function StepButton({
|
|
563
590
|
icon: Icon,
|
|
@@ -1105,6 +1132,9 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
1105
1132
|
disabled={disabled}
|
|
1106
1133
|
invalid={messages.isInvalid}
|
|
1107
1134
|
className={step ? "min-w-0 flex-1" : className}
|
|
1135
|
+
// Named for STEP_ROW_STACK_AT_LARGE; only in the row, so a caller's own
|
|
1136
|
+
// `data-slot` on a bare field is left alone.
|
|
1137
|
+
{...(wrapped ? { "data-slot": "date-step-field" } : {})}
|
|
1108
1138
|
hasValue={Boolean(value)}
|
|
1109
1139
|
triggerText={value ? render(value) : (placeholder ?? "")}
|
|
1110
1140
|
onClear={() => onChange("")}
|
|
@@ -1173,6 +1203,7 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
1173
1203
|
// the trigger, which raising the trigger alone covered. Hover raises nothing: it
|
|
1174
1204
|
// changes no border, and would let a hovered › cover a focused field's edge.
|
|
1175
1205
|
"[&>*:focus-within]:z-20 [&>*:active]:z-20 [&>*:has([aria-invalid=true])]:z-10",
|
|
1206
|
+
STEP_ROW_STACK_AT_LARGE,
|
|
1176
1207
|
className,
|
|
1177
1208
|
)}
|
|
1178
1209
|
>
|
|
@@ -155,6 +155,23 @@ function RenderEditor<T>({ render, ...props }: InlineEditorProps<T> & { render:
|
|
|
155
155
|
return render(props);
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
/**
|
|
159
|
+
* The value at Large and Extra large (0.32.1, keksdose's 0.32 report;
|
|
160
|
+
* docs/text-size-harmonization.md §4 "nothing truncates"): the display button, the
|
|
161
|
+
* locked one and the read-only text wrap instead of ending in "…", and break a word too
|
|
162
|
+
* long for the cell, so an IBAN or an e-mail address shows to its last character. The
|
|
163
|
+
* value IS the button's accessible name and the thing being edited: a reader who needs
|
|
164
|
+
* big type cannot recover a cut-off end by squinting. A `large:` class, so a table of
|
|
165
|
+
* these keeps its one-line cells at Normal.
|
|
166
|
+
*
|
|
167
|
+
* `break-words`, not `[overflow-wrap:anywhere]`: these live in table cells (keksdose's
|
|
168
|
+
* budget and invoice lines), and `anywhere` lowers the value's min-content width to one
|
|
169
|
+
* letter, so an auto-sized column could squeeze an amount to a digit per line. With
|
|
170
|
+
* `break-word` a column keeps each word whole; the button is `w-full`, so a word wider
|
|
171
|
+
* than a sized cell still breaks.
|
|
172
|
+
*/
|
|
173
|
+
const VALUE_WRAP_AT_LARGE = "large:whitespace-normal large:break-words";
|
|
174
|
+
|
|
158
175
|
/** `onCommit`'s result, if it is a promise. */
|
|
159
176
|
function asPromise(result: unknown): Promise<unknown> | null {
|
|
160
177
|
return result && typeof (result as Promise<unknown>).then === "function" ? (result as Promise<unknown>) : null;
|
|
@@ -415,7 +432,7 @@ export function InlineEditField<T = string>(props: InlineEditFieldProps<T>) {
|
|
|
415
432
|
let body: ReactNode;
|
|
416
433
|
if (readOnly) {
|
|
417
434
|
body = (
|
|
418
|
-
<span {...private_} className={cn("block min-w-0 truncate", alignClass)}>
|
|
435
|
+
<span {...private_} className={cn("block min-w-0 truncate", VALUE_WRAP_AT_LARGE, alignClass)}>
|
|
419
436
|
{content}
|
|
420
437
|
</span>
|
|
421
438
|
);
|
|
@@ -492,6 +509,7 @@ export function InlineEditField<T = string>(props: InlineEditFieldProps<T>) {
|
|
|
492
509
|
onClick={(e) => e.preventDefault()}
|
|
493
510
|
className={cn(
|
|
494
511
|
"block w-full min-w-0 cursor-not-allowed truncate rounded-sm focus:outline-none",
|
|
512
|
+
VALUE_WRAP_AT_LARGE,
|
|
495
513
|
FOCUS_RING,
|
|
496
514
|
alignClass,
|
|
497
515
|
)}
|
|
@@ -516,6 +534,7 @@ export function InlineEditField<T = string>(props: InlineEditFieldProps<T>) {
|
|
|
516
534
|
onClick={() => setOpen(true)}
|
|
517
535
|
className={cn(
|
|
518
536
|
"block w-full min-w-0 truncate rounded-sm underline-offset-2 hover:underline focus:outline-none",
|
|
537
|
+
VALUE_WRAP_AT_LARGE,
|
|
519
538
|
FOCUS_RING,
|
|
520
539
|
alignClass,
|
|
521
540
|
)}
|
|
@@ -962,7 +962,12 @@ export function MonthPicker(props: MonthPickerProps) {
|
|
|
962
962
|
</Popover>
|
|
963
963
|
);
|
|
964
964
|
return (
|
|
965
|
-
|
|
965
|
+
// `flex-wrap`: where the heading and the buttons do not fit one row, the buttons
|
|
966
|
+
// take the next line, at the end (0.32.1, the 360 px Extra-large sweep;
|
|
967
|
+
// docs/text-size-harmonization.md §4). In one row at 150 % "Today ‹ ›" left the
|
|
968
|
+
// heading 30 px on a 360 px phone, and "October 2026" was drawn under "Today".
|
|
969
|
+
// Where they fit, nothing changes.
|
|
970
|
+
<div {...wrapperRest} ref={rootRef} className={cn("flex min-w-0 flex-wrap items-center gap-2", className)}>
|
|
966
971
|
{Heading ? <Heading className="m-0 min-w-0 text-lg font-semibold">{trigger}</Heading> : trigger}
|
|
967
972
|
<div className="ms-auto flex shrink-0 items-center gap-1">
|
|
968
973
|
{showToday && (
|
|
@@ -242,7 +242,12 @@ export const PhoneInput = forwardRef<HTMLInputElement, PhoneInputProps>(function
|
|
|
242
242
|
role={named ? "group" : undefined}
|
|
243
243
|
aria-label={named ? label : undefined}
|
|
244
244
|
>
|
|
245
|
-
|
|
245
|
+
{/* On a phone at Large the number takes a line of its own under the country (0.32.1,
|
|
246
|
+
the 360 px Extra-large sweep; docs/text-size-harmonization.md §4 "nothing
|
|
247
|
+
truncates"): the 7rem select is 168 px at 150 %, and on a 360 px phone it left
|
|
248
|
+
the number 82 px — "Ph…" over "021 (". `large:max-sm:`, the scaled phone width,
|
|
249
|
+
so a wider screen at Large and Normal keep the one row. */}
|
|
250
|
+
<div className="flex items-stretch gap-2 large:max-sm:flex-wrap">
|
|
246
251
|
<Select
|
|
247
252
|
aria-label={text.countryCode}
|
|
248
253
|
value={country}
|
|
@@ -261,7 +266,7 @@ export const PhoneInput = forwardRef<HTMLInputElement, PhoneInputProps>(function
|
|
|
261
266
|
<option value="other">{text.other}</option>
|
|
262
267
|
</Select>
|
|
263
268
|
{/* Its own flex item, so nothing Input wraps itself in can take the sizing. */}
|
|
264
|
-
<div className="min-w-0 flex-1">
|
|
269
|
+
<div className="min-w-0 flex-1 large:max-sm:basis-full">
|
|
265
270
|
<Input
|
|
266
271
|
ref={ref}
|
|
267
272
|
{...rest}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isValidElement } from "react";
|
|
2
|
-
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
import type { KeyboardEvent, MouseEvent, ReactElement, ReactNode } from "react";
|
|
3
3
|
import { MoreHorizontal } from "lucide-react";
|
|
4
4
|
import type { LucideIcon } from "lucide-react";
|
|
5
5
|
import { cn } from "../lib/cn";
|
|
@@ -7,6 +7,7 @@ import { useLargeText } from "../hooks/use-large-text";
|
|
|
7
7
|
import { useKitLabels } from "../i18n/kit-labels";
|
|
8
8
|
import { Popover } from "./popover";
|
|
9
9
|
import { Button, CompactControls, IconButton } from "./ui";
|
|
10
|
+
import { useCommitReason } from "./write-lock";
|
|
10
11
|
import type { IconButtonSize } from "./ui";
|
|
11
12
|
import type { DataTableColumn } from "./data-table";
|
|
12
13
|
|
|
@@ -31,8 +32,25 @@ export interface RowAction {
|
|
|
31
32
|
key?: string;
|
|
32
33
|
/** The action's name: the inline icon's name and tooltip, the menu entry's words. */
|
|
33
34
|
label: string;
|
|
34
|
-
/**
|
|
35
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Runs it. The kit sends nothing — the app opens its confirmation or calls its API.
|
|
37
|
+
* Optional only beside `href`; an action has one or the other (or both: `onSelect`
|
|
38
|
+
* then runs as the link is followed).
|
|
39
|
+
*/
|
|
40
|
+
onSelect?: () => void;
|
|
41
|
+
/**
|
|
42
|
+
* The action NAVIGATES — an edit page, a mail to compose (0.32.1, kastlan's 0.32
|
|
43
|
+
* report): a real link inline and in the menu, so open in a new tab, a middle click
|
|
44
|
+
* and the URL on hover work, which `onSelect` + `navigate` lost. The kit's link rule
|
|
45
|
+
* applies ({@link Button}'s `href`): in-app through the provider's `linkComponent`, an
|
|
46
|
+
* external or `#anchor` one a plain `<a>`, `mailto:` included.
|
|
47
|
+
*
|
|
48
|
+
* A link cannot carry a reason or a spinner, so an action with a `disabledReason`
|
|
49
|
+
* (or under a write lock, with `commit`) or `pending` stays a button and says why.
|
|
50
|
+
*/
|
|
51
|
+
href?: string;
|
|
52
|
+
/** With `href`: leaves the app — a new tab, and a screen reader is told so. */
|
|
53
|
+
external?: boolean;
|
|
36
54
|
/**
|
|
37
55
|
* The glyph: a Lucide icon (`Pencil`), or an element (`<Pencil />`, an app's own
|
|
38
56
|
* svg) — DataTable's `rowActions` hand their `icon` node through. Inline, an action
|
|
@@ -104,8 +122,8 @@ export type RowActionsSize = Extract<IconButtonSize, "md" | "sm" | "xs" | "2xs">
|
|
|
104
122
|
export interface RowActionsProps {
|
|
105
123
|
actions: RowActionList;
|
|
106
124
|
/** The row's name, for the menu button's name — "Actions for Ada Example". Without it
|
|
107
|
-
* the button is "Actions". */
|
|
108
|
-
name?: string;
|
|
125
|
+
* (or `null`, as API fields often are) the button is "Actions". */
|
|
126
|
+
name?: string | null;
|
|
109
127
|
/** When the actions collapse into the menu — see {@link RowActionsCollapse}. */
|
|
110
128
|
collapse?: RowActionsCollapse;
|
|
111
129
|
/** The inline icons' and the "⋯" button's size. Default `xs`, a list row's action. */
|
|
@@ -198,81 +216,168 @@ export function RowActions({
|
|
|
198
216
|
)}
|
|
199
217
|
>
|
|
200
218
|
{(close) => (
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
219
|
+
// React carries an event out of a portal along the COMPONENT tree, so a click on
|
|
220
|
+
// an entry (or between two) reached a clickable row or a DataTable `onRowClick`
|
|
221
|
+
// the menu was opened from, though the panel is nowhere near it in the page —
|
|
222
|
+
// only the "⋯" trigger stopped its own (0.32.1, keksdose's 0.32 report). Stopped
|
|
223
|
+
// here for the whole panel: clicks, and the Enter / Space a row also listens for.
|
|
224
|
+
// Escape goes on, to the popover that closes on it.
|
|
225
|
+
<div role="presentation" onClick={stopClick} onKeyDown={stopActivationKeys}>
|
|
226
|
+
<ul data-slot="row-actions-menu" className="space-y-0.5">
|
|
227
|
+
{entries.map((action) => (
|
|
205
228
|
<li key={action.key ?? action.label}>
|
|
206
|
-
<
|
|
207
|
-
type="button"
|
|
208
|
-
variant="ghost"
|
|
209
|
-
size="sm"
|
|
210
|
-
stretch
|
|
211
|
-
tone={action.tone === "danger" ? "danger" : undefined}
|
|
212
|
-
disabledReason={action.disabledReason}
|
|
213
|
-
commit={action.commit}
|
|
214
|
-
pending={action.pending}
|
|
215
|
-
disabled={action.disabled}
|
|
216
|
-
className="w-full justify-start text-start"
|
|
217
|
-
onClick={() => {
|
|
218
|
-
close();
|
|
219
|
-
action.onSelect();
|
|
220
|
-
}}
|
|
221
|
-
>
|
|
222
|
-
{glyph && (
|
|
223
|
-
<span aria-hidden className="flex shrink-0 [&_svg]:size-4">
|
|
224
|
-
{glyph}
|
|
225
|
-
</span>
|
|
226
|
-
)}
|
|
227
|
-
{action.label}
|
|
228
|
-
</Button>
|
|
229
|
+
<MenuAction action={action} close={close} />
|
|
229
230
|
</li>
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
</
|
|
231
|
+
))}
|
|
232
|
+
</ul>
|
|
233
|
+
</div>
|
|
233
234
|
)}
|
|
234
235
|
</Popover>
|
|
235
236
|
);
|
|
236
237
|
}
|
|
237
238
|
|
|
238
|
-
|
|
239
|
+
function stopClick(e: MouseEvent) {
|
|
240
|
+
e.stopPropagation();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function stopActivationKeys(e: KeyboardEvent) {
|
|
244
|
+
if (e.key === "Enter" || e.key === " ") e.stopPropagation();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* The `href` an action renders as a link with, or `undefined` for a button: a link
|
|
249
|
+
* cannot carry a reason or a spinner (Button's link form has neither), so a refused
|
|
250
|
+
* action — its own `disabledReason`, or a write lock on a `commit` — and a pending one
|
|
251
|
+
* stay buttons and say so.
|
|
252
|
+
*/
|
|
253
|
+
function useActionHref(action: RowAction): string | undefined {
|
|
254
|
+
const reason = useCommitReason(action.commit, action.disabledReason);
|
|
255
|
+
const refused = reason !== undefined && reason !== null && reason !== false && reason !== "";
|
|
256
|
+
return action.href !== undefined && !action.pending && !refused ? action.href : undefined;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** One entry of the "⋯" menu: the words, as a link for an `href`, else a button. */
|
|
260
|
+
function MenuAction({ action, close }: { action: RowAction; close: () => void }) {
|
|
261
|
+
const href = useActionHref(action);
|
|
262
|
+
const glyph = glyphOf(action.icon);
|
|
263
|
+
const words = (
|
|
264
|
+
<>
|
|
265
|
+
{glyph && (
|
|
266
|
+
<span aria-hidden className="flex shrink-0 [&_svg]:size-4">
|
|
267
|
+
{glyph}
|
|
268
|
+
</span>
|
|
269
|
+
)}
|
|
270
|
+
{action.label}
|
|
271
|
+
</>
|
|
272
|
+
);
|
|
273
|
+
const tone = action.tone === "danger" ? "danger" : undefined;
|
|
274
|
+
const select = () => {
|
|
275
|
+
close();
|
|
276
|
+
action.onSelect?.();
|
|
277
|
+
};
|
|
278
|
+
if (href !== undefined) {
|
|
279
|
+
return (
|
|
280
|
+
<Button
|
|
281
|
+
href={href}
|
|
282
|
+
external={action.external}
|
|
283
|
+
variant="ghost"
|
|
284
|
+
size="sm"
|
|
285
|
+
stretch
|
|
286
|
+
tone={tone}
|
|
287
|
+
disabled={action.disabled}
|
|
288
|
+
className="w-full justify-start text-start"
|
|
289
|
+
onClick={select}
|
|
290
|
+
>
|
|
291
|
+
{words}
|
|
292
|
+
</Button>
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
return (
|
|
296
|
+
<Button
|
|
297
|
+
type="button"
|
|
298
|
+
variant="ghost"
|
|
299
|
+
size="sm"
|
|
300
|
+
stretch
|
|
301
|
+
tone={tone}
|
|
302
|
+
disabledReason={action.disabledReason}
|
|
303
|
+
commit={action.commit}
|
|
304
|
+
pending={action.pending}
|
|
305
|
+
disabled={action.disabled}
|
|
306
|
+
className="w-full justify-start text-start"
|
|
307
|
+
onClick={select}
|
|
308
|
+
>
|
|
309
|
+
{words}
|
|
310
|
+
</Button>
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** One inline action: an IconButton with its icon, else a small text button — each a
|
|
315
|
+
* link for an `href` (see {@link useActionHref}). */
|
|
239
316
|
function InlineAction({ action, size }: { action: RowAction; size: RowActionsSize }) {
|
|
317
|
+
const href = useActionHref(action);
|
|
240
318
|
const glyph = glyphOf(action.icon);
|
|
319
|
+
const tone = action.tone === "danger" ? "danger" : undefined;
|
|
241
320
|
if (glyph) {
|
|
321
|
+
if (href !== undefined) {
|
|
322
|
+
return (
|
|
323
|
+
<IconButton
|
|
324
|
+
href={href}
|
|
325
|
+
external={action.external}
|
|
326
|
+
size={size}
|
|
327
|
+
label={action.label}
|
|
328
|
+
tone={tone}
|
|
329
|
+
disabled={action.disabled}
|
|
330
|
+
stopPropagation
|
|
331
|
+
onClick={() => action.onSelect?.()}
|
|
332
|
+
>
|
|
333
|
+
{glyph}
|
|
334
|
+
</IconButton>
|
|
335
|
+
);
|
|
336
|
+
}
|
|
242
337
|
return (
|
|
243
338
|
<IconButton
|
|
244
339
|
size={size}
|
|
245
340
|
label={action.label}
|
|
246
|
-
tone={
|
|
341
|
+
tone={tone}
|
|
247
342
|
disabledReason={action.disabledReason}
|
|
248
343
|
commit={action.commit}
|
|
249
344
|
pending={action.pending}
|
|
250
345
|
disabled={action.disabled}
|
|
251
346
|
stopPropagation
|
|
252
|
-
onClick={action.onSelect}
|
|
347
|
+
onClick={() => action.onSelect?.()}
|
|
253
348
|
>
|
|
254
349
|
{glyph}
|
|
255
350
|
</IconButton>
|
|
256
351
|
);
|
|
257
352
|
}
|
|
353
|
+
// A row listening for clicks must not open as well (IconButton's stopPropagation).
|
|
354
|
+
const stop = {
|
|
355
|
+
onClick: (e: MouseEvent) => {
|
|
356
|
+
e.stopPropagation();
|
|
357
|
+
action.onSelect?.();
|
|
358
|
+
},
|
|
359
|
+
onKeyDown: (e: KeyboardEvent) => {
|
|
360
|
+
if (e.key === "Enter" || e.key === " ") e.stopPropagation();
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
if (href !== undefined) {
|
|
364
|
+
return (
|
|
365
|
+
<Button href={href} external={action.external} variant="ghost" size="sm" tone={tone} disabled={action.disabled} {...stop}>
|
|
366
|
+
{action.label}
|
|
367
|
+
</Button>
|
|
368
|
+
);
|
|
369
|
+
}
|
|
258
370
|
return (
|
|
259
371
|
<Button
|
|
260
372
|
type="button"
|
|
261
373
|
variant="ghost"
|
|
262
374
|
size="sm"
|
|
263
|
-
tone={
|
|
375
|
+
tone={tone}
|
|
264
376
|
disabledReason={action.disabledReason}
|
|
265
377
|
commit={action.commit}
|
|
266
378
|
pending={action.pending}
|
|
267
379
|
disabled={action.disabled}
|
|
268
|
-
|
|
269
|
-
// A row listening for clicks must not open as well (IconButton's stopPropagation).
|
|
270
|
-
e.stopPropagation();
|
|
271
|
-
action.onSelect();
|
|
272
|
-
}}
|
|
273
|
-
onKeyDown={(e) => {
|
|
274
|
-
if (e.key === "Enter" || e.key === " ") e.stopPropagation();
|
|
275
|
-
}}
|
|
380
|
+
{...stop}
|
|
276
381
|
>
|
|
277
382
|
{action.label}
|
|
278
383
|
</Button>
|
|
@@ -290,8 +395,9 @@ export interface RowActionsColumnOptions<T> {
|
|
|
290
395
|
key?: string;
|
|
291
396
|
/** The row's actions — `false` / `null` for the ones a condition leaves out. */
|
|
292
397
|
actions: (row: T) => RowActionList;
|
|
293
|
-
/** The row's name, for the "⋯" button's name ("Actions for Ada Example").
|
|
294
|
-
|
|
398
|
+
/** The row's name, for the "⋯" button's name ("Actions for Ada Example"). `null` and
|
|
399
|
+
* `undefined` both mean "Actions". */
|
|
400
|
+
name?: (row: T) => string | null | undefined;
|
|
295
401
|
collapse?: RowActionsCollapse;
|
|
296
402
|
/** Default `xs`. */
|
|
297
403
|
size?: RowActionsSize;
|
|
@@ -45,8 +45,12 @@ export interface ThemeSettingProps
|
|
|
45
45
|
* one segmented control instead of a select — system, light and dark side by side, the
|
|
46
46
|
* choice visible without opening anything, in every app (keksdose offered light and dark
|
|
47
47
|
* only; its store already knew "system"). A {@link ToggleGroup}'s props pass through —
|
|
48
|
-
* `labelPlacement`, `disabledReason`, `commit`, `size` — minus the options
|
|
49
|
-
* which this owns.
|
|
48
|
+
* `labelPlacement`, `disabledReason`, `commit`, `size`, `overflow` — minus the options
|
|
49
|
+
* and the value, which this owns.
|
|
50
|
+
*
|
|
51
|
+
* The options wrap rather than truncate (`overflow="wrap"`), as in its two siblings
|
|
52
|
+
* {@link TextSizeSetting} and {@link ContrastSetting} (0.32.1, Kurvenschmiede's 0.32
|
|
53
|
+
* report: "Syst… Li… D…" in a card at Extra large on a 360 px phone).
|
|
50
54
|
*
|
|
51
55
|
* Bind it to the PREFERENCE (`"system"` included), never to the resolved mode: on a
|
|
52
56
|
* system-dark device a control bound to the mode shows "dark" and cannot say "follow the
|
|
@@ -70,10 +74,11 @@ export interface ThemeSettingToggleProps
|
|
|
70
74
|
|
|
71
75
|
export function ThemeSetting(props: ThemeSettingProps | ThemeSettingToggleProps) {
|
|
72
76
|
if (props.variant === "toggle") {
|
|
73
|
-
const { variant: _variant, value, onChange, label, optionLabels, ...rest } = props;
|
|
77
|
+
const { variant: _variant, value, onChange, label, optionLabels, overflow = "wrap", ...rest } = props;
|
|
74
78
|
return (
|
|
75
79
|
<ToggleGroup<ThemePreference>
|
|
76
80
|
{...rest}
|
|
81
|
+
overflow={overflow}
|
|
77
82
|
label={label}
|
|
78
83
|
value={value}
|
|
79
84
|
onChange={onChange}
|
|
@@ -20,6 +20,7 @@ import { Tooltip } from "./tooltip";
|
|
|
20
20
|
import { Button, Card, IconButton, Spinner } from "./ui";
|
|
21
21
|
import { useWriteLock } from "./write-lock";
|
|
22
22
|
import type { CardProps } from "./ui";
|
|
23
|
+
import type { DisabledReasonDisplay } from "./field-parts";
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Who holds one record, and the owner's every way of changing that — Kurvenschmiede's
|
|
@@ -224,6 +225,16 @@ function ShareRoleChip({ role }: { role: ShareRole | undefined }) {
|
|
|
224
225
|
return role.description ? <Tooltip label={role.description}>{chip}</Tooltip> : chip;
|
|
225
226
|
}
|
|
226
227
|
|
|
228
|
+
/**
|
|
229
|
+
* A row's controls: the role toggle and the remove icon, or a pending grant's copy and
|
|
230
|
+
* revoke. Held to the row and wrapping inside it (0.32.1, the 360 px Extra-large sweep;
|
|
231
|
+
* docs/text-size-harmonization.md §4 "nothing overflows"): under a write lock on a touch
|
|
232
|
+
* screen or at Large the remove icon gives its reason as a line under it, up to 20rem
|
|
233
|
+
* wide, and a `shrink-0` box ran 31 px past a 390 px phone at Normal and 269 px past a
|
|
234
|
+
* 360 px one at Extra large. Where the controls fit in one row, nothing changes.
|
|
235
|
+
*/
|
|
236
|
+
const ROW_CONTROLS = "ms-auto flex min-w-0 max-w-full flex-wrap items-center justify-end gap-1";
|
|
237
|
+
|
|
227
238
|
/**
|
|
228
239
|
* A row's role choice saves on change, so it is a commit that cannot take `commit`:
|
|
229
240
|
* under a write lock it is disabled, and the lock's reason is put in the kit Tooltip
|
|
@@ -278,6 +289,12 @@ export function SharePanel({
|
|
|
278
289
|
const roleOf = (key: string | undefined) => roles.find((r) => r.key === key);
|
|
279
290
|
const choosable = roles.length > 1;
|
|
280
291
|
const canAdd = !readOnly && onAdd !== undefined;
|
|
292
|
+
// Under a write lock the form's Share button gives the lock's sentence as a line (on
|
|
293
|
+
// touch and at Large). A row's remove or revoke icon then keeps it in its tooltip, as
|
|
294
|
+
// the role toggle beside it does, rather than repeat the same line under every row
|
|
295
|
+
// (0.32.1, the 360 px Extra-large sweep). Without the form the rows' lines are the
|
|
296
|
+
// only place it is said, so they stay — and ROW_CONTROLS wraps them.
|
|
297
|
+
const rowReason: DisabledReasonDisplay = canAdd ? "tooltip" : "auto";
|
|
281
298
|
|
|
282
299
|
const run = async (key: string, action: () => MaybePromise): Promise<boolean> => {
|
|
283
300
|
setBusy(key);
|
|
@@ -457,7 +474,7 @@ export function SharePanel({
|
|
|
457
474
|
)}
|
|
458
475
|
</div>
|
|
459
476
|
{(changeable || (editable && onRemove)) && (
|
|
460
|
-
<div className=
|
|
477
|
+
<div className={ROW_CONTROLS}>
|
|
461
478
|
{changeable && (
|
|
462
479
|
<LockedRoleChoice reason={lock.locked ? lock.reason : undefined}>
|
|
463
480
|
<ToggleGroup<string>
|
|
@@ -484,6 +501,7 @@ export function SharePanel({
|
|
|
484
501
|
// spinner and the write lock, which a "⋯" menu entry
|
|
485
502
|
// cannot, and its confirmation names the action in words.
|
|
486
503
|
labelVisible={false}
|
|
504
|
+
disabledReasonDisplay={rowReason}
|
|
487
505
|
pending={busy === key}
|
|
488
506
|
disabled={busy !== null}
|
|
489
507
|
onClick={async () => {
|
|
@@ -529,7 +547,7 @@ export function SharePanel({
|
|
|
529
547
|
</div>
|
|
530
548
|
</div>
|
|
531
549
|
{(p.link || (!readOnly && onRevokePending)) && (
|
|
532
|
-
<div className=
|
|
550
|
+
<div className={ROW_CONTROLS}>
|
|
533
551
|
{p.link && <CopyButton size="xs" tone="muted" text={p.link} label={labels.copyLink} />}
|
|
534
552
|
{!readOnly && onRevokePending && (
|
|
535
553
|
<IconButton
|
|
@@ -541,6 +559,7 @@ export function SharePanel({
|
|
|
541
559
|
// copy icon (which is an icon at every size), and the
|
|
542
560
|
// confirmation says it in words.
|
|
543
561
|
labelVisible={false}
|
|
562
|
+
disabledReasonDisplay={rowReason}
|
|
544
563
|
pending={busy === key}
|
|
545
564
|
disabled={busy !== null}
|
|
546
565
|
onClick={async () => {
|
|
@@ -66,6 +66,28 @@ export function statusDotColor(tone: StatusDotTone): string {
|
|
|
66
66
|
|
|
67
67
|
const SIZE: Record<StatusDotSize, string> = { sm: "size-2", md: "size-2.5", lg: "size-3" };
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* A labelled dot at Large and Extra large (0.32.1, keksdose's 0.32 report;
|
|
71
|
+
* docs/text-size-harmonization.md §4 "nothing truncates"): the label wraps instead of
|
|
72
|
+
* ending in "…", so a legend entry or a status keeps its last word, the one that tells
|
|
73
|
+
* two states apart. The dot then sits on the label's FIRST line rather than halfway down
|
|
74
|
+
* the block: the row aligns to the top and the dot moves down by half of what one line
|
|
75
|
+
* (`1lh`, the label's own line height, which the dot inherits) leaves around it.
|
|
76
|
+
* `large:` classes, so Normal keeps its one centred line.
|
|
77
|
+
*
|
|
78
|
+
* `break-words`, not `[overflow-wrap:anywhere]`: a legend often sits in a table, and
|
|
79
|
+
* `anywhere` lowers the label's min-content width to one letter, so an auto-sized column
|
|
80
|
+
* squeezed it to a letter per line (the showcase's tone table at 360 px, Extra large).
|
|
81
|
+
* `break-word` keeps each word whole wherever the width is the content's to choose, and
|
|
82
|
+
* still breaks one that cannot fit its line at all.
|
|
83
|
+
*/
|
|
84
|
+
const LABEL_WRAP_AT_LARGE = "large:whitespace-normal large:break-words";
|
|
85
|
+
const DOT_ON_FIRST_LINE: Record<StatusDotSize, string> = {
|
|
86
|
+
sm: "large:mt-[calc((1lh_-_0.5rem)/2)]",
|
|
87
|
+
md: "large:mt-[calc((1lh_-_0.625rem)/2)]",
|
|
88
|
+
lg: "large:mt-[calc((1lh_-_0.75rem)/2)]",
|
|
89
|
+
};
|
|
90
|
+
|
|
69
91
|
export interface StatusDotProps extends Omit<ComponentPropsWithoutRef<"span">, "children"> {
|
|
70
92
|
tone?: StatusDotTone;
|
|
71
93
|
size?: StatusDotSize;
|
|
@@ -116,6 +138,7 @@ export function StatusDot({
|
|
|
116
138
|
SIZE[size],
|
|
117
139
|
FILL[tone],
|
|
118
140
|
ring && "ring-2 ring-[var(--bg-surface)]",
|
|
141
|
+
label != null && DOT_ON_FIRST_LINE[size],
|
|
119
142
|
label == null && className,
|
|
120
143
|
dotClassName,
|
|
121
144
|
)}
|
|
@@ -130,9 +153,9 @@ export function StatusDot({
|
|
|
130
153
|
return (
|
|
131
154
|
// No `aria-label` here: the visible label is the text, and a name on a role-less
|
|
132
155
|
// span is one ARIA forbids and screen readers ignore.
|
|
133
|
-
<span {...rest} className={cn("inline-flex min-w-0 items-center gap-1.5 text-sm", className)}>
|
|
156
|
+
<span {...rest} className={cn("inline-flex min-w-0 items-center gap-1.5 text-sm large:items-start", className)}>
|
|
134
157
|
{dot}
|
|
135
|
-
<span className="min-w-0 truncate">{label}</span>
|
|
158
|
+
<span className={cn("min-w-0 truncate", LABEL_WRAP_AT_LARGE)}>{label}</span>
|
|
136
159
|
</span>
|
|
137
160
|
);
|
|
138
161
|
}
|
|
@@ -269,8 +269,15 @@ export function SwipeableRow({
|
|
|
269
269
|
type="button"
|
|
270
270
|
onClick={action.onCommit}
|
|
271
271
|
className={cn(
|
|
272
|
-
"sr-only whitespace-nowrap rounded-md border
|
|
272
|
+
"sr-only whitespace-nowrap rounded-md border-[var(--border)] bg-[var(--bg-surface)] text-xs font-medium text-[var(--text-primary)] shadow-sm",
|
|
273
273
|
"focus:not-sr-only focus:pointer-events-auto focus:inline-flex focus:items-center focus:gap-1.5",
|
|
274
|
+
// The box's padding and border only once it shows (0.32.1, keksdose's
|
|
275
|
+
// 0.32 report). Unconditional, `px-2` and `border` beat `sr-only`'s
|
|
276
|
+
// `padding: 0` and `border-width: 0`, so the hidden button was a 1 px
|
|
277
|
+
// box with 2 × 0.5rem of padding round it: 26 px at Extra large, running
|
|
278
|
+
// past the row's end from the strip's start — invisible under its clip,
|
|
279
|
+
// but counted in the page's scroll width (§4: nothing overflows).
|
|
280
|
+
"focus:border focus:px-2 focus:py-1",
|
|
274
281
|
FOCUS_RING,
|
|
275
282
|
)}
|
|
276
283
|
>
|