@godxjp/ui 23.0.0 → 23.2.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/dist/app/date-format-labels.d.ts +14 -2
- package/dist/app/date-format-labels.js +1 -1
- package/dist/app/date-formats.d.ts +14 -2
- package/dist/app/date-formats.js +9 -2
- package/dist/components/data-display/card.d.ts +45 -2
- package/dist/components/data-display/card.js +4 -2
- package/dist/components/data-display/data-table.d.ts +8 -1
- package/dist/components/data-display/data-table.js +9 -2
- package/dist/components/data-display/feature-list.d.ts +26 -0
- package/dist/components/data-display/feature-list.js +29 -0
- package/dist/components/data-display/index.d.ts +6 -0
- package/dist/components/data-display/index.js +6 -0
- package/dist/components/data-display/swatch.d.ts +24 -0
- package/dist/components/data-display/swatch.js +19 -0
- package/dist/components/data-display/thumbnail.d.ts +37 -0
- package/dist/components/data-display/thumbnail.js +20 -0
- package/dist/components/data-entry/checkbox.js +1 -1
- package/dist/components/data-entry/form-field.js +2 -2
- package/dist/components/data-entry/input.js +1 -1
- package/dist/components/data-entry/number-input.js +27 -6
- package/dist/components/data-entry/radio.js +1 -1
- package/dist/components/data-entry/switch.js +1 -1
- package/dist/components/feedback/dialog.js +2 -2
- package/dist/components/feedback/index.d.ts +2 -2
- package/dist/components/feedback/index.js +19 -1
- package/dist/components/feedback/sheet.js +2 -22
- package/dist/components/feedback/skeleton.d.ts +33 -3
- package/dist/components/feedback/skeleton.js +154 -3
- package/dist/components/layout/flex.d.ts +1 -1
- package/dist/components/layout/flex.js +17 -5
- package/dist/components/layout/sidebar.js +9 -2
- package/dist/components/navigation/filter-bar.js +2 -1
- package/dist/components/navigation/tabs-scroll.d.ts +27 -0
- package/dist/components/navigation/tabs-scroll.js +36 -1
- package/dist/components/navigation/tabs.d.ts +2 -2
- package/dist/components/navigation/tabs.js +60 -16
- package/dist/i18n/messages/en.json +10 -1
- package/dist/i18n/messages/ja.json +10 -1
- package/dist/i18n/messages/vi.json +10 -1
- package/dist/lib/breakpoint-token.d.ts +8 -0
- package/dist/lib/breakpoint-token.js +29 -0
- package/dist/props/components/data-display.prop.d.ts +101 -1
- package/dist/props/components/feedback.prop.d.ts +85 -1
- package/dist/props/components/layout.prop.d.ts +72 -1
- package/dist/props/components/navigation.prop.d.ts +80 -2
- package/dist/props/registry.d.ts +124 -1
- package/dist/props/registry.js +140 -0
- package/dist/props/vocabulary/data.prop.d.ts +5 -0
- package/dist/props/vocabulary/index.d.ts +1 -1
- package/dist/styles/alert-layout.css +139 -0
- package/dist/styles/card-layout.css +48 -0
- package/dist/styles/control.css +41 -5
- package/dist/styles/data-display-layout.css +88 -0
- package/dist/styles/focus-ring.css +28 -0
- package/dist/styles/form-layout.css +4 -0
- package/dist/styles/layout.css +2 -1
- package/dist/styles/navigation-layout.css +89 -5
- package/dist/styles/shell-layout.css +22 -0
- package/dist/styles/table-layout.css +5 -2
- package/dist/tokens/components/card.css +2 -0
- package/dist/tokens/components/control.css +8 -3
- package/dist/tokens/components/data-display.css +23 -0
- package/dist/tokens/components/feedback.css +18 -0
- package/dist/tokens/components/navigation.css +4 -0
- package/dist/tokens/foundation.css +18 -16
- package/docs/CONSUMER-RULES.md +8 -3
- package/docs/CUSTOMER-THEMING.md +18 -8
- package/docs/DATETIME.md +11 -2
- package/docs/DESIGN-AUTHORITY.md +2 -1
- package/docs/FRAME-COVERAGE-REPORT.md +29 -3
- package/docs/SPACING.md +35 -5
- package/docs/TOKENS.md +1 -1
- package/docs/assets/shot-landscape.svg +12 -0
- package/docs/assets/shot-portrait.svg +10 -0
- package/docs/data-display/card/index.tsx +40 -0
- package/docs/data-display/data-table/index.tsx +17 -2
- package/docs/data-display/feature-list.tsx +159 -0
- package/docs/data-display/swatch.tsx +107 -0
- package/docs/data-display/table.tsx +1 -1
- package/docs/data-display/thumbnail.tsx +128 -0
- package/docs/data-entry/segmented-in-filter-row.tsx +77 -0
- package/docs/feedback/skeleton.tsx +57 -0
- package/docs/layout/flex.tsx +29 -0
- package/docs/layout/sidebar.tsx +66 -10
- package/docs/navigation/dropdown-menu.tsx +4 -4
- package/docs/navigation/tabs.tsx +131 -0
- package/docs/roadmap/ai-chat-components.md +1 -2
- package/docs/roadmap/tree-components.md +0 -1
- package/package.json +12 -21
- package/scripts/_agent-setup.mjs +18 -0
- package/scripts/consumer-rule.md +16 -0
- package/scripts/guinea-pig-skill.md +12 -6
- package/scripts/ui-audit.mjs +184 -7
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
import type { AppLocale } from "./types.js";
|
|
2
2
|
import type { AppDateFormat } from "./date-formats.js";
|
|
3
3
|
export declare const APP_DATE_FORMAT_OPTIONS: {
|
|
4
|
-
value: "dmy" | "iso" | "mdy";
|
|
4
|
+
value: "dmy" | "iso" | "mdy" | "ymd";
|
|
5
5
|
}[];
|
|
6
6
|
export declare function getDateFormatLabel(dateFormat: AppDateFormat, locale: AppLocale, fallbackLocale?: AppLocale): string;
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Suggested default per locale — vi → dmy, ja → ymd, en → mdy.
|
|
9
|
+
*
|
|
10
|
+
* `ja` used to be `iso` (`yyyy-MM-dd`). Nothing recorded a reason for it, and it is not the form a
|
|
11
|
+
* Japanese business document is written in: those use `YYYY/MM/DD` (or `YYYY年MM月DD日`). The cost
|
|
12
|
+
* of the old default was measured in a consumer — gino-cloud shipped its own date formatter as an
|
|
13
|
+
* explicit stopgap purely to get the slashes, which is the package's own rule 1 broken by the
|
|
14
|
+
* package's own default.
|
|
15
|
+
*
|
|
16
|
+
* ONLY THE DEFAULT MOVES. A stored preference still wins (see `resolveDateFormat` in
|
|
17
|
+
* app-provider.tsx), a service can pin any value through `defaultDateFormat`, and `iso` is still
|
|
18
|
+
* one keystroke away in `DateFormatPicker`.
|
|
19
|
+
*/
|
|
8
20
|
export declare function resolveDefaultDateFormat(locale: AppLocale): AppDateFormat;
|
|
@@ -6,7 +6,7 @@ function getDateFormatLabel(dateFormat, locale, fallbackLocale = "en") {
|
|
|
6
6
|
}
|
|
7
7
|
function resolveDefaultDateFormat(locale) {
|
|
8
8
|
if (locale === "en") return "mdy";
|
|
9
|
-
if (locale === "ja") return "
|
|
9
|
+
if (locale === "ja") return "ymd";
|
|
10
10
|
return "dmy";
|
|
11
11
|
}
|
|
12
12
|
export {
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
/** Date-only display preset — sent as `x-date-format` to backend. */
|
|
2
2
|
import { type AppTimeFormat } from "./time-formats.js";
|
|
3
|
-
export type AppDateFormat = "iso" | "dmy" | "mdy";
|
|
4
|
-
|
|
3
|
+
export type AppDateFormat = "iso" | "ymd" | "dmy" | "mdy";
|
|
4
|
+
/**
|
|
5
|
+
* `ymd` (`yyyy/MM/dd`) IS THE JAPANESE BUSINESS FORM, AND ITS ABSENCE WAS A HOLE WITH NO WAY OUT.
|
|
6
|
+
*
|
|
7
|
+
* The axis shipped three values and none of them said `2026/05/01`: `iso` writes hyphens, and the
|
|
8
|
+
* two slash forms put the day or the month first. A Japanese-first consumer (gino-cloud) therefore
|
|
9
|
+
* wrote its own formatter and marked it an explicit stopgap — the tell that a named axis was
|
|
10
|
+
* missing a value rather than the consumer wanting something exotic, since the only alternative
|
|
11
|
+
* the package left was the thing docs/DATETIME.md rule 1 forbids ("never use date-fns/format,
|
|
12
|
+
* toLocaleString, or raw ISO slices for UI").
|
|
13
|
+
*
|
|
14
|
+
* It sits BEFORE the day/month-first pair because it is the other year-first form, next to `iso`.
|
|
15
|
+
*/
|
|
16
|
+
export declare const APP_DATE_FORMATS: readonly ["iso", "ymd", "dmy", "mdy"];
|
|
5
17
|
export declare const APP_REQUEST_HEADER_DATE_FORMAT: "x-date-format";
|
|
6
18
|
/** date-fns pattern for date-only display. */
|
|
7
19
|
export declare function getDatePattern(dateFormat: AppDateFormat): string;
|
package/dist/app/date-formats.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { getTimePattern } from "./time-formats.js";
|
|
2
|
-
const APP_DATE_FORMATS = [
|
|
2
|
+
const APP_DATE_FORMATS = [
|
|
3
|
+
"iso",
|
|
4
|
+
"ymd",
|
|
5
|
+
"dmy",
|
|
6
|
+
"mdy"
|
|
7
|
+
];
|
|
3
8
|
const APP_REQUEST_HEADER_DATE_FORMAT = "x-date-format";
|
|
4
9
|
function getDatePattern(dateFormat) {
|
|
5
10
|
switch (dateFormat) {
|
|
11
|
+
case "ymd":
|
|
12
|
+
return "yyyy/MM/dd";
|
|
6
13
|
case "dmy":
|
|
7
14
|
return "dd/MM/yyyy";
|
|
8
15
|
case "mdy":
|
|
@@ -16,7 +23,7 @@ function getDateTimePattern(timeFormat, dateFormat) {
|
|
|
16
23
|
return `${getDatePattern(dateFormat)} ${getTimePattern(timeFormat)}`;
|
|
17
24
|
}
|
|
18
25
|
function isAppDateFormat(value) {
|
|
19
|
-
return value
|
|
26
|
+
return APP_DATE_FORMATS.includes(value);
|
|
20
27
|
}
|
|
21
28
|
export {
|
|
22
29
|
APP_DATE_FORMATS,
|
|
@@ -13,8 +13,13 @@ type CardAccent = "primary" | "success" | "warning" | "info" | "attention" | "de
|
|
|
13
13
|
* borrowing the brand colour.
|
|
14
14
|
*/
|
|
15
15
|
type CardAccentPlacement = "edge" | "perimeter";
|
|
16
|
-
/**
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Surface fill and edge. `outline` is antd's `outlined` — it drops the FILL and keeps the
|
|
18
|
+
* hairline; `borderless` is antd's `variant="borderless"` (its deprecated `bordered={false}`) —
|
|
19
|
+
* it drops the HAIRLINE and keeps the fill. They are two different cards, which is why both
|
|
20
|
+
* values exist: neither one substitutes for the other.
|
|
21
|
+
*/
|
|
22
|
+
type CardVariant = "default" | "muted" | "outline" | "borderless" | "featured";
|
|
18
23
|
/** Padding density — base 16px · tight 12px · cozy 20px. */
|
|
19
24
|
type CardDensity = "tight" | "cozy";
|
|
20
25
|
export type CardProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
@@ -26,6 +31,16 @@ export type CardProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
|
26
31
|
accentPlacement?: CardAccentPlacement;
|
|
27
32
|
variant?: CardVariant;
|
|
28
33
|
density?: CardDensity;
|
|
34
|
+
/**
|
|
35
|
+
* Lift the card on hover (antd `hoverable`) — the resting elevation steps up to
|
|
36
|
+
* `--card-hover-shadow` and the pointer becomes a `pointer`.
|
|
37
|
+
*
|
|
38
|
+
* This is a PRESENTATION flag, not an interaction: it announces nothing and binds no handler.
|
|
39
|
+
* A card that looks clickable has to BE clickable for everyone, so pair it with a real control
|
|
40
|
+
* — a `Link`/`Button` in the header or footer, or the whole card rendered as one — never with a
|
|
41
|
+
* bare `onClick` on this div, which a keyboard or screen-reader user cannot reach.
|
|
42
|
+
*/
|
|
43
|
+
hoverable?: boolean;
|
|
29
44
|
};
|
|
30
45
|
export declare const Card: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & {
|
|
31
46
|
accent?: CardAccent;
|
|
@@ -36,6 +51,16 @@ export declare const Card: React.ForwardRefExoticComponent<React.HTMLAttributes<
|
|
|
36
51
|
accentPlacement?: CardAccentPlacement;
|
|
37
52
|
variant?: CardVariant;
|
|
38
53
|
density?: CardDensity;
|
|
54
|
+
/**
|
|
55
|
+
* Lift the card on hover (antd `hoverable`) — the resting elevation steps up to
|
|
56
|
+
* `--card-hover-shadow` and the pointer becomes a `pointer`.
|
|
57
|
+
*
|
|
58
|
+
* This is a PRESENTATION flag, not an interaction: it announces nothing and binds no handler.
|
|
59
|
+
* A card that looks clickable has to BE clickable for everyone, so pair it with a real control
|
|
60
|
+
* — a `Link`/`Button` in the header or footer, or the whole card rendered as one — never with a
|
|
61
|
+
* bare `onClick` on this div, which a keyboard or screen-reader user cannot reach.
|
|
62
|
+
*/
|
|
63
|
+
hoverable?: boolean;
|
|
39
64
|
} & React.RefAttributes<HTMLDivElement>>;
|
|
40
65
|
/** Full-bleed cover media — first child; header below uses section top (φ⁰), not shell. */
|
|
41
66
|
export type CardCoverProps = React.HTMLAttributes<HTMLDivElement>;
|
|
@@ -90,12 +115,30 @@ export type CardFooterProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
|
90
115
|
separated?: boolean;
|
|
91
116
|
/** Full-bleed footer (the conventional `actions` bar). */
|
|
92
117
|
flush?: boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Split the footer into EQUAL-WIDTH cells divided by vertical rules — antd's `actions` row.
|
|
120
|
+
*
|
|
121
|
+
* A different band from `separated`, which packs its children at the inline end at their
|
|
122
|
+
* natural widths: that is the right shape for a Save/Cancel pair and the wrong one for antd's
|
|
123
|
+
* divided strip. Self-sufficient — it draws its own top rule and full-bleed edges, because
|
|
124
|
+
* upstream's actions row is never anything else.
|
|
125
|
+
*/
|
|
126
|
+
actions?: boolean;
|
|
93
127
|
};
|
|
94
128
|
export declare const CardFooter: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & {
|
|
95
129
|
/** Top border + symmetric action band — form Save/Cancel, table summary. */
|
|
96
130
|
separated?: boolean;
|
|
97
131
|
/** Full-bleed footer (the conventional `actions` bar). */
|
|
98
132
|
flush?: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Split the footer into EQUAL-WIDTH cells divided by vertical rules — antd's `actions` row.
|
|
135
|
+
*
|
|
136
|
+
* A different band from `separated`, which packs its children at the inline end at their
|
|
137
|
+
* natural widths: that is the right shape for a Save/Cancel pair and the wrong one for antd's
|
|
138
|
+
* divided strip. Self-sufficient — it draws its own top rule and full-bleed edges, because
|
|
139
|
+
* upstream's actions row is never anything else.
|
|
140
|
+
*/
|
|
141
|
+
actions?: boolean;
|
|
99
142
|
} & React.RefAttributes<HTMLDivElement>>;
|
|
100
143
|
export type CardBarProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
101
144
|
pad?: PadProp;
|
|
@@ -3,13 +3,14 @@ import { padStyle, padStepToken } from "../../lib/variants.js";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { cn } from "../../lib/utils.js";
|
|
5
5
|
const Card = React.forwardRef(
|
|
6
|
-
({ className, accent, accentPlacement, variant, density, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
6
|
+
({ className, accent, accentPlacement, variant, density, hoverable, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
7
7
|
"div",
|
|
8
8
|
{
|
|
9
9
|
ref,
|
|
10
10
|
className: cn("group/card", className),
|
|
11
11
|
"data-slot": "card",
|
|
12
12
|
"data-accent": accent,
|
|
13
|
+
"data-hoverable": hoverable ? "" : void 0,
|
|
13
14
|
"data-accent-placement": accentPlacement === "perimeter" ? "perimeter" : void 0,
|
|
14
15
|
"data-variant": variant && variant !== "default" ? variant : void 0,
|
|
15
16
|
"data-density": density,
|
|
@@ -61,13 +62,14 @@ const CardContent = React.forwardRef(
|
|
|
61
62
|
);
|
|
62
63
|
CardContent.displayName = "CardContent";
|
|
63
64
|
const CardFooter = React.forwardRef(
|
|
64
|
-
({ className, separated, flush, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
65
|
+
({ className, separated, flush, actions, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
65
66
|
"div",
|
|
66
67
|
{
|
|
67
68
|
ref,
|
|
68
69
|
"data-slot": "card-footer",
|
|
69
70
|
"data-separated": separated ? "" : void 0,
|
|
70
71
|
"data-flush": flush ? "" : void 0,
|
|
72
|
+
"data-actions": actions ? "" : void 0,
|
|
71
73
|
className,
|
|
72
74
|
...props
|
|
73
75
|
}
|
|
@@ -13,6 +13,13 @@ interface DataTableProps<T> {
|
|
|
13
13
|
columns: ColumnDef<T>[];
|
|
14
14
|
/** Required when `selectable` is true. Default: assume row.id (typed as any). */
|
|
15
15
|
getRowId?: (row: T) => string;
|
|
16
|
+
/**
|
|
17
|
+
* Human name of a row, announced by its selection checkbox or radio as "Select row {label}".
|
|
18
|
+
* Default: the text of the `priority: "primary"` column, else of the first column, when that
|
|
19
|
+
* value is a string or number; the row id only as a last resort — an id is a KEY, and announced
|
|
20
|
+
* it reads a UUID aloud. `rowSelection.getCheckboxProps` `aria-label` still overrides a row.
|
|
21
|
+
*/
|
|
22
|
+
getRowLabel?: (row: T) => string;
|
|
16
23
|
selectable?: boolean;
|
|
17
24
|
selected?: Set<string>;
|
|
18
25
|
onSelectChange?: (next: Set<string>) => void;
|
|
@@ -130,7 +137,7 @@ interface DataTableProps<T> {
|
|
|
130
137
|
className?: string;
|
|
131
138
|
children?: React.ReactNode;
|
|
132
139
|
}
|
|
133
|
-
export declare function DataTable<T>({ data, columns, getRowId, selectable, selected: controlledSelected, onSelectChange, onRowClick, density: controlledDensity, onDensityChange, sort, onSortChange, globalFilter: controlledGlobalFilter, onGlobalFilterChange, pagination: paginationProp, onPaginationChange, rowCount, columnVisibility: controlledVisibility, onColumnVisibilityChange, manualSorting, manualFiltering, manualPagination, loading, empty, error, denied, onRetry, striped, hoverable, stickyHeader, preset, collapseBelow, rowClassName, rowTone, rowSelection, expandable, summary, scroll, sticky, onRow, bordered, showSorterTooltip, sortDirections, onFilterChange, className, children, }: DataTableProps<T>): React.JSX.Element;
|
|
140
|
+
export declare function DataTable<T>({ data, columns, getRowId, getRowLabel, selectable, selected: controlledSelected, onSelectChange, onRowClick, density: controlledDensity, onDensityChange, sort, onSortChange, globalFilter: controlledGlobalFilter, onGlobalFilterChange, pagination: paginationProp, onPaginationChange, rowCount, columnVisibility: controlledVisibility, onColumnVisibilityChange, manualSorting, manualFiltering, manualPagination, loading, empty, error, denied, onRetry, striped, hoverable, stickyHeader, preset, collapseBelow, rowClassName, rowTone, rowSelection, expandable, summary, scroll, sticky, onRow, bordered, showSorterTooltip, sortDirections, onFilterChange, className, children, }: DataTableProps<T>): React.JSX.Element;
|
|
134
141
|
export declare namespace DataTable {
|
|
135
142
|
var Toolbar: ({ children, className, }: {
|
|
136
143
|
children?: React.ReactNode;
|
|
@@ -190,6 +190,7 @@ function DataTable({
|
|
|
190
190
|
data,
|
|
191
191
|
columns,
|
|
192
192
|
getRowId = noopGetRowId,
|
|
193
|
+
getRowLabel,
|
|
193
194
|
selectable = false,
|
|
194
195
|
selected: controlledSelected,
|
|
195
196
|
onSelectChange,
|
|
@@ -443,6 +444,7 @@ function DataTable({
|
|
|
443
444
|
toggleExpanded,
|
|
444
445
|
rowSelection,
|
|
445
446
|
getRowId,
|
|
447
|
+
getRowLabel,
|
|
446
448
|
showSorterTooltip,
|
|
447
449
|
sortDirections,
|
|
448
450
|
sortPriorities,
|
|
@@ -764,6 +766,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
764
766
|
toggleExpanded,
|
|
765
767
|
rowSelection,
|
|
766
768
|
getRowId,
|
|
769
|
+
getRowLabel,
|
|
767
770
|
showSorterTooltip,
|
|
768
771
|
sortDirections,
|
|
769
772
|
sortPriorities
|
|
@@ -791,6 +794,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
791
794
|
);
|
|
792
795
|
}
|
|
793
796
|
}, [missingHeaderNames]);
|
|
797
|
+
const selectLabelColumn = visibleColumns.find((col) => col.priority === "primary") ?? visibleColumns[0];
|
|
794
798
|
const scrollRef = React.useRef(null);
|
|
795
799
|
const [hasOverflowEnd, setHasOverflowEnd] = React.useState(false);
|
|
796
800
|
React.useEffect(() => {
|
|
@@ -1096,6 +1100,9 @@ DataTable.Content = function DataTableContent() {
|
|
|
1096
1100
|
const rowKey = getRowId(original);
|
|
1097
1101
|
const isSelected = row.getIsSelected();
|
|
1098
1102
|
const checkboxProps = rowSelection?.getCheckboxProps?.(original) ?? {};
|
|
1103
|
+
const labelValue = selectLabelColumn ? original[selectLabelColumn.key] : void 0;
|
|
1104
|
+
const rowLabel = getRowLabel?.(original) ?? (typeof labelValue === "string" && labelValue.trim() !== "" || typeof labelValue === "number" ? String(labelValue) : row.id);
|
|
1105
|
+
const selectRowLabel = checkboxProps["aria-label"] ?? t("dataTable.selectRow", { id: rowLabel });
|
|
1099
1106
|
const canExpand = expandColumnShown && (expandable?.rowExpandable?.(original) ?? true);
|
|
1100
1107
|
const isExpanded = canExpand && expandedKeys.includes(rowKey);
|
|
1101
1108
|
const rowProps = onRow?.(original, rowIndex) ?? {};
|
|
@@ -1206,7 +1213,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
1206
1213
|
RadioItem,
|
|
1207
1214
|
{
|
|
1208
1215
|
value: rowKey,
|
|
1209
|
-
"aria-label":
|
|
1216
|
+
"aria-label": selectRowLabel,
|
|
1210
1217
|
onClick: (e) => {
|
|
1211
1218
|
e.stopPropagation();
|
|
1212
1219
|
}
|
|
@@ -1222,7 +1229,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
1222
1229
|
onCheckedChange: (v) => {
|
|
1223
1230
|
row.toggleSelected(!!v);
|
|
1224
1231
|
},
|
|
1225
|
-
"aria-label":
|
|
1232
|
+
"aria-label": selectRowLabel,
|
|
1226
1233
|
onClick: (e) => {
|
|
1227
1234
|
e.stopPropagation();
|
|
1228
1235
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type { FeatureItemProp, FeatureListProp, FeatureListProp as FeatureListProps, FeatureStateProp, } from "../../props/components/data-display.prop.js";
|
|
2
|
+
import type { FeatureListProp } from "../../props/components/data-display.prop.js";
|
|
3
|
+
/**
|
|
4
|
+
* FeatureList — a list of STATEMENTS, each carrying a leading state glyph: what a plan includes,
|
|
5
|
+
* what a tier supports, which requirements a submission met.
|
|
6
|
+
*
|
|
7
|
+
* ## Why it is not one of the three components it looks like
|
|
8
|
+
*
|
|
9
|
+
* - **`ListRow`** is a single-line ENTITY row — a session, a token, a passkey — with entity
|
|
10
|
+
* padding, a divider between every row and a trailing action slot. A feature line is none of
|
|
11
|
+
* those: it has no action, it is not a thing you can open, and reading a plan's contents
|
|
12
|
+
* through a stack of ruled rows makes six facts look like six records.
|
|
13
|
+
* - **`Timeline`** has the glyph rail, but it is an ORDERED event list: a connector line runs
|
|
14
|
+
* dot-to-dot and the statuses are `done`/`current`/`pending`, which is time, not inclusion.
|
|
15
|
+
* - **`Descriptions`** is a term/value grid. A feature line has no value column — the state IS
|
|
16
|
+
* the value, and it is drawn, not written.
|
|
17
|
+
*
|
|
18
|
+
* ## The alignment, which is the actual reason this exists
|
|
19
|
+
*
|
|
20
|
+
* The glyph column is one line box tall (`1lh`) and the glyph is centred in it, so it lands on
|
|
21
|
+
* the FIRST line of a label that wraps to three. That is why the component can be handed a
|
|
22
|
+
* one-word label and a paragraph and both come out aligned, at any font size and at any density,
|
|
23
|
+
* with no number in the call site. The shape it replaces reached for `mt-0.5` — 2px, below
|
|
24
|
+
* `--space-1`, off every scale, and wrong the moment the text beside it changes size.
|
|
25
|
+
*/
|
|
26
|
+
export declare function FeatureList({ items, className, ...props }: FeatureListProp): import("react").JSX.Element;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Check, Minus, X } from "lucide-react";
|
|
4
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
5
|
+
import { cn } from "../../lib/utils.js";
|
|
6
|
+
const STATE = {
|
|
7
|
+
included: { Icon: Check, key: "dataDisplay.featureList.included" },
|
|
8
|
+
excluded: { Icon: X, key: "dataDisplay.featureList.excluded" },
|
|
9
|
+
limited: { Icon: Minus, key: "dataDisplay.featureList.limited" }
|
|
10
|
+
};
|
|
11
|
+
function FeatureList({ items, className, ...props }) {
|
|
12
|
+
const { t } = useTranslation();
|
|
13
|
+
return /* @__PURE__ */ jsx("ul", { className: cn("ui-feature-list", className), ...props, children: items.map((item, index) => {
|
|
14
|
+
const { Icon, key } = STATE[item.state];
|
|
15
|
+
return /* @__PURE__ */ jsxs("li", { className: "ui-feature-list-item", "data-state": item.state, children: [
|
|
16
|
+
/* @__PURE__ */ jsx("span", { className: "ui-feature-list-mark", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Icon, {}) }),
|
|
17
|
+
/* @__PURE__ */ jsxs("span", { className: "ui-feature-list-body", children: [
|
|
18
|
+
/* @__PURE__ */ jsxs("span", { className: "ui-feature-list-label", children: [
|
|
19
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: t(key) }),
|
|
20
|
+
item.label
|
|
21
|
+
] }),
|
|
22
|
+
item.description != null ? /* @__PURE__ */ jsx("span", { className: "ui-feature-list-description", children: item.description }) : null
|
|
23
|
+
] })
|
|
24
|
+
] }, index);
|
|
25
|
+
}) });
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
FeatureList
|
|
29
|
+
};
|
|
@@ -2,6 +2,12 @@ export { Badge, StatusBadge } from "./badge.js";
|
|
|
2
2
|
export type { BadgeProps, BadgeTone } from "./badge.js";
|
|
3
3
|
export { Legend } from "./legend.js";
|
|
4
4
|
export type { LegendItemProp, LegendProp, LegendProps } from "./legend.js";
|
|
5
|
+
export { Swatch } from "./swatch.js";
|
|
6
|
+
export type { SwatchProp, SwatchProps } from "./swatch.js";
|
|
7
|
+
export { FeatureList } from "./feature-list.js";
|
|
8
|
+
export type { FeatureItemProp, FeatureListProp, FeatureListProps, FeatureStateProp, } from "./feature-list.js";
|
|
9
|
+
export { Thumbnail } from "./thumbnail.js";
|
|
10
|
+
export type { ThumbnailProp, ThumbnailProps, ThumbnailSizeProp } from "./thumbnail.js";
|
|
5
11
|
export { ListRow } from "./list-row.js";
|
|
6
12
|
export type { ListRowProps, ListRowDensity } from "./list-row.js";
|
|
7
13
|
export { CredentialReveal } from "./credential-reveal.js";
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Badge, StatusBadge } from "./badge.js";
|
|
2
2
|
import { Legend } from "./legend.js";
|
|
3
|
+
import { Swatch } from "./swatch.js";
|
|
4
|
+
import { FeatureList } from "./feature-list.js";
|
|
5
|
+
import { Thumbnail } from "./thumbnail.js";
|
|
3
6
|
import { ListRow } from "./list-row.js";
|
|
4
7
|
import { CredentialReveal } from "./credential-reveal.js";
|
|
5
8
|
import { QrCode } from "./qr-code.js";
|
|
@@ -90,6 +93,7 @@ export {
|
|
|
90
93
|
DataTable,
|
|
91
94
|
Descriptions,
|
|
92
95
|
EmptyState,
|
|
96
|
+
FeatureList,
|
|
93
97
|
HoverCard,
|
|
94
98
|
HoverCardContent,
|
|
95
99
|
HoverCardTrigger,
|
|
@@ -114,12 +118,14 @@ export {
|
|
|
114
118
|
ServiceLauncherCardSkeleton,
|
|
115
119
|
StatCard,
|
|
116
120
|
StatusBadge,
|
|
121
|
+
Swatch,
|
|
117
122
|
Table,
|
|
118
123
|
TableBody,
|
|
119
124
|
TableCell,
|
|
120
125
|
TableHead,
|
|
121
126
|
TableHeader,
|
|
122
127
|
TableRow,
|
|
128
|
+
Thumbnail,
|
|
123
129
|
Timeline,
|
|
124
130
|
TimelineGrid,
|
|
125
131
|
Tree,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SwatchProp } from "../../props/components/data-display.prop.js";
|
|
2
|
+
export type { SwatchProp, SwatchProp as SwatchProps, } from "../../props/components/data-display.prop.js";
|
|
3
|
+
/**
|
|
4
|
+
* Swatch — a READ-ONLY sample of one colour a person chose (gh#527).
|
|
5
|
+
*
|
|
6
|
+
* The colour arrives as a VALUE, never as a token: it is a brand's `primary_color`, a calendar
|
|
7
|
+
* category, a tag a user tinted. That is the axis `Legend` cannot express — its `tone` is a closed
|
|
8
|
+
* set of semantic roles — and the reason there was no legal way to draw one: a hand-rolled
|
|
9
|
+
* `<span className="w-[10px] h-[10px] rounded-[2px] bg-[#c0392f]" />` is blocked by ui-audit three
|
|
10
|
+
* times over, so the only remaining move was a suppression comment.
|
|
11
|
+
*
|
|
12
|
+
* It is the same square mark `Legend` draws (a sample reads as a SAMPLE; a pill reads as a chip
|
|
13
|
+
* you could click), one step larger, because it stands beside a name rather than inside an 11px
|
|
14
|
+
* key. The hairline is not decoration: without it a white or near-white value is invisible on a
|
|
15
|
+
* white card, and the sample stops showing anything at all.
|
|
16
|
+
*
|
|
17
|
+
* ## The accessible name
|
|
18
|
+
*
|
|
19
|
+
* Given an `aria-label` the mark is a `role="img"` that announces it, which is what lets the
|
|
20
|
+
* colour be shown with no visible label beside it. Given none it is `aria-hidden` — the Legend
|
|
21
|
+
* rule, for the same reason: a swatch that repeats a label already on screen would announce the
|
|
22
|
+
* same thing twice. Colour is never the sole carrier either way.
|
|
23
|
+
*/
|
|
24
|
+
export declare function Swatch({ color, className, style, ...props }: SwatchProp): import("react").JSX.Element;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../lib/utils.js";
|
|
3
|
+
function Swatch({ color, className, style, ...props }) {
|
|
4
|
+
const named = props["aria-label"] !== void 0 || props["aria-labelledby"] !== void 0;
|
|
5
|
+
return /* @__PURE__ */ jsx(
|
|
6
|
+
"span",
|
|
7
|
+
{
|
|
8
|
+
"data-slot": "swatch",
|
|
9
|
+
className: cn("ui-swatch", className),
|
|
10
|
+
style: { ...style, ["--swatch-color"]: color },
|
|
11
|
+
role: named ? "img" : void 0,
|
|
12
|
+
"aria-hidden": named ? void 0 : true,
|
|
13
|
+
...props
|
|
14
|
+
}
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
Swatch
|
|
19
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type { ThumbnailProp, ThumbnailProp as ThumbnailProps, ThumbnailSizeProp, } from "../../props/components/data-display.prop.js";
|
|
3
|
+
/**
|
|
4
|
+
* Thumbnail — a framed picture at a FIXED HEIGHT and its OWN intrinsic width.
|
|
5
|
+
*
|
|
6
|
+
* That combination is the whole component. A wrapping row of screenshots holds portrait and
|
|
7
|
+
* landscape shots together; equal heights make the row read as one strip, and letting each
|
|
8
|
+
* width follow its own ratio is what keeps every shot uncropped.
|
|
9
|
+
*
|
|
10
|
+
* ## Why not the four components it is mistaken for
|
|
11
|
+
*
|
|
12
|
+
* - **`AspectRatio`** constrains a RATIO and spans `width: 100%`. Two different intrinsic ratios
|
|
13
|
+
* in one row can only be forced into one number by letterboxing or cropping them, which is the
|
|
14
|
+
* opposite of the requirement. It also draws no frame.
|
|
15
|
+
* - **`Avatar`** is an identity mark (with initials fallback), not a picture.
|
|
16
|
+
* - **`Card`** puts padding between its edge and the picture. The frame here is ON the image.
|
|
17
|
+
* - **`CardCover`** is the full-bleed media slot INSIDE a Card, not a standalone thumbnail.
|
|
18
|
+
*
|
|
19
|
+
* ## The frame is on the `<img>` itself
|
|
20
|
+
*
|
|
21
|
+
* No wrapper element. A wrapper would have to be told the image's width to hug it, which is
|
|
22
|
+
* exactly the number nobody has until the picture loads; a border on the replaced element is
|
|
23
|
+
* flush by construction at every ratio.
|
|
24
|
+
*
|
|
25
|
+
* ## Reserve the width before the picture arrives
|
|
26
|
+
*
|
|
27
|
+
* With an intrinsic width there is nothing to lay out until the bytes land, so a row of these
|
|
28
|
+
* reflows as they load. Pass the file's real pixel `width` and `height` attributes — the browser
|
|
29
|
+
* derives the ratio from them, and with the block size already fixed the frame takes its final
|
|
30
|
+
* width on the first paint (`<Thumbnail width={1280} height={800} … />`).
|
|
31
|
+
*/
|
|
32
|
+
export declare const Thumbnail: React.ForwardRefExoticComponent<Omit<React.ImgHTMLAttributes<HTMLImageElement>, "alt"> & {
|
|
33
|
+
src: string;
|
|
34
|
+
alt: string;
|
|
35
|
+
size?: import("./thumbnail.js").ThumbnailSizeProp;
|
|
36
|
+
className?: import("../../props/index.js").ClassNameProp;
|
|
37
|
+
} & React.RefAttributes<HTMLImageElement>>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "../../lib/utils.js";
|
|
4
|
+
const Thumbnail = React.forwardRef(
|
|
5
|
+
({ size = "md", className, alt, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
6
|
+
"img",
|
|
7
|
+
{
|
|
8
|
+
ref,
|
|
9
|
+
"data-slot": "thumbnail",
|
|
10
|
+
"data-size": size,
|
|
11
|
+
alt,
|
|
12
|
+
className: cn("ui-thumbnail", className),
|
|
13
|
+
...props
|
|
14
|
+
}
|
|
15
|
+
)
|
|
16
|
+
);
|
|
17
|
+
Thumbnail.displayName = "Thumbnail";
|
|
18
|
+
export {
|
|
19
|
+
Thumbnail
|
|
20
|
+
};
|
|
@@ -94,7 +94,7 @@ const CheckboxRoot = React.forwardRef((props, ref) => {
|
|
|
94
94
|
// itself). react-aria's root is a `<label>`, which is `display:inline` — without this the
|
|
95
95
|
// 16px box collapses to nothing. CheckboxVisual already carries the same three for the
|
|
96
96
|
// same reason.
|
|
97
|
-
"peer ui-checkbox data-[invalid]:border-destructive data-[state=checked]:border-primary data-[state=checked]:text-primary-foreground inline-flex shrink-0 items-center justify-center shadow-xs transition-shadow
|
|
97
|
+
"peer ui-checkbox data-[invalid]:border-destructive data-[state=checked]:border-primary data-[state=checked]:text-primary-foreground inline-flex shrink-0 items-center justify-center shadow-xs transition-shadow",
|
|
98
98
|
className
|
|
99
99
|
),
|
|
100
100
|
render: (domProps) => /* @__PURE__ */ jsx("label", { ...domProps, children: withOwnHitTarget(domProps.children) }),
|
|
@@ -47,7 +47,7 @@ function FormField({
|
|
|
47
47
|
const resolvedId = id ?? autoId;
|
|
48
48
|
const labelId = `${resolvedId}-label`;
|
|
49
49
|
const helperId = helper ? `${resolvedId}-helper` : void 0;
|
|
50
|
-
const helperNode = helper ? /* @__PURE__ */ jsx("p", { id: helperId, className: "text-muted-foreground text-xs", children: helper }) : null;
|
|
50
|
+
const helperNode = helper ? /* @__PURE__ */ jsx("p", { id: helperId, className: "ui-form-field-note text-muted-foreground text-xs", children: helper }) : null;
|
|
51
51
|
const validationStatus = error ? "error" : validateStatus;
|
|
52
52
|
const feedbackId = hasFeedback && validationStatus ? `${resolvedId}-feedback` : void 0;
|
|
53
53
|
const FeedbackIcon = validationStatus === "validating" ? LoaderCircle : validationStatus === "error" ? CircleAlert : validationStatus === "warning" ? TriangleAlert : CheckCircle;
|
|
@@ -175,7 +175,7 @@ function FormField({
|
|
|
175
175
|
t(`dataEntry.form.${validationStatus}`)
|
|
176
176
|
] }) : null,
|
|
177
177
|
helperPlacement === "after" ? helperNode : null,
|
|
178
|
-
error ? /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: "text-destructive text-xs", children: error }) : null
|
|
178
|
+
error ? /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: "ui-form-field-note text-destructive text-xs", children: error }) : null
|
|
179
179
|
] })
|
|
180
180
|
]
|
|
181
181
|
}
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "./control-appearance.js";
|
|
14
14
|
import { resolveAllowClear } from "./control-surface.js";
|
|
15
15
|
const inputBaseClass = [
|
|
16
|
-
"ui-control ui-input w-full rounded-[var(--control-radius)] transition-[color,box-shadow]
|
|
16
|
+
"ui-control ui-input w-full rounded-[var(--control-radius)] transition-[color,box-shadow]",
|
|
17
17
|
"selection:bg-primary selection:text-primary-foreground",
|
|
18
18
|
"placeholder:text-muted-foreground",
|
|
19
19
|
"aria-invalid:border-destructive",
|
|
@@ -57,6 +57,21 @@ const NumberInput = React.forwardRef(
|
|
|
57
57
|
}, ref) => {
|
|
58
58
|
const { t, locale } = useTranslation();
|
|
59
59
|
const fieldA11y = pickFieldA11y(ariaProps);
|
|
60
|
+
const stepperVerbId = React.useId();
|
|
61
|
+
const fieldLabel = typeof fieldA11y["aria-label"] === "string" ? fieldA11y["aria-label"] : void 0;
|
|
62
|
+
const fieldLabelledBy = typeof fieldA11y["aria-labelledby"] === "string" ? fieldA11y["aria-labelledby"] : void 0;
|
|
63
|
+
const stepperName = (direction) => {
|
|
64
|
+
const verb = t(`ui.numberInput.${direction}`);
|
|
65
|
+
if (fieldLabelledBy) {
|
|
66
|
+
return { "aria-labelledby": `${fieldLabelledBy} ${stepperVerbId}-${direction}` };
|
|
67
|
+
}
|
|
68
|
+
if (fieldLabel) {
|
|
69
|
+
return {
|
|
70
|
+
"aria-label": t(`ui.numberInput.${direction}Field`, { label: fieldLabel }) || verb
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return { "aria-label": verb };
|
|
74
|
+
};
|
|
60
75
|
const isControlled = controlledValue !== void 0;
|
|
61
76
|
const [internal, setInternal] = React.useState(defaultValue);
|
|
62
77
|
const numericValue = isControlled ? controlledValue ?? null : internal;
|
|
@@ -228,7 +243,7 @@ const NumberInput = React.forwardRef(
|
|
|
228
243
|
}
|
|
229
244
|
) : null,
|
|
230
245
|
controls ? /* @__PURE__ */ jsxs("span", { "data-slot": "number-input-steppers", className: "ui-number-input-steppers", children: [
|
|
231
|
-
/* @__PURE__ */
|
|
246
|
+
/* @__PURE__ */ jsxs(
|
|
232
247
|
Button,
|
|
233
248
|
{
|
|
234
249
|
type: "button",
|
|
@@ -237,12 +252,15 @@ const NumberInput = React.forwardRef(
|
|
|
237
252
|
className: "ui-number-input-step ui-number-input-step-up",
|
|
238
253
|
tabIndex: -1,
|
|
239
254
|
disabled: !interactive || atMax,
|
|
240
|
-
|
|
255
|
+
...stepperName("increment"),
|
|
241
256
|
onClick: () => stepBy(1),
|
|
242
|
-
children:
|
|
257
|
+
children: [
|
|
258
|
+
/* @__PURE__ */ jsx(ChevronUp, { "aria-hidden": "true" }),
|
|
259
|
+
/* @__PURE__ */ jsx("span", { id: `${stepperVerbId}-increment`, className: "sr-only", children: t("ui.numberInput.increment") })
|
|
260
|
+
]
|
|
243
261
|
}
|
|
244
262
|
),
|
|
245
|
-
/* @__PURE__ */
|
|
263
|
+
/* @__PURE__ */ jsxs(
|
|
246
264
|
Button,
|
|
247
265
|
{
|
|
248
266
|
type: "button",
|
|
@@ -251,9 +269,12 @@ const NumberInput = React.forwardRef(
|
|
|
251
269
|
className: "ui-number-input-step ui-number-input-step-down",
|
|
252
270
|
tabIndex: -1,
|
|
253
271
|
disabled: !interactive || atMin,
|
|
254
|
-
|
|
272
|
+
...stepperName("decrement"),
|
|
255
273
|
onClick: () => stepBy(-1),
|
|
256
|
-
children:
|
|
274
|
+
children: [
|
|
275
|
+
/* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": "true" }),
|
|
276
|
+
/* @__PURE__ */ jsx("span", { id: `${stepperVerbId}-decrement`, className: "sr-only", children: t("ui.numberInput.decrement") })
|
|
277
|
+
]
|
|
257
278
|
}
|
|
258
279
|
)
|
|
259
280
|
] }) : null
|
|
@@ -62,7 +62,7 @@ const RadioItem = React.forwardRef(
|
|
|
62
62
|
// records: react-aria's root is a `<label>`, which is `display:inline`, so without it the
|
|
63
63
|
// 16px dot collapses to nothing. `data-[invalid]` replaces `aria-invalid:` because
|
|
64
64
|
// react-aria puts `aria-invalid` on the `<input>` and `data-invalid` on this box.
|
|
65
|
-
"ui-radio data-[invalid]:border-destructive inline-flex shrink-0 items-center justify-center shadow-xs transition-shadow
|
|
65
|
+
"ui-radio data-[invalid]:border-destructive inline-flex shrink-0 items-center justify-center shadow-xs transition-shadow",
|
|
66
66
|
className
|
|
67
67
|
),
|
|
68
68
|
...props,
|
|
@@ -80,7 +80,7 @@ const Switch = React.forwardRef(
|
|
|
80
80
|
// components, so it silently outranked that token. Byte-identical: the token
|
|
81
81
|
// defaults to 0.5. (`shadow-xs` STAYS — .ui-switch declares --shadow-sm, so this
|
|
82
82
|
// utility is the switch's real resting elevation, not a duplicate.)
|
|
83
|
-
"peer ui-switch shadow-xs transition-all
|
|
83
|
+
"peer ui-switch shadow-xs transition-all",
|
|
84
84
|
className
|
|
85
85
|
),
|
|
86
86
|
children: [
|
|
@@ -149,7 +149,7 @@ function DialogContent({
|
|
|
149
149
|
showCloseButton ? /* @__PURE__ */ jsxs(
|
|
150
150
|
DialogClose,
|
|
151
151
|
{
|
|
152
|
-
className: "ui-focus-ring transition-opacity
|
|
152
|
+
className: "ui-focus-ring transition-opacity disabled:pointer-events-none",
|
|
153
153
|
children: [
|
|
154
154
|
/* @__PURE__ */ jsx(X, { className: "ui-dialog-close-icon", "aria-hidden": "true" }),
|
|
155
155
|
/* @__PURE__ */ jsx("span", { className: "sr-only", children: t("feedback.alert.dismiss") })
|
|
@@ -265,7 +265,7 @@ function AlertDialogContent({
|
|
|
265
265
|
{
|
|
266
266
|
type: "button",
|
|
267
267
|
"data-slot": "dialog-close",
|
|
268
|
-
className: "ui-focus-ring transition-opacity
|
|
268
|
+
className: "ui-focus-ring transition-opacity disabled:pointer-events-none",
|
|
269
269
|
"aria-label": t("feedback.alert.dismiss"),
|
|
270
270
|
onClick: () => {
|
|
271
271
|
state.setOpen(false);
|