@spark-ui/components
Version:
Spark (Leboncoin design system) components.
602 lines (581 loc) • 16.4 kB
JavaScript
import {
Icon
} from "./chunk-AESXFMCC.mjs";
import {
Slot
} from "./chunk-4F5DOL57.mjs";
// src/input/InputClearButton.tsx
import { DeleteOutline } from "@spark-ui/icons/DeleteOutline";
import { cx } from "class-variance-authority";
// src/input/InputGroupContext.ts
import { createContext, useContext } from "react";
var InputGroupContext = createContext(null);
var useInputGroup = () => {
const context = useContext(InputGroupContext);
return context || { isStandalone: true };
};
// src/input/InputClearButton.tsx
import { jsx } from "react/jsx-runtime";
var Root = ({ className, tabIndex = -1, onClick, ref, ...others }) => {
const { onClear, hasTrailingIcon } = useInputGroup();
const handleClick = (event) => {
if (onClick) {
onClick(event);
}
if (onClear) {
onClear();
}
};
return /* @__PURE__ */ jsx(
"button",
{
ref,
className: cx(
className,
"pointer-events-auto absolute top-1/2 -translate-y-1/2",
"inline-flex h-full items-center justify-center outline-hidden",
"text-neutral hover:text-neutral-hovered",
hasTrailingIcon ? "right-3xl px-sz-12" : "pl-md pr-lg right-0"
),
tabIndex,
onClick: handleClick,
type: "button",
...others,
children: /* @__PURE__ */ jsx(Icon, { size: "sm", children: /* @__PURE__ */ jsx(DeleteOutline, {}) })
}
);
};
var InputClearButton = Object.assign(Root, {
id: "ClearButton"
});
Root.displayName = "InputGroup.ClearButton";
// src/input/InputGroup.tsx
import { useFormFieldControl } from "@spark-ui/components/form-field";
import { useCombinedState } from "@spark-ui/hooks/use-combined-state";
import { useMergeRefs } from "@spark-ui/hooks/use-merge-refs";
import {
Children,
cloneElement,
isValidElement,
useCallback,
useEffect,
useMemo,
useRef
} from "react";
// src/input/InputGroup.styles.ts
import { cva } from "class-variance-authority";
var inputGroupStyles = cva(["relative inline-flex w-full"], {
variants: {
/**
* When `true`, prevents the user from interacting.
*/
disabled: {
true: [
"cursor-not-allowed",
"relative",
"after:absolute",
"after:top-0",
"after:h-full",
"after:w-full",
"after:border-sm after:border-outline",
"after:rounded-lg"
],
false: "after:hidden"
},
/**
* Sets the component as interactive or not.
*/
readOnly: {
true: [
"relative",
"after:absolute",
"after:top-0",
"after:h-full",
"after:w-full",
"after:border-sm after:border-outline",
"after:rounded-lg"
],
false: "after:hidden"
}
}
});
// src/input/InputGroup.tsx
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
var InputGroup = ({
className,
children: childrenProp,
state: stateProp,
disabled: disabledProp,
readOnly: readOnlyProp,
onClear,
ref: forwardedRef,
...others
}) => {
const getElementId = (element) => {
return element ? element.type.id : "";
};
const findElement = (...values) => {
return children.find((child) => values.includes(getElementId(child) || ""));
};
const children = Children.toArray(childrenProp).filter(isValidElement);
const input = findElement("Input");
const props = input?.props || {};
const inputRef = useRef(null);
const onClearRef = useRef(onClear);
const ref = useMergeRefs(input?.ref, inputRef);
const [value, onChange] = useCombinedState(
props.value,
props.defaultValue,
props.onValueChange
);
const field = useFormFieldControl();
const state = field.state ?? stateProp;
const disabled = field.disabled || !!disabledProp;
const readOnly = field.readOnly || !!readOnlyProp;
const leadingAddon = findElement("LeadingAddon");
const leadingIcon = findElement("LeadingIcon");
const clearButton = findElement("ClearButton");
const trailingIcon = findElement("TrailingIcon");
const trailingAddon = findElement("TrailingAddon");
const hasLeadingAddon = !!leadingAddon;
const hasTrailingAddon = !!trailingAddon;
const hasLeadingIcon = !!leadingIcon;
const hasTrailingIcon = !!trailingIcon;
const hasClearButton = !!value && !!clearButton && !disabled && !readOnly;
const handleChange = (event) => {
if (props.onChange) {
props.onChange(event);
}
onChange(event.target.value);
};
const handleClear = useCallback(() => {
if (onClearRef.current) {
onClearRef.current();
}
onChange("");
inputRef.current.focus();
}, [onChange]);
const current = useMemo(() => {
return {
state,
disabled,
readOnly,
hasLeadingIcon,
hasTrailingIcon,
hasLeadingAddon,
hasTrailingAddon,
hasClearButton,
onClear: handleClear
};
}, [
state,
disabled,
readOnly,
hasLeadingIcon,
hasTrailingIcon,
hasLeadingAddon,
hasTrailingAddon,
hasClearButton,
handleClear
]);
useEffect(() => {
onClearRef.current = onClear;
}, [onClear]);
return /* @__PURE__ */ jsx2(InputGroupContext.Provider, { value: current, children: /* @__PURE__ */ jsxs(
"div",
{
ref: forwardedRef,
className: inputGroupStyles({ disabled, readOnly, className }),
...others,
children: [
hasLeadingAddon && leadingAddon,
/* @__PURE__ */ jsxs("div", { className: "relative inline-flex w-full", children: [
input && cloneElement(input, {
ref,
defaultValue: void 0,
value: value ?? "",
onChange: handleChange
}),
leadingIcon,
hasClearButton && clearButton,
trailingIcon
] }),
hasTrailingAddon && trailingAddon
]
}
) });
};
InputGroup.displayName = "InputGroup";
// src/input/InputLeadingAddon.tsx
import { cx as cx2 } from "class-variance-authority";
// src/input/InputAddon.tsx
import { Children as Children2 } from "react";
// src/input/InputAddon.styles.ts
import { cva as cva2 } from "class-variance-authority";
var inputAddonStyles = cva2(
[
"overflow-hidden",
"border-sm",
"shrink-0",
"h-full",
"focus-visible:relative focus-visible:z-raised"
],
{
variants: {
/**
* Change the component to the HTML tag or custom component of the only child.
*/
asChild: { false: ["flex", "items-center", "px-lg"] },
intent: {
neutral: "border-outline",
error: "border-error",
alert: "border-alert",
success: "border-success"
},
/**
* Disable the input addon, preventing user interaction and adding opacity.
*/
disabled: {
true: ["pointer-events-none border-outline!"]
},
/**
* Changes input addon styles based on the read only status from the input.
*/
readOnly: {
true: ["pointer-events-none"]
},
/**
* Main style of the input addon.
*/
design: {
text: "",
solid: "",
inline: ""
}
},
compoundVariants: [
{
disabled: false,
readOnly: false,
design: "text",
class: ["bg-surface", "text-on-surface"]
},
{
disabled: true,
design: "text",
class: ["text-on-surface/dim-3"]
},
{
disabled: true,
design: ["solid", "inline"],
class: ["opacity-dim-3"]
}
],
defaultVariants: {
intent: "neutral"
}
}
);
// src/input/InputAddon.tsx
import { jsx as jsx3 } from "react/jsx-runtime";
var InputAddon = ({
asChild: asChildProp,
className,
children,
ref,
...others
}) => {
const { state, disabled, readOnly } = useInputGroup();
const isRawText = typeof children === "string";
const asChild = !!(isRawText ? false : asChildProp);
const child = isRawText ? children : Children2.only(children);
const Component = asChild && !isRawText ? Slot : "div";
const getDesign = () => {
if (isRawText) return "text";
return asChild ? "solid" : "inline";
};
const design = getDesign();
return /* @__PURE__ */ jsx3(
Component,
{
ref,
className: inputAddonStyles({
className,
intent: state,
disabled,
readOnly,
asChild,
design
}),
...disabled && { tabIndex: -1 },
...others,
children: child
}
);
};
InputAddon.displayName = "InputGroup.Addon";
// src/input/InputLeadingAddon.tsx
import { jsx as jsx4 } from "react/jsx-runtime";
var Root2 = ({ className, ref, ...others }) => {
const { disabled, readOnly } = useInputGroup();
const isInactive = disabled || readOnly;
return /* @__PURE__ */ jsx4("div", { className: cx2("rounded-l-lg", isInactive ? "bg-on-surface/dim-5" : null), children: /* @__PURE__ */ jsx4(
InputAddon,
{
ref,
className: cx2(className, "rounded-r-0! mr-[-1px] rounded-l-lg"),
...others
}
) });
};
var InputLeadingAddon = Object.assign(Root2, {
id: "LeadingAddon"
});
Root2.displayName = "InputGroup.LeadingAddon";
// src/input/InputLeadingIcon.tsx
import { cx as cx4 } from "class-variance-authority";
// src/input/InputIcon.tsx
import { cx as cx3 } from "class-variance-authority";
import { jsx as jsx5 } from "react/jsx-runtime";
var InputIcon = ({ className, intent, children, ...others }) => {
const { disabled, readOnly } = useInputGroup();
const isInactive = disabled || readOnly;
return /* @__PURE__ */ jsx5(
Icon,
{
intent,
className: cx3(
className,
"pointer-events-none absolute top-[calc(var(--spacing-sz-44)/2)] -translate-y-1/2",
intent ? void 0 : "text-neutral peer-focus:text-outline-high",
isInactive ? "opacity-dim-3" : void 0
),
...others,
children
}
);
};
InputIcon.displayName = "InputGroup.Icon";
// src/input/InputLeadingIcon.tsx
import { jsx as jsx6 } from "react/jsx-runtime";
var InputLeadingIcon = ({ className, ...others }) => /* @__PURE__ */ jsx6(InputIcon, { className: cx4(className, "left-lg text-body-1"), ...others });
InputLeadingIcon.id = "LeadingIcon";
InputLeadingIcon.displayName = "InputGroup.LeadingIcon";
// src/input/InputTrailingAddon.tsx
import { cx as cx5 } from "class-variance-authority";
import { jsx as jsx7 } from "react/jsx-runtime";
var Root3 = ({ className, ref, ...others }) => {
const { disabled, readOnly } = useInputGroup();
const isInactive = disabled || readOnly;
return /* @__PURE__ */ jsx7("div", { className: cx5("rounded-r-lg", isInactive ? "bg-on-surface/dim-5" : null), children: /* @__PURE__ */ jsx7(
InputAddon,
{
ref,
className: cx5(className, "rounded-l-0! ml-[-1px] rounded-r-lg"),
...others
}
) });
};
var InputTrailingAddon = Object.assign(Root3, {
id: "TrailingAddon"
});
Root3.displayName = "InputGroup.TrailingAddon";
// src/input/InputTrailingIcon.tsx
import { cx as cx6 } from "class-variance-authority";
import { jsx as jsx8 } from "react/jsx-runtime";
var InputTrailingIcon = ({ className, ...others }) => /* @__PURE__ */ jsx8(InputIcon, { className: cx6(className, "right-lg text-body-1"), ...others });
InputTrailingIcon.id = "TrailingIcon";
InputTrailingIcon.displayName = "InputGroup.TrailingIcon";
// src/input/Input.tsx
import { useFormFieldControl as useFormFieldControl2 } from "@spark-ui/components/form-field";
// src/input/Input.styles.ts
import { cva as cva3 } from "class-variance-authority";
var inputStyles = cva3(
[
"relative",
"border-sm",
"peer",
"w-full",
"appearance-none outline-hidden",
"bg-surface",
"text-ellipsis text-body-1 text-on-surface",
"caret-neutral",
"autofill:shadow-surface autofill:shadow-[inset_0_0_0px_1000px]",
"disabled:cursor-not-allowed disabled:border-outline disabled:bg-on-surface/dim-5 disabled:text-on-surface/dim-3",
"read-only:cursor-default read-only:pointer-events-none read-only:bg-on-surface/dim-5",
"focus:ring-1 focus:ring-inset"
],
{
variants: {
/**
* Change the component to the HTML tag or custom component of the only child.
*/
asChild: {
true: ["min-h-sz-44"],
false: ["h-sz-44"]
},
/**
* Color scheme of the button.
*/
intent: {
neutral: [
"border-outline",
"hover:border-outline-high",
"focus:ring-outline-high focus:border-outline-high"
],
success: ["border-success", "focus:ring-success"],
alert: ["border-alert", "focus:ring-alert"],
error: ["border-error", "focus:ring-error"]
},
/**
* Sets if there is an addon before the input text.
*/
hasLeadingAddon: {
true: ["rounded-l-0"],
false: ["rounded-l-lg"]
},
/**
* Sets if there is an addon after the input text.
*/
hasTrailingAddon: {
true: ["rounded-r-0"],
false: ["rounded-r-lg"]
},
/**
* Sets if there is an icon before the input text.
*/
hasLeadingIcon: {
true: ["pl-3xl"],
false: ["pl-lg"]
},
/**
* Sets if there is an icon after the input text.
*/
hasTrailingIcon: { true: "" },
/**
* Sets if there is a button to clear the input text.
*/
hasClearButton: { true: "" }
},
compoundVariants: [
{
hasTrailingIcon: false,
hasClearButton: false,
class: "pr-lg"
},
{
hasTrailingIcon: true,
hasClearButton: false,
class: "pr-3xl"
},
{
hasTrailingIcon: false,
hasClearButton: true,
class: "pr-3xl"
},
{
hasTrailingIcon: true,
hasClearButton: true,
class: "pr-[calc(var(--spacing-3xl)*2)]"
}
],
defaultVariants: {
intent: "neutral"
}
}
);
// src/input/Input.tsx
import { jsx as jsx9 } from "react/jsx-runtime";
var Root4 = ({
className,
asChild = false,
onValueChange,
onChange,
onKeyDown,
disabled: disabledProp,
readOnly: readOnlyProp,
ref,
...others
}) => {
const field = useFormFieldControl2();
const group = useInputGroup();
const { id, name, isInvalid, isRequired, description } = field;
const {
hasLeadingAddon,
hasTrailingAddon,
hasLeadingIcon,
hasTrailingIcon,
hasClearButton,
onClear
} = group;
const Component = asChild ? Slot : "input";
const state = field.state || group.state;
const disabled = field.disabled || group.disabled || disabledProp;
const readOnly = field.readOnly || group.readOnly || readOnlyProp;
const handleChange = (event) => {
if (onChange) {
onChange(event);
}
if (onValueChange) {
onValueChange(event.target.value);
}
};
const handleKeyDown = (event) => {
if (onKeyDown) {
onKeyDown(event);
}
if (hasClearButton && onClear && event.key === "Escape") {
onClear();
}
};
return /* @__PURE__ */ jsx9(
Component,
{
ref,
id,
name,
className: inputStyles({
asChild,
className,
intent: state,
hasLeadingAddon: !!hasLeadingAddon,
hasTrailingAddon: !!hasTrailingAddon,
hasLeadingIcon: !!hasLeadingIcon,
hasTrailingIcon: !!hasTrailingIcon,
hasClearButton: !!hasClearButton
}),
disabled,
readOnly,
required: isRequired,
"aria-describedby": description,
"aria-invalid": isInvalid,
onChange: handleChange,
onKeyDown: handleKeyDown,
...others
}
);
};
var Input = Object.assign(Root4, {
id: "Input"
});
Root4.displayName = "Input";
// src/input/index.ts
var InputGroup2 = Object.assign(InputGroup, {
LeadingAddon: InputLeadingAddon,
TrailingAddon: InputTrailingAddon,
LeadingIcon: InputLeadingIcon,
TrailingIcon: InputTrailingIcon,
ClearButton: InputClearButton
});
InputGroup2.displayName = "InputGroup";
InputLeadingAddon.displayName = "InputGroup.LeadingAddon";
InputTrailingAddon.displayName = "InputGroup.TrailingAddon";
InputLeadingIcon.displayName = "InputGroup.LeadingIcon";
InputTrailingIcon.displayName = "InputGroup.TrailingIcon";
InputClearButton.displayName = "InputGroup.ClearButton";
export {
useInputGroup,
Input,
InputGroup2 as InputGroup
};
//# sourceMappingURL=chunk-TUFNIIZE.mjs.map