UNPKG

@spark-ui/components

Version:

Spark (Leboncoin design system) components.

602 lines (581 loc) 16.4 kB
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