diff --git a/docs/components/TheHeader.vue b/docs/components/TheHeader.vue index be042de..5caa8a5 100644 --- a/docs/components/TheHeader.vue +++ b/docs/components/TheHeader.vue @@ -29,6 +29,7 @@ const overlayLinks: DropdownItem[] = [ const formLinks: DropdownItem[] = [ { label: 'Input', to: '/input' }, + { label: 'Select', to: '/select' }, { label: 'Switch', to: '/switch' }, { label: 'Checkbox', to: '/checkbox' }, { label: 'Radio Group', to: '/radio' }, diff --git a/docs/pages/select.vue b/docs/pages/select.vue new file mode 100644 index 0000000..03630af --- /dev/null +++ b/docs/pages/select.vue @@ -0,0 +1,77 @@ + + + diff --git a/src/runtime/components/forms/FormSelect.vue b/src/runtime/components/forms/FormSelect.vue new file mode 100644 index 0000000..dcf0dc0 --- /dev/null +++ b/src/runtime/components/forms/FormSelect.vue @@ -0,0 +1,171 @@ + + + diff --git a/src/runtime/types/formSelect.d.ts b/src/runtime/types/formSelect.d.ts new file mode 100644 index 0000000..f7c374b --- /dev/null +++ b/src/runtime/types/formSelect.d.ts @@ -0,0 +1,29 @@ +import type { FormFieldProps } from '#ui/types'; +import type { SelectContentProps, SelectRootProps } from 'radix-vue'; + +export interface FormSelectItem { + value: any; + label: string; + disabled?: boolean; +} + +export type FormSelectOptions = Record | FormSelectItem[]; + +export interface FormSelectProps extends SelectRootProps, Omit { + placeholder?: string; + + prefixIcon?: string; + prefixText?: string; + suffixIcon?: string; + indicatorIcon?: string; + scrollUpIcon?: string; + scrollDownIcon?: string; + + ui?: UiConfig; + + options?: FormSelectOptions; + + offset?: number | string; + align?: SelectContentProps['align']; + side?: SelectContentProps['side']; +} diff --git a/src/runtime/types/index.d.ts b/src/runtime/types/index.d.ts index 5e50bc4..b24c542 100644 --- a/src/runtime/types/index.d.ts +++ b/src/runtime/types/index.d.ts @@ -4,5 +4,6 @@ export * from './combobox'; export * from './dropdown'; export * from './formField'; export * from './formInput'; +export * from './formSelect'; export * from './link'; export * from './utils'; diff --git a/src/runtime/ui.config/formSelect.ts b/src/runtime/ui.config/formSelect.ts new file mode 100644 index 0000000..2691e02 --- /dev/null +++ b/src/runtime/ui.config/formSelect.ts @@ -0,0 +1,62 @@ +import type { SelectContentProps } from 'radix-vue'; + +export default /*ui*/ { + trigger: { + base: 'flex w-full items-center bg-white group-data-[error]:bg-red-50', + rounded: 'rounded-lg', + ring: 'focus-within:ring-2 focus-within:ring-primary-600 group-data-[error]:focus-within:ring-red-400', + border: 'border border-gray-900/10 group-data-[error]:border-red-800', + icon: 'size-4 text-gray-600 group-data-[error]:text-red-600', + font: { + value: 'text-sm text-left truncate text-gray-900 group-data-[error]:text-red-800', + addons: 'text-sm select-none text-gray-500 group-data-[error]:text-red-600', + }, + value: { + base: 'block h-8 w-full flex-1', + padding: 'px-2 py-1.5', + }, + }, + + content: { + base: 'z-30 bg-white shadow-md', + rounded: 'rounded-lg', + border: 'border border-gray-900/10', + size: 'w-[--radix-select-trigger-width] min-w-32', + transition: + 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', + }, + + viewport: 'p-1', + + group: { + label: 'text-xs text-gray-500 px-2 py-1 font-semibold', + separator: '-mx-1 my-1 h-px bg-gray-900/5', + }, + + item: { + base: 'relative flex cursor-pointer select-none rounded-sm items-center outline-none transition-colors', + label: 'text-sm', + padding: 'px-2 py-1.5', + inactive: '', + active: 'data-[highlighted]:outline-none data-[highlighted]:bg-gray-100 data-[highlighted]:text-gray-900', + disabled: 'data-[disabled]:pointer-events-none data-[disabled]:opacity-50', + indicator: 'mr-2 size-4 text-primary-600', + }, + + scrollButtons: { + base: 'flex items-center justify-center bg-white cursor-default', + size: 'h-6', + icon: 'size-4 text-gray-500', + }, + + default: { + offset: 8, + align: 'start' as SelectContentProps['align'], + side: 'bottom' as SelectContentProps['side'], + + suffixIcon: 'i-heroicons-chevron-down-20-solid', + indicatorIcon: 'i-heroicons-check-20-solid', + scrollUpIcon: 'i-heroicons-chevron-up-20-solid', + scrollDownIcon: 'i-heroicons-chevron-down-20-solid', + }, +}; diff --git a/src/runtime/ui.config/index.ts b/src/runtime/ui.config/index.ts index 7ec9a34..1b6475e 100644 --- a/src/runtime/ui.config/index.ts +++ b/src/runtime/ui.config/index.ts @@ -18,4 +18,5 @@ export { default as formField } from './formField'; export { default as formInput } from './formInput'; export { default as formLabel } from './formLabel'; export { default as formRadioGroupItem } from './formRadioGroupItem'; +export { default as formSelect } from './formSelect'; export { default as formSwitch } from './formSwitch';