@flytedan/flytebot-design-system 0.37.0 → 0.38.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/index.cjs +36 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -3
- package/dist/index.d.ts +20 -3
- package/dist/index.js +36 -36
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/styles/components.css +53 -0
package/dist/index.d.cts
CHANGED
|
@@ -19,8 +19,18 @@ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
|
19
19
|
loading?: boolean;
|
|
20
20
|
block?: boolean;
|
|
21
21
|
as?: "button" | "a";
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
/** When `disabled` (or `loading`), stay in the tab order instead of going natively
|
|
23
|
+
* `disabled`: renders `aria-disabled="true"` rather than the `disabled` attribute,
|
|
24
|
+
* keeps the `:focus-visible` ring, and swallows click/Enter/Space without ever
|
|
25
|
+
* submitting a form. Use whenever a button's disabled state can flip while it holds
|
|
26
|
+
* focus (e.g. a controlled panel re-seeding mid-edit) — a native `disabled` drops focus
|
|
27
|
+
* to `<body>` the instant it applies, and nothing can intercept or redirect that.
|
|
28
|
+
* Also makes `aria-disabled` always explicit ("true"/"false", never absent): since the
|
|
29
|
+
* browser can no longer tell assistive tech this button's state from `disabled` itself,
|
|
30
|
+
* the ARIA state has to say so on its own at every render, not only while disabled. */
|
|
31
|
+
focusableWhenDisabled?: boolean;
|
|
32
|
+
}
|
|
33
|
+
declare function Button({ children, variant, size, icon, trailingIcon, arrow, loading, disabled, focusableWhenDisabled, block, as, className, onClick, onKeyDown, ...rest }: ButtonProps): React.JSX.Element;
|
|
24
34
|
|
|
25
35
|
/** A square action carrying only a Phosphor glyph. Always needs a label for a11y. */
|
|
26
36
|
interface IconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
@@ -2239,12 +2249,19 @@ interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement
|
|
|
2239
2249
|
error?: string;
|
|
2240
2250
|
required?: boolean;
|
|
2241
2251
|
rows?: number;
|
|
2252
|
+
/** Read-only plus `aria-busy`, with the same visible look as `disabled` (the
|
|
2253
|
+
* `.fd-input-disabled` tokens) — for a value an in-flight request is about to replace
|
|
2254
|
+
* while the field stays reachable. Unlike `disabled`, `busy` never sets the native
|
|
2255
|
+
* `disabled` attribute: `readOnly` blocks editing without removing the control from the
|
|
2256
|
+
* tab order or dropping focus that was already on it, so a keyboard user's in-progress
|
|
2257
|
+
* action (e.g. Ctrl+Enter to resolve) still reaches it while busy. */
|
|
2258
|
+
busy?: boolean;
|
|
2242
2259
|
/** Applied to the outer .fd-field element — this is where width belongs. */
|
|
2243
2260
|
style?: React.CSSProperties;
|
|
2244
2261
|
/** Applied to the outer .fd-field element, like `style` — the same rule as Input. */
|
|
2245
2262
|
className?: string;
|
|
2246
2263
|
}
|
|
2247
|
-
declare function Textarea({ label, help, error, required, rows, disabled, id, className, style, ...rest }: TextareaProps): React.JSX.Element;
|
|
2264
|
+
declare function Textarea({ label, help, error, required, rows, disabled, busy, readOnly, id, className, style, ...rest }: TextareaProps): React.JSX.Element;
|
|
2248
2265
|
|
|
2249
2266
|
/** Numeric field with thousands formatting while unfocused, +/- steppers (press once for
|
|
2250
2267
|
* one step, press and hold to repeat with acceleration), ArrowUp/ArrowDown (Shift for
|
package/dist/index.d.ts
CHANGED
|
@@ -19,8 +19,18 @@ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
|
19
19
|
loading?: boolean;
|
|
20
20
|
block?: boolean;
|
|
21
21
|
as?: "button" | "a";
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
/** When `disabled` (or `loading`), stay in the tab order instead of going natively
|
|
23
|
+
* `disabled`: renders `aria-disabled="true"` rather than the `disabled` attribute,
|
|
24
|
+
* keeps the `:focus-visible` ring, and swallows click/Enter/Space without ever
|
|
25
|
+
* submitting a form. Use whenever a button's disabled state can flip while it holds
|
|
26
|
+
* focus (e.g. a controlled panel re-seeding mid-edit) — a native `disabled` drops focus
|
|
27
|
+
* to `<body>` the instant it applies, and nothing can intercept or redirect that.
|
|
28
|
+
* Also makes `aria-disabled` always explicit ("true"/"false", never absent): since the
|
|
29
|
+
* browser can no longer tell assistive tech this button's state from `disabled` itself,
|
|
30
|
+
* the ARIA state has to say so on its own at every render, not only while disabled. */
|
|
31
|
+
focusableWhenDisabled?: boolean;
|
|
32
|
+
}
|
|
33
|
+
declare function Button({ children, variant, size, icon, trailingIcon, arrow, loading, disabled, focusableWhenDisabled, block, as, className, onClick, onKeyDown, ...rest }: ButtonProps): React.JSX.Element;
|
|
24
34
|
|
|
25
35
|
/** A square action carrying only a Phosphor glyph. Always needs a label for a11y. */
|
|
26
36
|
interface IconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
@@ -2239,12 +2249,19 @@ interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement
|
|
|
2239
2249
|
error?: string;
|
|
2240
2250
|
required?: boolean;
|
|
2241
2251
|
rows?: number;
|
|
2252
|
+
/** Read-only plus `aria-busy`, with the same visible look as `disabled` (the
|
|
2253
|
+
* `.fd-input-disabled` tokens) — for a value an in-flight request is about to replace
|
|
2254
|
+
* while the field stays reachable. Unlike `disabled`, `busy` never sets the native
|
|
2255
|
+
* `disabled` attribute: `readOnly` blocks editing without removing the control from the
|
|
2256
|
+
* tab order or dropping focus that was already on it, so a keyboard user's in-progress
|
|
2257
|
+
* action (e.g. Ctrl+Enter to resolve) still reaches it while busy. */
|
|
2258
|
+
busy?: boolean;
|
|
2242
2259
|
/** Applied to the outer .fd-field element — this is where width belongs. */
|
|
2243
2260
|
style?: React.CSSProperties;
|
|
2244
2261
|
/** Applied to the outer .fd-field element, like `style` — the same rule as Input. */
|
|
2245
2262
|
className?: string;
|
|
2246
2263
|
}
|
|
2247
|
-
declare function Textarea({ label, help, error, required, rows, disabled, id, className, style, ...rest }: TextareaProps): React.JSX.Element;
|
|
2264
|
+
declare function Textarea({ label, help, error, required, rows, disabled, busy, readOnly, id, className, style, ...rest }: TextareaProps): React.JSX.Element;
|
|
2248
2265
|
|
|
2249
2266
|
/** Numeric field with thousands formatting while unfocused, +/- steppers (press once for
|
|
2250
2267
|
* one step, press and hold to repeat with acceleration), ArrowUp/ArrowDown (Shift for
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,9 @@ var VARIANTS = {
|
|
|
8
8
|
danger: "fd-btn-danger",
|
|
9
9
|
link: "fd-btn-link"
|
|
10
10
|
};
|
|
11
|
+
function isActivationKey(key) {
|
|
12
|
+
return key === "Enter" || key === " ";
|
|
13
|
+
}
|
|
11
14
|
function Button({
|
|
12
15
|
children,
|
|
13
16
|
variant = "primary",
|
|
@@ -17,12 +20,16 @@ function Button({
|
|
|
17
20
|
arrow = false,
|
|
18
21
|
loading = false,
|
|
19
22
|
disabled = false,
|
|
23
|
+
focusableWhenDisabled = false,
|
|
20
24
|
block = false,
|
|
21
25
|
as = "button",
|
|
22
26
|
className = "",
|
|
27
|
+
onClick,
|
|
28
|
+
onKeyDown,
|
|
23
29
|
...rest
|
|
24
30
|
}) {
|
|
25
31
|
const Tag2 = as;
|
|
32
|
+
const inert = (disabled || loading) && focusableWhenDisabled;
|
|
26
33
|
const cls = [
|
|
27
34
|
"fd-btn",
|
|
28
35
|
VARIANTS[variant] || VARIANTS.primary,
|
|
@@ -35,9 +42,16 @@ function Button({
|
|
|
35
42
|
Tag2,
|
|
36
43
|
{
|
|
37
44
|
className: cls,
|
|
38
|
-
disabled: Tag2 === "button" ? disabled || loading : void 0,
|
|
39
|
-
"aria-disabled": disabled || loading ? "true" : void 0,
|
|
45
|
+
disabled: Tag2 === "button" ? (disabled || loading) && !focusableWhenDisabled : void 0,
|
|
46
|
+
"aria-disabled": focusableWhenDisabled ? String(disabled || loading) : disabled || loading ? "true" : void 0,
|
|
40
47
|
"aria-busy": loading ? "true" : void 0,
|
|
48
|
+
onClick: inert ? (e) => {
|
|
49
|
+
e.preventDefault();
|
|
50
|
+
} : onClick,
|
|
51
|
+
onKeyDown: inert ? (e) => {
|
|
52
|
+
if (isActivationKey(e.key)) e.preventDefault();
|
|
53
|
+
onKeyDown?.(e);
|
|
54
|
+
} : onKeyDown,
|
|
41
55
|
...rest,
|
|
42
56
|
children: [
|
|
43
57
|
icon && !loading ? /* @__PURE__ */ jsx("i", { className: "ph ph-" + icon, "aria-hidden": "true" }) : null,
|
|
@@ -3196,12 +3210,23 @@ function DatePicker({ label, help, error, required = false, disabled = false, ra
|
|
|
3196
3210
|
|
|
3197
3211
|
// src/components/forms/Textarea.tsx
|
|
3198
3212
|
import { jsx as jsx44, jsxs as jsxs38 } from "react/jsx-runtime";
|
|
3199
|
-
function Textarea({ label, help, error, required = false, rows = 4, disabled = false, id, className = "", style, ...rest }) {
|
|
3213
|
+
function Textarea({ label, help, error, required = false, rows = 4, disabled = false, busy = false, readOnly, id, className = "", style, ...rest }) {
|
|
3200
3214
|
const fieldId = useFieldId(id);
|
|
3201
|
-
const box = ["fd-input", "fd-input-textarea", error ? "fd-input-error" : "", disabled ? "fd-input-disabled" : ""].filter(Boolean).join(" ");
|
|
3215
|
+
const box = ["fd-input", "fd-input-textarea", error ? "fd-input-error" : "", disabled || busy ? "fd-input-disabled" : ""].filter(Boolean).join(" ");
|
|
3202
3216
|
return /* @__PURE__ */ jsxs38("div", { className: ["fd-field", className].filter(Boolean).join(" "), style, children: [
|
|
3203
3217
|
label ? /* @__PURE__ */ jsx44(FieldLabel, { htmlFor: fieldId, required, children: label }) : null,
|
|
3204
|
-
/* @__PURE__ */ jsx44("div", { className: box, children: /* @__PURE__ */ jsx44(
|
|
3218
|
+
/* @__PURE__ */ jsx44("div", { className: box, children: /* @__PURE__ */ jsx44(
|
|
3219
|
+
"textarea",
|
|
3220
|
+
{
|
|
3221
|
+
id: fieldId,
|
|
3222
|
+
rows,
|
|
3223
|
+
disabled,
|
|
3224
|
+
readOnly: busy || readOnly,
|
|
3225
|
+
"aria-busy": busy || void 0,
|
|
3226
|
+
"aria-invalid": error ? "true" : void 0,
|
|
3227
|
+
...rest
|
|
3228
|
+
}
|
|
3229
|
+
) }),
|
|
3205
3230
|
error ? /* @__PURE__ */ jsxs38("span", { className: "fd-field-error", children: [
|
|
3206
3231
|
/* @__PURE__ */ jsx44("i", { className: "ph ph-warning-circle", "aria-hidden": "true" }),
|
|
3207
3232
|
error
|
|
@@ -3576,8 +3601,7 @@ function MultiSelectPanel({
|
|
|
3576
3601
|
rows: 4,
|
|
3577
3602
|
placeholder: field.pastePlaceholder || field.placeholder || "Paste names, one per line or comma-separated",
|
|
3578
3603
|
value: pasteText,
|
|
3579
|
-
|
|
3580
|
-
"aria-busy": pasteLoading || void 0,
|
|
3604
|
+
busy: pasteLoading,
|
|
3581
3605
|
onChange: (e) => setPasteText(e.target.value),
|
|
3582
3606
|
onKeyDown: (e) => {
|
|
3583
3607
|
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
@@ -3639,19 +3663,9 @@ function MultiSelectPanel({
|
|
|
3639
3663
|
{
|
|
3640
3664
|
size: "sm",
|
|
3641
3665
|
variant: "secondary",
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
e.preventDefault();
|
|
3646
|
-
return;
|
|
3647
|
-
}
|
|
3648
|
-
doResolve();
|
|
3649
|
-
},
|
|
3650
|
-
onKeyDown: (e) => {
|
|
3651
|
-
if (resolveDisabled && (e.key === "Enter" || e.key === " ")) {
|
|
3652
|
-
e.preventDefault();
|
|
3653
|
-
}
|
|
3654
|
-
},
|
|
3666
|
+
disabled: resolveDisabled,
|
|
3667
|
+
focusableWhenDisabled: true,
|
|
3668
|
+
onClick: doResolve,
|
|
3655
3669
|
children: pasteLoading ? "Resolving\u2026" : "Resolve"
|
|
3656
3670
|
}
|
|
3657
3671
|
) : void 0;
|
|
@@ -3788,16 +3802,6 @@ function RangePanel({
|
|
|
3788
3802
|
setDraft(next);
|
|
3789
3803
|
publish();
|
|
3790
3804
|
};
|
|
3791
|
-
const doneClick = (e) => {
|
|
3792
|
-
if (doneDisabled) {
|
|
3793
|
-
e.preventDefault();
|
|
3794
|
-
return;
|
|
3795
|
-
}
|
|
3796
|
-
onClose();
|
|
3797
|
-
};
|
|
3798
|
-
const doneKeyDown = (e) => {
|
|
3799
|
-
if (doneDisabled && (e.key === "Enter" || e.key === " ")) e.preventDefault();
|
|
3800
|
-
};
|
|
3801
3805
|
return /* @__PURE__ */ jsx45(
|
|
3802
3806
|
Popover,
|
|
3803
3807
|
{
|
|
@@ -3808,7 +3812,7 @@ function RangePanel({
|
|
|
3808
3812
|
offset: 6,
|
|
3809
3813
|
width: PANEL_WIDTH.range,
|
|
3810
3814
|
label: field.label,
|
|
3811
|
-
footer: /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary",
|
|
3815
|
+
footer: /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", disabled: doneDisabled, focusableWhenDisabled: true, onClick: onClose, children: "Done" }),
|
|
3812
3816
|
children: /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-sm", children: [
|
|
3813
3817
|
/* @__PURE__ */ jsx45("span", { ref: minWrapRef, children: /* @__PURE__ */ jsx45(
|
|
3814
3818
|
NumberInput,
|
|
@@ -3923,16 +3927,12 @@ function DatePanel({
|
|
|
3923
3927
|
};
|
|
3924
3928
|
const doneDisabled = kind === "withinDays" && !validity.allValid();
|
|
3925
3929
|
const confirm = () => {
|
|
3926
|
-
if (doneDisabled) return;
|
|
3927
3930
|
if (kind === "withinDays") {
|
|
3928
3931
|
const shown = shownWithinDays();
|
|
3929
3932
|
if (shown) onCommit(shown);
|
|
3930
3933
|
}
|
|
3931
3934
|
onClose();
|
|
3932
3935
|
};
|
|
3933
|
-
const doneKeyDown = (e) => {
|
|
3934
|
-
if (doneDisabled && (e.key === "Enter" || e.key === " ")) e.preventDefault();
|
|
3935
|
-
};
|
|
3936
3936
|
return /* @__PURE__ */ jsx45(
|
|
3937
3937
|
Popover,
|
|
3938
3938
|
{
|
|
@@ -3943,7 +3943,7 @@ function DatePanel({
|
|
|
3943
3943
|
offset: 6,
|
|
3944
3944
|
width: PANEL_WIDTH.date,
|
|
3945
3945
|
label: field.label,
|
|
3946
|
-
footer: /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary",
|
|
3946
|
+
footer: /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", disabled: doneDisabled, focusableWhenDisabled: true, onClick: confirm, children: "Done" }),
|
|
3947
3947
|
children: /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-sm", children: [
|
|
3948
3948
|
/* @__PURE__ */ jsx45(
|
|
3949
3949
|
SegmentedControl,
|