@ctrl-ui/react 0.9.11 → 0.9.12
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/README.md +1 -1
- package/dist/control-variants.d.ts +1 -1
- package/dist/control-variants.js +1 -0
- package/dist/knob-contracts/button-knobs.d.ts +1 -1
- package/dist/knob-contracts/button-knobs.js +11 -5
- package/dist/knob-contracts/dropzone-knobs.d.ts +1 -1
- package/dist/knob-contracts/dropzone-knobs.js +5 -1
- package/dist/knob-contracts/field-knobs.d.ts +1 -1
- package/dist/knob-contracts/field-knobs.js +9 -0
- package/dist/knob-contracts/page-layout-knobs.d.ts +1 -1
- package/dist/knob-contracts/page-layout-knobs.js +1 -0
- package/dist/knob-contracts/popup-knobs.d.ts +1 -1
- package/dist/knob-contracts/popup-knobs.js +3 -2
- package/dist/knob-contracts/sidebar-knobs.d.ts +1 -1
- package/dist/knob-contracts/sidebar-knobs.js +1 -0
- package/dist/lib/phone-input-format.js +1 -1
- package/dist/styles/recipes/avatar.css +2 -0
- package/dist/styles/recipes/button-states.css +43 -0
- package/dist/styles/recipes/button.css +29 -21
- package/dist/styles/recipes/dropzone.css +47 -4
- package/dist/styles/recipes/field-parts.css +93 -0
- package/dist/styles/recipes/page-layout.css +8 -1
- package/dist/styles/recipes/popup-controls.css +7 -1
- package/dist/styles/recipes/popup-lists.css +13 -5
- package/dist/styles/recipes/popup.css +2 -6
- package/dist/styles/recipes/sidebar-parts.css +6 -0
- package/dist/styles/recipes/sidebar.css +9 -8
- package/dist/styles/recipes/table.css +29 -0
- package/dist/ui/command.js +1 -1
- package/dist/ui/dropzone.d.ts +4 -1
- package/dist/ui/dropzone.js +16 -8
- package/dist/ui/field.d.ts +4 -1
- package/dist/ui/field.js +4 -2
- package/dist/ui/phone-input.js +2 -2
- package/dist/ui/table.d.ts +6 -0
- package/dist/ui/table.js +17 -1
- package/lint/class-policy.mjs +31 -1
- package/lint/eslint-plugin.mjs +48 -4
- package/lint/grit-rules.mjs +52 -11
- package/lint/no-component-style-classes.grit +41 -6
- package/lint/no-raw-type-classes.grit +1 -1
- package/package.json +1 -1
|
@@ -112,11 +112,13 @@
|
|
|
112
112
|
color: var(--muted-foreground);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
:where([data-control-family="popup"][data-popup-kind="command"][data-slot="dialog-root"])
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
:where([data-control-family="popup"][data-popup-part="surface"]:has(> [data-popup-kind="command"][data-slot="dialog-root"])),
|
|
116
|
+
:where(
|
|
117
|
+
[data-control-family="popup"][data-popup-part="backdrop"]:has(
|
|
118
|
+
~ [data-popup-part="surface"] > [data-popup-kind="command"][data-slot="dialog-root"]
|
|
119
|
+
)
|
|
120
|
+
) {
|
|
121
|
+
transition: none;
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
:where([data-control-family="popup"][data-popup-kind="autocomplete"][data-slot="group-label"]) {
|
|
@@ -366,3 +368,9 @@
|
|
|
366
368
|
inherits: true;
|
|
367
369
|
initial-value: 0px;
|
|
368
370
|
}
|
|
371
|
+
|
|
372
|
+
@property --cui-popup-shortcut-foreground {
|
|
373
|
+
syntax: "<color>";
|
|
374
|
+
inherits: true;
|
|
375
|
+
initial-value: transparent;
|
|
376
|
+
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
--cui-popup-title-line-height: var(--text-heading-3--line-height);
|
|
16
16
|
--cui-popup-title-letter-spacing: -0.025em;
|
|
17
17
|
--cui-popup-description-foreground: var(--muted-foreground);
|
|
18
|
+
--cui-popup-description-font-size: var(--text-body);
|
|
18
19
|
--cui-popup-close-inset: calc(var(--spacing) * 3);
|
|
19
20
|
--cui-popup-item-radius: var(--radius-popup-item-fit);
|
|
20
21
|
--cui-popup-item-foreground: var(--foreground);
|
|
@@ -146,6 +147,7 @@
|
|
|
146
147
|
--cui-popup-title-font-size: var(--text-body);
|
|
147
148
|
--cui-popup-title-line-height: 1.35;
|
|
148
149
|
--cui-popup-title-letter-spacing: -0.01em;
|
|
150
|
+
--cui-popup-description-font-size: var(--text-label);
|
|
149
151
|
max-block-size: var(--available-height);
|
|
150
152
|
max-inline-size: var(--available-width);
|
|
151
153
|
overflow: auto;
|
|
@@ -392,9 +394,3 @@
|
|
|
392
394
|
inherits: true;
|
|
393
395
|
initial-value: transparent;
|
|
394
396
|
}
|
|
395
|
-
|
|
396
|
-
@property --cui-popup-shortcut-foreground {
|
|
397
|
-
syntax: "<color>";
|
|
398
|
-
inherits: true;
|
|
399
|
-
initial-value: transparent;
|
|
400
|
-
}
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
--cui-sidebar-inner-border-width: 0px;
|
|
39
39
|
--cui-sidebar-inner-radius: 0px;
|
|
40
40
|
--cui-sidebar-inner-shadow: none;
|
|
41
|
+
--cui-sidebar-container-border-width: 1px;
|
|
41
42
|
--cui-sidebar-header-background: transparent;
|
|
42
43
|
--cui-sidebar-header-shadow: 0 0 transparent;
|
|
43
44
|
--cui-sidebar-header-border-width: 1px;
|
|
@@ -88,11 +89,11 @@
|
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
:where([data-control-family="sidebar"][data-slot="root"][data-variant="sidebar"][data-side="left"] [data-slot="container"]) {
|
|
91
|
-
border-inline-end:
|
|
92
|
+
border-inline-end: var(--cui-sidebar-container-border-width) solid var(--sidebar-border);
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
:where([data-control-family="sidebar"][data-slot="root"][data-variant="sidebar"][data-side="right"] [data-slot="container"]) {
|
|
95
|
-
border-inline-start:
|
|
96
|
+
border-inline-start: var(--cui-sidebar-container-border-width) solid var(--sidebar-border);
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
:where([data-control-family="sidebar"][data-slot="inner"]) {
|
|
@@ -132,12 +133,6 @@
|
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
@property --cui-sidebar-rail-divider-background {
|
|
136
|
-
syntax: "<color>";
|
|
137
|
-
inherits: true;
|
|
138
|
-
initial-value: transparent;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
136
|
@property --cui-sidebar-wrapper-background {
|
|
142
137
|
syntax: "<color>";
|
|
143
138
|
inherits: true;
|
|
@@ -171,6 +166,12 @@
|
|
|
171
166
|
inherits: true;
|
|
172
167
|
}
|
|
173
168
|
|
|
169
|
+
@property --cui-sidebar-container-border-width {
|
|
170
|
+
syntax: "<length>";
|
|
171
|
+
inherits: true;
|
|
172
|
+
initial-value: 0px;
|
|
173
|
+
}
|
|
174
|
+
|
|
174
175
|
@property --cui-sidebar-inner-border-width {
|
|
175
176
|
syntax: "<length>";
|
|
176
177
|
inherits: true;
|
|
@@ -44,6 +44,35 @@
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
:where([data-control-family="table"][data-slot="row"]:has([data-control-family="table"][data-slot="row-link"])) {
|
|
48
|
+
position: relative;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
:where([data-control-family="table"][data-slot="row-link"]) {
|
|
52
|
+
text-align: start;
|
|
53
|
+
cursor: pointer;
|
|
54
|
+
outline: none;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
:where([data-control-family="table"][data-slot="row-link"])::after {
|
|
58
|
+
content: "";
|
|
59
|
+
position: absolute;
|
|
60
|
+
inset: 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
:where([data-control-family="table"][data-slot="row-link"]:focus-visible)::after {
|
|
64
|
+
outline: var(--focus-ring-width) var(--focus-ring-style) var(--focus-ring);
|
|
65
|
+
outline-offset: calc(var(--focus-ring-width) * -1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
:where(
|
|
69
|
+
[data-control-family="table"][data-slot="row"]:has([data-control-family="table"][data-slot="row-link"])
|
|
70
|
+
:is(a[href], button, input, select, textarea, [role="checkbox"]):not([data-slot="row-link"])
|
|
71
|
+
) {
|
|
72
|
+
position: relative;
|
|
73
|
+
z-index: 1;
|
|
74
|
+
}
|
|
75
|
+
|
|
47
76
|
:where([data-control-family="table"][data-slot="row"][data-state="selected"]) {
|
|
48
77
|
background: var(--cui-table-row-selected-background);
|
|
49
78
|
}
|
package/dist/ui/command.js
CHANGED
|
@@ -46,7 +46,7 @@ function CommandDialog({ title = "Command palette", description = "Search for a
|
|
|
46
46
|
"data-popup-kind": "command",
|
|
47
47
|
"data-control-family": "popup",
|
|
48
48
|
"data-slot": "dialog-root",
|
|
49
|
-
className: cn("grid grid-rows-[auto_1fr]
|
|
49
|
+
className: cn("grid grid-rows-[auto_1fr]", commandProps?.className),
|
|
50
50
|
children
|
|
51
51
|
})
|
|
52
52
|
]
|
package/dist/ui/dropzone.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { DropzoneKnobStyle } from "../knob-contracts/dropzone-knobs.js";
|
|
|
5
5
|
import { CSSProperties, ComponentProps, ReactNode } from "react";
|
|
6
6
|
//#region src/ui/dropzone.d.ts
|
|
7
7
|
export type DropzoneOverlayScope = "local" | "global";
|
|
8
|
+
export type DropzoneSize = "default" | "sm";
|
|
8
9
|
export type DropzoneProps = Omit<ComponentProps<"div">, "defaultValue" | "onChange" | "onDrop" | "onError"> & {
|
|
9
10
|
value?: readonly File[];
|
|
10
11
|
defaultValue?: readonly File[];
|
|
@@ -16,6 +17,7 @@ export type DropzoneProps = Omit<ComponentProps<"div">, "defaultValue" | "onChan
|
|
|
16
17
|
getFilesFromEvent?: DropzoneGetFilesFromEvent;
|
|
17
18
|
onDrop?: (details: DropzoneDropDetails) => void;
|
|
18
19
|
onError?: (error: Error) => void;
|
|
20
|
+
size?: DropzoneSize;
|
|
19
21
|
} & {
|
|
20
22
|
style?: CSSProperties & DropzoneKnobStyle;
|
|
21
23
|
};
|
|
@@ -44,6 +46,7 @@ export type DropzoneRejectionListProps = Omit<ComponentProps<"ul">, "children">
|
|
|
44
46
|
};
|
|
45
47
|
export type DropzoneContextValue = UseDropzoneReturn & {
|
|
46
48
|
registerGlobalOverlay: () => () => void;
|
|
49
|
+
size: DropzoneSize;
|
|
47
50
|
};
|
|
48
51
|
export type DropzoneStatusProps = Omit<ComponentProps<"div">, "children" | "style"> & {
|
|
49
52
|
children?: (state: DropzoneContextValue) => ReactNode;
|
|
@@ -54,7 +57,7 @@ export type DropzoneRemoveProps = ComponentProps<typeof Button> & {
|
|
|
54
57
|
};
|
|
55
58
|
export type DropzoneClearProps = ComponentProps<typeof Button>;
|
|
56
59
|
export declare function useDropzoneContext(): DropzoneContextValue;
|
|
57
|
-
export declare function Dropzone({ value, defaultValue, onValueChange, policy, disabled, drag, paste, getFilesFromEvent, onDrop, onError, className, children, ...props }: DropzoneProps): import("react").JSX.Element;
|
|
60
|
+
export declare function Dropzone({ value, defaultValue, onValueChange, policy, disabled, drag, paste, getFilesFromEvent, onDrop, onError, size, className, children, ...props }: DropzoneProps): import("react").JSX.Element;
|
|
58
61
|
export declare function DropzoneArea({ className, ...props }: DropzoneAreaProps): import("react").JSX.Element;
|
|
59
62
|
export declare function DropzoneInput({ className, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, ...props }: DropzoneInputProps): import("react").JSX.Element;
|
|
60
63
|
export declare function DropzoneTrigger({ className, children, onClick, disabled, ref, ...props }: DropzoneTriggerProps): import("react").JSX.Element;
|
package/dist/ui/dropzone.js
CHANGED
|
@@ -64,7 +64,7 @@ function useFocusTargetRef(kind, ref) {
|
|
|
64
64
|
};
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
|
-
function Dropzone({ value, defaultValue, onValueChange, policy, disabled, drag, paste, getFilesFromEvent, onDrop, onError, className, children, ...props }) {
|
|
67
|
+
function Dropzone({ value, defaultValue, onValueChange, policy, disabled, drag, paste, getFilesFromEvent, onDrop, onError, size = "default", className, children, ...props }) {
|
|
68
68
|
const [globalOverlayCount, setGlobalOverlayCount] = useState(0);
|
|
69
69
|
const [focusTargets] = useState(createFocusTargets);
|
|
70
70
|
const [registerGlobalOverlay] = useState(() => () => {
|
|
@@ -91,7 +91,8 @@ function Dropzone({ value, defaultValue, onValueChange, policy, disabled, drag,
|
|
|
91
91
|
});
|
|
92
92
|
const context = {
|
|
93
93
|
...dropzone,
|
|
94
|
-
registerGlobalOverlay
|
|
94
|
+
registerGlobalOverlay,
|
|
95
|
+
size
|
|
95
96
|
};
|
|
96
97
|
const empty = dropzone.value.length === 0 && dropzone.fileRejections.length === 0;
|
|
97
98
|
return /* @__PURE__ */ jsx(DropzoneContext.Provider, {
|
|
@@ -103,6 +104,7 @@ function Dropzone({ value, defaultValue, onValueChange, policy, disabled, drag,
|
|
|
103
104
|
"data-control-ui": "dropzone",
|
|
104
105
|
"data-control-family": "dropzone",
|
|
105
106
|
"data-slot": "root",
|
|
107
|
+
"data-size": size,
|
|
106
108
|
"data-disabled": dropzone.disabled ? "true" : void 0,
|
|
107
109
|
"data-empty": empty ? "true" : void 0,
|
|
108
110
|
className: cn("min-w-0", className),
|
|
@@ -156,13 +158,16 @@ function DropzoneTrigger({ className, children, onClick, disabled, ref, ...props
|
|
|
156
158
|
"data-slot": "trigger",
|
|
157
159
|
"data-state": context.visualState,
|
|
158
160
|
"data-disabled": triggerDisabled || busy ? "true" : void 0,
|
|
159
|
-
className: cn("flex w-full cursor-pointer
|
|
161
|
+
className: cn("flex w-full cursor-pointer items-center disabled:cursor-not-allowed aria-disabled:cursor-not-allowed", context.size === "sm" ? "flex-row justify-start [&>svg]:size-4 [&>svg]:shrink-0" : "flex-col justify-center", className),
|
|
160
162
|
onClick: (event) => {
|
|
161
163
|
if (busy) return;
|
|
162
164
|
onClick?.(event);
|
|
163
165
|
if (!event.defaultPrevented) context.open();
|
|
164
166
|
},
|
|
165
|
-
children: children ?? /* @__PURE__ */ jsx(DropzoneFeedback, {
|
|
167
|
+
children: children ?? /* @__PURE__ */ jsx(DropzoneFeedback, {
|
|
168
|
+
state: context.visualState,
|
|
169
|
+
size: context.size
|
|
170
|
+
})
|
|
166
171
|
});
|
|
167
172
|
}
|
|
168
173
|
function DropzoneOverlay({ scope = "local", className, children, ...props }) {
|
|
@@ -182,7 +187,10 @@ function DropzoneOverlay({ scope = "local", className, children, ...props }) {
|
|
|
182
187
|
"data-active": active ? "true" : void 0,
|
|
183
188
|
"data-scope": scope,
|
|
184
189
|
className: cn("pointer-events-none absolute inset-0 z-10 hidden items-center justify-center data-[active=true]:flex", className),
|
|
185
|
-
children: children ?? /* @__PURE__ */ jsx(DropzoneFeedback, {
|
|
190
|
+
children: children ?? /* @__PURE__ */ jsx(DropzoneFeedback, {
|
|
191
|
+
state: context.visualState,
|
|
192
|
+
size: context.size
|
|
193
|
+
})
|
|
186
194
|
});
|
|
187
195
|
}
|
|
188
196
|
function DropzoneFileList({ className, children, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, ...props }) {
|
|
@@ -284,7 +292,7 @@ function DropzoneClear({ children, onClick, disabled, ...props }) {
|
|
|
284
292
|
children: children ?? "Clear all"
|
|
285
293
|
});
|
|
286
294
|
}
|
|
287
|
-
function DropzoneFeedback({ state }) {
|
|
295
|
+
function DropzoneFeedback({ state, size }) {
|
|
288
296
|
let icon = /* @__PURE__ */ jsx(CloudUpload, { "aria-hidden": "true" });
|
|
289
297
|
let message = "Drop files here or choose files.";
|
|
290
298
|
if (state === "processing") {
|
|
@@ -310,12 +318,12 @@ function DropzoneFeedback({ state }) {
|
|
|
310
318
|
"data-control-ui": "dropzone",
|
|
311
319
|
"data-control-family": "dropzone",
|
|
312
320
|
"data-slot": "feedback",
|
|
313
|
-
className: "flex flex-
|
|
321
|
+
className: cn("flex items-center", size === "sm" ? "flex-row" : "flex-col"),
|
|
314
322
|
children: [/* @__PURE__ */ jsx("span", {
|
|
315
323
|
"data-control-ui": "dropzone",
|
|
316
324
|
"data-control-family": "dropzone",
|
|
317
325
|
"data-slot": "feedback-icon",
|
|
318
|
-
className: "[&>svg]:size-6",
|
|
326
|
+
className: size === "sm" ? "flex [&>svg]:size-4" : "[&>svg]:size-6",
|
|
319
327
|
children: icon
|
|
320
328
|
}), /* @__PURE__ */ jsx("span", {
|
|
321
329
|
"data-control-ui": "dropzone",
|
package/dist/ui/field.d.ts
CHANGED
|
@@ -11,7 +11,9 @@ export type FieldProps = ComponentProps<"div"> & {
|
|
|
11
11
|
} & {
|
|
12
12
|
style?: CSSProperties & FieldKnobStyle;
|
|
13
13
|
};
|
|
14
|
+
export type FieldLabelVariant = "default" | "card";
|
|
14
15
|
export type FieldLabelProps = Omit<ComponentProps<"label">, "style"> & {
|
|
16
|
+
variant?: FieldLabelVariant;
|
|
15
17
|
style?: CSSProperties & FieldKnobStyle;
|
|
16
18
|
};
|
|
17
19
|
export type FieldContentProps = ComponentProps<"div"> & {
|
|
@@ -41,6 +43,7 @@ export type FieldSeparatorProps = Omit<ComponentProps<"div">, "style"> & {
|
|
|
41
43
|
style?: CSSProperties & FieldKnobStyle;
|
|
42
44
|
};
|
|
43
45
|
export type FieldItemProps = ComponentProps<"div"> & {
|
|
46
|
+
disabled?: boolean;
|
|
44
47
|
style?: CSSProperties & FieldKnobStyle;
|
|
45
48
|
};
|
|
46
49
|
export type FieldSetProps = Omit<ComponentProps<"fieldset">, "style"> & {
|
|
@@ -54,7 +57,7 @@ export type FieldLegendProps = Omit<ComponentProps<"div">, "style"> & {
|
|
|
54
57
|
export declare function Field({ className, orientation, ...props }: FieldProps): import("react").JSX.Element;
|
|
55
58
|
export declare function FieldContent({ className, ...props }: FieldContentProps): import("react").JSX.Element;
|
|
56
59
|
export declare function FieldTitle({ className, ...props }: FieldTitleProps): import("react").JSX.Element;
|
|
57
|
-
export declare function FieldLabel({ className, ...props }: FieldLabelProps): import("react").JSX.Element;
|
|
60
|
+
export declare function FieldLabel({ className, variant, ...props }: FieldLabelProps): import("react").JSX.Element;
|
|
58
61
|
type RefinedFieldControlProps = FieldControlProps & Pick<ComponentProps<typeof Field$1.Control>, "render">;
|
|
59
62
|
export declare function FieldControl({ className, render, ...props }: RefinedFieldControlProps): import("react").JSX.Element;
|
|
60
63
|
export declare function FieldDescription({ className, ...props }: FieldDescriptionProps): import("react").JSX.Element;
|
package/dist/ui/field.js
CHANGED
|
@@ -45,13 +45,15 @@ function FieldTitle({ className, ...props }) {
|
|
|
45
45
|
...props
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
|
-
function FieldLabel({ className, ...props }) {
|
|
48
|
+
function FieldLabel({ className, variant = "default", ...props }) {
|
|
49
49
|
return /* @__PURE__ */ jsx(Field$1.Label, {
|
|
50
50
|
"data-control-ui": "field",
|
|
51
51
|
"data-control-family": "field",
|
|
52
52
|
"data-field-kind": "field",
|
|
53
53
|
"data-slot": "label",
|
|
54
|
-
|
|
54
|
+
"data-variant": variant,
|
|
55
|
+
"data-focus-ring": variant === "card" ? "within" : void 0,
|
|
56
|
+
className: cn(variant === "card" && "flex w-full cursor-pointer items-start gap-3 has-[[data-disabled]]:cursor-not-allowed [&>[data-control-family=choice]]:mt-[calc((1lh_-_var(--spacing)*4)/2)]", className),
|
|
55
57
|
...props
|
|
56
58
|
});
|
|
57
59
|
}
|
package/dist/ui/phone-input.js
CHANGED
|
@@ -3,12 +3,12 @@ import { cn } from "../lib/cn.js";
|
|
|
3
3
|
import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
|
|
4
4
|
import { InputGroup, InputGroupAddon } from "./input-group.js";
|
|
5
5
|
import { Input } from "./input.js";
|
|
6
|
-
import { normalizePhoneInputText, normalizePhoneInputValue } from "../lib/phone-input-format.js";
|
|
7
6
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "./command.js";
|
|
7
|
+
import { normalizePhoneInputText, normalizePhoneInputValue } from "../lib/phone-input-format.js";
|
|
8
8
|
import { useState } from "react";
|
|
9
9
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
-
import PhoneNumberInput, { getCountryCallingCode, parsePhoneNumber } from "react-phone-number-input/max";
|
|
11
10
|
import flags from "react-phone-number-input/flags";
|
|
11
|
+
import PhoneNumberInput, { getCountryCallingCode, parsePhoneNumber } from "react-phone-number-input/max";
|
|
12
12
|
//#region src/ui/phone-input.tsx
|
|
13
13
|
const FLAG_COMPONENTS = flags;
|
|
14
14
|
const PhoneNumberInputWithRef = PhoneNumberInput;
|
package/dist/ui/table.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RenderProp } from "../control-props.js";
|
|
1
2
|
import { TableKnobStyle } from "../knob-contracts/table-knobs.js";
|
|
2
3
|
import { CSSProperties, ComponentProps } from "react";
|
|
3
4
|
//#region src/ui/table.d.ts
|
|
@@ -22,6 +23,10 @@ export type TableHeadProps = Omit<ComponentProps<"th">, "style"> & {
|
|
|
22
23
|
export type TableCellProps = ComponentProps<"td"> & {
|
|
23
24
|
style?: CSSProperties & TableKnobStyle;
|
|
24
25
|
};
|
|
26
|
+
export type TableRowLinkProps = Omit<ComponentProps<"button">, "style"> & {
|
|
27
|
+
render?: RenderProp<ComponentProps<"button">>;
|
|
28
|
+
style?: CSSProperties & TableKnobStyle;
|
|
29
|
+
};
|
|
25
30
|
export type TableCaptionProps = Omit<ComponentProps<"caption">, "style"> & {
|
|
26
31
|
style?: CSSProperties & TableKnobStyle;
|
|
27
32
|
};
|
|
@@ -33,4 +38,5 @@ export declare function TableRow({ className, ...props }: TableRowProps): import
|
|
|
33
38
|
export declare function TableHead({ className, ...props }: TableHeadProps): import("react").JSX.Element;
|
|
34
39
|
export declare function TableCell({ className, ...props }: TableCellProps): import("react").JSX.Element;
|
|
35
40
|
export declare function TableCaption({ className, ...props }: TableCaptionProps): import("react").JSX.Element;
|
|
41
|
+
export declare function TableRowLink({ render, ...props }: TableRowLinkProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
36
42
|
//#endregion
|
package/dist/ui/table.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { cn } from "../lib/cn.js";
|
|
2
3
|
import { ScrollArea } from "./scroll-area.js";
|
|
4
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
3
5
|
import { jsx } from "react/jsx-runtime";
|
|
4
6
|
//#region src/ui/table.tsx
|
|
5
7
|
function Table({ className, ...props }) {
|
|
@@ -85,5 +87,19 @@ function TableCaption({ className, ...props }) {
|
|
|
85
87
|
...props
|
|
86
88
|
});
|
|
87
89
|
}
|
|
90
|
+
function TableRowLink({ render, ...props }) {
|
|
91
|
+
return useRender({
|
|
92
|
+
defaultTagName: "button",
|
|
93
|
+
render,
|
|
94
|
+
props: {
|
|
95
|
+
type: render ? void 0 : "button",
|
|
96
|
+
...props,
|
|
97
|
+
"data-control-ui": "table",
|
|
98
|
+
"data-control-family": "table",
|
|
99
|
+
"data-slot": "row-link",
|
|
100
|
+
"data-focus-ring": "none"
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
88
104
|
//#endregion
|
|
89
|
-
export { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow };
|
|
105
|
+
export { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, TableRowLink };
|
package/lint/class-policy.mjs
CHANGED
|
@@ -6,7 +6,37 @@ export function controlUiImportPattern(importSources = []) {
|
|
|
6
6
|
return `(?:${[controlUiImportPath, ...importSources.map(escapeRegExp)].join("|")})`;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
const classDelimiter = String.raw`\s"'\x60`;
|
|
10
|
+
const classEnd = `(?:[${classDelimiter}]|$)`;
|
|
11
|
+
|
|
12
|
+
function classSuffixOtherThan(suffixes) {
|
|
13
|
+
const firsts = [...new Set(suffixes.filter(Boolean).map((suffix) => suffix[0]))];
|
|
14
|
+
const alternatives = [
|
|
15
|
+
...(suffixes.includes("") ? [] : [classEnd]),
|
|
16
|
+
`[^${firsts.join("")}${classDelimiter}]`,
|
|
17
|
+
...firsts.map(
|
|
18
|
+
(first) => first + classSuffixOtherThan(suffixes.filter((suffix) => suffix[0] === first).map((suffix) => suffix.slice(1))),
|
|
19
|
+
),
|
|
20
|
+
];
|
|
21
|
+
return `(?:${alternatives.join("|")})`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const textLayoutSuffixes = [
|
|
25
|
+
"left",
|
|
26
|
+
"center",
|
|
27
|
+
"right",
|
|
28
|
+
"justify",
|
|
29
|
+
"start",
|
|
30
|
+
"end",
|
|
31
|
+
"wrap",
|
|
32
|
+
"nowrap",
|
|
33
|
+
"balance",
|
|
34
|
+
"pretty",
|
|
35
|
+
"ellipsis",
|
|
36
|
+
"clip",
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
export const componentStyleClass = String.raw`[^${classDelimiter}]!${classEnd}|(?:^|[${classDelimiter}])![\w\[-]|(?:^|[^\w-])(?:p[xytrblse]?-|bg-[\w\[]|text-${classSuffixOtherThan(textLayoutSuffixes)}|font-|leading-|tracking-|border|divide-|rounded|shadow|inset-shadow|drop-shadow|ring\b|ring-|inset-ring|outline|opacity-|backdrop-|fill-|stroke-|caret-|accent-|scheme-|mask-|decoration-|underline|no-underline|overline|line-through|italic|not-italic|uppercase|lowercase|capitalize|normal-case|antialiased|subpixel-antialiased|ordinal|slashed-zero|(?:normal|lining|oldstyle|proportional|tabular)-nums|(?:diagonal|stacked)-fractions|(?:hover|focus|focus-visible|focus-within|active|disabled|enabled|open|checked|indeterminate|visited|target|invalid|valid|user-invalid|user-valid|required|optional|read-only|autofill|placeholder-shown|in-range|out-of-range|inert|starting|dark|placeholder|selection|marker|file|first-letter|first-line|backdrop|details-content|aria-[^:\s]*|data-[^:\s]*|group-[^:\s]*|peer-[^:\s]*|has-[^:\s]*|\*|\[&[^\s]*?\]):)`;
|
|
10
40
|
|
|
11
41
|
export const rawTypeClass = String.raw`(?:^|[^\w-])text-(?:micro|caption|label|body|body-lg|heading-[1-4]|display|xs|sm|base|lg|xl|[2-9]xl)\b|(?:^|[^\w-])text-\[\d[^\]]*\]`;
|
|
12
42
|
|
package/lint/eslint-plugin.mjs
CHANGED
|
@@ -11,9 +11,28 @@ function stringsIn(node) {
|
|
|
11
11
|
return Object.entries(node).flatMap(([key, child]) => (key === "parent" ? [] : stringsIn(child)));
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
function referencedNames(node) {
|
|
15
|
+
if (Array.isArray(node)) return node.flatMap(referencedNames);
|
|
16
|
+
if (!node || typeof node !== "object") return [];
|
|
17
|
+
if (node.type === "Identifier") return [node.name];
|
|
18
|
+
if (node.type === "MemberExpression" && !node.computed) return referencedNames(node.object);
|
|
19
|
+
if (node.type === "Property" && !node.computed) return referencedNames(node.value);
|
|
20
|
+
return Object.entries(node).flatMap(([key, child]) => (key === "parent" ? [] : referencedNames(child)));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const classLiteralTypes = new Set(["Literal", "TemplateLiteral", "ObjectExpression"]);
|
|
24
|
+
|
|
25
|
+
function classLiteral(init) {
|
|
26
|
+
const value = init?.type === "TSAsExpression" || init?.type === "TSSatisfiesExpression" ? init.expression : init;
|
|
27
|
+
return classLiteralTypes.has(value?.type) ? value : undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isClassName(attribute) {
|
|
31
|
+
return attribute.name.name === "className" && Boolean(attribute.value);
|
|
32
|
+
}
|
|
33
|
+
|
|
14
34
|
function classNameMatches(attribute, pattern) {
|
|
15
|
-
|
|
16
|
-
return stringsIn(attribute.value).some((text) => pattern.test(text));
|
|
35
|
+
return isClassName(attribute) && stringsIn(attribute.value).some((text) => pattern.test(text));
|
|
17
36
|
}
|
|
18
37
|
|
|
19
38
|
function elementRootName(name) {
|
|
@@ -22,6 +41,14 @@ function elementRootName(name) {
|
|
|
22
41
|
return undefined;
|
|
23
42
|
}
|
|
24
43
|
|
|
44
|
+
function renderPropOwner(element) {
|
|
45
|
+
const body =
|
|
46
|
+
element.parent.type === "ReturnStatement" && element.parent.parent.type === "BlockStatement" ? element.parent.parent : element;
|
|
47
|
+
const value = body.parent.type === "ArrowFunctionExpression" ? body.parent : element;
|
|
48
|
+
const attribute = value.parent.type === "JSXExpressionContainer" ? value.parent.parent : undefined;
|
|
49
|
+
return attribute?.type === "JSXAttribute" && attribute.name.name === "render" ? attribute.parent : undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
25
52
|
function controlUiLocalNames(program, controlUiImport) {
|
|
26
53
|
const names = new Set();
|
|
27
54
|
for (const statement of program.body) {
|
|
@@ -48,13 +75,30 @@ const noComponentStyleClasses = {
|
|
|
48
75
|
create(context) {
|
|
49
76
|
const controlUiImport = new RegExp(`^${controlUiImportPattern(context.options[0]?.importSources)}`);
|
|
50
77
|
let controlUiNames = new Set();
|
|
78
|
+
const isControlUi = (element) => element !== undefined && controlUiNames.has(elementRootName(element.name));
|
|
79
|
+
const constantClasses = new Map();
|
|
80
|
+
const componentClassNames = [];
|
|
51
81
|
return {
|
|
52
82
|
Program(program) {
|
|
53
83
|
controlUiNames = controlUiLocalNames(program, controlUiImport);
|
|
54
84
|
},
|
|
85
|
+
VariableDeclarator(declarator) {
|
|
86
|
+
const literal = classLiteral(declarator.init);
|
|
87
|
+
if (declarator.parent.kind !== "const" || declarator.id.type !== "Identifier" || !literal) return;
|
|
88
|
+
constantClasses.set(declarator.id.name, [...(constantClasses.get(declarator.id.name) ?? []), ...stringsIn(literal)]);
|
|
89
|
+
},
|
|
55
90
|
JSXAttribute(attribute) {
|
|
56
|
-
if (!
|
|
57
|
-
|
|
91
|
+
if (!isClassName(attribute)) return;
|
|
92
|
+
const element = attribute.parent;
|
|
93
|
+
if (isControlUi(element) || isControlUi(renderPropOwner(element.parent))) componentClassNames.push(attribute);
|
|
94
|
+
},
|
|
95
|
+
"Program:exit"() {
|
|
96
|
+
for (const attribute of componentClassNames) {
|
|
97
|
+
const constants = referencedNames(attribute.value).flatMap((name) => constantClasses.get(name) ?? []);
|
|
98
|
+
if ([...stringsIn(attribute.value), ...constants].some((text) => componentStyle.test(text))) {
|
|
99
|
+
context.report({ node: attribute, messageId: "componentStyle" });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
58
102
|
},
|
|
59
103
|
};
|
|
60
104
|
},
|
package/lint/grit-rules.mjs
CHANGED
|
@@ -8,24 +8,65 @@ function gritString(text) {
|
|
|
8
8
|
return JSON.stringify(text);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
const stringTexts =
|
|
11
|
+
const stringTexts = `or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement(), JsLiteralMemberName() as $memberName where { $memberName <: r"(?s)[\\"'].*" } }`;
|
|
12
|
+
const classLiterals = "or { JsStringLiteralExpression(), JsTemplateExpression(), JsObjectExpression() }";
|
|
13
|
+
const renderedTag = "or { JsxTagExpression(tag=$tag), JsParenthesizedExpression(expression=JsxTagExpression(tag=$tag)) }";
|
|
14
|
+
|
|
15
|
+
const styled = gritRegex(componentStyleClass);
|
|
16
|
+
const styledClassName = `$attr <: or {
|
|
17
|
+
contains ${stringTexts} as $text where { $text <: ${styled} },
|
|
18
|
+
contains JsReferenceIdentifier() as $reference where {
|
|
19
|
+
$reference <: r"(.+)"($referenced),
|
|
20
|
+
$module <: contains JsVariableDeclaration(declarators=$declarators) as $declaration where {
|
|
21
|
+
$declaration <: r"(?s)const\\s.*",
|
|
22
|
+
$declarators <: some JsVariableDeclarator(id=$referenced, initializer=JsInitializerClause(expression=$value)),
|
|
23
|
+
$value <: or { ${classLiterals}, TsAsExpression(expression=${classLiterals}), TsSatisfiesExpression(expression=${classLiterals}) },
|
|
24
|
+
$value <: contains ${stringTexts} as $constText where { $constText <: ${styled} }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}`;
|
|
28
|
+
|
|
29
|
+
function importedFromControlUi(component, importSources, suffix = "") {
|
|
30
|
+
return `$module <: contains JsImport() as $import${suffix} where {
|
|
31
|
+
$import${suffix} <: ${gritRegex(String.raw`from\s+['"]${controlUiImportPattern(importSources)}`)},
|
|
32
|
+
$import${suffix} <: contains JsIdentifierBinding() as $binding${suffix} where { $binding${suffix} <: ${component} }
|
|
33
|
+
}`;
|
|
34
|
+
}
|
|
12
35
|
|
|
13
36
|
export function gritRules(importSources = []) {
|
|
14
37
|
return {
|
|
15
38
|
"no-component-style-classes.grit": `language js(typescript, jsx)
|
|
16
39
|
|
|
17
40
|
or {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
$attr <: contains ${stringTexts} as $text where { $text <: ${gritRegex(componentStyleClass)} },
|
|
24
|
-
$element <: within JsModule() as $module,
|
|
25
|
-
$module <: contains JsImport() as $import where {
|
|
26
|
-
$import <: ${gritRegex(String.raw`from\s+['"]${controlUiImportPattern(importSources)}`)},
|
|
27
|
-
$import <: contains JsIdentifierBinding() as $binding where { $binding <: $component }
|
|
41
|
+
or {
|
|
42
|
+
JsxSelfClosingElement(name=$name, attributes=$attributes),
|
|
43
|
+
JsxOpeningElement(name=$name, attributes=$attributes)
|
|
44
|
+
} where {
|
|
45
|
+
$attributes <: some JsxAttribute(name=\`className\`) as $attr
|
|
28
46
|
},
|
|
47
|
+
JsxAttribute(name=\`render\`, initializer=JsxAttributeInitializerClause(value=JsxExpressionAttributeValue(expression=$rendered))) as $render where {
|
|
48
|
+
$rendered <: or {
|
|
49
|
+
JsxTagExpression(tag=$tag),
|
|
50
|
+
JsArrowFunctionExpression(body=or {
|
|
51
|
+
${renderedTag},
|
|
52
|
+
JsFunctionBody(statements=some JsReturnStatement(argument=${renderedTag}))
|
|
53
|
+
})
|
|
54
|
+
},
|
|
55
|
+
$tag <: or {
|
|
56
|
+
JsxSelfClosingElement(name=$renderedName, attributes=$renderedAttributes),
|
|
57
|
+
JsxElement(opening_element=JsxOpeningElement(name=$renderedName, attributes=$renderedAttributes))
|
|
58
|
+
},
|
|
59
|
+
$renderedAttributes <: some JsxAttribute(name=\`className\`) as $attr,
|
|
60
|
+
$render <: within or { JsxSelfClosingElement(name=$name), JsxOpeningElement(name=$name) },
|
|
61
|
+
$render <: within JsModule() as $module,
|
|
62
|
+
$renderedName <: r"([^.]+).*"($renderedComponent),
|
|
63
|
+
not ${importedFromControlUi("$renderedComponent", importSources, "Rendered")}
|
|
64
|
+
}
|
|
65
|
+
} where {
|
|
66
|
+
$name <: r"([A-Z]\\w*)(?:\\.[\\w.]+)?"($component),
|
|
67
|
+
$attr <: within JsModule() as $module,
|
|
68
|
+
${styledClassName},
|
|
69
|
+
${importedFromControlUi("$component", importSources)},
|
|
29
70
|
register_diagnostic(span=$attr, message=${gritString(componentStyleMessage)}, severity="error")
|
|
30
71
|
}
|
|
31
72
|
`,
|