@eifi1/ui-kit 0.32.0 → 0.32.2
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/page-header.js +6 -1
- package/dist/components/page-header.js.map +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/popover.d.ts +1 -1
- package/dist/components/popover.js +2 -0
- package/dist/components/popover.js.map +1 -1
- package/dist/components/row-actions.d.ts +1 -1
- package/dist/components/row-actions.js +97 -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 +6 -6
- 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-Dy4kJgUm.d.ts} +53 -14
- 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/app-shell.js +2 -1
- package/dist/shell/app-shell.js.map +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/page-header.tsx +15 -1
- package/src/components/phone-input.tsx +7 -2
- package/src/components/popover.tsx +4 -0
- package/src/components/row-actions.tsx +156 -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 +28 -9
- 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/shell/app-shell.tsx +11 -6
- 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
|
@@ -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}
|
|
@@ -110,6 +110,7 @@ export function Popover({
|
|
|
110
110
|
className,
|
|
111
111
|
style,
|
|
112
112
|
"aria-label": ariaLabel,
|
|
113
|
+
onKeyDown: onKeyDownProp,
|
|
113
114
|
...rest
|
|
114
115
|
}: PopoverProps) {
|
|
115
116
|
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
@@ -169,6 +170,9 @@ export function Popover({
|
|
|
169
170
|
});
|
|
170
171
|
|
|
171
172
|
const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
|
|
173
|
+
// A caller's own handler first (0.32.2: RowActions stops Enter / Space at the panel);
|
|
174
|
+
// it used to be dropped, overwritten by this one.
|
|
175
|
+
onKeyDownProp?.(e);
|
|
172
176
|
if (e.key !== "Escape") return;
|
|
173
177
|
// `useEscapeKey` above listens on the document and still covers the case where
|
|
174
178
|
// focus somehow is not in here. This handler is for the case that focus IS, which
|
|
@@ -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. */
|
|
@@ -196,83 +214,171 @@ export function RowActions({
|
|
|
196
214
|
<MoreHorizontal />
|
|
197
215
|
</IconButton>
|
|
198
216
|
)}
|
|
217
|
+
// React carries an event out of a portal along the COMPONENT tree, so a click in
|
|
218
|
+
// the menu reached a clickable row or a DataTable `onRowClick` the menu was opened
|
|
219
|
+
// from, though the panel is nowhere near it in the page — only the "⋯" trigger
|
|
220
|
+
// stopped its own (keksdose's 0.32 report). Stopped on the PANEL itself, its padding
|
|
221
|
+
// and border included (0.32.2: 0.32.1 stopped it on an inner box, and a click on
|
|
222
|
+
// the panel's `p-1` still went through): clicks, and the Enter / Space a row also
|
|
223
|
+
// listens for. Escape goes on, to the popover's own handler that closes it.
|
|
224
|
+
onClick={stopClick}
|
|
225
|
+
onKeyDown={stopActivationKeys}
|
|
199
226
|
>
|
|
200
227
|
{(close) => (
|
|
201
228
|
<ul data-slot="row-actions-menu" className="space-y-0.5">
|
|
202
|
-
{entries.map((action) =>
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
</li>
|
|
230
|
-
);
|
|
231
|
-
})}
|
|
229
|
+
{entries.map((action) => (
|
|
230
|
+
<li key={action.key ?? action.label}>
|
|
231
|
+
<MenuAction action={action} close={close} />
|
|
232
|
+
</li>
|
|
233
|
+
))}
|
|
232
234
|
</ul>
|
|
233
235
|
)}
|
|
234
236
|
</Popover>
|
|
235
237
|
);
|
|
236
238
|
}
|
|
237
239
|
|
|
238
|
-
|
|
240
|
+
function stopClick(e: MouseEvent) {
|
|
241
|
+
e.stopPropagation();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function stopActivationKeys(e: KeyboardEvent) {
|
|
245
|
+
if (e.key === "Enter" || e.key === " ") e.stopPropagation();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* The `href` an action renders as a link with, or `undefined` for a button: a link
|
|
250
|
+
* cannot carry a reason or a spinner (Button's link form has neither), so a refused
|
|
251
|
+
* action — its own `disabledReason`, or a write lock on a `commit` — and a pending one
|
|
252
|
+
* stay buttons and say so.
|
|
253
|
+
*/
|
|
254
|
+
function useActionHref(action: RowAction): string | undefined {
|
|
255
|
+
const reason = useCommitReason(action.commit, action.disabledReason);
|
|
256
|
+
const refused = reason !== undefined && reason !== null && reason !== false && reason !== "";
|
|
257
|
+
return action.href !== undefined && !action.pending && !refused ? action.href : undefined;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** One entry of the "⋯" menu: the words, as a link for an `href`, else a button. */
|
|
261
|
+
function MenuAction({ action, close }: { action: RowAction; close: () => void }) {
|
|
262
|
+
const href = useActionHref(action);
|
|
263
|
+
const glyph = glyphOf(action.icon);
|
|
264
|
+
const words = (
|
|
265
|
+
<>
|
|
266
|
+
{glyph && (
|
|
267
|
+
<span aria-hidden className="flex shrink-0 [&_svg]:size-4">
|
|
268
|
+
{glyph}
|
|
269
|
+
</span>
|
|
270
|
+
)}
|
|
271
|
+
{action.label}
|
|
272
|
+
</>
|
|
273
|
+
);
|
|
274
|
+
const tone = action.tone === "danger" ? "danger" : undefined;
|
|
275
|
+
const select = () => {
|
|
276
|
+
close();
|
|
277
|
+
action.onSelect?.();
|
|
278
|
+
};
|
|
279
|
+
if (href !== undefined) {
|
|
280
|
+
return (
|
|
281
|
+
<Button
|
|
282
|
+
href={href}
|
|
283
|
+
external={action.external}
|
|
284
|
+
variant="ghost"
|
|
285
|
+
size="sm"
|
|
286
|
+
stretch
|
|
287
|
+
tone={tone}
|
|
288
|
+
disabled={action.disabled}
|
|
289
|
+
className="w-full justify-start text-start"
|
|
290
|
+
onClick={select}
|
|
291
|
+
>
|
|
292
|
+
{words}
|
|
293
|
+
</Button>
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
return (
|
|
297
|
+
<Button
|
|
298
|
+
type="button"
|
|
299
|
+
variant="ghost"
|
|
300
|
+
size="sm"
|
|
301
|
+
stretch
|
|
302
|
+
tone={tone}
|
|
303
|
+
disabledReason={action.disabledReason}
|
|
304
|
+
commit={action.commit}
|
|
305
|
+
pending={action.pending}
|
|
306
|
+
disabled={action.disabled}
|
|
307
|
+
className="w-full justify-start text-start"
|
|
308
|
+
onClick={select}
|
|
309
|
+
>
|
|
310
|
+
{words}
|
|
311
|
+
</Button>
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** One inline action: an IconButton with its icon, else a small text button — each a
|
|
316
|
+
* link for an `href` (see {@link useActionHref}). */
|
|
239
317
|
function InlineAction({ action, size }: { action: RowAction; size: RowActionsSize }) {
|
|
318
|
+
const href = useActionHref(action);
|
|
240
319
|
const glyph = glyphOf(action.icon);
|
|
320
|
+
const tone = action.tone === "danger" ? "danger" : undefined;
|
|
241
321
|
if (glyph) {
|
|
322
|
+
if (href !== undefined) {
|
|
323
|
+
return (
|
|
324
|
+
<IconButton
|
|
325
|
+
href={href}
|
|
326
|
+
external={action.external}
|
|
327
|
+
size={size}
|
|
328
|
+
label={action.label}
|
|
329
|
+
tone={tone}
|
|
330
|
+
disabled={action.disabled}
|
|
331
|
+
stopPropagation
|
|
332
|
+
onClick={() => action.onSelect?.()}
|
|
333
|
+
>
|
|
334
|
+
{glyph}
|
|
335
|
+
</IconButton>
|
|
336
|
+
);
|
|
337
|
+
}
|
|
242
338
|
return (
|
|
243
339
|
<IconButton
|
|
244
340
|
size={size}
|
|
245
341
|
label={action.label}
|
|
246
|
-
tone={
|
|
342
|
+
tone={tone}
|
|
247
343
|
disabledReason={action.disabledReason}
|
|
248
344
|
commit={action.commit}
|
|
249
345
|
pending={action.pending}
|
|
250
346
|
disabled={action.disabled}
|
|
251
347
|
stopPropagation
|
|
252
|
-
onClick={action.onSelect}
|
|
348
|
+
onClick={() => action.onSelect?.()}
|
|
253
349
|
>
|
|
254
350
|
{glyph}
|
|
255
351
|
</IconButton>
|
|
256
352
|
);
|
|
257
353
|
}
|
|
354
|
+
// A row listening for clicks must not open as well (IconButton's stopPropagation).
|
|
355
|
+
const stop = {
|
|
356
|
+
onClick: (e: MouseEvent) => {
|
|
357
|
+
e.stopPropagation();
|
|
358
|
+
action.onSelect?.();
|
|
359
|
+
},
|
|
360
|
+
onKeyDown: (e: KeyboardEvent) => {
|
|
361
|
+
if (e.key === "Enter" || e.key === " ") e.stopPropagation();
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
if (href !== undefined) {
|
|
365
|
+
return (
|
|
366
|
+
<Button href={href} external={action.external} variant="ghost" size="sm" tone={tone} disabled={action.disabled} {...stop}>
|
|
367
|
+
{action.label}
|
|
368
|
+
</Button>
|
|
369
|
+
);
|
|
370
|
+
}
|
|
258
371
|
return (
|
|
259
372
|
<Button
|
|
260
373
|
type="button"
|
|
261
374
|
variant="ghost"
|
|
262
375
|
size="sm"
|
|
263
|
-
tone={
|
|
376
|
+
tone={tone}
|
|
264
377
|
disabledReason={action.disabledReason}
|
|
265
378
|
commit={action.commit}
|
|
266
379
|
pending={action.pending}
|
|
267
380
|
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
|
-
}}
|
|
381
|
+
{...stop}
|
|
276
382
|
>
|
|
277
383
|
{action.label}
|
|
278
384
|
</Button>
|
|
@@ -290,8 +396,9 @@ export interface RowActionsColumnOptions<T> {
|
|
|
290
396
|
key?: string;
|
|
291
397
|
/** The row's actions — `false` / `null` for the ones a condition leaves out. */
|
|
292
398
|
actions: (row: T) => RowActionList;
|
|
293
|
-
/** The row's name, for the "⋯" button's name ("Actions for Ada Example").
|
|
294
|
-
|
|
399
|
+
/** The row's name, for the "⋯" button's name ("Actions for Ada Example"). `null` and
|
|
400
|
+
* `undefined` both mean "Actions". */
|
|
401
|
+
name?: (row: T) => string | null | undefined;
|
|
295
402
|
collapse?: RowActionsCollapse;
|
|
296
403
|
/** Default `xs`. */
|
|
297
404
|
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
|
>
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
DISABLED_REASON_LINE_CLASS,
|
|
10
10
|
FIELD_CAPTION_CLASS,
|
|
11
11
|
hasContent,
|
|
12
|
+
isTextHint,
|
|
12
13
|
LabelStrip,
|
|
13
14
|
useDisabledReasonLine,
|
|
14
15
|
useInlineHint,
|
|
@@ -130,8 +131,15 @@ export interface ToggleGroupBaseProps<T extends string>
|
|
|
130
131
|
* two stack and both keep their corners.
|
|
131
132
|
*/
|
|
132
133
|
chromeClassName?: string;
|
|
133
|
-
/**
|
|
134
|
-
*
|
|
134
|
+
/**
|
|
135
|
+
* As on a labelled {@link Select}: a {@link FieldHint} "?" on the label line, and plain
|
|
136
|
+
* text (a string) as a caption under the group. Only with `label`.
|
|
137
|
+
*
|
|
138
|
+
* 0.32.1 (keksdose's 0.32 report): text went on the label line in every placement, so
|
|
139
|
+
* `TextSizeSetting`'s hint with `labelPlacement="above"` sat beside the label in body
|
|
140
|
+
* type, and at Extra large became a column one word wide — on the setting a reader who
|
|
141
|
+
* needs large text opens first. The label line holds the "?" only (`isTextHint`).
|
|
142
|
+
*/
|
|
135
143
|
hint?: ReactNode;
|
|
136
144
|
/** The message under the field when it is wrong: paints the field's border with
|
|
137
145
|
* `--danger`, marks the group `aria-invalid` and describes it with the message, as
|
|
@@ -196,9 +204,9 @@ const CHROME_PAD: Record<"sm" | "md", string> = { md: "pt-5 pb-0.5", sm: "pt-5 p
|
|
|
196
204
|
const CHROME_SEGMENT: Record<"sm" | "md", string> = { md: "py-0 leading-[1.125rem]", sm: "py-0 leading-4" };
|
|
197
205
|
|
|
198
206
|
/**
|
|
199
|
-
* A
|
|
207
|
+
* A group at Large and Extra large (0.32,
|
|
200
208
|
* docs/text-size-harmonization.md §4 "nothing truncates", §3.3): the segments flow onto a
|
|
201
|
-
* second row inside the
|
|
209
|
+
* second row inside the group's frame instead of overflowing it. At 125 % a 390 px phone
|
|
202
210
|
* is 312 px of Normal type, and "Month / Quarter / Half-year / Year" in one row of the
|
|
203
211
|
* field ran 7 px past it: a one-row flex group is as wide as all its labels end to end
|
|
204
212
|
* when a grid or a stretched column asks how small it can get, however its segments
|
|
@@ -212,12 +220,16 @@ const CHROME_SEGMENT: Record<"sm" | "md", string> = { md: "py-0 leading-[1.125re
|
|
|
212
220
|
* Only where it has to: `flex-wrap` breaks a row only when the segments do not fit, so
|
|
213
221
|
* a wide field at Large stays one row, and a segment shares out what its row leaves
|
|
214
222
|
* (`flex-1`); a label wider than the whole row still truncates as the last resort.
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
223
|
+
* A `large:` class, so it costs no render and is right on the first paint.
|
|
224
|
+
* `overflow="wrap"` wraps at every size already.
|
|
225
|
+
*
|
|
226
|
+
* 0.32.1 (Kurvenschmiede's 0.32 report): every count and every placement but the
|
|
227
|
+
* strip, not only a field of four or more. Since a row breaks only where its labels do
|
|
228
|
+
* not fit, a wrap only ever replaces a truncation — and three options in a card at
|
|
229
|
+
* Extra large on a 360 px phone read "Syst… Li… D…". The strip keeps one row: its
|
|
230
|
+
* 26 px group is what lines it up with the 42 px fields beside it (`STRIP_SEGMENT`).
|
|
218
231
|
*/
|
|
219
232
|
const WRAP_AT_LARGE = "large:flex-wrap";
|
|
220
|
-
const WRAP_AT_LARGE_FROM = 4;
|
|
221
233
|
|
|
222
234
|
/**
|
|
223
235
|
* The strip placement's segments: a 26px group (1 + 2 + 20 + 2 + 1) under the 16px
|
|
@@ -335,9 +347,10 @@ export function ToggleGroup<T extends string>(props: ToggleGroupProps<T>): React
|
|
|
335
347
|
const locked = hasContent(disabledReason);
|
|
336
348
|
// §4: at Large and on touch the reason is a line under the group, not a bubble.
|
|
337
349
|
const reasonLine = useDisabledReasonLine(disabledReasonDisplay) && locked;
|
|
338
|
-
// §4: likewise a FieldHint "?" on the label line becomes a caption under the group
|
|
350
|
+
// §4: likewise a FieldHint "?" on the label line becomes a caption under the group at
|
|
351
|
+
// Large and on touch; text is a caption at every size, as Select's (see `hint`).
|
|
339
352
|
const inlineHint = useInlineHint(hint);
|
|
340
|
-
const hintAsCaption = inlineHint !== hint ? inlineHint : undefined;
|
|
353
|
+
const hintAsCaption = isTextHint(hint) ? hint : inlineHint !== hint ? inlineHint : undefined;
|
|
341
354
|
const labelHint = hintAsCaption === undefined ? hint : undefined;
|
|
342
355
|
// Locked looks disabled — the dimmed group, the not-allowed cursor, no hover offer —
|
|
343
356
|
// but stays focusable; `disabled` alone also takes the segments out of the tab order.
|
|
@@ -346,10 +359,11 @@ export function ToggleGroup<T extends string>(props: ToggleGroupProps<T>): React
|
|
|
346
359
|
// `field` is the chrome; a label placed above or in a strip keeps the bare group's
|
|
347
360
|
// own box.
|
|
348
361
|
const field = labelled && labelPlacement === "field";
|
|
349
|
-
// At Large and Extra large a FIELD of four or more options wraps (see WRAP_AT_LARGE).
|
|
350
|
-
const wrapAtLarge = field && overflow === "truncate" && options.length >= WRAP_AT_LARGE_FROM;
|
|
351
362
|
const above = labelled && labelPlacement === "above";
|
|
352
363
|
const strip = labelled && labelPlacement === "strip";
|
|
364
|
+
// At Large and Extra large a truncating group wraps instead, the strip excepted (see
|
|
365
|
+
// WRAP_AT_LARGE).
|
|
366
|
+
const wrapAtLarge = overflow === "truncate" && !strip;
|
|
353
367
|
const hasError = labelled && hasContent(error);
|
|
354
368
|
// Taken off the rest so neither reaches the DOM; `props` keeps them paired, which is
|
|
355
369
|
// what lets the `onChange` below be called with `null` only in the mode that allows it.
|
|
@@ -150,7 +150,7 @@ export function TranslationReviewEditor({
|
|
|
150
150
|
<div className={cn("min-w-0 space-y-3 text-sm", className)}>
|
|
151
151
|
<code className="block break-all text-xs text-[var(--text-muted)]">{row.key}</code>
|
|
152
152
|
|
|
153
|
-
<div className="grid gap-3 md:grid-cols-2">
|
|
153
|
+
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
|
|
154
154
|
<div className="min-w-0">
|
|
155
155
|
<SectionLabel as="span" size="xs">
|
|
156
156
|
{referenceLabel}
|
|
@@ -2,7 +2,7 @@ import { useEffect, useId, useMemo, useRef, useState } from "react";
|
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
3
|
import { Check, ListChecks, PencilLine, RotateCcw } from "lucide-react";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { usePhoneLayout } from "../hooks/use-breakpoint";
|
|
6
6
|
import { useKitLabels, useKitLocale } from "../i18n/kit-labels";
|
|
7
7
|
import { cn } from "../lib/cn";
|
|
8
8
|
import {
|
|
@@ -458,9 +458,10 @@ export function TranslationReviewPanel({
|
|
|
458
458
|
const labels = useKitLabels("translationReview", DEFAULT_TRANSLATION_REVIEW_LABELS, labelsProp);
|
|
459
459
|
const locale = useKitLocale();
|
|
460
460
|
const lock = useWriteLock();
|
|
461
|
-
// The
|
|
462
|
-
//
|
|
463
|
-
|
|
461
|
+
// The phone layout DataTable switches its layout on, so "a phone" means the same here
|
|
462
|
+
// as in the table, at every text size: `usePhoneLayout()` itself (§10.4; 0.32.1,
|
|
463
|
+
// keksdose's 0.32 report — `!useBreakpoint("md")` could disagree with it at 767.5 px).
|
|
464
|
+
const phone = usePhoneLayout();
|
|
464
465
|
const baseId = useId();
|
|
465
466
|
const rootRef = useRef<HTMLDivElement>(null);
|
|
466
467
|
const editable = !readOnly && onSave !== undefined;
|