@godxjp/ui 23.4.5 → 23.4.7
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/components/data-display/data-table.js +2 -1
- package/dist/components/data-entry/form-field.js +2 -1
- package/dist/components/general/float-button.js +3 -2
- package/dist/components/general/typography.js +2 -1
- package/dist/components/layout/app-shell.js +2 -1
- package/dist/components/layout/error-surface.js +2 -1
- package/dist/components/ui/password-input.js +2 -1
- package/dist/components/ui/separator.js +2 -1
- package/dist/lib/dev.d.ts +45 -0
- package/dist/lib/dev.js +7 -0
- package/dist/styles/chart-layout.css +4 -0
- package/dist/styles/navigation-layout.css +26 -0
- package/dist/styles/table-layout.css +13 -0
- package/docs/navigation/tabs.tsx +20 -0
- package/package.json +2 -2
|
@@ -73,6 +73,7 @@ import {
|
|
|
73
73
|
tableCellPaddingClass,
|
|
74
74
|
tableRowHeightClass
|
|
75
75
|
} from "../../lib/control-styles.js";
|
|
76
|
+
import { isDevelopment } from "../../lib/dev.js";
|
|
76
77
|
function resolveColumnHideBelow(col) {
|
|
77
78
|
if (col.hideBelow) return col.hideBelow;
|
|
78
79
|
if (col.hiddenOnMobile) return "md";
|
|
@@ -796,7 +797,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
796
797
|
const fixedTableLayout = scroll?.x !== void 0 || visibleColumns.some((col) => col.ellipsis || columnWidth(col.width).style !== void 0);
|
|
797
798
|
const missingHeaderNames = visibleColumns.filter((col) => isEmptyHeader(col.header) && !col.ariaLabel).map((col) => col.key).join("|");
|
|
798
799
|
React.useEffect(() => {
|
|
799
|
-
if (
|
|
800
|
+
if (!isDevelopment() || !missingHeaderNames) return;
|
|
800
801
|
for (const key of missingHeaderNames.split("|")) {
|
|
801
802
|
console.warn(
|
|
802
803
|
`[DataTable] Column "${key}" renders a <th> with no visible or accessible text. Give it a visible \`header\`, or set \`ariaLabel\` (e.g. "Actions"/"Select") so screen readers can announce the column and axe reports no empty-table-header violation.`
|
|
@@ -10,6 +10,7 @@ import { FieldIdentityContext, FieldNameContext, mergeAriaIds } from "../../lib/
|
|
|
10
10
|
import { useOptionalAppContext } from "../../app/app-provider.js";
|
|
11
11
|
import { useFormLayout } from "./form.js";
|
|
12
12
|
import { firstBagMessage, useClaimErrorKey, useFormErrorsRegistry } from "./form-errors.js";
|
|
13
|
+
import { isDevelopment } from "../../lib/dev.js";
|
|
13
14
|
const toCssLength = (v) => typeof v === "number" ? `${v}px` : v;
|
|
14
15
|
const FOCUSABLE_SELECTOR = 'input:not([type="hidden"]), select, textarea, button, [tabindex]:not([tabindex="-1"])';
|
|
15
16
|
function FormField({
|
|
@@ -55,7 +56,7 @@ function FormField({
|
|
|
55
56
|
const fieldKey = field ?? name ?? id;
|
|
56
57
|
const emitFieldNames = useOptionalAppContext()?.emitFieldNames ?? false;
|
|
57
58
|
const isStatic = staticText !== void 0;
|
|
58
|
-
if (!isStatic &&
|
|
59
|
+
if (!isStatic && isDevelopment() && !React.isValidElement(children)) {
|
|
59
60
|
console.warn(
|
|
60
61
|
"FormField expects a single React element child to receive aria-describedby/aria-errormessage; the helper text and error message will not be associated with the control. Pass plain text via `staticText` instead of `children` for a read-only value row."
|
|
61
62
|
);
|
|
@@ -8,6 +8,7 @@ import { numberFormat } from "../../lib/intl-cache.js";
|
|
|
8
8
|
import { cn } from "../../lib/utils.js";
|
|
9
9
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../feedback/tooltip.js";
|
|
10
10
|
import { Button } from "./button.js";
|
|
11
|
+
import { isDevelopment } from "../../lib/dev.js";
|
|
11
12
|
const GroupContext = React.createContext(null);
|
|
12
13
|
const DEFAULT_ICON = /* @__PURE__ */ jsx(FileText, { "aria-hidden": "true" });
|
|
13
14
|
const BUTTON_VARIANT = {
|
|
@@ -67,7 +68,7 @@ const FloatButtonRoot = React.forwardRef(function FloatButton({
|
|
|
67
68
|
const mergedContent = isRenderable(content) ? content : description;
|
|
68
69
|
const hasContent = isRenderable(mergedContent);
|
|
69
70
|
const mergedIcon = !hasContent && icon == null ? DEFAULT_ICON : icon;
|
|
70
|
-
if (
|
|
71
|
+
if (isDevelopment() && mergedShape === "circle" && hasContent) {
|
|
71
72
|
console.warn(
|
|
72
73
|
"[@godxjp/ui] FloatButton: `content` is supported only when `shape` is `square` \u2014 a circle has no room for a line of text."
|
|
73
74
|
);
|
|
@@ -153,7 +154,7 @@ function FloatButtonGroup({
|
|
|
153
154
|
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);
|
|
154
155
|
const isControlled = open != null;
|
|
155
156
|
const isOpen = isControlled ? open : uncontrolledOpen;
|
|
156
|
-
if (
|
|
157
|
+
if (isDevelopment() && isControlled && !trigger) {
|
|
157
158
|
console.warn(
|
|
158
159
|
"[@godxjp/ui] FloatButton.Group: `open` needs to be used together with `trigger`."
|
|
159
160
|
);
|
|
@@ -6,6 +6,7 @@ import { cn } from "../../lib/utils.js";
|
|
|
6
6
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
7
7
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../feedback/tooltip.js";
|
|
8
8
|
import { Textarea } from "../data-entry/textarea.js";
|
|
9
|
+
import { isDevelopment } from "../../lib/dev.js";
|
|
9
10
|
const COPIED_RESET_MS = 3e3;
|
|
10
11
|
const DECORATION_ELEMENTS = [
|
|
11
12
|
["strong", "strong"],
|
|
@@ -409,7 +410,7 @@ const TextBase = React.forwardRef((props, ref) => {
|
|
|
409
410
|
const ellipsisRowCount = ellipsis ? ellipsisRows(ellipsis) : 0;
|
|
410
411
|
const effectiveClamp = ellipsisRowCount > 1 ? ellipsisRowCount : ellipsis ? void 0 : clampLines;
|
|
411
412
|
const truncating = (truncate === true || ellipsisRowCount === 1) && effectiveClamp === void 0;
|
|
412
|
-
if (
|
|
413
|
+
if (isDevelopment()) {
|
|
413
414
|
if (clamp !== void 0 && clampLines === void 0) {
|
|
414
415
|
console.warn(`Text: \`clamp\` must be a finite number \u2265 1 (got ${String(clamp)}); ignored.`);
|
|
415
416
|
}
|
|
@@ -7,6 +7,7 @@ import { useTranslation } from "../../i18n/use-translation.js";
|
|
|
7
7
|
import { Sheet, SheetBody, SheetContent, SheetHeader, SheetTrigger } from "../feedback/sheet.js";
|
|
8
8
|
import { NavSurfaceProvider } from "./nav-surface.js";
|
|
9
9
|
import { TopbarItem } from "./topbar-item.js";
|
|
10
|
+
import { isDevelopment } from "../../lib/dev.js";
|
|
10
11
|
function useAppShellNavigationMode() {
|
|
11
12
|
return useMediaQuery("(width <= 56.25rem)") ? "drawer" : "docked";
|
|
12
13
|
}
|
|
@@ -59,7 +60,7 @@ function AppShell({
|
|
|
59
60
|
}
|
|
60
61
|
};
|
|
61
62
|
const hasTopbarContent = topbar !== void 0 || topbarLeft !== void 0 || topbarRight !== void 0 || logo !== void 0;
|
|
62
|
-
if (
|
|
63
|
+
if (isDevelopment() && topbar !== void 0) {
|
|
63
64
|
const ignored = [
|
|
64
65
|
topbarLeft !== void 0 && "topbarLeft",
|
|
65
66
|
topbarRight !== void 0 && "topbarRight"
|
|
@@ -9,6 +9,7 @@ import { Progress } from "../data-display/progress.js";
|
|
|
9
9
|
import { Text } from "../general/typography.js";
|
|
10
10
|
import { CenteredShell } from "./centered-shell.js";
|
|
11
11
|
import { dateTimeFormat, numberFormat } from "../../lib/intl-cache.js";
|
|
12
|
+
import { isDevelopment } from "../../lib/dev.js";
|
|
12
13
|
const STATUS_META = {
|
|
13
14
|
400: { icon: TriangleAlert, tone: "warning" },
|
|
14
15
|
403: { icon: ShieldAlert, tone: "warning" },
|
|
@@ -23,7 +24,7 @@ function singleAction(action) {
|
|
|
23
24
|
const flattened = React.Children.toArray(action).flatMap(
|
|
24
25
|
(child) => React.isValidElement(child) && child.type === React.Fragment ? React.Children.toArray(child.props.children) : [child]
|
|
25
26
|
);
|
|
26
|
-
if (flattened.length > 1 &&
|
|
27
|
+
if (flattened.length > 1 && isDevelopment()) {
|
|
27
28
|
console.error(
|
|
28
29
|
"[@godxjp/ui] ErrorSurface: `action` takes EXACTLY ONE recovery action. The extra elements were dropped. Put support contact or secondary guidance in `description`."
|
|
29
30
|
);
|
|
@@ -5,6 +5,7 @@ import { Eye, EyeOff } from "lucide-react";
|
|
|
5
5
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
7
|
import { Input } from "../data-entry/input.js";
|
|
8
|
+
import { isDevelopment } from "../../lib/dev.js";
|
|
8
9
|
const PasswordInput = React.forwardRef(
|
|
9
10
|
({ className, visibilityToggle = true, iconRender, suffix, trailingIcon, allowClear, ...props }, ref) => {
|
|
10
11
|
const { t } = useTranslation();
|
|
@@ -19,7 +20,7 @@ const PasswordInput = React.forwardRef(
|
|
|
19
20
|
onVisibleChange?.(next);
|
|
20
21
|
};
|
|
21
22
|
const toggleOwnsTrailing = showToggle;
|
|
22
|
-
if (toggleOwnsTrailing &&
|
|
23
|
+
if (toggleOwnsTrailing && isDevelopment() && (suffix != null || trailingIcon != null || allowClear)) {
|
|
23
24
|
console.warn(
|
|
24
25
|
"[godxjp-ui] PasswordInput: the reveal toggle owns the trailing slot, so `suffix` / `trailingIcon` / `allowClear` are ignored. Use `leadingIcon` (or `prefix`), or pass `visibilityToggle={false}` to hand the slot back."
|
|
25
26
|
);
|
|
@@ -3,6 +3,7 @@ import * as React from "react";
|
|
|
3
3
|
import { Separator as AriaSeparator } from "react-aria-components";
|
|
4
4
|
import { padStepToken } from "../../lib/variants.js";
|
|
5
5
|
import { cn } from "../../lib/utils.js";
|
|
6
|
+
import { isDevelopment } from "../../lib/dev.js";
|
|
6
7
|
const Separator = React.forwardRef(
|
|
7
8
|
({
|
|
8
9
|
className,
|
|
@@ -20,7 +21,7 @@ const Separator = React.forwardRef(
|
|
|
20
21
|
}, ref) => {
|
|
21
22
|
const hasLabel = typeof label === "string" && label.trim() !== "";
|
|
22
23
|
const labelled = hasLabel && orientation === "horizontal";
|
|
23
|
-
if (
|
|
24
|
+
if (isDevelopment()) {
|
|
24
25
|
if (hasLabel && orientation === "vertical") {
|
|
25
26
|
console.warn(
|
|
26
27
|
'Separator: `label` is rendered on orientation="horizontal" only; it was ignored. Use a horizontal rule for a day divider / unread watermark.'
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `isDevelopment()` — the ONE place this package is allowed to ask whether it is in a dev build.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS, and it is a crash rather than a tidiness question. Eight shipped modules read
|
|
5
|
+
* `process.env.NODE_ENV` to decide whether to emit a development warning. Three of them guarded
|
|
6
|
+
* the read with `typeof process !== "undefined"`; five did not, and `process` is not a browser
|
|
7
|
+
* global. Nothing in the build replaces it either — `tsup.config.ts` declares no `define`, and
|
|
8
|
+
* `grep -o 'process\.env' dist` on the published tarball of 23.4.4 returns 9 hits, raw.
|
|
9
|
+
*
|
|
10
|
+
* Measured, not theorised: this repo's own preview app (Vite 8, dev mode) renders
|
|
11
|
+
* `/isolate/data-entry-password-strength` as **"Preview render failed — process is not defined"**,
|
|
12
|
+
* six DOM nodes and nothing else. `PasswordInput` is one of the five unguarded modules and that
|
|
13
|
+
* page renders it. A bundler that happens to define `process.env.NODE_ENV` papers over it, which
|
|
14
|
+
* is exactly why it survived: most consumers' toolchains do, and the ones that do not get a blank
|
|
15
|
+
* screen rather than a warning.
|
|
16
|
+
*
|
|
17
|
+
* The helper reads the guard ONCE so the two halves cannot drift apart again, and it fails SAFE:
|
|
18
|
+
* where `process` is absent we cannot tell dev from production, so the answer is `false` and the
|
|
19
|
+
* warnings go quiet. A missing warning is a cost; a component that throws while rendering is not
|
|
20
|
+
* a cost, it is a broken page.
|
|
21
|
+
*
|
|
22
|
+
* A new bare `process.env` read in `src/components/**` is a CI failure — see
|
|
23
|
+
* `src/lib/__tests__/dev-guard.test.ts`.
|
|
24
|
+
*
|
|
25
|
+
* TWO THINGS ABOUT THE SHAPE BELOW, and the first one is why the three "guarded" modules were
|
|
26
|
+
* only half guarded.
|
|
27
|
+
*
|
|
28
|
+
* NO OPTIONAL CHAINING ON `process.env`. It does not survive the build. Written as
|
|
29
|
+
* `process.env?.NODE_ENV`, the transformed output measured in this repo's own vitest pipeline is
|
|
30
|
+
*
|
|
31
|
+
* return typeof process !== "undefined" && process.env.NODE_ENV !== "production";
|
|
32
|
+
*
|
|
33
|
+
* — the `?.` is GONE, because a bundler `define` matches the text `process.env` and substitutes
|
|
34
|
+
* through it, discarding the chain. So `?.` is not a guard here; it reads like one, which is
|
|
35
|
+
* worse than nothing. `typeof process` does survive (measured in the same dump), and a truthiness
|
|
36
|
+
* test on `process.env` survives as a truthiness test whatever it is replaced with.
|
|
37
|
+
*
|
|
38
|
+
* THE LITERAL IS KEPT ON THE LAST LINE ON PURPOSE. `process.env.NODE_ENV` is the exact text every
|
|
39
|
+
* bundler's `define` looks for, and that substitution is what lets a consumer's production build
|
|
40
|
+
* fold these branches away and drop the warning strings entirely. Reading the global indirectly
|
|
41
|
+
* (`globalThis.process`) would be equally crash-proof and would cost every consumer that
|
|
42
|
+
* dead-code elimination, so the order of the checks does the work instead: the guard runs first,
|
|
43
|
+
* and the replaceable literal is only reached once `env` is known to exist.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isDevelopment(): boolean;
|
package/dist/lib/dev.js
ADDED
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
color: hsl(var(--muted-foreground));
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
.ui-chart .recharts-legend-item-text {
|
|
31
|
+
color: hsl(var(--muted-foreground)) !important;
|
|
32
|
+
}
|
|
33
|
+
|
|
30
34
|
.ui-chart .recharts-default-tooltip {
|
|
31
35
|
border-radius: var(--radius);
|
|
32
36
|
border: 1px solid hsl(var(--border)) !important;
|
|
@@ -219,6 +219,32 @@
|
|
|
219
219
|
box-shadow: inset 0 var(--tabs-card-rail-border-width) 0 hsl(var(--border));
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
[data-slot="tabs"][data-variant="card"][data-placement="start"] [data-slot="tabs-list"],
|
|
223
|
+
[data-slot="tabs"][data-variant="editable-card"][data-placement="start"]
|
|
224
|
+
[data-slot="tabs-list"] {
|
|
225
|
+
box-shadow: inset calc(-1 * var(--tabs-card-rail-border-width)) 0 0 hsl(var(--border));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
[data-slot="tabs"][data-variant="card"][data-placement="end"] [data-slot="tabs-list"],
|
|
229
|
+
[data-slot="tabs"][data-variant="editable-card"][data-placement="end"]
|
|
230
|
+
[data-slot="tabs-list"] {
|
|
231
|
+
box-shadow: inset var(--tabs-card-rail-border-width) 0 0 hsl(var(--border));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
[dir="rtl"] [data-slot="tabs"][data-variant="card"][data-placement="start"]
|
|
235
|
+
[data-slot="tabs-list"],
|
|
236
|
+
[dir="rtl"] [data-slot="tabs"][data-variant="editable-card"][data-placement="start"]
|
|
237
|
+
[data-slot="tabs-list"] {
|
|
238
|
+
box-shadow: inset var(--tabs-card-rail-border-width) 0 0 hsl(var(--border));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
[dir="rtl"] [data-slot="tabs"][data-variant="card"][data-placement="end"]
|
|
242
|
+
[data-slot="tabs-list"],
|
|
243
|
+
[dir="rtl"] [data-slot="tabs"][data-variant="editable-card"][data-placement="end"]
|
|
244
|
+
[data-slot="tabs-list"] {
|
|
245
|
+
box-shadow: inset calc(-1 * var(--tabs-card-rail-border-width)) 0 0 hsl(var(--border));
|
|
246
|
+
}
|
|
247
|
+
|
|
222
248
|
.ui-tabs-tab-remove {
|
|
223
249
|
display: inline-flex;
|
|
224
250
|
flex: 0 0 auto;
|
|
@@ -121,6 +121,7 @@
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
.ui-data-table-sort-button {
|
|
124
|
+
position: relative;
|
|
124
125
|
display: inline-flex;
|
|
125
126
|
align-items: center;
|
|
126
127
|
gap: var(--space-inline-xs);
|
|
@@ -132,6 +133,18 @@
|
|
|
132
133
|
outline: none;
|
|
133
134
|
}
|
|
134
135
|
|
|
136
|
+
.ui-data-table-sort-button::after {
|
|
137
|
+
content: "";
|
|
138
|
+
position: absolute;
|
|
139
|
+
inset-block-start: 50%;
|
|
140
|
+
inset-inline-start: 50%;
|
|
141
|
+
translate: -50% -50%;
|
|
142
|
+
min-inline-size: var(--touch-target-min);
|
|
143
|
+
min-block-size: var(--touch-target-min);
|
|
144
|
+
inline-size: 100%;
|
|
145
|
+
block-size: 100%;
|
|
146
|
+
}
|
|
147
|
+
|
|
135
148
|
.ui-data-table-bulk {
|
|
136
149
|
display: flex;
|
|
137
150
|
align-items: center;
|
package/docs/navigation/tabs.tsx
CHANGED
|
@@ -600,6 +600,26 @@ export default function Demo() {
|
|
|
600
600
|
size="lg"
|
|
601
601
|
items={journalItems}
|
|
602
602
|
/>
|
|
603
|
+
{/* CARD on the vertical axis. `card` + `bottom` above had shipped upside down, and
|
|
604
|
+
`start`/`end` had no card example at all — so the rail, which is drawn with a
|
|
605
|
+
box-shadow and therefore cannot be logical, had nothing to be measured against
|
|
606
|
+
in either direction. */}
|
|
607
|
+
<Tabs
|
|
608
|
+
id="antd-card-start"
|
|
609
|
+
defaultValue="pending"
|
|
610
|
+
variant="card"
|
|
611
|
+
tabPlacement="start"
|
|
612
|
+
size="sm"
|
|
613
|
+
items={journalItems}
|
|
614
|
+
/>
|
|
615
|
+
<Tabs
|
|
616
|
+
id="antd-card-end"
|
|
617
|
+
defaultValue="pending"
|
|
618
|
+
variant="card"
|
|
619
|
+
tabPlacement="end"
|
|
620
|
+
size="sm"
|
|
621
|
+
items={journalItems}
|
|
622
|
+
/>
|
|
603
623
|
</Flex>
|
|
604
624
|
</CardContent>
|
|
605
625
|
</Card>
|