@neuronix-tech/ui 0.0.0-stage → 0.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/cli/index.js +4 -0
- package/dist/components/button/button.d.ts +18 -0
- package/dist/components/button/button.js +40 -0
- package/dist/components/button/button.js.map +1 -0
- package/dist/components/button/button.types.d.ts +36 -0
- package/dist/components/button/button.types.js +1 -0
- package/dist/components/button/button.types.js.map +1 -0
- package/dist/components/button/button.variants.d.ts +32 -0
- package/dist/components/button/button.variants.js +45 -0
- package/dist/components/button/button.variants.js.map +1 -0
- package/dist/components/button/index.d.ts +8 -0
- package/dist/components/button/index.js +5 -0
- package/dist/components/button/index.js.map +1 -0
- package/dist/components/form-currency-input/form-currency-input.d.ts +12 -0
- package/dist/components/form-currency-input/form-currency-input.js +62 -0
- package/dist/components/form-currency-input/form-currency-input.js.map +1 -0
- package/dist/components/form-currency-input/form-currency-input.types.d.ts +32 -0
- package/dist/components/form-currency-input/form-currency-input.types.js +1 -0
- package/dist/components/form-currency-input/form-currency-input.types.js.map +1 -0
- package/dist/components/form-currency-input/index.d.ts +4 -0
- package/dist/components/form-currency-input/index.js +5 -0
- package/dist/components/form-currency-input/index.js.map +1 -0
- package/dist/components/form-number-input/form-number-input.d.ts +13 -0
- package/dist/components/form-number-input/form-number-input.js +75 -0
- package/dist/components/form-number-input/form-number-input.js.map +1 -0
- package/dist/components/form-number-input/form-number-input.types.d.ts +34 -0
- package/dist/components/form-number-input/form-number-input.types.js +1 -0
- package/dist/components/form-number-input/form-number-input.types.js.map +1 -0
- package/dist/components/form-number-input/index.d.ts +4 -0
- package/dist/components/form-number-input/index.js +5 -0
- package/dist/components/form-number-input/index.js.map +1 -0
- package/dist/components/form-percentage-input/form-percentage-input.d.ts +12 -0
- package/dist/components/form-percentage-input/form-percentage-input.js +65 -0
- package/dist/components/form-percentage-input/form-percentage-input.js.map +1 -0
- package/dist/components/form-percentage-input/form-percentage-input.types.d.ts +33 -0
- package/dist/components/form-percentage-input/form-percentage-input.types.js +1 -0
- package/dist/components/form-percentage-input/form-percentage-input.types.js.map +1 -0
- package/dist/components/form-percentage-input/index.d.ts +4 -0
- package/dist/components/form-percentage-input/index.js +5 -0
- package/dist/components/form-percentage-input/index.js.map +1 -0
- package/dist/components/form-text-input/form-text-input.d.ts +19 -0
- package/dist/components/form-text-input/form-text-input.js +64 -0
- package/dist/components/form-text-input/form-text-input.js.map +1 -0
- package/dist/components/form-text-input/form-text-input.types.d.ts +45 -0
- package/dist/components/form-text-input/form-text-input.types.js +1 -0
- package/dist/components/form-text-input/form-text-input.types.js.map +1 -0
- package/dist/components/form-text-input/index.d.ts +4 -0
- package/dist/components/form-text-input/index.js +5 -0
- package/dist/components/form-text-input/index.js.map +1 -0
- package/dist/components/icon-button/icon-button.d.ts +20 -0
- package/dist/components/icon-button/icon-button.js +32 -0
- package/dist/components/icon-button/icon-button.js.map +1 -0
- package/dist/components/icon-button/icon-button.types.d.ts +24 -0
- package/dist/components/icon-button/icon-button.types.js +1 -0
- package/dist/components/icon-button/icon-button.types.js.map +1 -0
- package/dist/components/icon-button/index.d.ts +8 -0
- package/dist/components/icon-button/index.js +5 -0
- package/dist/components/icon-button/index.js.map +1 -0
- package/dist/components/spinner/index.d.ts +2 -0
- package/dist/components/spinner/index.js +5 -0
- package/dist/components/spinner/index.js.map +1 -0
- package/dist/components/spinner/spinner.d.ts +15 -0
- package/dist/components/spinner/spinner.js +9 -0
- package/dist/components/spinner/spinner.js.map +1 -0
- package/dist/components/toast/index.d.ts +3 -0
- package/dist/components/toast/index.js +8 -0
- package/dist/components/toast/index.js.map +1 -0
- package/dist/components/toast/toast.d.ts +30 -0
- package/dist/components/toast/toast.js +75 -0
- package/dist/components/toast/toast.js.map +1 -0
- package/dist/components/toast/toast.types.d.ts +22 -0
- package/dist/components/toast/toast.types.js +1 -0
- package/dist/components/toast/toast.types.js.map +1 -0
- package/dist/components/toast/toast.variants.d.ts +23 -0
- package/dist/components/toast/toast.variants.js +19 -0
- package/dist/components/toast/toast.variants.js.map +1 -0
- package/dist/hooks/use-number-mask.d.ts +24 -0
- package/dist/hooks/use-number-mask.js +59 -0
- package/dist/hooks/use-number-mask.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/cn.d.ts +10 -0
- package/dist/lib/cn.js +8 -0
- package/dist/lib/cn.js.map +1 -0
- package/dist/lib/format/number.d.ts +6 -0
- package/dist/lib/format/number.js +26 -0
- package/dist/lib/format/number.js.map +1 -0
- package/dist/lib/format/percent.d.ts +15 -0
- package/dist/lib/format/percent.js +7 -0
- package/dist/lib/format/percent.js.map +1 -0
- package/dist/lib/sealed.d.ts +14 -0
- package/dist/lib/sealed.js +1 -0
- package/dist/lib/sealed.js.map +1 -0
- package/dist/primitives/field.d.ts +17 -0
- package/dist/primitives/field.js +74 -0
- package/dist/primitives/field.js.map +1 -0
- package/dist/primitives/form-field.d.ts +27 -0
- package/dist/primitives/form-field.js +27 -0
- package/dist/primitives/form-field.js.map +1 -0
- package/dist/primitives/input-group.d.ts +19 -0
- package/dist/primitives/input-group.js +87 -0
- package/dist/primitives/input-group.js.map +1 -0
- package/dist/primitives/input.d.ts +11 -0
- package/dist/primitives/input.js +22 -0
- package/dist/primitives/input.js.map +1 -0
- package/dist/primitives/label.d.ts +6 -0
- package/dist/primitives/label.js +19 -0
- package/dist/primitives/label.js.map +1 -0
- package/dist/primitives/spinner.d.ts +10 -0
- package/dist/primitives/spinner.js +19 -0
- package/dist/primitives/spinner.js.map +1 -0
- package/dist/primitives/toast.d.ts +31 -0
- package/dist/primitives/toast.js +102 -0
- package/dist/primitives/toast.js.map +1 -0
- package/dist/primitives/tooltip.d.ts +17 -0
- package/dist/primitives/tooltip.js +51 -0
- package/dist/primitives/tooltip.js.map +1 -0
- package/dist/styles.css +2 -0
- package/package.json +70 -3
- package/README.md +0 -3
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { FieldValues, FieldPath, ControllerRenderProps, FieldError } from 'react-hook-form';
|
|
2
|
+
|
|
3
|
+
interface FormPercentageInputProps<T extends FieldValues, N extends FieldPath<T>> {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
/** Required is the DEFAULT: non-optional fields render "label *". */
|
|
7
|
+
optional?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* react-hook-form binding. Required: the mask owns the value round-trip.
|
|
10
|
+
*
|
|
11
|
+
* OUTPUT CONTRACT — form state stores PERCENT POINTS as `number | null`:
|
|
12
|
+
* the user types 15 → state holds 15, NEVER 0.15. This is the UI convention
|
|
13
|
+
* (state matches the screen; Stripe/Shopify store rates the same way). When
|
|
14
|
+
* a backend or formula expects a fraction, convert explicitly at the
|
|
15
|
+
* boundary with `percentToFraction` (exported from @neuronix-tech/ui).
|
|
16
|
+
*/
|
|
17
|
+
field: ControllerRenderProps<T, N>;
|
|
18
|
+
/** react-hook-form field error; presence turns the field invalid. */
|
|
19
|
+
error?: FieldError;
|
|
20
|
+
/** Spinner overlay while an async source seeds the value; also disables the control. */
|
|
21
|
+
isLoading?: boolean;
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
autoFocus?: boolean;
|
|
24
|
+
placeholder?: string;
|
|
25
|
+
/** Explanation behind the label (dotted underline + tooltip). */
|
|
26
|
+
labelHint?: string;
|
|
27
|
+
/** Inclusive lower bound applied on blur. Default 0. */
|
|
28
|
+
min?: number;
|
|
29
|
+
/** Inclusive upper bound applied on blur. Default 100. */
|
|
30
|
+
max?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type { FormPercentageInputProps };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=form-percentage-input.types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/form-percentage-input/index.ts"],"sourcesContent":["export { FormPercentageInput } from \"./form-percentage-input.js\"\nexport type { FormPercentageInputProps } from \"./form-percentage-input.types.js\"\n"],"mappings":"AAAA,SAAS,2BAA2B;","names":[]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { FieldValues, FieldPath } from 'react-hook-form';
|
|
3
|
+
import { FormTextInputProps } from './form-text-input.types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The standard text field: label (required by default, "*"), control, error —
|
|
7
|
+
* always the same skeleton. Bind it through a react-hook-form <Controller>:
|
|
8
|
+
*
|
|
9
|
+
* <Controller
|
|
10
|
+
* control={form.control}
|
|
11
|
+
* name="name"
|
|
12
|
+
* render={({ field, fieldState }) => (
|
|
13
|
+
* <FormTextInput id="name" label="Name" field={field} error={fieldState.error} />
|
|
14
|
+
* )}
|
|
15
|
+
* />
|
|
16
|
+
*/
|
|
17
|
+
declare function FormTextInput<T extends FieldValues, N extends FieldPath<T>>({ id, label, optional, field, error, isLoading, disabled, autoFocus, maxLength, uppercase, type, placeholder, prefix, labelHint, }: FormTextInputProps<T, N>): react.JSX.Element;
|
|
18
|
+
|
|
19
|
+
export { FormTextInput };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Field, FieldError } from "../../primitives/field.js";
|
|
4
|
+
import { ControlLoadingOverlay, FormFieldLabel } from "../../primitives/form-field.js";
|
|
5
|
+
import { InputPrimitive } from "../../primitives/input.js";
|
|
6
|
+
import {
|
|
7
|
+
InputGroup,
|
|
8
|
+
InputGroupAddon,
|
|
9
|
+
InputGroupInput,
|
|
10
|
+
InputGroupText
|
|
11
|
+
} from "../../primitives/input-group.js";
|
|
12
|
+
function FormTextInput({
|
|
13
|
+
id,
|
|
14
|
+
label,
|
|
15
|
+
optional = false,
|
|
16
|
+
field,
|
|
17
|
+
error,
|
|
18
|
+
isLoading = false,
|
|
19
|
+
disabled = false,
|
|
20
|
+
autoFocus = false,
|
|
21
|
+
maxLength,
|
|
22
|
+
uppercase = false,
|
|
23
|
+
type = "text",
|
|
24
|
+
placeholder,
|
|
25
|
+
prefix,
|
|
26
|
+
labelHint
|
|
27
|
+
}) {
|
|
28
|
+
const controlledValue = field && field.value !== null && field.value !== void 0 ? String(field.value) : "";
|
|
29
|
+
const inputProps = {
|
|
30
|
+
id,
|
|
31
|
+
name: field?.name,
|
|
32
|
+
ref: field?.ref,
|
|
33
|
+
value: field ? controlledValue : void 0,
|
|
34
|
+
maxLength,
|
|
35
|
+
type,
|
|
36
|
+
placeholder,
|
|
37
|
+
autoFocus,
|
|
38
|
+
autoComplete: "off",
|
|
39
|
+
disabled: disabled || isLoading,
|
|
40
|
+
"aria-invalid": !!error || void 0,
|
|
41
|
+
onBlur: field?.onBlur,
|
|
42
|
+
onChange: (e) => {
|
|
43
|
+
if (uppercase) {
|
|
44
|
+
e.target.value = e.target.value.toUpperCase();
|
|
45
|
+
}
|
|
46
|
+
field?.onChange?.(e);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
return /* @__PURE__ */ jsxs(Field, { "data-invalid": !!error || void 0, className: "nxui:gap-1", children: [
|
|
50
|
+
/* @__PURE__ */ jsx(FormFieldLabel, { htmlFor: id, label, optional, labelHint }),
|
|
51
|
+
/* @__PURE__ */ jsxs("div", { className: "nxui:relative", children: [
|
|
52
|
+
prefix ? /* @__PURE__ */ jsxs(InputGroup, { children: [
|
|
53
|
+
/* @__PURE__ */ jsx(InputGroupAddon, { align: "inline-start", children: /* @__PURE__ */ jsx(InputGroupText, { children: prefix }) }),
|
|
54
|
+
/* @__PURE__ */ jsx(InputGroupInput, { ...inputProps })
|
|
55
|
+
] }) : /* @__PURE__ */ jsx(InputPrimitive, { ...inputProps }),
|
|
56
|
+
isLoading && /* @__PURE__ */ jsx(ControlLoadingOverlay, {})
|
|
57
|
+
] }),
|
|
58
|
+
/* @__PURE__ */ jsx(FieldError, { errors: [error] })
|
|
59
|
+
] });
|
|
60
|
+
}
|
|
61
|
+
export {
|
|
62
|
+
FormTextInput
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=form-text-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/form-text-input/form-text-input.tsx"],"sourcesContent":["\"use client\"\n\nimport type { FieldPath, FieldValues } from \"react-hook-form\"\n\nimport type { FormTextInputProps } from \"./form-text-input.types.js\"\nimport { Field, FieldError } from \"../../primitives/field.js\"\nimport { ControlLoadingOverlay, FormFieldLabel } from \"../../primitives/form-field.js\"\nimport { InputPrimitive } from \"../../primitives/input.js\"\nimport {\n InputGroup,\n InputGroupAddon,\n InputGroupInput,\n InputGroupText,\n} from \"../../primitives/input-group.js\"\n\n/**\n * The standard text field: label (required by default, \"*\"), control, error —\n * always the same skeleton. Bind it through a react-hook-form <Controller>:\n *\n * <Controller\n * control={form.control}\n * name=\"name\"\n * render={({ field, fieldState }) => (\n * <FormTextInput id=\"name\" label=\"Name\" field={field} error={fieldState.error} />\n * )}\n * />\n */\nexport function FormTextInput<T extends FieldValues, N extends FieldPath<T>>({\n id,\n label,\n optional = false,\n field,\n error,\n isLoading = false,\n disabled = false,\n autoFocus = false,\n maxLength,\n uppercase = false,\n type = \"text\",\n placeholder,\n prefix,\n labelHint,\n}: FormTextInputProps<T, N>) {\n const controlledValue =\n field && field.value !== null && field.value !== undefined ? String(field.value) : \"\"\n\n const inputProps = {\n id,\n name: field?.name,\n ref: field?.ref,\n value: field ? controlledValue : undefined,\n maxLength,\n type,\n placeholder,\n autoFocus,\n autoComplete: \"off\",\n disabled: disabled || isLoading,\n \"aria-invalid\": !!error || undefined,\n onBlur: field?.onBlur,\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => {\n if (uppercase) {\n e.target.value = e.target.value.toUpperCase()\n }\n field?.onChange?.(e)\n },\n }\n\n return (\n <Field data-invalid={!!error || undefined} className=\"nxui:gap-1\">\n <FormFieldLabel htmlFor={id} label={label} optional={optional} labelHint={labelHint} />\n <div className=\"nxui:relative\">\n {prefix ? (\n <InputGroup>\n <InputGroupAddon align=\"inline-start\">\n <InputGroupText>{prefix}</InputGroupText>\n </InputGroupAddon>\n <InputGroupInput {...inputProps} />\n </InputGroup>\n ) : (\n <InputPrimitive {...inputProps} />\n )}\n {isLoading && <ControlLoadingOverlay />}\n </div>\n <FieldError errors={[error]} />\n </Field>\n )\n}\n"],"mappings":";AAqEM,cAGI,YAHJ;AAhEN,SAAS,OAAO,kBAAkB;AAClC,SAAS,uBAAuB,sBAAsB;AACtD,SAAS,sBAAsB;AAC/B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAcA,SAAS,cAA6D;AAAA,EAC3E;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,YAAY;AAAA,EACZ;AAAA,EACA,YAAY;AAAA,EACZ,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AACF,GAA6B;AAC3B,QAAM,kBACJ,SAAS,MAAM,UAAU,QAAQ,MAAM,UAAU,SAAY,OAAO,MAAM,KAAK,IAAI;AAErF,QAAM,aAAa;AAAA,IACjB;AAAA,IACA,MAAM,OAAO;AAAA,IACb,KAAK,OAAO;AAAA,IACZ,OAAO,QAAQ,kBAAkB;AAAA,IACjC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd,UAAU,YAAY;AAAA,IACtB,gBAAgB,CAAC,CAAC,SAAS;AAAA,IAC3B,QAAQ,OAAO;AAAA,IACf,UAAU,CAAC,MAA2C;AACpD,UAAI,WAAW;AACb,UAAE,OAAO,QAAQ,EAAE,OAAO,MAAM,YAAY;AAAA,MAC9C;AACA,aAAO,WAAW,CAAC;AAAA,IACrB;AAAA,EACF;AAEA,SACE,qBAAC,SAAM,gBAAc,CAAC,CAAC,SAAS,QAAW,WAAU,cACnD;AAAA,wBAAC,kBAAe,SAAS,IAAI,OAAc,UAAoB,WAAsB;AAAA,IACrF,qBAAC,SAAI,WAAU,iBACZ;AAAA,eACC,qBAAC,cACC;AAAA,4BAAC,mBAAgB,OAAM,gBACrB,8BAAC,kBAAgB,kBAAO,GAC1B;AAAA,QACA,oBAAC,mBAAiB,GAAG,YAAY;AAAA,SACnC,IAEA,oBAAC,kBAAgB,GAAG,YAAY;AAAA,MAEjC,aAAa,oBAAC,yBAAsB;AAAA,OACvC;AAAA,IACA,oBAAC,cAAW,QAAQ,CAAC,KAAK,GAAG;AAAA,KAC/B;AAEJ;","names":[]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { FieldValues, FieldPath, ControllerRenderProps, FieldError } from 'react-hook-form';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Deliberately a closed union — numeric entry must go through FormNumberInput
|
|
5
|
+
* (which formats and stores number | null); `type="number"` is not available.
|
|
6
|
+
*/
|
|
7
|
+
type TextInputType = "text" | "email" | "password" | "url" | "tel" | "search";
|
|
8
|
+
interface FormTextInputProps<T extends FieldValues, N extends FieldPath<T>> {
|
|
9
|
+
id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
/**
|
|
12
|
+
* Required is the DEFAULT: non-optional fields render "label *". Pass
|
|
13
|
+
* `optional` to drop the asterisk — inverted from the usual `required` flag
|
|
14
|
+
* so the common case needs no prop.
|
|
15
|
+
*/
|
|
16
|
+
optional?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* react-hook-form binding from a <Controller>. Optional so the input can be
|
|
19
|
+
* driven externally/uncontrolled in rare non-form contexts.
|
|
20
|
+
*/
|
|
21
|
+
field?: ControllerRenderProps<T, N>;
|
|
22
|
+
/** react-hook-form field error; presence turns the field invalid (red label, ring, message). */
|
|
23
|
+
error?: FieldError;
|
|
24
|
+
/** Overlays a spinner on the control while an async source fills it. */
|
|
25
|
+
isLoading?: boolean;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
autoFocus?: boolean;
|
|
28
|
+
maxLength?: number;
|
|
29
|
+
/** Forces typed text to uppercase at the event level (value stored uppercase). */
|
|
30
|
+
uppercase?: boolean;
|
|
31
|
+
type?: TextInputType;
|
|
32
|
+
placeholder?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Fixed adornment rendered before the value (e.g. "#" for a channel name).
|
|
35
|
+
* Purely visual — never part of the field's value.
|
|
36
|
+
*/
|
|
37
|
+
prefix?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Explanation behind the label (dotted underline + tooltip) for fields whose
|
|
40
|
+
* meaning is not obvious from the label alone.
|
|
41
|
+
*/
|
|
42
|
+
labelHint?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type { FormTextInputProps, TextInputType };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=form-text-input.types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/form-text-input/index.ts"],"sourcesContent":["export { FormTextInput } from \"./form-text-input.js\"\nexport type { FormTextInputProps, TextInputType } from \"./form-text-input.types.js\"\n"],"mappings":"AAAA,SAAS,qBAAqB;","names":[]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { IconButtonProps } from './icon-button.types.js';
|
|
3
|
+
import '@base-ui/react/button';
|
|
4
|
+
import '../../lib/sealed.js';
|
|
5
|
+
import '../button/button.variants.js';
|
|
6
|
+
import 'class-variance-authority/types';
|
|
7
|
+
import 'class-variance-authority';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Square, icon-only button at the project's control size. Pass one icon as the
|
|
11
|
+
* child; it is auto-sized. Give it a `tooltip` — an icon alone rarely explains
|
|
12
|
+
* itself.
|
|
13
|
+
*
|
|
14
|
+
* <IconButton aria-label="Delete" tooltip="Delete" variant="destructive">
|
|
15
|
+
* <IconTrash />
|
|
16
|
+
* </IconButton>
|
|
17
|
+
*/
|
|
18
|
+
declare function IconButton({ variant, isLoading, disabled, children, tooltip, ...props }: IconButtonProps): react.JSX.Element;
|
|
19
|
+
|
|
20
|
+
export { IconButton };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Button as ButtonPrimitive } from "@base-ui/react/button";
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
|
+
import { SpinnerPrimitive } from "../../primitives/spinner.js";
|
|
6
|
+
import { TooltipContent, TooltipRoot, TooltipTrigger } from "../../primitives/tooltip.js";
|
|
7
|
+
import { buttonVariants, spinnerSizes } from "../button/button.variants.js";
|
|
8
|
+
function IconButton({ variant = "ghost", isLoading = false, disabled, children, tooltip, ...props }) {
|
|
9
|
+
const button = /* @__PURE__ */ jsx(
|
|
10
|
+
ButtonPrimitive,
|
|
11
|
+
{
|
|
12
|
+
"data-slot": "icon-button",
|
|
13
|
+
"aria-busy": isLoading || void 0,
|
|
14
|
+
disabled: disabled || isLoading,
|
|
15
|
+
...props,
|
|
16
|
+
className: cn(buttonVariants({ variant, size: "icon" })),
|
|
17
|
+
children: isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
18
|
+
/* @__PURE__ */ jsx("span", { className: "nxui:invisible nxui:contents", children }),
|
|
19
|
+
/* @__PURE__ */ jsx(SpinnerPrimitive, { className: cn("nxui:absolute nxui:inset-0 nxui:m-auto", spinnerSizes.icon) })
|
|
20
|
+
] }) : children
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
if (!tooltip) return button;
|
|
24
|
+
return /* @__PURE__ */ jsxs(TooltipRoot, { children: [
|
|
25
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { render: button }),
|
|
26
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: tooltip })
|
|
27
|
+
] });
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
IconButton
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=icon-button.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/icon-button/icon-button.tsx"],"sourcesContent":["\"use client\"\n\nimport { Button as ButtonPrimitive } from \"@base-ui/react/button\"\n\nimport type { IconButtonProps } from \"./icon-button.types.js\"\nimport { cn } from \"../../lib/cn.js\"\nimport { SpinnerPrimitive } from \"../../primitives/spinner.js\"\nimport { TooltipContent, TooltipRoot, TooltipTrigger } from \"../../primitives/tooltip.js\"\nimport { buttonVariants, spinnerSizes } from \"../button/button.variants.js\"\n\n/**\n * Square, icon-only button at the project's control size. Pass one icon as the\n * child; it is auto-sized. Give it a `tooltip` — an icon alone rarely explains\n * itself.\n *\n * <IconButton aria-label=\"Delete\" tooltip=\"Delete\" variant=\"destructive\">\n * <IconTrash />\n * </IconButton>\n */\nfunction IconButton({ variant = \"ghost\", isLoading = false, disabled, children, tooltip, ...props }: IconButtonProps) {\n const button = (\n <ButtonPrimitive\n data-slot=\"icon-button\"\n aria-busy={isLoading || undefined}\n disabled={disabled || isLoading}\n {...props}\n // className comes AFTER the spread: a Base UI `render` merge (e.g. Toast\n // Action/Close rendering this component) injects its own className prop,\n // and the sealed classes must always win.\n className={cn(buttonVariants({ variant, size: \"icon\" }))}\n >\n {isLoading ? (\n <>\n <span className=\"nxui:invisible nxui:contents\">{children}</span>\n <SpinnerPrimitive className={cn(\"nxui:absolute nxui:inset-0 nxui:m-auto\", spinnerSizes.icon)} />\n </>\n ) : (\n children\n )}\n </ButtonPrimitive>\n )\n\n if (!tooltip) return button\n\n return (\n <TooltipRoot>\n <TooltipTrigger render={button} />\n <TooltipContent>{tooltip}</TooltipContent>\n </TooltipRoot>\n )\n}\n\nexport { IconButton }\n"],"mappings":";AAgCQ,mBACE,KADF;AA9BR,SAAS,UAAU,uBAAuB;AAG1C,SAAS,UAAU;AACnB,SAAS,wBAAwB;AACjC,SAAS,gBAAgB,aAAa,sBAAsB;AAC5D,SAAS,gBAAgB,oBAAoB;AAW7C,SAAS,WAAW,EAAE,UAAU,SAAS,YAAY,OAAO,UAAU,UAAU,SAAS,GAAG,MAAM,GAAoB;AACpH,QAAM,SACJ;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,aAAW,aAAa;AAAA,MACxB,UAAU,YAAY;AAAA,MACrB,GAAG;AAAA,MAIJ,WAAW,GAAG,eAAe,EAAE,SAAS,MAAM,OAAO,CAAC,CAAC;AAAA,MAEtD,sBACC,iCACE;AAAA,4BAAC,UAAK,WAAU,gCAAgC,UAAS;AAAA,QACzD,oBAAC,oBAAiB,WAAW,GAAG,0CAA0C,aAAa,IAAI,GAAG;AAAA,SAChG,IAEA;AAAA;AAAA,EAEJ;AAGF,MAAI,CAAC,QAAS,QAAO;AAErB,SACE,qBAAC,eACC;AAAA,wBAAC,kBAAe,QAAQ,QAAQ;AAAA,IAChC,oBAAC,kBAAgB,mBAAQ;AAAA,KAC3B;AAEJ;","names":[]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Button } from '@base-ui/react/button';
|
|
2
|
+
import { Sealed } from '../../lib/sealed.js';
|
|
3
|
+
import { ButtonVariantProps } from '../button/button.variants.js';
|
|
4
|
+
import 'class-variance-authority/types';
|
|
5
|
+
import 'class-variance-authority';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Icon-only button: a SHAPE, not a size — it renders square at the project's
|
|
9
|
+
* control size. `aria-label` is required by the type because an icon-only
|
|
10
|
+
* control has no accessible name without it. Prefer also passing `tooltip`
|
|
11
|
+
* (usually the same text): an icon alone rarely explains itself.
|
|
12
|
+
*/
|
|
13
|
+
interface IconButtonProps extends Sealed<Button.Props>, Omit<ButtonVariantProps, "size"> {
|
|
14
|
+
"aria-label": string;
|
|
15
|
+
/** Same no-resize loading overlay as Button. */
|
|
16
|
+
isLoading?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Text shown in a tooltip above the button. Tooltips are a prop, never a
|
|
19
|
+
* wrapper you compose — the library owns delay, positioning, and styling.
|
|
20
|
+
*/
|
|
21
|
+
tooltip?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type { IconButtonProps };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=icon-button.types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { IconButton } from './icon-button.js';
|
|
2
|
+
export { IconButtonProps } from './icon-button.types.js';
|
|
3
|
+
import 'react';
|
|
4
|
+
import '@base-ui/react/button';
|
|
5
|
+
import '../../lib/sealed.js';
|
|
6
|
+
import '../button/button.variants.js';
|
|
7
|
+
import 'class-variance-authority/types';
|
|
8
|
+
import 'class-variance-authority';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/icon-button/index.ts"],"sourcesContent":["export { IconButton } from \"./icon-button.js\"\nexport type { IconButtonProps } from \"./icon-button.types.js\"\n"],"mappings":"AAAA,SAAS,kBAAkB;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/spinner/index.ts"],"sourcesContent":["export { Spinner, type SpinnerProps } from \"./spinner.js\"\n"],"mappings":"AAAA,SAAS,eAAkC;","names":[]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
interface SpinnerProps {
|
|
4
|
+
/** Accessible label announced to screen readers. Defaults to "Loading". */
|
|
5
|
+
"aria-label"?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Standalone loading spinner, one size — sizing is a library decision, not a
|
|
9
|
+
* caller decision. Components that embed a spinner (Button's isLoading, future
|
|
10
|
+
* DataLoading) size it internally. Inside a Button, use `isLoading` instead —
|
|
11
|
+
* never compose a Spinner into button children manually.
|
|
12
|
+
*/
|
|
13
|
+
declare function Spinner({ "aria-label": ariaLabel }: SpinnerProps): react.JSX.Element;
|
|
14
|
+
|
|
15
|
+
export { Spinner, type SpinnerProps };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { SpinnerPrimitive } from "../../primitives/spinner.js";
|
|
3
|
+
function Spinner({ "aria-label": ariaLabel = "Loading" }) {
|
|
4
|
+
return /* @__PURE__ */ jsx(SpinnerPrimitive, { className: "nxui:size-4", "aria-label": ariaLabel });
|
|
5
|
+
}
|
|
6
|
+
export {
|
|
7
|
+
Spinner
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=spinner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/spinner/spinner.tsx"],"sourcesContent":["import { SpinnerPrimitive } from \"../../primitives/spinner.js\"\n\nexport interface SpinnerProps {\n /** Accessible label announced to screen readers. Defaults to \"Loading\". */\n \"aria-label\"?: string\n}\n\n/**\n * Standalone loading spinner, one size — sizing is a library decision, not a\n * caller decision. Components that embed a spinner (Button's isLoading, future\n * DataLoading) size it internally. Inside a Button, use `isLoading` instead —\n * never compose a Spinner into button children manually.\n */\nfunction Spinner({ \"aria-label\": ariaLabel = \"Loading\" }: SpinnerProps) {\n return <SpinnerPrimitive className=\"nxui:size-4\" aria-label={ariaLabel} />\n}\n\nexport { Spinner }\n"],"mappings":"AAcS;AAdT,SAAS,wBAAwB;AAajC,SAAS,QAAQ,EAAE,cAAc,YAAY,UAAU,GAAiB;AACtE,SAAO,oBAAC,oBAAiB,WAAU,eAAc,cAAY,WAAW;AAC1E;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/toast/index.ts"],"sourcesContent":["export { Toaster, successToast, warningToast, errorToast } from \"./toast.js\"\nexport type { ToastOptions, ToastActionOptions } from \"./toast.types.js\"\n"],"mappings":"AAAA,SAAS,SAAS,cAAc,cAAc,kBAAkB;","names":[]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ToastOptions } from './toast.types.js';
|
|
3
|
+
|
|
4
|
+
/** Renders every live toast in the current provider. Internal — not exported publicly. */
|
|
5
|
+
declare function ToastList(): react.JSX.Element[];
|
|
6
|
+
/**
|
|
7
|
+
* Mount ONCE, at the root of the app (e.g. Next.js `app/layout.tsx`, as a
|
|
8
|
+
* sibling of `{children}`). It renders the toast viewport; every
|
|
9
|
+
* successToast / warningToast / errorToast call anywhere in the app shows up
|
|
10
|
+
* here. It takes no props: placement, timing, and stacking are library-owned.
|
|
11
|
+
*/
|
|
12
|
+
declare function Toaster(): react.JSX.Element;
|
|
13
|
+
/**
|
|
14
|
+
* Confirms that something the user did worked ("Client saved"). The most
|
|
15
|
+
* common toast — fire it from a mutation's success callback, never on render.
|
|
16
|
+
*/
|
|
17
|
+
declare function successToast(title: string, options?: ToastOptions): void;
|
|
18
|
+
/**
|
|
19
|
+
* The action went through but something deserves attention ("Saved, but 2
|
|
20
|
+
* contacts were skipped"). Not for failures — those are errorToast.
|
|
21
|
+
*/
|
|
22
|
+
declare function warningToast(title: string, options?: ToastOptions): void;
|
|
23
|
+
/**
|
|
24
|
+
* Something the user asked for failed and was NOT applied ("Couldn't save the
|
|
25
|
+
* client"). Announced assertively to screen readers. Field-level validation
|
|
26
|
+
* errors belong on the field (the Form* `error` prop), never in a toast.
|
|
27
|
+
*/
|
|
28
|
+
declare function errorToast(title: string, options?: ToastOptions): void;
|
|
29
|
+
|
|
30
|
+
export { ToastList, Toaster, errorToast, successToast, warningToast };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { IconAlertOctagon, IconAlertTriangle, IconCircleCheck, IconX } from "@tabler/icons-react";
|
|
4
|
+
import { toastIconVariants, toastViewportClasses } from "./toast.variants.js";
|
|
5
|
+
import { Button } from "../button/index.js";
|
|
6
|
+
import { IconButton } from "../icon-button/index.js";
|
|
7
|
+
import {
|
|
8
|
+
ToastAction,
|
|
9
|
+
ToastClose,
|
|
10
|
+
ToastContent,
|
|
11
|
+
ToastDescription,
|
|
12
|
+
ToastPortal,
|
|
13
|
+
ToastProvider,
|
|
14
|
+
ToastRoot,
|
|
15
|
+
ToastTitle,
|
|
16
|
+
ToastViewport,
|
|
17
|
+
toastManager,
|
|
18
|
+
useToastManager
|
|
19
|
+
} from "../../primitives/toast.js";
|
|
20
|
+
const ICONS = {
|
|
21
|
+
success: IconCircleCheck,
|
|
22
|
+
warning: IconAlertTriangle,
|
|
23
|
+
error: IconAlertOctagon
|
|
24
|
+
};
|
|
25
|
+
function isToastType(type) {
|
|
26
|
+
return type !== void 0 && type in ICONS;
|
|
27
|
+
}
|
|
28
|
+
function ToastList() {
|
|
29
|
+
const { toasts } = useToastManager();
|
|
30
|
+
return toasts.map((item) => {
|
|
31
|
+
const type = isToastType(item.type) ? item.type : null;
|
|
32
|
+
const Icon = type ? ICONS[type] : null;
|
|
33
|
+
return /* @__PURE__ */ jsx(ToastRoot, { toast: item, children: /* @__PURE__ */ jsxs(ToastContent, { children: [
|
|
34
|
+
Icon && /* @__PURE__ */ jsx("span", { "data-slot": "toast-icon", className: toastIconVariants({ type }), children: /* @__PURE__ */ jsx(Icon, { "aria-hidden": "true" }) }),
|
|
35
|
+
/* @__PURE__ */ jsxs("div", { className: "nxui:flex nxui:min-w-0 nxui:flex-1 nxui:flex-col", children: [
|
|
36
|
+
/* @__PURE__ */ jsx(ToastTitle, {}),
|
|
37
|
+
/* @__PURE__ */ jsx(ToastDescription, {})
|
|
38
|
+
] }),
|
|
39
|
+
/* @__PURE__ */ jsx(ToastAction, { render: /* @__PURE__ */ jsx(Button, { variant: "outline" }) }),
|
|
40
|
+
/* @__PURE__ */ jsx(ToastClose, { render: /* @__PURE__ */ jsx(IconButton, { variant: "ghost", "aria-label": "Close" }), children: /* @__PURE__ */ jsx(IconX, { "aria-hidden": "true" }) })
|
|
41
|
+
] }) }, item.id);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function Toaster() {
|
|
45
|
+
return /* @__PURE__ */ jsx(ToastProvider, { toastManager, children: /* @__PURE__ */ jsx(ToastPortal, { children: /* @__PURE__ */ jsx(ToastViewport, { className: toastViewportClasses, children: /* @__PURE__ */ jsx(ToastList, {}) }) }) });
|
|
46
|
+
}
|
|
47
|
+
function addToast(type, title, options = {}) {
|
|
48
|
+
const { description, action } = options;
|
|
49
|
+
toastManager.add({
|
|
50
|
+
type,
|
|
51
|
+
title,
|
|
52
|
+
description,
|
|
53
|
+
// Errors interrupt the screen reader (role="alert"); success and warning
|
|
54
|
+
// are announced politely so they never cut off what the user is hearing.
|
|
55
|
+
priority: type === "error" ? "high" : "low",
|
|
56
|
+
actionProps: action ? { children: action.label, onClick: action.onClick } : void 0
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function successToast(title, options) {
|
|
60
|
+
addToast("success", title, options);
|
|
61
|
+
}
|
|
62
|
+
function warningToast(title, options) {
|
|
63
|
+
addToast("warning", title, options);
|
|
64
|
+
}
|
|
65
|
+
function errorToast(title, options) {
|
|
66
|
+
addToast("error", title, options);
|
|
67
|
+
}
|
|
68
|
+
export {
|
|
69
|
+
ToastList,
|
|
70
|
+
Toaster,
|
|
71
|
+
errorToast,
|
|
72
|
+
successToast,
|
|
73
|
+
warningToast
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=toast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/toast/toast.tsx"],"sourcesContent":["\"use client\"\n\nimport { IconAlertOctagon, IconAlertTriangle, IconCircleCheck, IconX } from \"@tabler/icons-react\"\n\nimport type { ToastOptions } from \"./toast.types.js\"\nimport { toastIconVariants, toastViewportClasses, type ToastType } from \"./toast.variants.js\"\nimport { Button } from \"../button/index.js\"\nimport { IconButton } from \"../icon-button/index.js\"\nimport {\n ToastAction,\n ToastClose,\n ToastContent,\n ToastDescription,\n ToastPortal,\n ToastProvider,\n ToastRoot,\n ToastTitle,\n ToastViewport,\n toastManager,\n useToastManager,\n} from \"../../primitives/toast.js\"\n\nconst ICONS = {\n success: IconCircleCheck,\n warning: IconAlertTriangle,\n error: IconAlertOctagon,\n} as const satisfies Record<ToastType, unknown>\n\nfunction isToastType(type: string | undefined): type is ToastType {\n return type !== undefined && type in ICONS\n}\n\n/** Renders every live toast in the current provider. Internal — not exported publicly. */\nfunction ToastList() {\n const { toasts } = useToastManager()\n\n return toasts.map((item) => {\n const type = isToastType(item.type) ? item.type : null\n const Icon = type ? ICONS[type] : null\n return (\n <ToastRoot key={item.id} toast={item}>\n <ToastContent>\n {Icon && (\n <span data-slot=\"toast-icon\" className={toastIconVariants({ type })}>\n <Icon aria-hidden=\"true\" />\n </span>\n )}\n <div className=\"nxui:flex nxui:min-w-0 nxui:flex-1 nxui:flex-col\">\n <ToastTitle />\n <ToastDescription />\n </div>\n <ToastAction render={<Button variant=\"outline\" />} />\n <ToastClose render={<IconButton variant=\"ghost\" aria-label=\"Close\" />}>\n <IconX aria-hidden=\"true\" />\n </ToastClose>\n </ToastContent>\n </ToastRoot>\n )\n })\n}\n\n/**\n * Mount ONCE, at the root of the app (e.g. Next.js `app/layout.tsx`, as a\n * sibling of `{children}`). It renders the toast viewport; every\n * successToast / warningToast / errorToast call anywhere in the app shows up\n * here. It takes no props: placement, timing, and stacking are library-owned.\n */\nfunction Toaster() {\n return (\n <ToastProvider toastManager={toastManager}>\n <ToastPortal>\n <ToastViewport className={toastViewportClasses}>\n <ToastList />\n </ToastViewport>\n </ToastPortal>\n </ToastProvider>\n )\n}\n\nfunction addToast(type: ToastType, title: string, options: ToastOptions = {}) {\n const { description, action } = options\n toastManager.add({\n type,\n title,\n description,\n // Errors interrupt the screen reader (role=\"alert\"); success and warning\n // are announced politely so they never cut off what the user is hearing.\n priority: type === \"error\" ? \"high\" : \"low\",\n actionProps: action ? { children: action.label, onClick: action.onClick } : undefined,\n })\n}\n\n/**\n * Confirms that something the user did worked (\"Client saved\"). The most\n * common toast — fire it from a mutation's success callback, never on render.\n */\nfunction successToast(title: string, options?: ToastOptions) {\n addToast(\"success\", title, options)\n}\n\n/**\n * The action went through but something deserves attention (\"Saved, but 2\n * contacts were skipped\"). Not for failures — those are errorToast.\n */\nfunction warningToast(title: string, options?: ToastOptions) {\n addToast(\"warning\", title, options)\n}\n\n/**\n * Something the user asked for failed and was NOT applied (\"Couldn't save the\n * client\"). Announced assertively to screen readers. Field-level validation\n * errors belong on the field (the Form* `error` prop), never in a toast.\n */\nfunction errorToast(title: string, options?: ToastOptions) {\n addToast(\"error\", title, options)\n}\n\nexport { Toaster, ToastList, successToast, warningToast, errorToast }\n"],"mappings":";AA4Cc,cAGJ,YAHI;AA1Cd,SAAS,kBAAkB,mBAAmB,iBAAiB,aAAa;AAG5E,SAAS,mBAAmB,4BAA4C;AACxE,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,QAAQ;AAAA,EACZ,SAAS;AAAA,EACT,SAAS;AAAA,EACT,OAAO;AACT;AAEA,SAAS,YAAY,MAA6C;AAChE,SAAO,SAAS,UAAa,QAAQ;AACvC;AAGA,SAAS,YAAY;AACnB,QAAM,EAAE,OAAO,IAAI,gBAAgB;AAEnC,SAAO,OAAO,IAAI,CAAC,SAAS;AAC1B,UAAM,OAAO,YAAY,KAAK,IAAI,IAAI,KAAK,OAAO;AAClD,UAAM,OAAO,OAAO,MAAM,IAAI,IAAI;AAClC,WACE,oBAAC,aAAwB,OAAO,MAC9B,+BAAC,gBACE;AAAA,cACC,oBAAC,UAAK,aAAU,cAAa,WAAW,kBAAkB,EAAE,KAAK,CAAC,GAChE,8BAAC,QAAK,eAAY,QAAO,GAC3B;AAAA,MAEF,qBAAC,SAAI,WAAU,oDACb;AAAA,4BAAC,cAAW;AAAA,QACZ,oBAAC,oBAAiB;AAAA,SACpB;AAAA,MACA,oBAAC,eAAY,QAAQ,oBAAC,UAAO,SAAQ,WAAU,GAAI;AAAA,MACnD,oBAAC,cAAW,QAAQ,oBAAC,cAAW,SAAQ,SAAQ,cAAW,SAAQ,GACjE,8BAAC,SAAM,eAAY,QAAO,GAC5B;AAAA,OACF,KAfc,KAAK,EAgBrB;AAAA,EAEJ,CAAC;AACH;AAQA,SAAS,UAAU;AACjB,SACE,oBAAC,iBAAc,cACb,8BAAC,eACC,8BAAC,iBAAc,WAAW,sBACxB,8BAAC,aAAU,GACb,GACF,GACF;AAEJ;AAEA,SAAS,SAAS,MAAiB,OAAe,UAAwB,CAAC,GAAG;AAC5E,QAAM,EAAE,aAAa,OAAO,IAAI;AAChC,eAAa,IAAI;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA;AAAA;AAAA,IAGA,UAAU,SAAS,UAAU,SAAS;AAAA,IACtC,aAAa,SAAS,EAAE,UAAU,OAAO,OAAO,SAAS,OAAO,QAAQ,IAAI;AAAA,EAC9E,CAAC;AACH;AAMA,SAAS,aAAa,OAAe,SAAwB;AAC3D,WAAS,WAAW,OAAO,OAAO;AACpC;AAMA,SAAS,aAAa,OAAe,SAAwB;AAC3D,WAAS,WAAW,OAAO,OAAO;AACpC;AAOA,SAAS,WAAW,OAAe,SAAwB;AACzD,WAAS,SAAS,OAAO,OAAO;AAClC;","names":[]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single follow-up action rendered as an outline button inside the toast
|
|
3
|
+
* (e.g. "Undo", "View", "Retry"). Clicking it runs `onClick`; the toast stays
|
|
4
|
+
* open unless the handler moves the user elsewhere or the timer runs out.
|
|
5
|
+
*/
|
|
6
|
+
interface ToastActionOptions {
|
|
7
|
+
label: string;
|
|
8
|
+
onClick: () => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Everything a call site may decide about a toast. Placement, duration, icon,
|
|
12
|
+
* color, and stacking are library-owned — the function you call (success /
|
|
13
|
+
* warning / error) is the whole styling menu.
|
|
14
|
+
*/
|
|
15
|
+
interface ToastOptions {
|
|
16
|
+
/** Secondary line under the title, in muted text. Keep it to one sentence. */
|
|
17
|
+
description?: string;
|
|
18
|
+
/** Optional follow-up action. One per toast — a toast is not a dialog. */
|
|
19
|
+
action?: ToastActionOptions;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type { ToastActionOptions, ToastOptions };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=toast.types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as class_variance_authority_types from 'class-variance-authority/types';
|
|
2
|
+
import { VariantProps } from 'class-variance-authority';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Placement is a library decision: bottom-right on desktop, full-width (with a
|
|
6
|
+
* 16px gutter) on narrow screens — identical in every app.
|
|
7
|
+
*/
|
|
8
|
+
declare const toastViewportClasses = "nxui:fixed nxui:inset-x-4 nxui:bottom-4 nxui:z-50 nxui:mx-auto nxui:w-auto nxui:max-w-sm nxui:sm:right-4 nxui:sm:left-auto nxui:sm:mx-0 nxui:sm:w-full";
|
|
9
|
+
/**
|
|
10
|
+
* The type only tints the leading icon; the surface stays the neutral popover
|
|
11
|
+
* so a stack of mixed toasts reads as one family. nx-automations tinted only
|
|
12
|
+
* the error icon — every type gets its status color here (Law 14).
|
|
13
|
+
*
|
|
14
|
+
* The icon box is one control tall, like the title row and the action/close
|
|
15
|
+
* buttons, so the whole first line shares one center even when the
|
|
16
|
+
* description wraps below it.
|
|
17
|
+
*/
|
|
18
|
+
declare const toastIconVariants: (props?: ({
|
|
19
|
+
type?: "error" | "success" | "warning" | null | undefined;
|
|
20
|
+
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
21
|
+
type ToastType = NonNullable<VariantProps<typeof toastIconVariants>["type"]>;
|
|
22
|
+
|
|
23
|
+
export { type ToastType, toastIconVariants, toastViewportClasses };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { cva } from "class-variance-authority";
|
|
2
|
+
const toastViewportClasses = "nxui:fixed nxui:inset-x-4 nxui:bottom-4 nxui:z-50 nxui:mx-auto nxui:w-auto nxui:max-w-sm nxui:sm:right-4 nxui:sm:left-auto nxui:sm:mx-0 nxui:sm:w-full";
|
|
3
|
+
const toastIconVariants = cva(
|
|
4
|
+
"nxui:flex nxui:h-control-md nxui:shrink-0 nxui:items-center nxui:[&_svg]:pointer-events-none nxui:[&_svg:not([class*=size-])]:size-4",
|
|
5
|
+
{
|
|
6
|
+
variants: {
|
|
7
|
+
type: {
|
|
8
|
+
success: "nxui:text-success",
|
|
9
|
+
warning: "nxui:text-warning",
|
|
10
|
+
error: "nxui:text-destructive"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
);
|
|
15
|
+
export {
|
|
16
|
+
toastIconVariants,
|
|
17
|
+
toastViewportClasses
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=toast.variants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/toast/toast.variants.ts"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\"\n\n/**\n * Placement is a library decision: bottom-right on desktop, full-width (with a\n * 16px gutter) on narrow screens — identical in every app.\n */\nexport const toastViewportClasses =\n \"nxui:fixed nxui:inset-x-4 nxui:bottom-4 nxui:z-50 nxui:mx-auto nxui:w-auto nxui:max-w-sm nxui:sm:right-4 nxui:sm:left-auto nxui:sm:mx-0 nxui:sm:w-full\"\n\n/**\n * The type only tints the leading icon; the surface stays the neutral popover\n * so a stack of mixed toasts reads as one family. nx-automations tinted only\n * the error icon — every type gets its status color here (Law 14).\n *\n * The icon box is one control tall, like the title row and the action/close\n * buttons, so the whole first line shares one center even when the\n * description wraps below it.\n */\nexport const toastIconVariants = cva(\n \"nxui:flex nxui:h-control-md nxui:shrink-0 nxui:items-center nxui:[&_svg]:pointer-events-none nxui:[&_svg:not([class*=size-])]:size-4\",\n {\n variants: {\n type: {\n success: \"nxui:text-success\",\n warning: \"nxui:text-warning\",\n error: \"nxui:text-destructive\",\n },\n },\n }\n)\n\nexport type ToastType = NonNullable<VariantProps<typeof toastIconVariants>[\"type\"]>\n"],"mappings":"AAAA,SAAS,WAA8B;AAMhC,MAAM,uBACX;AAWK,MAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
interface UseNumberMaskArgs {
|
|
4
|
+
value: number | null;
|
|
5
|
+
sanitize: (raw: string) => string;
|
|
6
|
+
format: (value: number) => string;
|
|
7
|
+
onValueChange: (value: number | null) => void;
|
|
8
|
+
group?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Live-formatting numeric input mask (ported from nx-automations): groups
|
|
12
|
+
* thousands as the user types and keeps the caret stable when separators
|
|
13
|
+
* shift. The form state stores `number | null` — the grouped string is
|
|
14
|
+
* display only. Blur normalization (padding, clamping) is the caller's job
|
|
15
|
+
* via `reformat`.
|
|
16
|
+
*/
|
|
17
|
+
declare function useNumberMask({ value, sanitize, format, onValueChange, group, }: UseNumberMaskArgs): {
|
|
18
|
+
ref: react.RefObject<HTMLInputElement | null>;
|
|
19
|
+
display: string;
|
|
20
|
+
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
21
|
+
reformat: (v?: number | null) => void;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export { useNumberMask };
|