@jbpark/ui-kit 10.0.1 → 10.1.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/README.ko.md +40 -25
- package/README.md +40 -25
- package/dist/Breadcrumb.d.mts +39 -0
- package/dist/Breadcrumb.d.mts.map +1 -0
- package/dist/Breadcrumb.mjs +103 -0
- package/dist/Breadcrumb.mjs.map +1 -0
- package/dist/Pagination.d.mts +53 -0
- package/dist/Pagination.d.mts.map +1 -0
- package/dist/Pagination.mjs +208 -0
- package/dist/Pagination.mjs.map +1 -0
- package/dist/Table.d.mts +48 -0
- package/dist/Table.d.mts.map +1 -0
- package/dist/Table.mjs +119 -0
- package/dist/Table.mjs.map +1 -0
- package/dist/components/atoms/checkbox/checkbox.mjs +2 -1
- package/dist/components/atoms/checkbox/checkbox.mjs.map +1 -1
- package/dist/components/atoms/checkbox/group.mjs +2 -1
- package/dist/components/atoms/checkbox/group.mjs.map +1 -1
- package/dist/components/atoms/color-picker.mjs +1 -1
- package/dist/components/atoms/date-picker.mjs +1 -1
- package/dist/components/atoms/float-button/back-top.mjs +1 -1
- package/dist/components/atoms/index.d.mts +2 -1
- package/dist/components/atoms/input/search.mjs +3 -1
- package/dist/components/atoms/input/search.mjs.map +1 -1
- package/dist/components/atoms/popover.mjs +1 -1
- package/dist/components/atoms/radio/group.mjs +1 -1
- package/dist/components/atoms/radio/radio.mjs +2 -1
- package/dist/components/atoms/radio/radio.mjs.map +1 -1
- package/dist/components/atoms/select-multiple.d.mts +29 -0
- package/dist/components/atoms/select-multiple.d.mts.map +1 -0
- package/dist/components/atoms/select-multiple.mjs +97 -0
- package/dist/components/atoms/select-multiple.mjs.map +1 -0
- package/dist/components/atoms/select-searchable-options.d.mts +21 -0
- package/dist/components/atoms/select-searchable-options.d.mts.map +1 -0
- package/dist/components/atoms/select-searchable-options.mjs +62 -0
- package/dist/components/atoms/select-searchable-options.mjs.map +1 -0
- package/dist/components/atoms/select-searchable.d.mts +22 -0
- package/dist/components/atoms/select-searchable.d.mts.map +1 -0
- package/dist/components/atoms/select-searchable.mjs +65 -0
- package/dist/components/atoms/select-searchable.mjs.map +1 -0
- package/dist/components/atoms/select.d.mts +8 -2
- package/dist/components/atoms/select.d.mts.map +1 -1
- package/dist/components/atoms/select.mjs +11 -1
- package/dist/components/atoms/select.mjs.map +1 -1
- package/dist/components/atoms/slider/slider.mjs +1 -1
- package/dist/components/atoms/switch.mjs +1 -1
- package/dist/components/atoms/tooltip.d.mts +21 -0
- package/dist/components/atoms/tooltip.d.mts.map +1 -0
- package/dist/components/atoms/tooltip.mjs +78 -0
- package/dist/components/atoms/tooltip.mjs.map +1 -0
- package/dist/components/atoms/typography/paragraph.mjs +1 -0
- package/dist/components/atoms/typography/paragraph.mjs.map +1 -1
- package/dist/components/atoms/typography/title.mjs +1 -0
- package/dist/components/atoms/typography/title.mjs.map +1 -1
- package/dist/components/molecules/dropdown.mjs +1 -1
- package/dist/components/molecules/index.d.mts +13 -10
- package/dist/components/molecules/marquees/marquees.mjs +1 -1
- package/dist/components/molecules/menu/menu.mjs +2 -1
- package/dist/components/molecules/menu/menu.mjs.map +1 -1
- package/dist/components/molecules/tabs.d.mts +37 -0
- package/dist/components/molecules/tabs.d.mts.map +1 -0
- package/dist/components/molecules/tabs.mjs +51 -0
- package/dist/components/molecules/tabs.mjs.map +1 -0
- package/dist/components/molecules/upload.mjs +2 -1
- package/dist/components/molecules/upload.mjs.map +1 -1
- package/dist/components/organisms/drawer.d.mts.map +1 -1
- package/dist/components/organisms/drawer.mjs +7 -2
- package/dist/components/organisms/drawer.mjs.map +1 -1
- package/dist/components/organisms/index.d.mts +6 -5
- package/dist/components/organisms/list/list.mjs +1 -1
- package/dist/components/organisms/modal.d.mts.map +1 -1
- package/dist/components/organisms/modal.mjs +7 -4
- package/dist/components/organisms/modal.mjs.map +1 -1
- package/dist/components/organisms/toast.d.mts.map +1 -1
- package/dist/components/organisms/toast.mjs +2 -1
- package/dist/components/organisms/toast.mjs.map +1 -1
- package/dist/components/templates/empty.mjs +1 -0
- package/dist/components/templates/empty.mjs.map +1 -1
- package/dist/components/templates/layout/content.mjs +1 -0
- package/dist/components/templates/layout/content.mjs.map +1 -1
- package/dist/components/templates/layout/footer.mjs +1 -0
- package/dist/components/templates/layout/footer.mjs.map +1 -1
- package/dist/components/templates/layout/header.mjs +1 -0
- package/dist/components/templates/layout/header.mjs.map +1 -1
- package/dist/components/templates/layout/layout.mjs +1 -0
- package/dist/components/templates/layout/layout.mjs.map +1 -1
- package/dist/components/templates/layout/sider.mjs +3 -1
- package/dist/components/templates/layout/sider.mjs.map +1 -1
- package/dist/components/templates/page-header.mjs +1 -0
- package/dist/components/templates/page-header.mjs.map +1 -1
- package/dist/components/templates/result.mjs +1 -0
- package/dist/components/templates/result.mjs.map +1 -1
- package/dist/core/badge.d.mts +1 -1
- package/dist/core/button.d.mts +1 -1
- package/dist/core/combobox.d.mts +7 -0
- package/dist/core/combobox.d.mts.map +1 -0
- package/dist/core/combobox.mjs +99 -0
- package/dist/core/combobox.mjs.map +1 -0
- package/dist/core/dialog.mjs.map +1 -1
- package/dist/core/drawer.mjs.map +1 -1
- package/dist/core/field.mjs +2 -4
- package/dist/core/field.mjs.map +1 -1
- package/dist/core/index.d.mts +4 -1
- package/dist/core/select.mjs +27 -22
- package/dist/core/select.mjs.map +1 -1
- package/dist/core/tabs.d.mts +9 -0
- package/dist/core/tabs.d.mts.map +1 -0
- package/dist/core/tabs.mjs +59 -0
- package/dist/core/tabs.mjs.map +1 -0
- package/dist/core/tooltip.d.mts +2 -0
- package/dist/core/tooltip.mjs +41 -0
- package/dist/core/tooltip.mjs.map +1 -0
- package/dist/index.d.mts +31 -26
- package/dist/index.mjs +7 -2
- package/dist/lib/z-layers.mjs +21 -0
- package/dist/lib/z-layers.mjs.map +1 -1
- package/dist/providers/config/dark-mode.mjs +2 -10
- package/dist/providers/config/dark-mode.mjs.map +1 -1
- package/dist/style.css +195 -16
- package/package.json +17 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.mjs","names":["input","Search","SearchOutlined"],"sources":["../../../../src/components/atoms/input/search.tsx"],"sourcesContent":["'use client';\n\nimport { useRef, useState } from 'react';\n\nimport { useMergedRef } from '@jbpark/use-hooks';\nimport { CircleX, Search as SearchOutlined } from 'lucide-react';\n\nimport { DEFAULT_LOCALE, useConfig } from '@repo/ui/providers';\nimport { cn } from '@repo/ui/utils';\n\nimport { input } from '../../../core';\nimport Button from '../button';\n\nconst { Input: Core } = input;\n\nexport interface Props extends React.ComponentPropsWithRef<'input'> {\n allowClear?: boolean;\n onChange?: (\n e: React.ChangeEvent<HTMLInputElement> | { target: { value: string } },\n ) => void;\n onSearch?: (value: string) => void;\n}\n\nconst Search = ({\n ref,\n className,\n defaultValue,\n value,\n disabled,\n allowClear = true,\n onChange,\n onKeyDown: _onKeyDown,\n onSearch: _onSearch,\n ...props\n}: Props) => {\n const { locale } = useConfig();\n const inputRef = useRef<HTMLInputElement>(null);\n\n const mergedRef = useMergedRef(inputRef, ref);\n\n const [uncontrolledHasValue, setUncontrolledHasValue] =\n useState(!!defaultValue);\n const hasValue = value !== undefined ? !!value : uncontrolledHasValue;\n\n const onClear = () => {\n if (!inputRef.current) {\n return;\n }\n\n if (value === undefined) {\n inputRef.current.value = '';\n setUncontrolledHasValue(false);\n }\n\n onChange?.({ target: { value: '' } });\n };\n\n const onSearch = () => {\n if (disabled) {\n return;\n }\n if (inputRef.current) {\n _onSearch?.(inputRef.current.value);\n }\n };\n\n return (\n <div\n className={cn(\n 'flex items-center',\n 'rounded-md',\n className,\n //\n )}\n >\n <div\n className={cn(\n 'relative grow',\n 'inline-flex items-center',\n //\n )}\n >\n <Core\n {...props}\n ref={mergedRef}\n inputMode=\"search\"\n type=\"search\"\n enterKeyHint=\"search\"\n disabled={disabled}\n className={cn(\n 'rounded-r-none',\n '[&::-webkit-search-cancel-button]:hidden',\n '[&::-webkit-search-decoration]:hidden',\n //\n )}\n value={value}\n defaultValue={defaultValue}\n onChange={e => {\n if (value === undefined) {\n setUncontrolledHasValue(!!e.target.value);\n }\n onChange?.(e);\n }}\n onKeyDown={e => {\n _onKeyDown?.(e);\n if (e.key === 'Enter') {\n onSearch();\n }\n }}\n />\n <button\n type=\"button\"\n aria-label={locale.clear ?? DEFAULT_LOCALE.clear}\n disabled={disabled}\n className={cn(\n 'absolute right-2',\n 'shrink-0 cursor-pointer',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n (!allowClear || !hasValue) && 'hidden',\n )}\n onClick={onClear}\n >\n <CircleX className=\"size-4\" />\n </button>\n </div>\n <Button\n icon={<SearchOutlined />}\n disabled={disabled}\n className={cn(\n 'rounded-l-none',\n 'size-9',\n //\n )}\n onClick={onSearch}\n />\n </div>\n );\n};\n\nSearch.displayName = 'Search';\n\nexport default Search;\n"],"mappings":";;;;;;;;;;AAaA,MAAM,EAAE,OAAO,SAASA;AAUxB,MAAMC,YAAU,EACd,KACA,WACA,cACA,OACA,UACA,aAAa,MACb,UACA,WAAW,YACX,UAAU,WACV,GAAG,YACQ;CACX,MAAM,EAAE,WAAW,UAAU;CAC7B,MAAM,WAAW,OAAyB,IAAI;CAE9C,MAAM,YAAY,aAAa,UAAU,GAAG;CAE5C,MAAM,CAAC,sBAAsB,2BAC3B,SAAS,CAAC,CAAC,YAAY;CACzB,MAAM,WAAW,UAAU,KAAA,IAAY,CAAC,CAAC,QAAQ;CAEjD,MAAM,gBAAgB;EACpB,IAAI,CAAC,SAAS,SACZ;EAGF,IAAI,UAAU,KAAA,GAAW;GACvB,SAAS,QAAQ,QAAQ;GACzB,wBAAwB,KAAK;EAC/B;EAEA,WAAW,EAAE,QAAQ,EAAE,OAAO,GAAG,EAAE,CAAC;CACtC;CAEA,MAAM,iBAAiB;EACrB,IAAI,UACF;EAEF,IAAI,SAAS,SACX,YAAY,SAAS,QAAQ,KAAK;CAEtC;CAEA,OACE,qBAAC,OAAD;EACE,WAAW,GACT,qBACA,cACA,SAEF;
|
|
1
|
+
{"version":3,"file":"search.mjs","names":["input","Search","SearchOutlined"],"sources":["../../../../src/components/atoms/input/search.tsx"],"sourcesContent":["'use client';\n\nimport { useRef, useState } from 'react';\n\nimport { useMergedRef } from '@jbpark/use-hooks';\nimport { CircleX, Search as SearchOutlined } from 'lucide-react';\n\nimport { DEFAULT_LOCALE, useConfig } from '@repo/ui/providers';\nimport { cn } from '@repo/ui/utils';\n\nimport { input } from '../../../core';\nimport Button from '../button';\n\nconst { Input: Core } = input;\n\nexport interface Props extends React.ComponentPropsWithRef<'input'> {\n allowClear?: boolean;\n onChange?: (\n e: React.ChangeEvent<HTMLInputElement> | { target: { value: string } },\n ) => void;\n onSearch?: (value: string) => void;\n}\n\nconst Search = ({\n ref,\n className,\n defaultValue,\n value,\n disabled,\n allowClear = true,\n onChange,\n onKeyDown: _onKeyDown,\n onSearch: _onSearch,\n ...props\n}: Props) => {\n const { locale } = useConfig();\n const inputRef = useRef<HTMLInputElement>(null);\n\n const mergedRef = useMergedRef(inputRef, ref);\n\n const [uncontrolledHasValue, setUncontrolledHasValue] =\n useState(!!defaultValue);\n const hasValue = value !== undefined ? !!value : uncontrolledHasValue;\n\n const onClear = () => {\n if (!inputRef.current) {\n return;\n }\n\n if (value === undefined) {\n inputRef.current.value = '';\n setUncontrolledHasValue(false);\n }\n\n onChange?.({ target: { value: '' } });\n };\n\n const onSearch = () => {\n if (disabled) {\n return;\n }\n if (inputRef.current) {\n _onSearch?.(inputRef.current.value);\n }\n };\n\n return (\n <div\n data-slot=\"input-search\"\n className={cn(\n 'flex items-center',\n 'rounded-md',\n className,\n //\n )}\n >\n <div\n className={cn(\n 'relative grow',\n 'inline-flex items-center',\n //\n )}\n >\n <Core\n {...props}\n ref={mergedRef}\n inputMode=\"search\"\n type=\"search\"\n enterKeyHint=\"search\"\n disabled={disabled}\n className={cn(\n 'rounded-r-none',\n '[&::-webkit-search-cancel-button]:hidden',\n '[&::-webkit-search-decoration]:hidden',\n //\n )}\n value={value}\n defaultValue={defaultValue}\n onChange={e => {\n if (value === undefined) {\n setUncontrolledHasValue(!!e.target.value);\n }\n onChange?.(e);\n }}\n onKeyDown={e => {\n _onKeyDown?.(e);\n if (e.key === 'Enter') {\n onSearch();\n }\n }}\n />\n <button\n type=\"button\"\n data-slot=\"input-search-clear\"\n aria-label={locale.clear ?? DEFAULT_LOCALE.clear}\n disabled={disabled}\n className={cn(\n 'absolute right-2',\n 'shrink-0 cursor-pointer',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n (!allowClear || !hasValue) && 'hidden',\n )}\n onClick={onClear}\n >\n <CircleX className=\"size-4\" />\n </button>\n </div>\n <Button\n icon={<SearchOutlined />}\n disabled={disabled}\n className={cn(\n 'rounded-l-none',\n 'size-9',\n //\n )}\n onClick={onSearch}\n />\n </div>\n );\n};\n\nSearch.displayName = 'Search';\n\nexport default Search;\n"],"mappings":";;;;;;;;;;AAaA,MAAM,EAAE,OAAO,SAASA;AAUxB,MAAMC,YAAU,EACd,KACA,WACA,cACA,OACA,UACA,aAAa,MACb,UACA,WAAW,YACX,UAAU,WACV,GAAG,YACQ;CACX,MAAM,EAAE,WAAW,UAAU;CAC7B,MAAM,WAAW,OAAyB,IAAI;CAE9C,MAAM,YAAY,aAAa,UAAU,GAAG;CAE5C,MAAM,CAAC,sBAAsB,2BAC3B,SAAS,CAAC,CAAC,YAAY;CACzB,MAAM,WAAW,UAAU,KAAA,IAAY,CAAC,CAAC,QAAQ;CAEjD,MAAM,gBAAgB;EACpB,IAAI,CAAC,SAAS,SACZ;EAGF,IAAI,UAAU,KAAA,GAAW;GACvB,SAAS,QAAQ,QAAQ;GACzB,wBAAwB,KAAK;EAC/B;EAEA,WAAW,EAAE,QAAQ,EAAE,OAAO,GAAG,EAAE,CAAC;CACtC;CAEA,MAAM,iBAAiB;EACrB,IAAI,UACF;EAEF,IAAI,SAAS,SACX,YAAY,SAAS,QAAQ,KAAK;CAEtC;CAEA,OACE,qBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,qBACA,cACA,SAEF;EAPF,UAAA,CASE,qBAAC,OAAD;GACE,WAAW,GACT,iBACA,0BAEF;GALF,UAAA,CAOE,oBAAC,MAAD;IACE,GAAI;IACJ,KAAK;IACL,WAAU;IACV,MAAK;IACL,cAAa;IACH;IACV,WAAW,GACT,kBACA,4CACA,uCAEF;IACO;IACO;IACd,WAAU,MAAK;KACb,IAAI,UAAU,KAAA,GACZ,wBAAwB,CAAC,CAAC,EAAE,OAAO,KAAK;KAE1C,WAAW,CAAC;IACd;IACA,YAAW,MAAK;KACd,aAAa,CAAC;KACd,IAAI,EAAE,QAAQ,SACZ,SAAS;IAEb;GACD,CAAA,GACD,oBAAC,UAAD;IACE,MAAK;IACL,aAAU;IACV,cAAY,OAAO,SAAS,eAAe;IACjC;IACV,WAAW,GACT,oBACA,2BACA,oDACC,CAAC,cAAc,CAAC,aAAa,QAChC;IACA,SAAS;IAET,UAAA,oBAAC,SAAD,EAAS,WAAU,SAAU,CAAA;GACvB,CAAA,CACL;EACL,CAAA,GAAA,oBAAC,QAAD;GACE,MAAM,oBAACC,QAAD,CAAiB,CAAA;GACb;GACV,WAAW,GACT,kBACA,QAEF;GACA,SAAS;EACV,CAAA,CACE;;AAET;AAEA,SAAO,cAAc"}
|
|
@@ -3,8 +3,8 @@ import { cn, renderConditional } from "../../utils.mjs";
|
|
|
3
3
|
import { popover_exports } from "../../core/popover.mjs";
|
|
4
4
|
import Typography from "../../Typography.mjs";
|
|
5
5
|
import { Children } from "react";
|
|
6
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
6
|
import { useControllableState } from "@jbpark/use-hooks";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
//#region src/components/atoms/popover.tsx
|
|
9
9
|
const { Popover: CorePopover, PopoverContent, PopoverTrigger, PopoverArrow, PopoverTitle } = popover_exports;
|
|
10
10
|
const placementMap = {
|
|
@@ -6,9 +6,9 @@ import { normalizeOptions, warnOnDuplicateOptionValues } from "../option-group.m
|
|
|
6
6
|
import { RadioGroupContext } from "./context.mjs";
|
|
7
7
|
import Radio$1 from "./radio.mjs";
|
|
8
8
|
import { useId } from "react";
|
|
9
|
+
import { useControllableState } from "@jbpark/use-hooks";
|
|
9
10
|
import { jsx } from "react/jsx-runtime";
|
|
10
11
|
import { Radio } from "@base-ui/react/radio";
|
|
11
|
-
import { useControllableState } from "@jbpark/use-hooks";
|
|
12
12
|
//#region src/components/atoms/radio/group.tsx
|
|
13
13
|
const { RadioGroup: Core } = radio_group_exports;
|
|
14
14
|
const RadioGroup = ({ orientation = "horizontal", placement = "left", className, classNames = {}, options: _options = [], name, optionType = "default", buttonStyle = "solid", size, disabled, defaultValue, value: _value, onChange: _onChange = () => {}, ...props }) => {
|
|
@@ -5,8 +5,8 @@ import { radio_group_exports } from "../../../core/radio-group.mjs";
|
|
|
5
5
|
import { RadioGroupContext } from "./context.mjs";
|
|
6
6
|
import { useContext, useId, useRef } from "react";
|
|
7
7
|
import { Circle, CircleCheck } from "lucide-react";
|
|
8
|
-
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
9
8
|
import { useControllableState, useKeyPress } from "@jbpark/use-hooks";
|
|
9
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
10
10
|
//#region src/components/atoms/radio/radio.tsx
|
|
11
11
|
const { RadioGroup: Core, RadioGroupItem: Item } = radio_group_exports;
|
|
12
12
|
const { Field, FieldLabel } = field_exports;
|
|
@@ -84,6 +84,7 @@ const Radio = ({ placement = "left", value: _value = "", children, className, ic
|
|
|
84
84
|
children: renderField
|
|
85
85
|
});
|
|
86
86
|
return /* @__PURE__ */ jsx("div", {
|
|
87
|
+
"data-slot": "radio-field",
|
|
87
88
|
className: cn("flex items-center", icons && placement === "right" && "flex-row-reverse", cursorClassName, className),
|
|
88
89
|
...props,
|
|
89
90
|
children: renderContent
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radio.mjs","names":["radio","field"],"sources":["../../../../src/components/atoms/radio/radio.tsx"],"sourcesContent":["'use client';\n\nimport { useContext, useId, useRef } from 'react';\n\nimport { useControllableState, useKeyPress } from '@jbpark/use-hooks';\nimport { Circle, CircleCheck } from 'lucide-react';\n\nimport { cn } from '@repo/ui/utils';\n\nimport { field, radio } from '../../../core';\nimport { RadioGroupContext } from './context';\nimport { type OptionValue } from './group';\n\nconst { RadioGroup: Core, RadioGroupItem: Item } = radio;\nconst { Field, FieldLabel } = field;\n\n// Spreads onto the wrapper `<div>`, so the host props are div-based. (Base UI's\n// `RadioGroup` — `Core` here — types `style`/`className` as state callbacks,\n// which a native `<div>` can't take; the meaningful control props below are\n// re-declared explicitly anyway.)\nexport interface Props extends Omit<\n React.ComponentPropsWithoutRef<'div'>,\n 'onChange'\n> {\n placement?: 'left' | 'right';\n defaultChecked?: boolean;\n checked?: boolean;\n value?: OptionValue;\n /** Native form field name — participates in `FormData` when set. */\n name?: string;\n disabled?: boolean;\n /**\n * Id shared by the underlying control and its label. Generated internally\n * when omitted. `RadioGroup` passes a deterministic id so that the group's\n * single Base UI root can address each option.\n */\n id?: string;\n icons?: Partial<{ checked: React.ReactNode; unchecked: React.ReactNode }>;\n onChange?: (checked: boolean) => void;\n}\n\nconst Radio = ({\n placement = 'left',\n value: _value = '',\n children,\n className,\n icons,\n disabled,\n defaultChecked,\n checked: _checked,\n id: _id,\n name,\n onChange: _onChange = () => {},\n ...props\n}: Props) => {\n const [checked, setChecked] = useControllableState<boolean>({\n value: _checked,\n defaultValue: defaultChecked ?? false,\n onChange: _onChange,\n });\n\n const generatedId = useId();\n const id = _id ?? generatedId;\n // Base UI identifies/submits an item by its `value`, which is separate\n // from `id` (used only for the DOM id/label pairing) — stringified since\n // the group value is a string, while `OptionValue` also allows\n // number/boolean.\n const itemValue = String(_value);\n const grouped = useContext(RadioGroupContext);\n\n const cursorClassName = disabled ? 'cursor-not-allowed' : 'cursor-pointer';\n\n const onChange = (next: boolean) => {\n if (disabled) {\n return;\n }\n\n setChecked(next);\n };\n\n const iconTriggerRef = useRef<HTMLSpanElement>(null);\n\n useKeyPress(\n ['Enter', 'space'],\n event => {\n event.preventDefault();\n onChange(true);\n },\n { target: iconTriggerRef, enabled: !!icons && !disabled },\n );\n\n // When rendered inside a `RadioGroup`, the group owns the single shared\n // `Core` (Base UI RadioGroup root) so that arrow keys move focus across\n // every option. A standalone `Radio` still has to provide its own root.\n const renderField = (\n <Field\n orientation=\"horizontal\"\n className={cn('flex gap-2', placement === 'right' && 'flex-row-reverse')}\n data-disabled={disabled}\n >\n <Item value={itemValue} id={id} disabled={disabled} />\n <FieldLabel htmlFor={id} className={cursorClassName}>\n {children}\n </FieldLabel>\n </Field>\n );\n\n const renderContent = icons ? (\n <>\n <input\n id={id}\n hidden\n type=\"radio\"\n name={name}\n value={itemValue}\n checked={checked}\n disabled={disabled}\n onChange={e => {\n onChange(e.target.checked);\n }}\n />\n <span\n ref={iconTriggerRef}\n role=\"radio\"\n aria-checked={checked}\n aria-disabled={disabled}\n tabIndex={disabled ? -1 : 0}\n className={cn(cursorClassName, disabled && 'opacity-50')}\n onClick={() => {\n onChange(true);\n }}\n >\n {checked\n ? (icons.checked ?? <CircleCheck />)\n : (icons.unchecked ?? <Circle />)}\n </span>\n {children && (\n <label\n className={cn(cursorClassName, disabled && 'opacity-50')}\n htmlFor={id}\n >\n {children}\n </label>\n )}\n </>\n ) : grouped ? (\n renderField\n ) : (\n <Core\n name={name}\n value={checked ? itemValue : ''}\n onValueChange={() => onChange(true)}\n >\n {renderField}\n </Core>\n );\n\n return (\n <div\n className={cn(\n 'flex items-center',\n icons && placement === 'right' && 'flex-row-reverse',\n cursorClassName,\n className,\n //\n )}\n {...props}\n >\n {renderContent}\n </div>\n );\n};\n\nexport default Radio;\n"],"mappings":";;;;;;;;;;AAaA,MAAM,EAAE,YAAY,MAAM,gBAAgB,SAASA;AACnD,MAAM,EAAE,OAAO,eAAeC;AA2B9B,MAAM,SAAS,EACb,YAAY,QACZ,OAAO,SAAS,IAChB,UACA,WACA,OACA,UACA,gBACA,SAAS,UACT,IAAI,KACJ,MACA,UAAU,kBAAkB,CAAC,GAC7B,GAAG,YACQ;CACX,MAAM,CAAC,SAAS,cAAc,qBAA8B;EAC1D,OAAO;EACP,cAAc,kBAAkB;EAChC,UAAU;CACZ,CAAC;CAED,MAAM,cAAc,MAAM;CAC1B,MAAM,KAAK,OAAO;CAKlB,MAAM,YAAY,OAAO,MAAM;CAC/B,MAAM,UAAU,WAAW,iBAAiB;CAE5C,MAAM,kBAAkB,WAAW,uBAAuB;CAE1D,MAAM,YAAY,SAAkB;EAClC,IAAI,UACF;EAGF,WAAW,IAAI;CACjB;CAEA,MAAM,iBAAiB,OAAwB,IAAI;CAEnD,YACE,CAAC,SAAS,OAAO,IACjB,UAAS;EACP,MAAM,eAAe;EACrB,SAAS,IAAI;CACf,GACA;EAAE,QAAQ;EAAgB,SAAS,CAAC,CAAC,SAAS,CAAC;CAAS,CAC1D;CAKA,MAAM,cACJ,qBAAC,OAAD;EACE,aAAY;EACZ,WAAW,GAAG,cAAc,cAAc,WAAW,kBAAkB;EACvE,iBAAe;EAHjB,UAAA,CAKE,oBAAC,MAAD;GAAM,OAAO;GAAe;GAAc;EAAW,CAAA,GACrD,oBAAC,YAAD;GAAY,SAAS;GAAI,WAAW;GACjC;EACS,CAAA,CACP;;CAGT,MAAM,gBAAgB,QACpB,qBAAA,YAAA,EAAA,UAAA;EACE,oBAAC,SAAD;GACM;GACJ,QAAA;GACA,MAAK;GACC;GACN,OAAO;GACE;GACC;GACV,WAAU,MAAK;IACb,SAAS,EAAE,OAAO,OAAO;GAC3B;EACD,CAAA;EACD,oBAAC,QAAD;GACE,KAAK;GACL,MAAK;GACL,gBAAc;GACd,iBAAe;GACf,UAAU,WAAW,KAAK;GAC1B,WAAW,GAAG,iBAAiB,YAAY,YAAY;GACvD,eAAe;IACb,SAAS,IAAI;GACf;GAEC,UAAA,UACI,MAAM,WAAW,oBAAC,aAAD,CAAc,CAAA,IAC/B,MAAM,aAAa,oBAAC,QAAD,CAAS,CAAA;EAC7B,CAAA;EACL,YACC,oBAAC,SAAD;GACE,WAAW,GAAG,iBAAiB,YAAY,YAAY;GACvD,SAAS;GAER;EACI,CAAA;CAET,EAAA,CAAA,IACA,UACF,cAEA,oBAAC,MAAD;EACQ;EACN,OAAO,UAAU,YAAY;EAC7B,qBAAqB,SAAS,IAAI;EAEjC,UAAA;CACG,CAAA;CAGR,OACE,oBAAC,OAAD;EACE,WAAW,GACT,qBACA,SAAS,cAAc,WAAW,oBAClC,iBACA,SAEF;EACA,GAAI;EAEH,UAAA;CACE,CAAA;AAET"}
|
|
1
|
+
{"version":3,"file":"radio.mjs","names":["radio","field"],"sources":["../../../../src/components/atoms/radio/radio.tsx"],"sourcesContent":["'use client';\n\nimport { useContext, useId, useRef } from 'react';\n\nimport { useControllableState, useKeyPress } from '@jbpark/use-hooks';\nimport { Circle, CircleCheck } from 'lucide-react';\n\nimport { cn } from '@repo/ui/utils';\n\nimport { field, radio } from '../../../core';\nimport { RadioGroupContext } from './context';\nimport { type OptionValue } from './group';\n\nconst { RadioGroup: Core, RadioGroupItem: Item } = radio;\nconst { Field, FieldLabel } = field;\n\n// Spreads onto the wrapper `<div>`, so the host props are div-based. (Base UI's\n// `RadioGroup` — `Core` here — types `style`/`className` as state callbacks,\n// which a native `<div>` can't take; the meaningful control props below are\n// re-declared explicitly anyway.)\nexport interface Props extends Omit<\n React.ComponentPropsWithoutRef<'div'>,\n 'onChange'\n> {\n placement?: 'left' | 'right';\n defaultChecked?: boolean;\n checked?: boolean;\n value?: OptionValue;\n /** Native form field name — participates in `FormData` when set. */\n name?: string;\n disabled?: boolean;\n /**\n * Id shared by the underlying control and its label. Generated internally\n * when omitted. `RadioGroup` passes a deterministic id so that the group's\n * single Base UI root can address each option.\n */\n id?: string;\n icons?: Partial<{ checked: React.ReactNode; unchecked: React.ReactNode }>;\n onChange?: (checked: boolean) => void;\n}\n\nconst Radio = ({\n placement = 'left',\n value: _value = '',\n children,\n className,\n icons,\n disabled,\n defaultChecked,\n checked: _checked,\n id: _id,\n name,\n onChange: _onChange = () => {},\n ...props\n}: Props) => {\n const [checked, setChecked] = useControllableState<boolean>({\n value: _checked,\n defaultValue: defaultChecked ?? false,\n onChange: _onChange,\n });\n\n const generatedId = useId();\n const id = _id ?? generatedId;\n // Base UI identifies/submits an item by its `value`, which is separate\n // from `id` (used only for the DOM id/label pairing) — stringified since\n // the group value is a string, while `OptionValue` also allows\n // number/boolean.\n const itemValue = String(_value);\n const grouped = useContext(RadioGroupContext);\n\n const cursorClassName = disabled ? 'cursor-not-allowed' : 'cursor-pointer';\n\n const onChange = (next: boolean) => {\n if (disabled) {\n return;\n }\n\n setChecked(next);\n };\n\n const iconTriggerRef = useRef<HTMLSpanElement>(null);\n\n useKeyPress(\n ['Enter', 'space'],\n event => {\n event.preventDefault();\n onChange(true);\n },\n { target: iconTriggerRef, enabled: !!icons && !disabled },\n );\n\n // When rendered inside a `RadioGroup`, the group owns the single shared\n // `Core` (Base UI RadioGroup root) so that arrow keys move focus across\n // every option. A standalone `Radio` still has to provide its own root.\n const renderField = (\n <Field\n orientation=\"horizontal\"\n className={cn('flex gap-2', placement === 'right' && 'flex-row-reverse')}\n data-disabled={disabled}\n >\n <Item value={itemValue} id={id} disabled={disabled} />\n <FieldLabel htmlFor={id} className={cursorClassName}>\n {children}\n </FieldLabel>\n </Field>\n );\n\n const renderContent = icons ? (\n <>\n <input\n id={id}\n hidden\n type=\"radio\"\n name={name}\n value={itemValue}\n checked={checked}\n disabled={disabled}\n onChange={e => {\n onChange(e.target.checked);\n }}\n />\n <span\n ref={iconTriggerRef}\n role=\"radio\"\n aria-checked={checked}\n aria-disabled={disabled}\n tabIndex={disabled ? -1 : 0}\n className={cn(cursorClassName, disabled && 'opacity-50')}\n onClick={() => {\n onChange(true);\n }}\n >\n {checked\n ? (icons.checked ?? <CircleCheck />)\n : (icons.unchecked ?? <Circle />)}\n </span>\n {children && (\n <label\n className={cn(cursorClassName, disabled && 'opacity-50')}\n htmlFor={id}\n >\n {children}\n </label>\n )}\n </>\n ) : grouped ? (\n renderField\n ) : (\n <Core\n name={name}\n value={checked ? itemValue : ''}\n onValueChange={() => onChange(true)}\n >\n {renderField}\n </Core>\n );\n\n return (\n <div\n data-slot=\"radio-field\"\n className={cn(\n 'flex items-center',\n icons && placement === 'right' && 'flex-row-reverse',\n cursorClassName,\n className,\n //\n )}\n {...props}\n >\n {renderContent}\n </div>\n );\n};\n\nexport default Radio;\n"],"mappings":";;;;;;;;;;AAaA,MAAM,EAAE,YAAY,MAAM,gBAAgB,SAASA;AACnD,MAAM,EAAE,OAAO,eAAeC;AA2B9B,MAAM,SAAS,EACb,YAAY,QACZ,OAAO,SAAS,IAChB,UACA,WACA,OACA,UACA,gBACA,SAAS,UACT,IAAI,KACJ,MACA,UAAU,kBAAkB,CAAC,GAC7B,GAAG,YACQ;CACX,MAAM,CAAC,SAAS,cAAc,qBAA8B;EAC1D,OAAO;EACP,cAAc,kBAAkB;EAChC,UAAU;CACZ,CAAC;CAED,MAAM,cAAc,MAAM;CAC1B,MAAM,KAAK,OAAO;CAKlB,MAAM,YAAY,OAAO,MAAM;CAC/B,MAAM,UAAU,WAAW,iBAAiB;CAE5C,MAAM,kBAAkB,WAAW,uBAAuB;CAE1D,MAAM,YAAY,SAAkB;EAClC,IAAI,UACF;EAGF,WAAW,IAAI;CACjB;CAEA,MAAM,iBAAiB,OAAwB,IAAI;CAEnD,YACE,CAAC,SAAS,OAAO,IACjB,UAAS;EACP,MAAM,eAAe;EACrB,SAAS,IAAI;CACf,GACA;EAAE,QAAQ;EAAgB,SAAS,CAAC,CAAC,SAAS,CAAC;CAAS,CAC1D;CAKA,MAAM,cACJ,qBAAC,OAAD;EACE,aAAY;EACZ,WAAW,GAAG,cAAc,cAAc,WAAW,kBAAkB;EACvE,iBAAe;EAHjB,UAAA,CAKE,oBAAC,MAAD;GAAM,OAAO;GAAe;GAAc;EAAW,CAAA,GACrD,oBAAC,YAAD;GAAY,SAAS;GAAI,WAAW;GACjC;EACS,CAAA,CACP;;CAGT,MAAM,gBAAgB,QACpB,qBAAA,YAAA,EAAA,UAAA;EACE,oBAAC,SAAD;GACM;GACJ,QAAA;GACA,MAAK;GACC;GACN,OAAO;GACE;GACC;GACV,WAAU,MAAK;IACb,SAAS,EAAE,OAAO,OAAO;GAC3B;EACD,CAAA;EACD,oBAAC,QAAD;GACE,KAAK;GACL,MAAK;GACL,gBAAc;GACd,iBAAe;GACf,UAAU,WAAW,KAAK;GAC1B,WAAW,GAAG,iBAAiB,YAAY,YAAY;GACvD,eAAe;IACb,SAAS,IAAI;GACf;GAEC,UAAA,UACI,MAAM,WAAW,oBAAC,aAAD,CAAc,CAAA,IAC/B,MAAM,aAAa,oBAAC,QAAD,CAAS,CAAA;EAC7B,CAAA;EACL,YACC,oBAAC,SAAD;GACE,WAAW,GAAG,iBAAiB,YAAY,YAAY;GACvD,SAAS;GAER;EACI,CAAA;CAET,EAAA,CAAA,IACA,UACF,cAEA,oBAAC,MAAD;EACQ;EACN,OAAO,UAAU,YAAY;EAC7B,qBAAqB,SAAS,IAAI;EAEjC,UAAA;CACG,CAAA;CAGR,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,qBACA,SAAS,cAAc,WAAW,oBAClC,iBACA,SAEF;EACA,GAAI;EAEH,UAAA;CACE,CAAA;AAET"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { RootProps } from "../../core/combobox.mjs";
|
|
2
|
+
import "../../core/index.mjs";
|
|
3
|
+
import { ClassNames, Option, OptionGroup } from "./select-searchable-options.mjs";
|
|
4
|
+
import { ReactNode } from "react";
|
|
5
|
+
//#region src/components/atoms/select-multiple.d.ts
|
|
6
|
+
interface Props extends Omit<RootProps<string, Option, true>, 'children' | 'items' | 'filteredItems' | 'onValueChange' | 'itemToStringLabel' | 'itemToStringValue' | 'isItemEqualToValue'> {
|
|
7
|
+
searchable: true;
|
|
8
|
+
multiple: true;
|
|
9
|
+
/** Accessible name for the multi-select input. */
|
|
10
|
+
label: string;
|
|
11
|
+
options: (Option | OptionGroup)[];
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
searchPlaceholder?: string;
|
|
14
|
+
emptyText?: ReactNode;
|
|
15
|
+
allowClear?: boolean;
|
|
16
|
+
/** Instructions announced when a selected chip receives focus. */
|
|
17
|
+
chipDescription?: string;
|
|
18
|
+
/** Instructions announced on the input when chips are selected. */
|
|
19
|
+
inputDescription?: (selectedCount: number) => string;
|
|
20
|
+
className?: string;
|
|
21
|
+
classNames?: ClassNames & {
|
|
22
|
+
chips?: string;
|
|
23
|
+
chip?: string;
|
|
24
|
+
};
|
|
25
|
+
onChange?: (values: string[]) => void;
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { Props };
|
|
29
|
+
//# sourceMappingURL=select-multiple.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select-multiple.d.mts","names":[],"sources":["../../../src/components/atoms/select-multiple.tsx"],"mappings":";;;;;UAgCiB,cAAc,KAC7B,kBAA2B;EAS3B;EACA;;EAEA;EACA,UAAU,SAAS;EACnB;EACA;EACA,YAAY;EACZ;;EAEA;;EAEA,oBAAoB;EACpB;EACA,aAAa;IACX;IACA;;EAEF,YAAY"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../../utils.mjs";
|
|
3
|
+
import { DEFAULT_LOCALE, useConfig } from "../../providers/config/context.mjs";
|
|
4
|
+
import { combobox_exports } from "../../core/combobox.mjs";
|
|
5
|
+
import { selectTriggerStyles } from "../../core/select.mjs";
|
|
6
|
+
import Button from "../../Button.mjs";
|
|
7
|
+
import Input from "./input/index.mjs";
|
|
8
|
+
import SearchableOptions, { useSelectItems } from "./select-searchable-options.mjs";
|
|
9
|
+
import { useMemo } from "react";
|
|
10
|
+
import { ChevronDownIcon, XIcon } from "lucide-react";
|
|
11
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
|
+
//#region src/components/atoms/select-multiple.tsx
|
|
13
|
+
const { Combobox: Core, ComboboxChip, ComboboxChipRemove, ComboboxChips, ComboboxClear, ComboboxContent, ComboboxInput, ComboboxInputGroup, ComboboxTrigger, ComboboxValue } = combobox_exports;
|
|
14
|
+
const MultipleSelect = ({ label, options, placeholder = "Select options", searchPlaceholder = "Search...", emptyText = "No results found.", allowClear = true, chipDescription = "Press Backspace or Delete to remove", inputDescription = (count) => `${count} selected. From the start of the input, press Left Arrow to focus selected options`, className, classNames, onChange, onOpenChange, searchable: _searchable, multiple, ...props }) => {
|
|
15
|
+
const { locale } = useConfig();
|
|
16
|
+
const items = useSelectItems(options);
|
|
17
|
+
const labels = useMemo(() => {
|
|
18
|
+
const result = /* @__PURE__ */ new Map();
|
|
19
|
+
options.forEach((option) => {
|
|
20
|
+
if ("options" in option) option.options.forEach((item) => result.set(item.value, item.label));
|
|
21
|
+
else result.set(option.value, option.label);
|
|
22
|
+
});
|
|
23
|
+
return result;
|
|
24
|
+
}, [options]);
|
|
25
|
+
return /* @__PURE__ */ jsxs(Core, {
|
|
26
|
+
items,
|
|
27
|
+
multiple,
|
|
28
|
+
onValueChange: (values) => onChange?.(values),
|
|
29
|
+
onOpenChange: (open, details) => {
|
|
30
|
+
if (!open && details.reason === "item-press") details.cancel();
|
|
31
|
+
onOpenChange?.(open, details);
|
|
32
|
+
},
|
|
33
|
+
...props,
|
|
34
|
+
children: [/* @__PURE__ */ jsxs(ComboboxInputGroup, {
|
|
35
|
+
"data-slot": "select-multiple-input-group",
|
|
36
|
+
className: cn(selectTriggerStyles, "h-auto min-h-9 w-full max-w-64 flex-wrap gap-1 py-1", className),
|
|
37
|
+
children: [
|
|
38
|
+
/* @__PURE__ */ jsx(ComboboxValue, { children: (values) => /* @__PURE__ */ jsxs(ComboboxChips, {
|
|
39
|
+
"aria-label": values.length > 0 ? `${label} selected options` : void 0,
|
|
40
|
+
className: cn("flex min-w-0 flex-1 flex-wrap items-center gap-1", classNames?.chips),
|
|
41
|
+
children: [values.map((value) => {
|
|
42
|
+
const name = labels.get(value) ?? value;
|
|
43
|
+
return /* @__PURE__ */ jsxs(ComboboxChip, {
|
|
44
|
+
"data-slot": "select-chip",
|
|
45
|
+
"aria-label": name,
|
|
46
|
+
"aria-description": chipDescription,
|
|
47
|
+
className: cn(`bg-accent text-accent-foreground flex min-w-0
|
|
48
|
+
items-center gap-1 rounded-sm ps-2 pe-1 text-xs
|
|
49
|
+
outline-none`, "focus-visible:ring-ring/50 focus-visible:ring-[3px]", classNames?.chip),
|
|
50
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
51
|
+
className: "max-w-40 truncate",
|
|
52
|
+
children: name
|
|
53
|
+
}), /* @__PURE__ */ jsx(ComboboxChipRemove, {
|
|
54
|
+
"data-slot": "select-chip-remove",
|
|
55
|
+
"aria-label": `${locale.clear ?? DEFAULT_LOCALE.clear} ${name}`,
|
|
56
|
+
className: "hover:bg-background/70 flex size-5 shrink-0\n items-center justify-center rounded-sm",
|
|
57
|
+
children: /* @__PURE__ */ jsx(XIcon, { className: "size-3" })
|
|
58
|
+
})]
|
|
59
|
+
}, value);
|
|
60
|
+
}), /* @__PURE__ */ jsx(ComboboxInput, { render: /* @__PURE__ */ jsx(Input, {
|
|
61
|
+
"aria-label": label,
|
|
62
|
+
"aria-description": values.length > 0 ? inputDescription(values.length) : void 0,
|
|
63
|
+
placeholder: values.length > 0 ? searchPlaceholder : placeholder,
|
|
64
|
+
className: cn(`h-6 min-w-16 flex-1 border-0 bg-transparent px-0 py-0
|
|
65
|
+
shadow-none focus-visible:border-0 focus-visible:ring-0`, classNames?.input)
|
|
66
|
+
}) })]
|
|
67
|
+
}) }),
|
|
68
|
+
allowClear && /* @__PURE__ */ jsx(ComboboxClear, {
|
|
69
|
+
"data-slot": "select-multiple-clear",
|
|
70
|
+
render: /* @__PURE__ */ jsx(Button, {
|
|
71
|
+
variant: "text",
|
|
72
|
+
size: "icon-sm",
|
|
73
|
+
icon: /* @__PURE__ */ jsx(XIcon, {}),
|
|
74
|
+
"aria-label": locale.clear ?? DEFAULT_LOCALE.clear
|
|
75
|
+
})
|
|
76
|
+
}),
|
|
77
|
+
/* @__PURE__ */ jsx(ComboboxTrigger, { render: /* @__PURE__ */ jsx(Button, {
|
|
78
|
+
variant: "text",
|
|
79
|
+
size: "icon-sm",
|
|
80
|
+
icon: /* @__PURE__ */ jsx(ChevronDownIcon, {}),
|
|
81
|
+
"aria-label": locale.expand ?? DEFAULT_LOCALE.expand
|
|
82
|
+
}) })
|
|
83
|
+
]
|
|
84
|
+
}), /* @__PURE__ */ jsx(ComboboxContent, {
|
|
85
|
+
"aria-label": label,
|
|
86
|
+
className: classNames?.content,
|
|
87
|
+
children: /* @__PURE__ */ jsx(SearchableOptions, {
|
|
88
|
+
emptyText,
|
|
89
|
+
classNames
|
|
90
|
+
})
|
|
91
|
+
})]
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
//#endregion
|
|
95
|
+
export { MultipleSelect as default };
|
|
96
|
+
|
|
97
|
+
//# sourceMappingURL=select-multiple.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select-multiple.mjs","names":["combobox","select.selectTriggerStyles"],"sources":["../../../src/components/atoms/select-multiple.tsx"],"sourcesContent":["'use client';\n\nimport { type ReactNode, useMemo } from 'react';\n\nimport { ChevronDownIcon, XIcon } from 'lucide-react';\n\nimport { DEFAULT_LOCALE, useConfig } from '@repo/ui/providers';\nimport { cn } from '@repo/ui/utils';\n\nimport { combobox, select } from '../../core';\nimport Button from './button';\nimport Input from './input';\nimport SearchableOptions, {\n type ClassNames,\n type Option,\n type OptionGroup,\n useSelectItems,\n} from './select-searchable-options';\n\nconst {\n Combobox: Core,\n ComboboxChip,\n ComboboxChipRemove,\n ComboboxChips,\n ComboboxClear,\n ComboboxContent,\n ComboboxInput,\n ComboboxInputGroup,\n ComboboxTrigger,\n ComboboxValue,\n} = combobox;\n\nexport interface Props extends Omit<\n combobox.RootProps<string, Option, true>,\n | 'children'\n | 'items'\n | 'filteredItems'\n | 'onValueChange'\n | 'itemToStringLabel'\n | 'itemToStringValue'\n | 'isItemEqualToValue'\n> {\n searchable: true;\n multiple: true;\n /** Accessible name for the multi-select input. */\n label: string;\n options: (Option | OptionGroup)[];\n placeholder?: string;\n searchPlaceholder?: string;\n emptyText?: ReactNode;\n allowClear?: boolean;\n /** Instructions announced when a selected chip receives focus. */\n chipDescription?: string;\n /** Instructions announced on the input when chips are selected. */\n inputDescription?: (selectedCount: number) => string;\n className?: string;\n classNames?: ClassNames & {\n chips?: string;\n chip?: string;\n };\n onChange?: (values: string[]) => void;\n}\n\nconst MultipleSelect = ({\n label,\n options,\n placeholder = 'Select options',\n searchPlaceholder = 'Search...',\n emptyText = 'No results found.',\n allowClear = true,\n chipDescription = 'Press Backspace or Delete to remove',\n inputDescription = count =>\n `${count} selected. From the start of the input, press Left Arrow to focus selected options`,\n className,\n classNames,\n onChange,\n onOpenChange,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- Do not forward the public mode switch to Base UI.\n searchable: _searchable,\n multiple,\n ...props\n}: Props) => {\n const { locale } = useConfig();\n const items = useSelectItems(options);\n const labels = useMemo(() => {\n const result = new Map<string, string>();\n\n options.forEach(option => {\n if ('options' in option) {\n option.options.forEach(item => result.set(item.value, item.label));\n } else {\n result.set(option.value, option.label);\n }\n });\n\n return result;\n }, [options]);\n\n return (\n <Core\n items={items}\n multiple={multiple}\n onValueChange={values => onChange?.(values)}\n onOpenChange={(open, details) => {\n if (!open && details.reason === 'item-press') {\n details.cancel();\n }\n\n onOpenChange?.(open, details);\n }}\n {...props}\n >\n <ComboboxInputGroup\n data-slot=\"select-multiple-input-group\"\n className={cn(\n select.selectTriggerStyles,\n 'h-auto min-h-9 w-full max-w-64 flex-wrap gap-1 py-1',\n className,\n )}\n >\n <ComboboxValue>\n {(values: string[]) => (\n <ComboboxChips\n aria-label={\n values.length > 0 ? `${label} selected options` : undefined\n }\n className={cn(\n 'flex min-w-0 flex-1 flex-wrap items-center gap-1',\n classNames?.chips,\n )}\n >\n {values.map(value => {\n const name = labels.get(value) ?? value;\n\n return (\n <ComboboxChip\n key={value}\n data-slot=\"select-chip\"\n aria-label={name}\n aria-description={chipDescription}\n className={cn(\n `bg-accent text-accent-foreground flex min-w-0\n items-center gap-1 rounded-sm ps-2 pe-1 text-xs\n outline-none`,\n 'focus-visible:ring-ring/50 focus-visible:ring-[3px]',\n classNames?.chip,\n )}\n >\n <span className=\"max-w-40 truncate\">{name}</span>\n <ComboboxChipRemove\n data-slot=\"select-chip-remove\"\n aria-label={`${locale.clear ?? DEFAULT_LOCALE.clear} ${name}`}\n className=\"hover:bg-background/70 flex size-5 shrink-0\n items-center justify-center rounded-sm\"\n >\n <XIcon className=\"size-3\" />\n </ComboboxChipRemove>\n </ComboboxChip>\n );\n })}\n <ComboboxInput\n render={\n <Input\n aria-label={label}\n aria-description={\n values.length > 0\n ? inputDescription(values.length)\n : undefined\n }\n placeholder={\n values.length > 0 ? searchPlaceholder : placeholder\n }\n className={cn(\n `h-6 min-w-16 flex-1 border-0 bg-transparent px-0 py-0\n shadow-none focus-visible:border-0 focus-visible:ring-0`,\n classNames?.input,\n )}\n />\n }\n />\n </ComboboxChips>\n )}\n </ComboboxValue>\n {allowClear && (\n <ComboboxClear\n data-slot=\"select-multiple-clear\"\n render={\n <Button\n variant=\"text\"\n size=\"icon-sm\"\n icon={<XIcon />}\n aria-label={locale.clear ?? DEFAULT_LOCALE.clear}\n />\n }\n />\n )}\n <ComboboxTrigger\n render={\n <Button\n variant=\"text\"\n size=\"icon-sm\"\n icon={<ChevronDownIcon />}\n aria-label={locale.expand ?? DEFAULT_LOCALE.expand}\n />\n }\n />\n </ComboboxInputGroup>\n <ComboboxContent aria-label={label} className={classNames?.content}>\n <SearchableOptions emptyText={emptyText} classNames={classNames} />\n </ComboboxContent>\n </Core>\n );\n};\n\nexport default MultipleSelect;\n"],"mappings":";;;;;;;;;;;;AAmBA,MAAM,EACJ,UAAU,MACV,cACA,oBACA,eACA,eACA,iBACA,eACA,oBACA,iBACA,kBACEA;AAiCJ,MAAM,kBAAkB,EACtB,OACA,SACA,cAAc,kBACd,oBAAoB,aACpB,YAAY,qBACZ,aAAa,MACb,kBAAkB,uCAClB,oBAAmB,UACjB,GAAG,MAAM,qFACX,WACA,YACA,UACA,cAEA,YAAY,aACZ,UACA,GAAG,YACQ;CACX,MAAM,EAAE,WAAW,UAAU;CAC7B,MAAM,QAAQ,eAAe,OAAO;CACpC,MAAM,SAAS,cAAc;EAC3B,MAAM,yBAAS,IAAI,IAAoB;EAEvC,QAAQ,SAAQ,WAAU;GACxB,IAAI,aAAa,QACf,OAAO,QAAQ,SAAQ,SAAQ,OAAO,IAAI,KAAK,OAAO,KAAK,KAAK,CAAC;QAEjE,OAAO,IAAI,OAAO,OAAO,OAAO,KAAK;EAEzC,CAAC;EAED,OAAO;CACT,GAAG,CAAC,OAAO,CAAC;CAEZ,OACE,qBAAC,MAAD;EACS;EACG;EACV,gBAAe,WAAU,WAAW,MAAM;EAC1C,eAAe,MAAM,YAAY;GAC/B,IAAI,CAAC,QAAQ,QAAQ,WAAW,cAC9B,QAAQ,OAAO;GAGjB,eAAe,MAAM,OAAO;EAC9B;EACA,GAAI;EAXN,UAAA,CAaE,qBAAC,oBAAD;GACE,aAAU;GACV,WAAW,GACTC,qBACA,uDACA,SACF;GANF,UAAA;IAQE,oBAAC,eAAD,EAAA,WACI,WACA,qBAAC,eAAD;KACE,cACE,OAAO,SAAS,IAAI,GAAG,MAAM,qBAAqB,KAAA;KAEpD,WAAW,GACT,oDACA,YAAY,KACd;KAPF,UAAA,CASG,OAAO,KAAI,UAAS;MACnB,MAAM,OAAO,OAAO,IAAI,KAAK,KAAK;MAElC,OACE,qBAAC,cAAD;OAEE,aAAU;OACV,cAAY;OACZ,oBAAkB;OAClB,WAAW,GACT;;qCAGA,uDACA,YAAY,IACd;OAXF,UAAA,CAaE,oBAAC,QAAD;QAAM,WAAU;QAAqB,UAAA;OAAW,CAAA,GAChD,oBAAC,oBAAD;QACE,aAAU;QACV,cAAY,GAAG,OAAO,SAAS,eAAe,MAAM,GAAG;QACvD,WAAU;QAGV,UAAA,oBAAC,OAAD,EAAO,WAAU,SAAU,CAAA;OACT,CAAA,CACR;MArBP,GAAA,KAqBO;KAElB,CAAC,GACD,oBAAC,eAAD,EACE,QACE,oBAAC,OAAD;MACE,cAAY;MACZ,oBACE,OAAO,SAAS,IACZ,iBAAiB,OAAO,MAAM,IAC9B,KAAA;MAEN,aACE,OAAO,SAAS,IAAI,oBAAoB;MAE1C,WAAW,GACT;gFAEA,YAAY,KACd;KACD,CAAA,EAEJ,CAAA,CACY;IAEJ,CAAA,EAAA,CAAA;IACd,cACC,oBAAC,eAAD;KACE,aAAU;KACV,QACE,oBAAC,QAAD;MACE,SAAQ;MACR,MAAK;MACL,MAAM,oBAAC,OAAD,CAAQ,CAAA;MACd,cAAY,OAAO,SAAS,eAAe;KAC5C,CAAA;IAEJ,CAAA;IAEH,oBAAC,iBAAD,EACE,QACE,oBAAC,QAAD;KACE,SAAQ;KACR,MAAK;KACL,MAAM,oBAAC,iBAAD,CAAkB,CAAA;KACxB,cAAY,OAAO,UAAU,eAAe;IAC7C,CAAA,EAEJ,CAAA;GACiB;EACpB,CAAA,GAAA,oBAAC,iBAAD;GAAiB,cAAY;GAAO,WAAW,YAAY;GACzD,UAAA,oBAAC,mBAAD;IAA8B;IAAuB;GAAa,CAAA;EACnD,CAAA,CACb;;AAEV"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
//#region src/components/atoms/select-searchable-options.d.ts
|
|
3
|
+
interface Option {
|
|
4
|
+
value: string;
|
|
5
|
+
label: string;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
}
|
|
8
|
+
interface OptionGroup {
|
|
9
|
+
label: string;
|
|
10
|
+
options: Option[];
|
|
11
|
+
}
|
|
12
|
+
interface ClassNames {
|
|
13
|
+
input?: string;
|
|
14
|
+
content?: string;
|
|
15
|
+
list?: string;
|
|
16
|
+
item?: string;
|
|
17
|
+
empty?: string;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { ClassNames, Option, OptionGroup };
|
|
21
|
+
//# sourceMappingURL=select-searchable-options.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select-searchable-options.d.mts","names":[],"sources":["../../../src/components/atoms/select-searchable-options.tsx"],"mappings":";;UAgBiB;EACf;EACA;EACA;;UAGe;EACf;EACA,SAAS;;UAGM;EACf;EACA;EACA;EACA;EACA"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { combobox_exports } from "../../core/combobox.mjs";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
//#region src/components/atoms/select-searchable-options.tsx
|
|
6
|
+
const { ComboboxCollection, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxItem, ComboboxList, createItems } = combobox_exports;
|
|
7
|
+
const isGroup = (option) => {
|
|
8
|
+
return "options" in option && Array.isArray(option.options);
|
|
9
|
+
};
|
|
10
|
+
const useSelectItems = (options) => {
|
|
11
|
+
const groups = useMemo(() => {
|
|
12
|
+
const result = [];
|
|
13
|
+
options.forEach((option, index) => {
|
|
14
|
+
if (isGroup(option)) result.push({
|
|
15
|
+
key: index,
|
|
16
|
+
value: option.label,
|
|
17
|
+
items: option.options,
|
|
18
|
+
grouped: true
|
|
19
|
+
});
|
|
20
|
+
else {
|
|
21
|
+
const previous = result.at(-1);
|
|
22
|
+
if (previous && !previous.grouped) previous.items.push(option);
|
|
23
|
+
else result.push({
|
|
24
|
+
key: index,
|
|
25
|
+
value: "",
|
|
26
|
+
items: [option],
|
|
27
|
+
grouped: false
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return result;
|
|
32
|
+
}, [options]);
|
|
33
|
+
return useMemo(() => createItems(groups, {
|
|
34
|
+
getValue: (option) => option.value,
|
|
35
|
+
getLabel: (option) => option.label
|
|
36
|
+
}), [groups]);
|
|
37
|
+
};
|
|
38
|
+
const SearchableOptions = ({ emptyText, classNames }) => {
|
|
39
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ComboboxEmpty, {
|
|
40
|
+
className: classNames?.empty,
|
|
41
|
+
children: emptyText
|
|
42
|
+
}), /* @__PURE__ */ jsx(ComboboxList, {
|
|
43
|
+
className: classNames?.list,
|
|
44
|
+
children: (group) => /* @__PURE__ */ jsxs(ComboboxGroup, {
|
|
45
|
+
items: group.items,
|
|
46
|
+
children: [group.grouped && /* @__PURE__ */ jsx(ComboboxGroupLabel, {
|
|
47
|
+
"data-slot": "combobox-group-label",
|
|
48
|
+
className: "text-muted-foreground px-2 py-1.5 text-xs",
|
|
49
|
+
children: group.value
|
|
50
|
+
}), /* @__PURE__ */ jsx(ComboboxCollection, { children: (option) => /* @__PURE__ */ jsx(ComboboxItem, {
|
|
51
|
+
value: option.value,
|
|
52
|
+
disabled: option.disabled,
|
|
53
|
+
className: classNames?.item,
|
|
54
|
+
children: option.label
|
|
55
|
+
}, option.value) })]
|
|
56
|
+
}, group.key)
|
|
57
|
+
})] });
|
|
58
|
+
};
|
|
59
|
+
//#endregion
|
|
60
|
+
export { SearchableOptions as default, useSelectItems };
|
|
61
|
+
|
|
62
|
+
//# sourceMappingURL=select-searchable-options.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select-searchable-options.mjs","names":["combobox"],"sources":["../../../src/components/atoms/select-searchable-options.tsx"],"sourcesContent":["'use client';\n\nimport { type ReactNode, useMemo } from 'react';\n\nimport { combobox } from '../../core';\n\nconst {\n ComboboxCollection,\n ComboboxEmpty,\n ComboboxGroup,\n ComboboxGroupLabel,\n ComboboxItem,\n ComboboxList,\n createItems,\n} = combobox;\n\nexport interface Option {\n value: string;\n label: string;\n disabled?: boolean;\n}\n\nexport interface OptionGroup {\n label: string;\n options: Option[];\n}\n\nexport interface ClassNames {\n input?: string;\n content?: string;\n list?: string;\n item?: string;\n empty?: string;\n}\n\ninterface ItemsGroup {\n key: number;\n value: string;\n items: Option[];\n grouped: boolean;\n}\n\nconst isGroup = (option: Option | OptionGroup): option is OptionGroup => {\n return 'options' in option && Array.isArray(option.options);\n};\n\nexport const useSelectItems = (options: (Option | OptionGroup)[]) => {\n const groups = useMemo(() => {\n const result: ItemsGroup[] = [];\n\n options.forEach((option, index) => {\n if (isGroup(option)) {\n result.push({\n key: index,\n value: option.label,\n items: option.options,\n grouped: true,\n });\n } else {\n const previous = result.at(-1);\n\n if (previous && !previous.grouped) {\n previous.items.push(option);\n } else {\n result.push({\n key: index,\n value: '',\n items: [option],\n grouped: false,\n });\n }\n }\n });\n\n return result;\n }, [options]);\n const items = useMemo(\n () =>\n createItems<Option, string>(groups, {\n getValue: option => option.value,\n getLabel: option => option.label,\n }),\n [groups],\n );\n\n return items;\n};\n\ninterface Props {\n emptyText: ReactNode;\n classNames?: ClassNames;\n}\n\nconst SearchableOptions = ({ emptyText, classNames }: Props) => {\n return (\n <>\n <ComboboxEmpty className={classNames?.empty}>{emptyText}</ComboboxEmpty>\n <ComboboxList className={classNames?.list}>\n {(group: ItemsGroup) => (\n <ComboboxGroup key={group.key} items={group.items}>\n {group.grouped && (\n <ComboboxGroupLabel\n data-slot=\"combobox-group-label\"\n className=\"text-muted-foreground px-2 py-1.5 text-xs\"\n >\n {group.value}\n </ComboboxGroupLabel>\n )}\n <ComboboxCollection>\n {(option: Option) => (\n <ComboboxItem\n key={option.value}\n value={option.value}\n disabled={option.disabled}\n className={classNames?.item}\n >\n {option.label}\n </ComboboxItem>\n )}\n </ComboboxCollection>\n </ComboboxGroup>\n )}\n </ComboboxList>\n </>\n );\n};\n\nexport default SearchableOptions;\n"],"mappings":";;;;;AAMA,MAAM,EACJ,oBACA,eACA,eACA,oBACA,cACA,cACA,gBACEA;AA4BJ,MAAM,WAAW,WAAwD;CACvE,OAAO,aAAa,UAAU,MAAM,QAAQ,OAAO,OAAO;AAC5D;AAEA,MAAa,kBAAkB,YAAsC;CACnE,MAAM,SAAS,cAAc;EAC3B,MAAM,SAAuB,CAAC;EAE9B,QAAQ,SAAS,QAAQ,UAAU;GACjC,IAAI,QAAQ,MAAM,GAChB,OAAO,KAAK;IACV,KAAK;IACL,OAAO,OAAO;IACd,OAAO,OAAO;IACd,SAAS;GACX,CAAC;QACI;IACL,MAAM,WAAW,OAAO,GAAG,EAAE;IAE7B,IAAI,YAAY,CAAC,SAAS,SACxB,SAAS,MAAM,KAAK,MAAM;SAE1B,OAAO,KAAK;KACV,KAAK;KACL,OAAO;KACP,OAAO,CAAC,MAAM;KACd,SAAS;IACX,CAAC;GAEL;EACF,CAAC;EAED,OAAO;CACT,GAAG,CAAC,OAAO,CAAC;CAUZ,OATc,cAEV,YAA4B,QAAQ;EAClC,WAAU,WAAU,OAAO;EAC3B,WAAU,WAAU,OAAO;CAC7B,CAAC,GACH,CAAC,MAAM,CAGE;AACb;AAOA,MAAM,qBAAqB,EAAE,WAAW,iBAAwB;CAC9D,OACE,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,eAAD;EAAe,WAAW,YAAY;EAAQ,UAAA;CAAyB,CAAA,GACvE,oBAAC,cAAD;EAAc,WAAW,YAAY;EACjC,WAAA,UACA,qBAAC,eAAD;GAA+B,OAAO,MAAM;GAA5C,UAAA,CACG,MAAM,WACL,oBAAC,oBAAD;IACE,aAAU;IACV,WAAU;IAET,UAAA,MAAM;GACW,CAAA,GAEtB,oBAAC,oBAAD,EAAA,WACI,WACA,oBAAC,cAAD;IAEE,OAAO,OAAO;IACd,UAAU,OAAO;IACjB,WAAW,YAAY;IAEtB,UAAA,OAAO;GACI,GANP,OAAO,KAMA,EAEE,CAAA,CACP;EArBK,GAAA,MAAM,GAqBX;CAEL,CAAA,CACd,EAAA,CAAA;AAEN"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { RootProps } from "../../core/combobox.mjs";
|
|
2
|
+
import "../../core/index.mjs";
|
|
3
|
+
import { ClassNames, Option, OptionGroup } from "./select-searchable-options.mjs";
|
|
4
|
+
import { ReactNode } from "react";
|
|
5
|
+
//#region src/components/atoms/select-searchable.d.ts
|
|
6
|
+
interface Props extends Omit<RootProps<string, Option>, 'children' | 'items' | 'multiple' | 'filteredItems' | 'onValueChange' | 'itemToStringLabel' | 'itemToStringValue' | 'isItemEqualToValue'> {
|
|
7
|
+
searchable: true;
|
|
8
|
+
multiple?: false;
|
|
9
|
+
/** Accessible name for the trigger and search popup. */
|
|
10
|
+
label: string;
|
|
11
|
+
options: (Option | OptionGroup)[];
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
searchPlaceholder?: string;
|
|
14
|
+
emptyText?: ReactNode;
|
|
15
|
+
allowClear?: boolean;
|
|
16
|
+
className?: string;
|
|
17
|
+
classNames?: ClassNames;
|
|
18
|
+
onChange?: (value: string | null) => void;
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { Props };
|
|
22
|
+
//# sourceMappingURL=select-searchable.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select-searchable.d.mts","names":[],"sources":["../../../src/components/atoms/select-searchable.tsx"],"mappings":";;;;;UA6BiB,cAAc,KAC7B,kBAA2B;EAU3B;EACA;;EAEA;EACA,UAAU,SAAS;EACnB;EACA;EACA,YAAY;EACZ;EACA;EACA,aAAa;EACb,YAAY"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../../utils.mjs";
|
|
3
|
+
import { DEFAULT_LOCALE, useConfig } from "../../providers/config/context.mjs";
|
|
4
|
+
import { combobox_exports } from "../../core/combobox.mjs";
|
|
5
|
+
import { selectTriggerStyles } from "../../core/select.mjs";
|
|
6
|
+
import Button from "../../Button.mjs";
|
|
7
|
+
import Input from "./input/index.mjs";
|
|
8
|
+
import SearchableOptions, { useSelectItems } from "./select-searchable-options.mjs";
|
|
9
|
+
import { ChevronDownIcon, XIcon } from "lucide-react";
|
|
10
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
//#region src/components/atoms/select-searchable.tsx
|
|
12
|
+
const { Combobox: Core, ComboboxClear, ComboboxContent, ComboboxIcon, ComboboxInput, ComboboxTrigger, ComboboxValue } = combobox_exports;
|
|
13
|
+
const SearchableSelect = ({ label, options, placeholder, searchPlaceholder = "Search...", emptyText = "No results found.", allowClear = true, className, classNames, onChange, searchable: _searchable, multiple: _multiple, ...props }) => {
|
|
14
|
+
const { locale } = useConfig();
|
|
15
|
+
const items = useSelectItems(options);
|
|
16
|
+
return /* @__PURE__ */ jsxs(Core, {
|
|
17
|
+
items,
|
|
18
|
+
onValueChange: (value) => onChange?.(value),
|
|
19
|
+
...props,
|
|
20
|
+
children: [/* @__PURE__ */ jsxs(ComboboxTrigger, {
|
|
21
|
+
"data-slot": "select-trigger",
|
|
22
|
+
"data-size": "default",
|
|
23
|
+
"aria-label": label,
|
|
24
|
+
className: cn(selectTriggerStyles, "w-full max-w-48", className),
|
|
25
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
26
|
+
"data-slot": "select-value",
|
|
27
|
+
className: "min-w-0 flex-1 truncate text-start",
|
|
28
|
+
children: /* @__PURE__ */ jsx(ComboboxValue, { placeholder })
|
|
29
|
+
}), /* @__PURE__ */ jsx(ComboboxIcon, {
|
|
30
|
+
className: "opacity-50",
|
|
31
|
+
children: /* @__PURE__ */ jsx(ChevronDownIcon, {})
|
|
32
|
+
})]
|
|
33
|
+
}), /* @__PURE__ */ jsxs(ComboboxContent, {
|
|
34
|
+
"aria-label": label,
|
|
35
|
+
className: classNames?.content,
|
|
36
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
37
|
+
className: "border-b p-1",
|
|
38
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
39
|
+
className: "relative flex items-center",
|
|
40
|
+
children: [/* @__PURE__ */ jsx(ComboboxInput, { render: /* @__PURE__ */ jsx(Input, {
|
|
41
|
+
"aria-label": `${label} search`,
|
|
42
|
+
placeholder: searchPlaceholder,
|
|
43
|
+
className: cn(allowClear && "pe-8", classNames?.input)
|
|
44
|
+
}) }), allowClear && /* @__PURE__ */ jsx(ComboboxClear, {
|
|
45
|
+
"data-slot": "combobox-clear",
|
|
46
|
+
render: /* @__PURE__ */ jsx(Button, {
|
|
47
|
+
variant: "text",
|
|
48
|
+
size: "icon-sm",
|
|
49
|
+
icon: /* @__PURE__ */ jsx(XIcon, {}),
|
|
50
|
+
"aria-label": locale.clear ?? DEFAULT_LOCALE.clear,
|
|
51
|
+
className: "absolute end-0"
|
|
52
|
+
})
|
|
53
|
+
})]
|
|
54
|
+
})
|
|
55
|
+
}), /* @__PURE__ */ jsx(SearchableOptions, {
|
|
56
|
+
emptyText,
|
|
57
|
+
classNames
|
|
58
|
+
})]
|
|
59
|
+
})]
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
//#endregion
|
|
63
|
+
export { SearchableSelect as default };
|
|
64
|
+
|
|
65
|
+
//# sourceMappingURL=select-searchable.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select-searchable.mjs","names":["combobox","select.selectTriggerStyles"],"sources":["../../../src/components/atoms/select-searchable.tsx"],"sourcesContent":["'use client';\n\nimport { type ReactNode } from 'react';\n\nimport { ChevronDownIcon, XIcon } from 'lucide-react';\n\nimport { DEFAULT_LOCALE, useConfig } from '@repo/ui/providers';\nimport { cn } from '@repo/ui/utils';\n\nimport { combobox, select } from '../../core';\nimport Button from './button';\nimport Input from './input';\nimport SearchableOptions, {\n type ClassNames,\n type Option,\n type OptionGroup,\n useSelectItems,\n} from './select-searchable-options';\n\nconst {\n Combobox: Core,\n ComboboxClear,\n ComboboxContent,\n ComboboxIcon,\n ComboboxInput,\n ComboboxTrigger,\n ComboboxValue,\n} = combobox;\n\nexport interface Props extends Omit<\n combobox.RootProps<string, Option>,\n | 'children'\n | 'items'\n | 'multiple'\n | 'filteredItems'\n | 'onValueChange'\n | 'itemToStringLabel'\n | 'itemToStringValue'\n | 'isItemEqualToValue'\n> {\n searchable: true;\n multiple?: false;\n /** Accessible name for the trigger and search popup. */\n label: string;\n options: (Option | OptionGroup)[];\n placeholder?: string;\n searchPlaceholder?: string;\n emptyText?: ReactNode;\n allowClear?: boolean;\n className?: string;\n classNames?: ClassNames;\n onChange?: (value: string | null) => void;\n}\n\nconst SearchableSelect = ({\n label,\n options,\n placeholder,\n searchPlaceholder = 'Search...',\n emptyText = 'No results found.',\n allowClear = true,\n className,\n classNames,\n onChange,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- Do not forward the public mode switch to Base UI.\n searchable: _searchable,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- The single-select primitive fixes this mode internally.\n multiple: _multiple,\n ...props\n}: Props) => {\n const { locale } = useConfig();\n const items = useSelectItems(options);\n\n return (\n <Core items={items} onValueChange={value => onChange?.(value)} {...props}>\n <ComboboxTrigger\n data-slot=\"select-trigger\"\n data-size=\"default\"\n aria-label={label}\n className={cn(select.selectTriggerStyles, 'w-full max-w-48', className)}\n >\n <span\n data-slot=\"select-value\"\n className=\"min-w-0 flex-1 truncate text-start\"\n >\n <ComboboxValue placeholder={placeholder} />\n </span>\n <ComboboxIcon className=\"opacity-50\">\n <ChevronDownIcon />\n </ComboboxIcon>\n </ComboboxTrigger>\n <ComboboxContent aria-label={label} className={classNames?.content}>\n <div className=\"border-b p-1\">\n <div className=\"relative flex items-center\">\n <ComboboxInput\n render={\n <Input\n aria-label={`${label} search`}\n placeholder={searchPlaceholder}\n className={cn(allowClear && 'pe-8', classNames?.input)}\n />\n }\n />\n {allowClear && (\n <ComboboxClear\n data-slot=\"combobox-clear\"\n render={\n <Button\n variant=\"text\"\n size=\"icon-sm\"\n icon={<XIcon />}\n aria-label={locale.clear ?? DEFAULT_LOCALE.clear}\n className=\"absolute end-0\"\n />\n }\n />\n )}\n </div>\n </div>\n <SearchableOptions emptyText={emptyText} classNames={classNames} />\n </ComboboxContent>\n </Core>\n );\n};\n\nexport default SearchableSelect;\n"],"mappings":";;;;;;;;;;;AAmBA,MAAM,EACJ,UAAU,MACV,eACA,iBACA,cACA,eACA,iBACA,kBACEA;AA2BJ,MAAM,oBAAoB,EACxB,OACA,SACA,aACA,oBAAoB,aACpB,YAAY,qBACZ,aAAa,MACb,WACA,YACA,UAEA,YAAY,aAEZ,UAAU,WACV,GAAG,YACQ;CACX,MAAM,EAAE,WAAW,UAAU;CAC7B,MAAM,QAAQ,eAAe,OAAO;CAEpC,OACE,qBAAC,MAAD;EAAa;EAAO,gBAAe,UAAS,WAAW,KAAK;EAAG,GAAI;EAAnE,UAAA,CACE,qBAAC,iBAAD;GACE,aAAU;GACV,aAAU;GACV,cAAY;GACZ,WAAW,GAAGC,qBAA4B,mBAAmB,SAAS;GAJxE,UAAA,CAME,oBAAC,QAAD;IACE,aAAU;IACV,WAAU;IAEV,UAAA,oBAAC,eAAD,EAA4B,YAAc,CAAA;GACtC,CAAA,GACN,oBAAC,cAAD;IAAc,WAAU;IACtB,UAAA,oBAAC,iBAAD,CAAkB,CAAA;GACN,CAAA,CACC;EACjB,CAAA,GAAA,qBAAC,iBAAD;GAAiB,cAAY;GAAO,WAAW,YAAY;GAA3D,UAAA,CACE,oBAAC,OAAD;IAAK,WAAU;IACb,UAAA,qBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,oBAAC,eAAD,EACE,QACE,oBAAC,OAAD;MACE,cAAY,GAAG,MAAM;MACrB,aAAa;MACb,WAAW,GAAG,cAAc,QAAQ,YAAY,KAAK;KACtD,CAAA,EAEJ,CAAA,GACA,cACC,oBAAC,eAAD;MACE,aAAU;MACV,QACE,oBAAC,QAAD;OACE,SAAQ;OACR,MAAK;OACL,MAAM,oBAAC,OAAD,CAAQ,CAAA;OACd,cAAY,OAAO,SAAS,eAAe;OAC3C,WAAU;MACX,CAAA;KAEJ,CAAA,CAEA;;GACF,CAAA,GACL,oBAAC,mBAAD;IAA8B;IAAuB;GAAa,CAAA,CACnD;EACb,CAAA,CAAA;;AAEV"}
|
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
import { Select as Select$1 } from "../../core/select.mjs";
|
|
2
2
|
import "../../core/index.mjs";
|
|
3
|
+
import { Props as Props$1 } from "./select-multiple.mjs";
|
|
4
|
+
import { Props as Props$2 } from "./select-searchable.mjs";
|
|
3
5
|
import React from "react";
|
|
4
6
|
//#region src/components/atoms/select.d.ts
|
|
5
7
|
declare const Core: typeof Select$1;
|
|
6
8
|
interface Option {
|
|
7
9
|
label: React.ReactNode;
|
|
8
10
|
value: string;
|
|
11
|
+
disabled?: boolean;
|
|
9
12
|
}
|
|
10
13
|
interface OptionGroup {
|
|
11
14
|
label: React.ReactNode;
|
|
12
15
|
options: Option[];
|
|
13
16
|
}
|
|
14
|
-
interface
|
|
17
|
+
interface StaticProps extends Omit<React.ComponentProps<typeof Core>, 'onValueChange'> {
|
|
18
|
+
searchable?: false;
|
|
15
19
|
placeholder?: string;
|
|
16
20
|
className?: string;
|
|
17
21
|
options?: (Option | OptionGroup)[];
|
|
18
22
|
onChange?: (value: string) => void;
|
|
23
|
+
'aria-label'?: string;
|
|
19
24
|
}
|
|
20
|
-
|
|
25
|
+
type Props = StaticProps | Props$2 | Props$1;
|
|
26
|
+
declare const Select: (props: Props) => React.JSX.Element;
|
|
21
27
|
//#endregion
|
|
22
28
|
export { Props, Select as default };
|
|
23
29
|
//# sourceMappingURL=select.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select.d.mts","names":[],"sources":["../../../src/components/atoms/select.tsx"],"mappings":"
|
|
1
|
+
{"version":3,"file":"select.d.mts","names":[],"sources":["../../../src/components/atoms/select.tsx"],"mappings":";;;;;;cAWU,aAAI;UASJ;EACR,OAAO,MAAM;EACb;EACA;;UAGQ;EACR,OAAO,MAAM;EACb,SAAS;;UAOD,oBAAoB,KAC5B,MAAM,sBAAsB;EAG5B;EACA;EACA;EACA,WAAW,SAAS;EACpB,YAAY;EACZ;;KAGU,QAAQ,cAAc,UAAkB;cA8D9C,SAAU,OAAO,UAAK,MAAA,IAAA"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { cn } from "../../utils.mjs";
|
|
2
2
|
import { select_exports } from "../../core/select.mjs";
|
|
3
|
+
import MultipleSelect from "./select-multiple.mjs";
|
|
4
|
+
import SearchableSelect from "./select-searchable.mjs";
|
|
3
5
|
import "react";
|
|
4
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
7
|
//#region src/components/atoms/select.tsx
|
|
@@ -7,7 +9,7 @@ const { Select: Core, SelectContent, SelectGroup, SelectItem, SelectLabel, Selec
|
|
|
7
9
|
const isGroup = (option) => {
|
|
8
10
|
return "options" in option && Array.isArray(option.options);
|
|
9
11
|
};
|
|
10
|
-
const
|
|
12
|
+
const StaticSelect = ({ searchable: _searchable, className, placeholder, options, onChange, "aria-label": ariaLabel, ...props }) => {
|
|
11
13
|
const items = options?.flatMap((option) => isGroup(option) ? option.options : option);
|
|
12
14
|
return /* @__PURE__ */ jsxs(Core, {
|
|
13
15
|
items,
|
|
@@ -16,17 +18,25 @@ const Select = ({ className, placeholder, options, onChange, ...props }) => {
|
|
|
16
18
|
},
|
|
17
19
|
...props,
|
|
18
20
|
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
21
|
+
"aria-label": ariaLabel,
|
|
19
22
|
className: cn("w-full max-w-48", className),
|
|
20
23
|
children: /* @__PURE__ */ jsx(SelectValue, { placeholder })
|
|
21
24
|
}), /* @__PURE__ */ jsx(SelectContent, { children: options?.map((option, index) => isGroup(option) ? /* @__PURE__ */ jsxs(SelectGroup, { children: [/* @__PURE__ */ jsx(SelectLabel, { children: option.label }), option.options.map((item) => /* @__PURE__ */ jsx(SelectItem, {
|
|
22
25
|
value: item.value,
|
|
26
|
+
disabled: item.disabled,
|
|
23
27
|
children: item.label
|
|
24
28
|
}, item.value))] }, index) : /* @__PURE__ */ jsx(SelectItem, {
|
|
25
29
|
value: option.value,
|
|
30
|
+
disabled: option.disabled,
|
|
26
31
|
children: option.label
|
|
27
32
|
}, option.value)) })]
|
|
28
33
|
});
|
|
29
34
|
};
|
|
35
|
+
const Select = (props) => {
|
|
36
|
+
if (props.multiple) return /* @__PURE__ */ jsx(MultipleSelect, { ...props });
|
|
37
|
+
if (props.searchable) return /* @__PURE__ */ jsx(SearchableSelect, { ...props });
|
|
38
|
+
return /* @__PURE__ */ jsx(StaticSelect, { ...props });
|
|
39
|
+
};
|
|
30
40
|
//#endregion
|
|
31
41
|
export { Select as default };
|
|
32
42
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select.mjs","names":["select"],"sources":["../../../src/components/atoms/select.tsx"],"sourcesContent":["import React from 'react';\n\nimport { cn } from '@repo/ui/utils';\n\nimport { select } from '../../core';\n\nconst {\n Select: Core,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectTrigger,\n SelectValue,\n} = select;\n\ninterface Option {\n label: React.ReactNode;\n value: string;\n}\n\ninterface OptionGroup {\n label: React.ReactNode;\n options: Option[];\n}\n\nconst isGroup = (option: Option | OptionGroup): option is OptionGroup => {\n return 'options' in option && Array.isArray(option.options);\n};\n\
|
|
1
|
+
{"version":3,"file":"select.mjs","names":["select"],"sources":["../../../src/components/atoms/select.tsx"],"sourcesContent":["import React from 'react';\n\nimport { cn } from '@repo/ui/utils';\n\nimport { select } from '../../core';\nimport MultipleSelect, { type Props as MultipleProps } from './select-multiple';\nimport SearchableSelect, {\n type Props as SearchableProps,\n} from './select-searchable';\n\nconst {\n Select: Core,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectTrigger,\n SelectValue,\n} = select;\n\ninterface Option {\n label: React.ReactNode;\n value: string;\n disabled?: boolean;\n}\n\ninterface OptionGroup {\n label: React.ReactNode;\n options: Option[];\n}\n\nconst isGroup = (option: Option | OptionGroup): option is OptionGroup => {\n return 'options' in option && Array.isArray(option.options);\n};\n\ninterface StaticProps extends Omit<\n React.ComponentProps<typeof Core>,\n 'onValueChange'\n> {\n searchable?: false;\n placeholder?: string;\n className?: string;\n options?: (Option | OptionGroup)[];\n onChange?: (value: string) => void;\n 'aria-label'?: string;\n}\n\nexport type Props = StaticProps | SearchableProps | MultipleProps;\n\nconst StaticSelect = ({\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- Do not forward the public mode switch to Base UI.\n searchable: _searchable,\n className,\n placeholder,\n options,\n onChange,\n 'aria-label': ariaLabel,\n ...props\n}: StaticProps) => {\n const items = options?.flatMap(option =>\n isGroup(option) ? option.options : option,\n );\n\n return (\n <Core\n items={items}\n onValueChange={value => {\n if (value !== null) {\n onChange?.(value);\n }\n }}\n {...props}\n >\n <SelectTrigger\n aria-label={ariaLabel}\n className={cn('w-full max-w-48', className)}\n >\n <SelectValue placeholder={placeholder} />\n </SelectTrigger>\n <SelectContent>\n {options?.map((option, index) =>\n isGroup(option) ? (\n <SelectGroup key={index}>\n <SelectLabel>{option.label}</SelectLabel>\n {option.options.map(item => (\n <SelectItem\n key={item.value}\n value={item.value}\n disabled={item.disabled}\n >\n {item.label}\n </SelectItem>\n ))}\n </SelectGroup>\n ) : (\n <SelectItem\n key={option.value}\n value={option.value}\n disabled={option.disabled}\n >\n {option.label}\n </SelectItem>\n ),\n )}\n </SelectContent>\n </Core>\n );\n};\n\nconst Select = (props: Props) => {\n if (props.multiple) {\n return <MultipleSelect {...props} />;\n }\n\n if (props.searchable) {\n return <SearchableSelect {...props} />;\n }\n\n return <StaticSelect {...props} />;\n};\n\nexport default Select;\n"],"mappings":";;;;;;;AAUA,MAAM,EACJ,QAAQ,MACR,eACA,aACA,YACA,aACA,eACA,gBACEA;AAaJ,MAAM,WAAW,WAAwD;CACvE,OAAO,aAAa,UAAU,MAAM,QAAQ,OAAO,OAAO;AAC5D;AAgBA,MAAM,gBAAgB,EAEpB,YAAY,aACZ,WACA,aACA,SACA,UACA,cAAc,WACd,GAAG,YACc;CACjB,MAAM,QAAQ,SAAS,SAAQ,WAC7B,QAAQ,MAAM,IAAI,OAAO,UAAU,MACrC;CAEA,OACE,qBAAC,MAAD;EACS;EACP,gBAAe,UAAS;GACtB,IAAI,UAAU,MACZ,WAAW,KAAK;EAEpB;EACA,GAAI;EAPN,UAAA,CASE,oBAAC,eAAD;GACE,cAAY;GACZ,WAAW,GAAG,mBAAmB,SAAS;GAE1C,UAAA,oBAAC,aAAD,EAA0B,YAAc,CAAA;EAC3B,CAAA,GACf,oBAAC,eAAD,EAAA,UACG,SAAS,KAAK,QAAQ,UACrB,QAAQ,MAAM,IACZ,qBAAC,aAAD,EAAA,UAAA,CACE,oBAAC,aAAD,EAAA,UAAc,OAAO,MAAmB,CAAA,GACvC,OAAO,QAAQ,KAAI,SAClB,oBAAC,YAAD;GAEE,OAAO,KAAK;GACZ,UAAU,KAAK;GAEd,UAAA,KAAK;EACI,GALL,KAAK,KAKA,CACb,CACU,EAAA,GAXK,KAWL,IAEb,oBAAC,YAAD;GAEE,OAAO,OAAO;GACd,UAAU,OAAO;GAEhB,UAAA,OAAO;EACE,GALL,OAAO,KAKF,CAEhB,EACa,CAAA,CACX;;AAEV;AAEA,MAAM,UAAU,UAAiB;CAC/B,IAAI,MAAM,UACR,OAAO,oBAAC,gBAAD,EAAgB,GAAI,MAAQ,CAAA;CAGrC,IAAI,MAAM,YACR,OAAO,oBAAC,kBAAD,EAAkB,GAAI,MAAQ,CAAA;CAGvC,OAAO,oBAAC,cAAD,EAAc,GAAI,MAAQ,CAAA;AACnC"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../../../utils.mjs";
|
|
3
3
|
import { slider_exports } from "../../../core/slider.mjs";
|
|
4
|
-
import { jsx } from "react/jsx-runtime";
|
|
5
4
|
import { useControllableState } from "@jbpark/use-hooks";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
6
|
//#region src/components/atoms/slider/slider.tsx
|
|
7
7
|
const { Slider: Core } = slider_exports;
|
|
8
8
|
const toArray = (value) => {
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { cn } from "../../utils.mjs";
|
|
3
3
|
import { useConfig } from "../../providers/config/context.mjs";
|
|
4
4
|
import { switch_exports } from "../../core/switch.mjs";
|
|
5
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
5
|
import { useControllableState } from "@jbpark/use-hooks";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
//#region src/components/atoms/switch.tsx
|
|
8
8
|
const { Switch: Core } = switch_exports;
|
|
9
9
|
const sizeConfig = {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ReactElement, ReactNode } from "react";
|
|
2
|
+
//#region src/components/atoms/tooltip.d.ts
|
|
3
|
+
declare const Core: <Payload>(props: import("@base-ui/react").TooltipRoot.Props<Payload>) => React.JSX.Element;
|
|
4
|
+
type Placement = 'top' | 'left' | 'right' | 'bottom' | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight' | 'leftTop' | 'leftBottom' | 'rightTop' | 'rightBottom';
|
|
5
|
+
interface Props extends Omit<React.ComponentProps<typeof Core>, 'children' | 'handle' | 'triggerId' | 'defaultTriggerId'> {
|
|
6
|
+
children: ReactElement;
|
|
7
|
+
/** Supplementary visual hint; keep essential information visible elsewhere. */
|
|
8
|
+
content: ReactNode;
|
|
9
|
+
/** Popup class; the child retains its own className. */
|
|
10
|
+
className?: string;
|
|
11
|
+
placement?: Placement;
|
|
12
|
+
/** Distance between the trigger and popup in pixels. */
|
|
13
|
+
offset?: number;
|
|
14
|
+
/** Hover delay in milliseconds. Keyboard focus opens without this delay. */
|
|
15
|
+
delay?: number;
|
|
16
|
+
closeDelay?: number;
|
|
17
|
+
}
|
|
18
|
+
declare const Tooltip: ({ children, content, className, placement, offset, delay, closeDelay, ...props }: Props) => import("react").JSX.Element;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { Props, Tooltip as default };
|
|
21
|
+
//# sourceMappingURL=tooltip.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tooltip.d.mts","names":[],"sources":["../../../src/components/atoms/tooltip.tsx"],"mappings":";;cAMiB,OAAI,SAAA,gCAAA,YAAA,MAAA,aAAA,MAAA,IAAA;KAEhB;UAgCY,cAAc,KAC7B,MAAM,sBAAsB;EAG5B,UAAU;;EAEV,SAAS;;EAET;EACA,YAAY;;EAEZ;;EAEA;EACA;;cAGI,YAAW,UAAA,SAAA,WAAA,WAAA,QAAA,OAAA,eAAA,SASd,0BAAK,IAAA"}
|