diff --git a/.cursor/rules/coding.mdc b/.cursor/rules/coding.mdc index 9db6045..d64aa75 100644 --- a/.cursor/rules/coding.mdc +++ b/.cursor/rules/coding.mdc @@ -12,12 +12,38 @@ alwaysApply: true - Before starting coding, always present a plan for approval. - Tasks are in docs/project_plan.md - Each task will be implemented via a feature branch and a PR to the develop branch. -- Always follow the following recipe for implementing tasks: - 1. Start implementing after the user has explicitly told so. - 2. After finishing implementing, perform all necessary tests and check if the DoD for this task is met. - 3. Report on test results and DoD criteria and ask user if a PR should be submitted. - 4. If the user approves submitting a PR, and before PR is submitted, the task is marked with "PR" in docs/project_plan.md. - 5. After PR ist submitted, report to the user and wait for manual instructions. - 6. The user will then review and merge the PR or ask for updates. - 7. Pull the repo again to check if the PR has been merged. - 8. After task is completed (PR merged), the task is marked with a checkmark in docs/project_plan.md + +# Recipe for implementing tasks +Always follow the following recipe for implementing tasks: +1. Only start implementing a task after the user has explicitly told so. +2. Before starting coding, create a task planning document (.md) in the docs/task-planning folder. +3. The task planning document contains a table with three columns: tasks description, DoD (Definition of Done) and Status. Status is open, working, checking, review, complete. Initially all tasks are "open". + 1. Open = the task hasn't been worked on. + 2. Working = the task is currently being implemented. + 3. Checking = the DoD of the tasks are being checked. + 4. Review = the user is reviewing the result. + 5. complete = the user has approved the result and the task is complete. +4. After creating the task planning document, let the user review the doc. +5. After the user has approved the task planning document, implement each task one after the other. +6. After implementing a task, check the criteria for DoD and report the result to the user. +7. If DoD is not met, fix the problem until DoD is met. +8. Before and after each task, update each task in the table with the right status. +9. After finishing implementing, perform all necessary tests and check if the DoD for this task is met. +10. Report on test results and DoD criteria and ask user if a PR should be submitted. +11. If the user approves submitting a PR, and before PR is submitted, the task is marked with "PR" in docs/project_plan.md. +12. After PR ist submitted, report to the user and wait for manual instructions. +13. The user will then review and merge the PR or ask for updates. +14. Pull the repo again to check if the PR has been merged. +15. After task is completed (PR merged), the task is marked with a checkmark in docs/project_plan.md. This change does not warrant a separate PR, it will be included in the next PR. Just do git add for the file. + +# Instructions to handle error situations: +- CI Pipeline fails: + 1. List the last github actions and find out which actions failed. + 2. Fetch the git actions log with `gh run view --log | cat` + 3. Analyze the log and make a proposal how to fix it. + 4. Let the user review / update the proposal, then execute the reviewed/updated proposal. +- Linter errors: + 1. Run `pnpm run lint` + 2. Find the linter errors + 3. Fix + \ No newline at end of file diff --git a/.cursor/rules/styling_rules.mdc b/.cursor/rules/styling_rules.mdc index e25e479..46c86f5 100644 --- a/.cursor/rules/styling_rules.mdc +++ b/.cursor/rules/styling_rules.mdc @@ -1,6 +1,13 @@ +--- +description: +globs: +alwaysApply: false +--- # Cursor Rule File – Tailwind, Shadcn, DaisyUI - Tailwind classes **only inside ui-kit**; no classes in consuming apps. +- Always use shadcn ui components with the shadcn CLI (npx shadcn@latest ) +- Initialitze - Never override Shadcn component CSS directly; extend via wrapper props. - Colours/spacings via CSS vars mapped to DaisyUI tokens. - Use `@apply` sparingly inside `theme.css`, never in JSX. diff --git a/components.json b/components.json new file mode 100644 index 0000000..3154a3d --- /dev/null +++ b/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/styles/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/docs/project_plan.md b/docs/project_plan.md index b315299..6790b0c 100644 --- a/docs/project_plan.md +++ b/docs/project_plan.md @@ -34,7 +34,7 @@ A pragmatic breakdown into **four one‑week sprints** plus a preparatory **Spri | 1.2c | Configure and verify Storybook addon‑a11y. | axe‑a11y addon shows zero violations. | ✓ | | 1.3 | Add global theme bridging (`theme.css` ↔ DaisyUI). | Cypress visual diff (light/dark) matches golden images. | ✓ | | 1.4 | Create first **AuthShell** layout with logo slot. | Rendered via Storybook; Playwright snapshot approved. | ✓ | -| 1.5 | Document design tokens (`DESIGN_TOKENS.md`). | File exists; CI step `tokens-check` verifies presence of each CSS var. | PR | +| 1.5 | Document design tokens (`DESIGN_TOKENS.md`). | File exists; CI step `tokens-check` verifies presence of each CSS var. | ✓ | --- @@ -42,7 +42,7 @@ A pragmatic breakdown into **four one‑week sprints** plus a preparatory **Spri | # | Task | DoD | Status | | --- | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ------ | -| 2.1 | Add NumberInput, Select, Checkbox, RadioGroup components. | Unit & a11y tests pass; form story displays all. | | +| 2.1 | Add NumberInput, Select, Checkbox, RadioGroup components. | Unit & a11y tests pass; form story displays all. | PR | | 2.2 | Integrate **React Hook Form + Zod**; create `FormGrid` + `FormGroup`. | Story "Form Example" submits & reports validation errors in Storybook interaction test. | | | 2.3 | Implement Zustand session store skeleton with dark‑mode flag. | Vitest verifies default state + setter actions. | | | 2.4 | ESLint rule enforcing named `useEffect` & cleanup. | Failing example in test repo triggers lint error; real code passes. | | diff --git a/docs/task-planning/task-2.1-form-components.md b/docs/task-planning/task-2.1-form-components.md new file mode 100644 index 0000000..804f516 --- /dev/null +++ b/docs/task-planning/task-2.1-form-components.md @@ -0,0 +1,47 @@ +# Task 2.1 Planning: Form Components + +## Overview + +This task involves implementing four form-related components: + +- NumberInput +- Select +- Checkbox +- RadioGroup + +These components will follow the established design patterns in the existing codebase, utilizing Shadcn UI as the foundation and conforming to the accessibility requirements. + +## Task Breakdown + +| Task Description | Definition of Done (DoD) | Status | +| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -------- | +| Set up directory structure for each component | All four component directories created with appropriate files (component, types, tests, stories) | complete | +| Implement NumberInput component | Component renders correctly and accepts all required props; Unit tests pass; a11y tests pass | working | +| Implement Select component | Component renders correctly and accepts all required props; Unit tests pass; a11y tests pass | open | +| Implement Checkbox component | Component renders correctly and accepts all required props; Unit tests pass; a11y tests pass | open | +| Implement RadioGroup component | Component renders correctly and accepts all required props; Unit tests pass; a11y tests pass | open | +| Update barrel exports in index.ts | All components are properly exported and accessible from the package's public API | open | +| Create consolidated form story | Story shows all components working together in a cohesive form; All components render correctly with proper styling | open | +| Final review and documentation | All components are properly documented; Code adheres to project standards; No lint errors; All tests pass | open | + +## Implementation Strategy + +### Component Architecture + +- Each component will be implemented following the same pattern as existing components (Button, TextInput) +- Components will be built on top of Shadcn UI primitives +- Each component will have proper TypeScript typing +- Components will include accessibility features + +### Testing Approach + +- Unit tests with Vitest for functionality +- Accessibility tests with axe-core +- Storybook stories to demonstrate all variants and states +- Snapshot tests for visual regression + +## Timeline + +- Estimated completion: 3-4 days +- First 2 days: Implement individual components +- Last 1-2 days: Integration, testing, and documentation diff --git a/docs/task-planning/task-2.1.md b/docs/task-planning/task-2.1.md new file mode 100644 index 0000000..1ea4a52 --- /dev/null +++ b/docs/task-planning/task-2.1.md @@ -0,0 +1,22 @@ +# Task 2.1 Planning - Form Primitives + +## Overview + +Task 2.1 involves completing the implementation of core form components (NumberInput, Select, Checkbox, RadioGroup) which already have base implementations but require tests and integration into a form story. + +## Tasks + +| Task Description | DoD (Definition of Done) | Status | +| --------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -------- | +| Create unit tests for NumberInput component | Tests verify component renders correctly with all prop variations (label, description, error states, etc.) | Complete | +| Create unit tests for Select component | Tests verify component renders correctly with all prop variations and options are displayed correctly | Complete | +| Create unit tests for Checkbox component | Tests verify component renders correctly with all prop variations and state changes work | Complete | +| Create unit tests for RadioGroup component | Tests verify component renders correctly with all prop variations and option selection works | Complete | +| Set up a11y tests for all form components | All components pass accessibility tests with no violations | Complete | +| Create a form story displaying all components | Story shows all components with their variants in Storybook | Complete | + +## Implementation Notes + +- Unit tests will follow the pattern established in the existing TextInput and Button tests +- a11y tests will use the Storybook addon-a11y +- The form story will showcase all components with their various states (default, disabled, with error, etc.) diff --git a/docs/task-planning/task-2.1a-shadcn-upgrade.md b/docs/task-planning/task-2.1a-shadcn-upgrade.md new file mode 100644 index 0000000..29273ad --- /dev/null +++ b/docs/task-planning/task-2.1a-shadcn-upgrade.md @@ -0,0 +1,41 @@ +# Task 2.1a Planning: Shadcn UI Component Upgrade + +## Overview + +This task involves upgrading the current form components to use the Shadcn CLI for better alignment with the latest Shadcn UI implementations. This will ensure the components are easier to maintain and update in the future. + +## Task Breakdown + +| Task Description | Definition of Done (DoD) | Status | +| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| Set up Shadcn CLI and configuration | components.json file created with proper configuration; CLI commands functioning correctly | complete | +| Reorganize project structure to match Shadcn standards | Directory structure and imports aligned with Shadcn expectations; Utils in correct location | complete | +| Update existing components using Shadcn CLI | All components (Button, Input, Select, Checkbox, RadioGroup) updated using the Shadcn CLI; Components maintain existing functionality | complete | +| Ensure wrapper components align with updated base components | All wrapper components (NumberInput, Select, Checkbox, RadioGroup) function correctly with the updated base components; No regressions in functionality | complete | + +## Implementation Strategy + +### Step 1: Project Structure Reorganization (Completed) + +- Create `src/lib` directory for utilities to match Shadcn standards +- Move `cn` utility from `src/utils/cn.ts` to `src/lib/utils.ts` +- Update imports across the project to use the new paths +- Update components.json to reflect the correct paths + +### Step 2: Component Updates (Completed) + +- Use the Shadcn CLI to update/reinstall all base components +- Ensure the updated components use the latest patterns and styles +- Verify all dependencies are correctly installed + +### Step 3: Wrapper Component Alignment (Completed) + +- Update wrapper components to work with the new base components +- Ensure all functionality is maintained +- Run tests to verify no regressions + +## Timeline + +- Estimated completion: 1-2 days (Completed in 1 day) +- First step: Setup and initial component upgrades (Completed) +- Second step: Complete remaining component updates and testing (Completed) diff --git a/package.json b/package.json index 00bb254..4c1a771 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@changesets/cli": "^2.29.4", "@commitlint/cli": "^19.8.1", "@commitlint/config-conventional": "^19.8.1", + "@eslint/js": "^9.27.0", "@storybook/addon-a11y": "8.6.14", "@storybook/addon-docs": "8.6.14", "@storybook/builder-vite": "8.6.14", @@ -34,7 +35,6 @@ "eslint": "^9.27.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", - "@eslint/js": "^9.27.0", "husky": "^8.0.0", "lint-staged": "^16.0.0", "postcss": "^8.4.35", @@ -46,6 +46,9 @@ "vitest": "^3.1.3" }, "dependencies": { + "@radix-ui/react-checkbox": "^1.3.1", + "@radix-ui/react-radio-group": "^1.3.6", + "@radix-ui/react-select": "^2.2.4", "react": "^19.1.0", "react-dom": "^19.1.0" }, diff --git a/packages/ui-kit/components.json b/packages/ui-kit/components.json new file mode 100644 index 0000000..3154a3d --- /dev/null +++ b/packages/ui-kit/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/styles/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/packages/ui-kit/package.json b/packages/ui-kit/package.json index 578dc18..ba06bab 100644 --- a/packages/ui-kit/package.json +++ b/packages/ui-kit/package.json @@ -21,7 +21,7 @@ "build": "tsc && vite build", "lint": "eslint .", "preview": "vite preview", - "test": "vitest", + "test": "vitest run", "test:coverage": "vitest run --coverage", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", @@ -34,14 +34,18 @@ "tokens-check": "node scripts/tokens-check.js" }, "dependencies": { - "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-checkbox": "^1.3.1", + "@radix-ui/react-radio-group": "^1.3.6", + "@radix-ui/react-select": "^2.2.4", + "@radix-ui/react-slot": "^1.2.2", "@types/testing-library__jest-dom": "^6.0.0", - "class-variance-authority": "^0.7.0", - "clsx": "^2.1.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "jsdom": "^26.1.0", + "lucide-react": "^0.511.0", "react": "^19.1.0", "react-dom": "^19.1.0", - "tailwind-merge": "^2.2.1", + "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7" }, "devDependencies": { diff --git a/packages/ui-kit/src/components/FormExample.stories.tsx b/packages/ui-kit/src/components/FormExample.stories.tsx new file mode 100644 index 0000000..521dbe9 --- /dev/null +++ b/packages/ui-kit/src/components/FormExample.stories.tsx @@ -0,0 +1,115 @@ +import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { Button } from './primitives/Button'; +import { TextInput } from './primitives/TextInput'; +import { NumberInput } from './primitives/NumberInput'; +import { Select } from './primitives/Select'; +import { Checkbox } from './primitives/Checkbox'; +import { RadioGroup } from './primitives/RadioGroup'; + +const colorOptions = [ + { value: 'red', label: 'Red' }, + { value: 'green', label: 'Green' }, + { value: 'blue', label: 'Blue' }, + { value: 'yellow', label: 'Yellow', disabled: true }, +]; + +const contactOptions = [ + { value: 'email', label: 'Email' }, + { value: 'phone', label: 'Phone' }, + { value: 'post', label: 'Post' }, +]; + +const FormExample = () => { + const [formState, setFormState] = React.useState({ + firstName: '', + age: '', + color: '', + contactMethod: '', + newsletter: false, + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + alert(JSON.stringify(formState, null, 2)); + }; + + const handleChange = (field: string, value: string | boolean) => { + setFormState((prev) => ({ ...prev, [field]: value })); + }; + + return ( +
+
+ ) => handleChange('firstName', e.target.value)} + /> + + ) => handleChange('age', e.target.value)} + description="Must be between 0 and 120" + /> + + + {description && !error && ( +

{description}

+ )} + {error && ( +

{error}

+ )} +
+ ); + } +); + +NumberInput.displayName = 'NumberInput'; \ No newline at end of file diff --git a/packages/ui-kit/src/components/primitives/NumberInput/index.ts b/packages/ui-kit/src/components/primitives/NumberInput/index.ts new file mode 100644 index 0000000..0e3b691 --- /dev/null +++ b/packages/ui-kit/src/components/primitives/NumberInput/index.ts @@ -0,0 +1 @@ +export * from './NumberInput'; \ No newline at end of file diff --git a/packages/ui-kit/src/components/primitives/NumberInput/types.ts b/packages/ui-kit/src/components/primitives/NumberInput/types.ts new file mode 100644 index 0000000..443ddda --- /dev/null +++ b/packages/ui-kit/src/components/primitives/NumberInput/types.ts @@ -0,0 +1,16 @@ +import { Input } from '@/components/ui/input'; + +export interface NumberInputProps extends Omit, 'type'> { + /** Label text displayed above the input */ + label?: string; + /** Description/help text rendered below */ + description?: string; + /** Marks input as invalid */ + error?: string; + /** Minimum value allowed */ + min?: number; + /** Maximum value allowed */ + max?: number; + /** Step value for incrementing/decrementing */ + step?: number; +} \ No newline at end of file diff --git a/packages/ui-kit/src/components/primitives/RadioGroup/RadioGroup.stories.tsx b/packages/ui-kit/src/components/primitives/RadioGroup/RadioGroup.stories.tsx new file mode 100644 index 0000000..8dc6b9b --- /dev/null +++ b/packages/ui-kit/src/components/primitives/RadioGroup/RadioGroup.stories.tsx @@ -0,0 +1,65 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { RadioGroup } from './RadioGroup'; + +const options = [ + { value: 'option1', label: 'Option 1' }, + { value: 'option2', label: 'Option 2' }, + { value: 'option3', label: 'Option 3' }, + { value: 'option4', label: 'Option 4', disabled: true }, +]; + +const meta = { + title: 'Components/Form/RadioGroup', + component: RadioGroup, + args: { + options, + }, + argTypes: { + label: { control: 'text' }, + description: { control: 'text' }, + error: { control: 'text' }, + disabled: { control: 'boolean' }, + value: { control: 'select', options: ['option1', 'option2', 'option3', 'option4'] }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +export const WithLabel: Story = { + args: { + label: 'Select an option', + }, +}; + +export const WithDescription: Story = { + args: { + label: 'Select an option', + description: 'Choose the best option for you', + }, +}; + +export const WithError: Story = { + args: { + label: 'Select an option', + error: 'This field is required', + }, +}; + +export const WithValue: Story = { + args: { + label: 'Select an option', + value: 'option1', + }, +}; + +export const Disabled: Story = { + args: { + label: 'Select an option', + disabled: true, + }, +}; \ No newline at end of file diff --git a/packages/ui-kit/src/components/primitives/RadioGroup/RadioGroup.test.tsx b/packages/ui-kit/src/components/primitives/RadioGroup/RadioGroup.test.tsx new file mode 100644 index 0000000..5054b38 --- /dev/null +++ b/packages/ui-kit/src/components/primitives/RadioGroup/RadioGroup.test.tsx @@ -0,0 +1,50 @@ +import { render, screen } from '@testing-library/react'; +import { describe, it, expect } from 'vitest'; +import { RadioGroup } from './RadioGroup'; + +const mockOptions = [ + { value: 'option1', label: 'Option 1' }, + { value: 'option2', label: 'Option 2' }, + { value: 'option3', label: 'Option 3', disabled: true } +]; + +describe('RadioGroup', () => { + it('renders with label', () => { + render(); + expect(screen.getByText('Choose an option')).toBeInTheDocument(); + }); + + it('renders all options', () => { + render(); + expect(screen.getByText('Option 1')).toBeInTheDocument(); + expect(screen.getByText('Option 2')).toBeInTheDocument(); + expect(screen.getByText('Option 3')).toBeInTheDocument(); + }); + + it('renders with selected value', () => { + render(); + // Get all radio buttons and find the one that's checked + const radioButtons = screen.getAllByRole('radio'); + const checkedRadio = radioButtons.find(radio => radio.getAttribute('data-state') === 'checked'); + expect(checkedRadio).not.toBeNull(); + expect(checkedRadio).toHaveAttribute('value', 'option1'); + }); + + it('shows error message', () => { + render(); + expect(screen.getByText('Please select an option')).toBeInTheDocument(); + }); + + it('shows description text', () => { + render(); + expect(screen.getByText('Select one of the options')).toBeInTheDocument(); + }); + + it('renders in disabled state', () => { + render(); + const radioButtons = screen.getAllByRole('radio'); + radioButtons.forEach(radio => { + expect(radio).toBeDisabled(); + }); + }); +}); \ No newline at end of file diff --git a/packages/ui-kit/src/components/primitives/RadioGroup/RadioGroup.tsx b/packages/ui-kit/src/components/primitives/RadioGroup/RadioGroup.tsx new file mode 100644 index 0000000..df578df --- /dev/null +++ b/packages/ui-kit/src/components/primitives/RadioGroup/RadioGroup.tsx @@ -0,0 +1,70 @@ +import * as React from 'react'; +import { cn } from '@/lib/utils'; +import { RadioGroup as ShadcnRadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; + +export interface RadioOption { + value: string; + label: string; + disabled?: boolean; +} + +export interface RadioGroupProps extends Omit, 'children'> { + /** Label text displayed above the radio group */ + label?: string; + /** Description/help text rendered below */ + description?: string; + /** Marks input as invalid */ + error?: string; + /** Options for the radio group */ + options: RadioOption[]; + /** CSS class name */ + className?: string; +} + +export const RadioGroup = React.forwardRef( + ( + { label, description, error, className, options, disabled, ...props }, + ref + ) => { + return ( +
+ {label && ( + + )} + + {options.map((option) => ( +
+ + +
+ ))} +
+ {description && !error && ( +

{description}

+ )} + {error && ( +

{error}

+ )} +
+ ); + } +); + +RadioGroup.displayName = 'RadioGroup'; \ No newline at end of file diff --git a/packages/ui-kit/src/components/primitives/RadioGroup/index.ts b/packages/ui-kit/src/components/primitives/RadioGroup/index.ts new file mode 100644 index 0000000..3e3ca7e --- /dev/null +++ b/packages/ui-kit/src/components/primitives/RadioGroup/index.ts @@ -0,0 +1 @@ +export * from './RadioGroup'; \ No newline at end of file diff --git a/packages/ui-kit/src/components/primitives/RadioGroup/types.ts b/packages/ui-kit/src/components/primitives/RadioGroup/types.ts new file mode 100644 index 0000000..262f33f --- /dev/null +++ b/packages/ui-kit/src/components/primitives/RadioGroup/types.ts @@ -0,0 +1,20 @@ +import { RadioGroup as ShadcnRadioGroup } from '@/components/ui/radio-group'; + +export interface RadioOption { + value: string; + label: string; + disabled?: boolean; +} + +export interface RadioGroupProps extends React.ComponentPropsWithoutRef { + /** Label text displayed above the radio group */ + label?: string; + /** Description/help text rendered below */ + description?: string; + /** Marks radio group as invalid */ + error?: string; + /** Array of options for the radio group */ + options: RadioOption[]; + /** Optional CSS class name */ + className?: string; +} \ No newline at end of file diff --git a/packages/ui-kit/src/components/primitives/Select/Select.stories.tsx b/packages/ui-kit/src/components/primitives/Select/Select.stories.tsx new file mode 100644 index 0000000..46ad258 --- /dev/null +++ b/packages/ui-kit/src/components/primitives/Select/Select.stories.tsx @@ -0,0 +1,66 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Select } from './Select'; + +const options = [ + { value: 'option1', label: 'Option 1' }, + { value: 'option2', label: 'Option 2' }, + { value: 'option3', label: 'Option 3' }, + { value: 'option4', label: 'Option 4', disabled: true }, +]; + +const meta = { + title: 'Components/Form/Select', + component: Select, + args: { + placeholder: 'Select an option', + options, + }, + argTypes: { + label: { control: 'text' }, + description: { control: 'text' }, + error: { control: 'text' }, + disabled: { control: 'boolean' }, + value: { control: 'select', options: ['option1', 'option2', 'option3', 'option4'] }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +export const WithLabel: Story = { + args: { + label: 'Favorite Color', + }, +}; + +export const WithDescription: Story = { + args: { + label: 'Favorite Color', + description: 'Select your favorite color', + }, +}; + +export const WithError: Story = { + args: { + label: 'Favorite Color', + error: 'Please select a color', + }, +}; + +export const WithValue: Story = { + args: { + label: 'Favorite Color', + value: 'option1', + }, +}; + +export const Disabled: Story = { + args: { + label: 'Favorite Color', + disabled: true, + }, +}; \ No newline at end of file diff --git a/packages/ui-kit/src/components/primitives/Select/Select.test.tsx b/packages/ui-kit/src/components/primitives/Select/Select.test.tsx new file mode 100644 index 0000000..7ff2878 --- /dev/null +++ b/packages/ui-kit/src/components/primitives/Select/Select.test.tsx @@ -0,0 +1,31 @@ +import { render, screen } from '@testing-library/react'; +import { describe, it, expect } from 'vitest'; +import { Select } from './Select'; + +const mockOptions = [ + { value: 'option1', label: 'Option 1' }, + { value: 'option2', label: 'Option 2' }, + { value: 'option3', label: 'Option 3', disabled: true } +]; + +describe('Select', () => { + it('shows label', () => { + render(); + expect(screen.getByText('Select a color')).toBeInTheDocument(); + }); + + it('shows error message', () => { + render(); + expect(screen.getByText('Choose your favorite color')).toBeInTheDocument(); + }); +}); \ No newline at end of file diff --git a/packages/ui-kit/src/components/primitives/Select/Select.tsx b/packages/ui-kit/src/components/primitives/Select/Select.tsx new file mode 100644 index 0000000..c4450ff --- /dev/null +++ b/packages/ui-kit/src/components/primitives/Select/Select.tsx @@ -0,0 +1,92 @@ +import * as React from 'react'; +import { cn } from '@/lib/utils'; +import { + Select as ShadcnSelect, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; + +export interface SelectOption { + value: string; + label: string; + disabled?: boolean; +} + +export interface SelectProps extends Omit, 'children'> { + /** Label text displayed above the select */ + label?: string; + /** Description/help text rendered below */ + description?: string; + /** Marks select as invalid */ + error?: string; + /** Placeholder text */ + placeholder?: string; + /** Options to display in the dropdown */ + options: SelectOption[]; + /** CSS class name */ + className?: string; +} + +export const Select = React.forwardRef( + ( + { + label, + description, + error, + className, + placeholder, + options, + value, + onValueChange, + disabled, + ...props + }, + ref + ) => { + return ( +
+ {label && ( + + )} + + + + + + {options.map((option) => ( + + {option.label} + + ))} + + + {description && !error && ( +

{description}

+ )} + {error && ( +

{error}

+ )} +
+ ); + } +); + +Select.displayName = 'Select'; \ No newline at end of file diff --git a/packages/ui-kit/src/components/primitives/Select/index.ts b/packages/ui-kit/src/components/primitives/Select/index.ts new file mode 100644 index 0000000..e4f6f2b --- /dev/null +++ b/packages/ui-kit/src/components/primitives/Select/index.ts @@ -0,0 +1 @@ +export * from './Select'; \ No newline at end of file diff --git a/packages/ui-kit/src/components/primitives/Select/types.ts b/packages/ui-kit/src/components/primitives/Select/types.ts new file mode 100644 index 0000000..4b13939 --- /dev/null +++ b/packages/ui-kit/src/components/primitives/Select/types.ts @@ -0,0 +1,26 @@ +import { Select as ShadcnSelect } from '@/components/ui/select'; + +export interface SelectOption { + value: string; + label: string; + disabled?: boolean; +} + +export interface SelectProps extends React.ComponentPropsWithoutRef { + /** Label text displayed above the select */ + label?: string; + /** Description/help text rendered below */ + description?: string; + /** Marks select as invalid */ + error?: string; + /** Placeholder text when no option is selected */ + placeholder?: string; + /** Array of options for the select */ + options: SelectOption[]; + /** Current value of the select */ + value?: string; + /** Callback when value changes */ + onValueChange?: (value: string) => void; + /** Optional CSS class name */ + className?: string; +} \ No newline at end of file diff --git a/packages/ui-kit/src/components/primitives/TextInput/TextInput.tsx b/packages/ui-kit/src/components/primitives/TextInput/TextInput.tsx index b9e7e75..e1bdc5f 100644 --- a/packages/ui-kit/src/components/primitives/TextInput/TextInput.tsx +++ b/packages/ui-kit/src/components/primitives/TextInput/TextInput.tsx @@ -1,20 +1,43 @@ import * as React from 'react'; -import { Input as ShadcnInput, type InputProps as ShadcnInputProps } from '@/components/ui/input'; +import { Input } from '@/components/ui/input'; +import { cn } from '@/lib/utils'; -export interface TextInputProps extends ShadcnInputProps { +export interface TextInputProps extends Omit, 'size'> { /** Label text displayed above the input */ label?: string; /** Description/help text rendered below */ description?: string; /** Marks input as invalid */ error?: string; + /** Visual size of the input */ + size?: 'default' | 'sm' | 'lg'; + /** Optional CSS class name for the wrapper div */ + className?: string; + /** Optional CSS class name for the input element */ + inputClassName?: string; } export const TextInput = React.forwardRef( ( - { label, description, error, className, ...props }, + { + label, + description, + error, + size = 'default', + className, + inputClassName, + ...props + }, ref ) => { + // Size classes for the input element + let sizeClass = ''; + if (size === 'sm') { + sizeClass = 'h-8 text-xs'; + } else if (size === 'lg') { + sizeClass = 'h-10 text-base'; + } + return (
{label && ( @@ -22,11 +45,15 @@ export const TextInput = React.forwardRef( {label} )} - {description && !error && (

{description}

diff --git a/packages/ui-kit/src/components/primitives/TextInput/types.ts b/packages/ui-kit/src/components/primitives/TextInput/types.ts deleted file mode 100644 index 8af0b3a..0000000 --- a/packages/ui-kit/src/components/primitives/TextInput/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { InputProps as ShadInputProps } from '@/components/ui/input'; - -export interface TextInputProps extends Omit { - /** Visual size of the input */ - size?: 'default' | 'sm' | 'lg'; -} \ No newline at end of file diff --git a/packages/ui-kit/src/components/primitives/index.ts b/packages/ui-kit/src/components/primitives/index.ts index 1332698..937c488 100644 --- a/packages/ui-kit/src/components/primitives/index.ts +++ b/packages/ui-kit/src/components/primitives/index.ts @@ -1,2 +1,6 @@ export * from './Button' -export * from './TextInput' \ No newline at end of file +export * from './TextInput' +export * from './NumberInput' +export * from './Select' +export * from './Checkbox' +export * from './RadioGroup' \ No newline at end of file diff --git a/packages/ui-kit/src/components/ui/button.tsx b/packages/ui-kit/src/components/ui/button.tsx index f65c2c5..65d4fcd 100644 --- a/packages/ui-kit/src/components/ui/button.tsx +++ b/packages/ui-kit/src/components/ui/button.tsx @@ -1,28 +1,57 @@ -import * as React from "react"; -import { Slot } from "@radix-ui/react-slot"; -import { type VariantProps } from "class-variance-authority"; -import { cn } from "@/utils/cn"; -import { buttonVariants } from "./button-variants"; +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + { + variants: { + variant: { + default: + "bg-primary text-primary-foreground shadow hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + outline: + "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-8", + icon: "h-9 w-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) export interface ButtonProps - extends React.ButtonHTMLAttributes, + extends React.ButtonHTMLAttributes, VariantProps { - asChild?: boolean; + asChild?: boolean } const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button"; - return ( - - ); - } -); - -Button.displayName = "Button"; + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" -export { Button }; \ No newline at end of file +export { Button, buttonVariants } diff --git a/packages/ui-kit/src/components/ui/checkbox.tsx b/packages/ui-kit/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..26c488a --- /dev/null +++ b/packages/ui-kit/src/components/ui/checkbox.tsx @@ -0,0 +1,30 @@ +"use client" + +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { Check } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)) +Checkbox.displayName = CheckboxPrimitive.Root.displayName + +export { Checkbox } diff --git a/packages/ui-kit/src/components/ui/input.tsx b/packages/ui-kit/src/components/ui/input.tsx index 1c9e494..69b64fb 100644 --- a/packages/ui-kit/src/components/ui/input.tsx +++ b/packages/ui-kit/src/components/ui/input.tsx @@ -1,23 +1,22 @@ -import * as React from "react"; -import { cn } from "@/utils/cn"; +import * as React from "react" -export type InputProps = React.InputHTMLAttributes; +import { cn } from "@/lib/utils" -const Input = React.forwardRef( - ({ className, type, ...props }, ref) => { - return ( - - ); - } -); -Input.displayName = "Input"; +const Input = React.forwardRef>( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = "Input" -export { Input }; \ No newline at end of file +export { Input } diff --git a/packages/ui-kit/src/components/ui/radio-group.tsx b/packages/ui-kit/src/components/ui/radio-group.tsx new file mode 100644 index 0000000..56f96f0 --- /dev/null +++ b/packages/ui-kit/src/components/ui/radio-group.tsx @@ -0,0 +1,42 @@ +import * as React from "react" +import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" +import { Circle } from "lucide-react" + +import { cn } from "@/lib/utils" + +const RadioGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + ) +}) +RadioGroup.displayName = RadioGroupPrimitive.Root.displayName + +const RadioGroupItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + + + + + ) +}) +RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName + +export { RadioGroup, RadioGroupItem } diff --git a/packages/ui-kit/src/components/ui/select.tsx b/packages/ui-kit/src/components/ui/select.tsx new file mode 100644 index 0000000..2002f34 --- /dev/null +++ b/packages/ui-kit/src/components/ui/select.tsx @@ -0,0 +1,157 @@ +import * as React from "react" +import * as SelectPrimitive from "@radix-ui/react-select" +import { Check, ChevronDown, ChevronUp } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1", + className + )} + {...props} + > + {children} + + + + +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +} diff --git a/packages/ui-kit/src/lib/utils.ts b/packages/ui-kit/src/lib/utils.ts new file mode 100644 index 0000000..bd0c391 --- /dev/null +++ b/packages/ui-kit/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/packages/ui-kit/src/styles/globals.css b/packages/ui-kit/src/styles/globals.css index 58b7cf4..d9cd673 100644 --- a/packages/ui-kit/src/styles/globals.css +++ b/packages/ui-kit/src/styles/globals.css @@ -3,4 +3,69 @@ @tailwind base; @tailwind components; -@tailwind utilities; \ No newline at end of file +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55% + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} \ No newline at end of file diff --git a/packages/ui-kit/tailwind.config.js b/packages/ui-kit/tailwind.config.js index 6e5c03f..f534e99 100644 --- a/packages/ui-kit/tailwind.config.js +++ b/packages/ui-kit/tailwind.config.js @@ -4,66 +4,77 @@ export default { './src/**/*.{js,ts,jsx,tsx}', './.storybook/**/*.{js,ts,jsx,tsx}', ], - darkMode: 'class', + darkMode: ['class', 'class'], theme: { - extend: { - colors: { - border: 'hsl(var(--border))', - input: 'hsl(var(--input))', - ring: 'hsl(var(--ring))', - background: 'hsl(var(--background))', - foreground: 'hsl(var(--foreground))', - primary: { - DEFAULT: 'hsl(var(--primary))', - foreground: 'hsl(var(--primary-foreground))', - }, - secondary: { - DEFAULT: 'hsl(var(--secondary))', - foreground: 'hsl(var(--secondary-foreground))', - }, - accent: { - DEFAULT: 'hsl(var(--accent))', - foreground: 'hsl(var(--accent-foreground))', - }, - destructive: { - DEFAULT: 'hsl(var(--destructive))', - foreground: 'hsl(var(--destructive-foreground))', - }, - card: { - DEFAULT: 'hsl(var(--card))', - foreground: 'hsl(var(--card-foreground))', - }, - popover: { - DEFAULT: 'hsl(var(--popover))', - foreground: 'hsl(var(--popover-foreground))', - }, - success: { - DEFAULT: 'hsl(var(--success))', - foreground: 'hsl(var(--success-foreground))', - }, - warning: { - DEFAULT: 'hsl(var(--warning))', - foreground: 'hsl(var(--warning-foreground))', - }, - info: { - DEFAULT: 'hsl(var(--info))', - foreground: 'hsl(var(--info-foreground))', - }, - }, - borderRadius: { - lg: 'var(--radius)', - md: 'calc(var(--radius) - 2px)', - sm: 'calc(var(--radius) - 4px)', - }, - boxShadow: { - sm: 'var(--shadow-sm)', - DEFAULT: 'var(--shadow)', - md: 'var(--shadow-md)', - lg: 'var(--shadow-lg)', - }, - }, + extend: { + colors: { + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))' + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))' + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))' + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))' + }, + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))' + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))' + }, + success: { + DEFAULT: 'hsl(var(--success))', + foreground: 'hsl(var(--success-foreground))' + }, + warning: { + DEFAULT: 'hsl(var(--warning))', + foreground: 'hsl(var(--warning-foreground))' + }, + info: { + DEFAULT: 'hsl(var(--info))', + foreground: 'hsl(var(--info-foreground))' + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))' + }, + chart: { + '1': 'hsl(var(--chart-1))', + '2': 'hsl(var(--chart-2))', + '3': 'hsl(var(--chart-3))', + '4': 'hsl(var(--chart-4))', + '5': 'hsl(var(--chart-5))' + } + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + }, + boxShadow: { + sm: 'var(--shadow-sm)', + DEFAULT: 'var(--shadow)', + md: 'var(--shadow-md)', + lg: 'var(--shadow-lg)' + } + } }, - plugins: [require('daisyui')], + plugins: [require('daisyui'), require("tailwindcss-animate")], daisyui: { themes: ['light', 'dark'], base: true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ee428c..0c055b9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,15 @@ importers: .: dependencies: + '@radix-ui/react-checkbox': + specifier: ^1.3.1 + version: 1.3.1(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-radio-group': + specifier: ^1.3.6 + version: 1.3.6(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-select': + specifier: ^2.2.4 + version: 2.2.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: specifier: ^19.1.0 version: 19.1.0 @@ -108,21 +117,33 @@ importers: packages/ui-kit: dependencies: + '@radix-ui/react-checkbox': + specifier: ^1.3.1 + version: 1.3.1(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-radio-group': + specifier: ^1.3.6 + version: 1.3.6(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-select': + specifier: ^2.2.4 + version: 2.2.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-slot': - specifier: ^1.0.2 + specifier: ^1.2.2 version: 1.2.2(@types/react@19.1.4)(react@19.1.0) '@types/testing-library__jest-dom': specifier: ^6.0.0 version: 6.0.0 class-variance-authority: - specifier: ^0.7.0 + specifier: ^0.7.1 version: 0.7.1 clsx: - specifier: ^2.1.0 + specifier: ^2.1.1 version: 2.1.1 jsdom: specifier: ^26.1.0 version: 26.1.0 + lucide-react: + specifier: ^0.511.0 + version: 0.511.0(react@19.1.0) react: specifier: ^19.1.0 version: 19.1.0 @@ -130,7 +151,7 @@ importers: specifier: ^19.1.0 version: 19.1.0(react@19.1.0) tailwind-merge: - specifier: ^2.2.1 + specifier: ^2.6.0 version: 2.6.0 tailwindcss-animate: specifier: ^1.0.7 @@ -1307,6 +1328,21 @@ packages: resolution: {integrity: sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@floating-ui/core@1.7.0': + resolution: {integrity: sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA==} + + '@floating-ui/dom@1.7.0': + resolution: {integrity: sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg==} + + '@floating-ui/react-dom@2.1.2': + resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.9': + resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -1407,6 +1443,51 @@ packages: engines: {node: '>=18'} hasBin: true + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.2': + resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==} + + '@radix-ui/react-arrow@1.1.6': + resolution: {integrity: sha512-2JMfHJf/eVnwq+2dewT3C0acmCWD3XiVA1Da+jTDqo342UlU13WvXtqHhG+yJw5JeQmu4ue2eMy6gcEArLBlcw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-checkbox@1.3.1': + resolution: {integrity: sha512-xTaLKAO+XXMPK/BpVTSaAAhlefmvMSACjIhK9mGsImvX2ljcTDm8VGR1CuS1uYcNdR5J+oiOhoJZc5un6bh3VQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.6': + resolution: {integrity: sha512-PbhRFK4lIEw9ADonj48tiYWzkllz81TM7KVYyyMMw2cwHO7D5h4XKEblL8NlaRisTK3QTe6tBEhDccFUryxHBQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-compose-refs@1.1.2': resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} peerDependencies: @@ -1416,6 +1497,159 @@ packages: '@types/react': optional: true + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.9': + resolution: {integrity: sha512-way197PiTvNp+WBP7svMJasHl+vibhWGQDb6Mgf5mhEWJkgb85z7Lfl9TUdkqpWsf8GRNmoopx9ZxCyDzmgRMQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.2': + resolution: {integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.6': + resolution: {integrity: sha512-r9zpYNUQY+2jWHWZGyddQLL9YHkM/XvSFHVcWs7bdVuxMAnCwTAuy6Pf47Z4nw7dYcUou1vg/VgjjrrH03VeBw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-popper@1.2.6': + resolution: {integrity: sha512-7iqXaOWIjDBfIG7aq8CUEeCSsQMLFdn7VEE8TaFz704DtEzpPHR7w/uuzRflvKgltqSAImgcmxQ7fFX3X7wasg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.8': + resolution: {integrity: sha512-hQsTUIn7p7fxCPvao/q6wpbxmCwgLrlz+nOrJgC+RwfZqWY/WN+UMqkXzrtKbPrF82P43eCTl3ekeKuyAQbFeg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.4': + resolution: {integrity: sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.2': + resolution: {integrity: sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-radio-group@1.3.6': + resolution: {integrity: sha512-1tfTAqnYZNVwSpFhCT273nzK8qGBReeYnNTPspCggqk1fvIrfVxJekIuBFidNivzpdiMqDwVGnQvHqXrRPM4Og==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.9': + resolution: {integrity: sha512-ZzrIFnMYHHCNqSNCsuN6l7wlewBEq0O0BCSBkabJMFXVO51LRUTq71gLP1UxFvmrXElqmPjA5VX7IqC9VpazAQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.2.4': + resolution: {integrity: sha512-/OOm58Gil4Ev5zT8LyVzqfBcij4dTHYdeyuF5lMHZ2bIp0Lk9oETocYiJ5QC0dHekEQnK6L/FNJCceeb4AkZ6Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-slot@1.2.2': resolution: {integrity: sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ==} peerDependencies: @@ -1425,6 +1659,94 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.2': + resolution: {integrity: sha512-ORCmRUbNiZIv6uV5mhFrhsIKw4UX/N3syZtyqvry61tbGm4JlgQuSn0hk5TwCARsCjkcnuRkSdCE3xfb+ADHew==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@rollup/pluginutils@5.1.4': resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} engines: {node: '>=14.0.0'} @@ -2062,6 +2384,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} @@ -2601,6 +2927,9 @@ packages: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -3026,6 +3355,10 @@ packages: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -3686,6 +4019,11 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@0.511.0: + resolution: {integrity: sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -4239,6 +4577,36 @@ packages: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.0: + resolution: {integrity: sha512-sGsQtcjMqdQyijAHytfGEELB8FufGbfXIsvUTe+NLx1GDRJCXtCFLBLUI1eyZCKXXvbEU2C6gai0PZKoIE9Vbg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + react@19.1.0: resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} @@ -4856,6 +5224,26 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + utf8-byte-length@1.0.5: resolution: {integrity: sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==} @@ -6341,6 +6729,23 @@ snapshots: '@eslint/core': 0.14.0 levn: 0.4.1 + '@floating-ui/core@1.7.0': + dependencies: + '@floating-ui/utils': 0.2.9 + + '@floating-ui/dom@1.7.0': + dependencies: + '@floating-ui/core': 1.7.0 + '@floating-ui/utils': 0.2.9 + + '@floating-ui/react-dom@2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@floating-ui/dom': 1.7.0 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@floating-ui/utils@0.2.9': {} + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.6': @@ -6449,12 +6854,213 @@ snapshots: dependencies: playwright: 1.52.0 + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.2': {} + + '@radix-ui/react-arrow@1.1.6(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.4 + '@types/react-dom': 19.1.5(@types/react@19.1.4) + + '@radix-ui/react-checkbox@1.3.1(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.4)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.4 + '@types/react-dom': 19.1.5(@types/react@19.1.4) + + '@radix-ui/react-collection@1.1.6(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.2(@types/react@19.1.4)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.4 + '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.4)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: '@types/react': 19.1.4 + '@radix-ui/react-context@1.1.2(@types/react@19.1.4)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.4 + + '@radix-ui/react-direction@1.1.1(@types/react@19.1.4)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.4 + + '@radix-ui/react-dismissable-layer@1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.4)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.4 + '@types/react-dom': 19.1.5(@types/react@19.1.4) + + '@radix-ui/react-focus-guards@1.1.2(@types/react@19.1.4)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.4 + + '@radix-ui/react-focus-scope@1.1.6(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.4 + '@types/react-dom': 19.1.5(@types/react@19.1.4) + + '@radix-ui/react-id@1.1.1(@types/react@19.1.4)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.4 + + '@radix-ui/react-popper@1.2.6(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@floating-ui/react-dom': 2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-arrow': 1.1.6(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/rect': 1.1.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.4 + '@types/react-dom': 19.1.5(@types/react@19.1.4) + + '@radix-ui/react-portal@1.1.8(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.4 + '@types/react-dom': 19.1.5(@types/react@19.1.4) + + '@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.4 + '@types/react-dom': 19.1.5(@types/react@19.1.4) + + '@radix-ui/react-primitive@2.1.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-slot': 1.2.2(@types/react@19.1.4)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.4 + '@types/react-dom': 19.1.5(@types/react@19.1.4) + + '@radix-ui/react-radio-group@1.3.6(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-roving-focus': 1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.4)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.4 + '@types/react-dom': 19.1.5(@types/react@19.1.4) + + '@radix-ui/react-roving-focus@1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.6(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.4 + '@types/react-dom': 19.1.5(@types/react@19.1.4) + + '@radix-ui/react-select@2.2.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.6(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.6(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-popper': 1.2.6(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-portal': 1.1.8(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-visually-hidden': 1.2.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + aria-hidden: 1.2.6 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-remove-scroll: 2.7.0(@types/react@19.1.4)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.4 + '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-slot@1.2.2(@types/react@19.1.4)(react@19.1.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) @@ -6462,6 +7068,71 @@ snapshots: optionalDependencies: '@types/react': 19.1.4 + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.4)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.4 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.4)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.4 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.4)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.4 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.4)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.4 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.4)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.4 + + '@radix-ui/react-use-previous@1.1.1(@types/react@19.1.4)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.4 + + '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.4)(react@19.1.0)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.4 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.1.4)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.4 + + '@radix-ui/react-visually-hidden@1.2.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.4 + '@types/react-dom': 19.1.5(@types/react@19.1.4) + + '@radix-ui/rect@1.1.1': {} + '@rollup/pluginutils@5.1.4(rollup@4.41.0)': dependencies: '@types/estree': 1.0.7 @@ -7242,6 +7913,10 @@ snapshots: argparse@2.0.1: {} + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + aria-query@5.3.0: dependencies: dequal: 2.0.3 @@ -7828,6 +8503,8 @@ snapshots: detect-indent@6.1.0: {} + detect-node-es@1.1.0: {} + didyoumean@1.2.2: {} diff-sequences@29.6.3: {} @@ -8409,6 +9086,8 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 + get-nonce@1.0.1: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -9112,6 +9791,10 @@ snapshots: dependencies: yallist: 3.1.1 + lucide-react@0.511.0(react@19.1.0): + dependencies: + react: 19.1.0 + lz-string@1.5.0: {} magic-string@0.27.0: @@ -9600,6 +10283,33 @@ snapshots: react-refresh@0.17.0: {} + react-remove-scroll-bar@2.3.8(@types/react@19.1.4)(react@19.1.0): + dependencies: + react: 19.1.0 + react-style-singleton: 2.2.3(@types/react@19.1.4)(react@19.1.0) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.4 + + react-remove-scroll@2.7.0(@types/react@19.1.4)(react@19.1.0): + dependencies: + react: 19.1.0 + react-remove-scroll-bar: 2.3.8(@types/react@19.1.4)(react@19.1.0) + react-style-singleton: 2.2.3(@types/react@19.1.4)(react@19.1.0) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.1.4)(react@19.1.0) + use-sidecar: 1.1.3(@types/react@19.1.4)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.4 + + react-style-singleton@2.2.3(@types/react@19.1.4)(react@19.1.0): + dependencies: + get-nonce: 1.0.1 + react: 19.1.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.4 + react@19.1.0: {} read-cache@1.0.0: @@ -10295,6 +11005,21 @@ snapshots: dependencies: punycode: 2.3.1 + use-callback-ref@1.3.3(@types/react@19.1.4)(react@19.1.0): + dependencies: + react: 19.1.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.4 + + use-sidecar@1.1.3(@types/react@19.1.4)(react@19.1.0): + dependencies: + detect-node-es: 1.1.0 + react: 19.1.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.4 + utf8-byte-length@1.0.5: {} util-deprecate@1.0.2: {} diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..9175a85 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} \ No newline at end of file