diff --git a/.gitignore b/.gitignore index 2a5274f..5a452a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules .kiro .vscode -.agents/ \ No newline at end of file +.agents/ +.gemini \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8133aaa..c802987 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,6 +53,30 @@ A new file will be created in the `.changeset` directory. Commit this file with - For components: `pnpm dev:kombase` - For documentation: `pnpm dev:docs` +## Source Code Location + +All component source code lives in the `registry/` directory, organized by category: + +- `registry/ui/` — UI primitives (button, input, dialog, etc.) +- `registry/components/` — Complex components (data-table, tour, stepper, etc.) +- `registry/form/` — Form wrappers with react-hook-form integration +- `registry/hooks/` — Custom React hooks +- `registry/lib/` — Utility functions and helpers + +**This is the single source of truth.** When editing components, always edit files in `registry/`, not `packages/src/`. + +The `packages/src/` directory is maintained temporarily for npm backward compatibility and will be removed after full registry cutover. + +### Building the Registry + +To build the registry output (generates JSON files for shadcn CLI): + +```bash +pnpm build:registry +``` + +Output is written to `docs/public/r/`. + ## Documentation Our documentation is built with [Fumadocs](https://fumadocs.dev/). If you are updating components, please also update the corresponding documentation in the `docs` package. diff --git a/commitlint.config.js b/commitlint.config.mjs similarity index 100% rename from commitlint.config.js rename to commitlint.config.mjs diff --git a/docs/app/content/components/action-bar.mdx b/docs/app/content/components/action-bar.mdx index 0cc4825..28001d9 100644 --- a/docs/app/content/components/action-bar.mdx +++ b/docs/app/content/components/action-bar.mdx @@ -4,6 +4,12 @@ description: A floating toolbar that appears conditionally, typically used for b preview: false --- +## Installation + +```bash +npx shadcn@latest add @kombase/action-bar +``` + ## Overview @@ -20,7 +26,7 @@ import { ActionBarItem, ActionBarSelection, ActionBarSeparator, -} from "kombase"; +} from "@/components/action-bar"; ``` ## Anatomy diff --git a/docs/app/content/components/avatar-group.mdx b/docs/app/content/components/avatar-group.mdx index 291e4c5..ddc4649 100644 --- a/docs/app/content/components/avatar-group.mdx +++ b/docs/app/content/components/avatar-group.mdx @@ -4,6 +4,12 @@ description: A container component for grouping overlapping avatars or custom ic preview: false --- +## Installation + +```bash +npx shadcn@latest add @kombase/avatar-group +``` + ## Examples ### Truncation diff --git a/docs/app/content/components/confirm-dialog.mdx b/docs/app/content/components/confirm-dialog.mdx index fb4f4c0..5b9e5d1 100644 --- a/docs/app/content/components/confirm-dialog.mdx +++ b/docs/app/content/components/confirm-dialog.mdx @@ -4,6 +4,12 @@ description: Confirmation dialog component built on top of AlertDialog. preview: false --- +## Installation + +```bash +npx shadcn@latest add @kombase/confirm-dialog +``` + ## Overview `ConfirmDialog` is a reusable confirmation modal built using `AlertDialog`. diff --git a/docs/app/content/components/data-table.mdx b/docs/app/content/components/data-table.mdx index dd8ee69..2e64628 100644 --- a/docs/app/content/components/data-table.mdx +++ b/docs/app/content/components/data-table.mdx @@ -7,6 +7,12 @@ links: api: /docs/components/radix/data-table#api-reference --- +## Installation + +```bash +npx shadcn@latest add @kombase/data-table +``` + Reference the [TanStack Table Column Definitions Guide](https://tanstack.com/table/latest/docs/guide/column-defs#column-definitions-guide) for detailed column definition guide. @@ -29,12 +35,10 @@ Reference the [TanStack Table Column Definitions Guide](https://tanstack.com/tab Import all components and combine them: ```tsx -import { - DataTable, - DataTableToolbar, - DataTableAdvanceFilter, - useDataTable -} from "kombase"; +import { DataTable } from "@/components/data-table/data-table"; +import { DataTableAdvanceFilter } from "@/components/data-table/data-table-advance-filter"; +import { DataTableToolbar } from "@/components/data-table/data-table-toolbar"; +import { useDataTable } from "@/hooks/use-data-table"; const { table } = useDataTable({ data, @@ -75,7 +79,7 @@ const { table } = useDataTable({ ```tsx import { Text, CalendarIcon, DollarSign } from "lucide-react"; - import { DataTableColumnHeader } from "kombase"; + import { DataTableColumnHeader } from "@/components/data-table/data-table-column-header"; const columns = React.useMemo(() => [ { @@ -105,7 +109,7 @@ const { table } = useDataTable({ Initialize the table state using the `useDataTable` hook: ```tsx - import { useDataTable } from "kombase"; + import { useDataTable } from "@/hooks/use-data-table"; function TableDemo() { const { table } = useDataTable({ @@ -133,7 +137,8 @@ const { table } = useDataTable({ Pass the table instance to the `DataTable`, and `DataTableToolbar` components: ```tsx - import { DataTable, DataTableToolbar } from "kombase"; + import { DataTable } from "@/components/data-table/data-table"; + import { DataTableToolbar } from "@/components/data-table/data-table-toolbar"; function DataTableDemo() { return ( @@ -208,7 +213,7 @@ The `useDataTable` hook acts as an intelligent wrapper around `useReactTable` fr ### Import ```ts -import { useDataTable } from "kombase"; +import { useDataTable } from "@/hooks/use-data-table"; ``` ### Usage @@ -219,7 +224,9 @@ First, define your columns. Use the `meta` property to configure filter behavior ```tsx import { useMemo, useState } from "react"; -import { useDataTable, DataTable, DataTableColumnHeader } from "kombase"; +import { DataTable } from "@/components/data-table/data-table"; +import { DataTableColumnHeader } from "@/components/data-table/data-table-column-header"; +import { useDataTable } from "@/hooks/use-data-table"; import type { ColumnDef } from "@tanstack/react-table"; function useInvoiceColumns(): ColumnDef[] { @@ -296,7 +303,8 @@ If you don't provide `page`, `perPage`, or `onPageChange`, the hook will manage ```tsx import * as React from "react"; import { useInfiniteQuery } from "@tanstack/react-query"; -import { DataTable, useDataTable } from "kombase"; +import { DataTable } from "@/components/data-table/data-table"; +import { useDataTable } from "@/hooks/use-data-table"; function InfiniteScrollTable() { const { @@ -368,7 +376,10 @@ Here is a complete step-by-step walkthrough for React Router v7 / Remix: ```tsx import { useSearchParams } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; -import { DataTable, DataTableToolbar, DataTablePagination, useDataTable } from "kombase"; +import { DataTable } from "@/components/data-table/data-table"; +import { DataTablePagination } from "@/components/data-table/data-table-pagination"; +import { DataTableToolbar } from "@/components/data-table/data-table-toolbar"; +import { useDataTable } from "@/hooks/use-data-table"; function InvoicesTable() { const [searchParams, setSearchParams] = useSearchParams(); @@ -479,7 +490,7 @@ Instead of writing manual mapper functions for every column, the built-in `resol ##### Import ```ts -import { resolveFiltersToFlatParams, mapDateFilterToParams } from "kombase"; +import { resolveFiltersToFlatParams, mapDateFilterToParams } from "@/lib/filter-helper"; ``` ##### Configuration Options @@ -663,7 +674,7 @@ meta: { #### Skeleton Usage ```tsx -import { DataTableSkeleton } from "kombase"; +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"; function MyTableSkeleton() { return ( @@ -683,7 +694,7 @@ The recommended pattern when using frameworks like Next.js or React Router v7 wi ```tsx import { Suspense } from "react"; -import { DataTableSkeleton } from "kombase"; +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"; function Page() { return ( diff --git a/docs/app/content/components/file-upload.mdx b/docs/app/content/components/file-upload.mdx index 648aaa0..34ba078 100644 --- a/docs/app/content/components/file-upload.mdx +++ b/docs/app/content/components/file-upload.mdx @@ -4,6 +4,12 @@ description: is a flexible, highly customizable file upload dropzone and list sy preview: false --- +## Installation + +```bash +npx shadcn@latest add @kombase/file-upload +``` + ## Examples ### Direct Upload diff --git a/docs/app/content/components/long-text.mdx b/docs/app/content/components/long-text.mdx index 2bbc04f..765f6c7 100644 --- a/docs/app/content/components/long-text.mdx +++ b/docs/app/content/components/long-text.mdx @@ -4,6 +4,12 @@ description: Utility component for truncating long text with tooltip and popover preview: false --- +## Installation + +```bash +npx shadcn@latest add @kombase/long-text +``` + ## Overview `LongText` automatically truncates overflowing text using CSS `truncate`. diff --git a/docs/app/content/components/password-input.mdx b/docs/app/content/components/password-input.mdx new file mode 100644 index 0000000..0dddb56 --- /dev/null +++ b/docs/app/content/components/password-input.mdx @@ -0,0 +1,27 @@ +--- +title: PasswordInput +description: A password field with a show/hide toggle button. +preview: false +--- + +## Installation + +```bash +npx shadcn@latest add @kombase/password-input +``` + +## Examples + +### Basic + + + +### With Form Validation + + + +## API + +### FormInputPassword + + diff --git a/docs/app/content/components/phone-input.mdx b/docs/app/content/components/phone-input.mdx index 459af36..bd13aec 100644 --- a/docs/app/content/components/phone-input.mdx +++ b/docs/app/content/components/phone-input.mdx @@ -4,6 +4,12 @@ description: An accessible phone input component with automatic country detectio preview: false --- +## Installation + +```bash +npx shadcn@latest add @kombase/phone-input +``` + ## Examples ### Basic diff --git a/docs/app/content/components/rating.mdx b/docs/app/content/components/rating.mdx index 3c711fb..4265a31 100644 --- a/docs/app/content/components/rating.mdx +++ b/docs/app/content/components/rating.mdx @@ -4,10 +4,16 @@ description: A customizable rating component with support for custom sizes, valu preview: false --- +## Installation + +```bash +npx shadcn@latest add @kombase/rating +``` + ## Components Layout ```tsx -import { Rating, RatingItem } from "kombase"; +import { Rating, RatingItem } from "@/components/rating"; export default function RatingDemo() { return ( diff --git a/docs/app/content/components/stepper.mdx b/docs/app/content/components/stepper.mdx index 0a923cb..b92ed56 100644 --- a/docs/app/content/components/stepper.mdx +++ b/docs/app/content/components/stepper.mdx @@ -4,6 +4,12 @@ description: A flexible multi-step process indicator component supporting custom preview: false --- +## Installation + +```bash +npx shadcn@latest add @kombase/stepper +``` + ## Examples ### Vertical Orientation diff --git a/docs/app/content/components/timeline.mdx b/docs/app/content/components/timeline.mdx index c9715ed..adc736b 100644 --- a/docs/app/content/components/timeline.mdx +++ b/docs/app/content/components/timeline.mdx @@ -4,6 +4,12 @@ description: A customizable, visual timeline component for presenting events, st preview: false --- +## Installation + +```bash +npx shadcn@latest add @kombase/timeline +``` + ## Components Layout ```tsx @@ -17,7 +23,7 @@ import { TimelineItem, TimelineTime, TimelineTitle, -} from "kombase"; +} from "@/components/timeline"; export default function TimelineDemo() { return ( diff --git a/docs/app/content/components/tour.mdx b/docs/app/content/components/tour.mdx index 17138ca..a4e0098 100644 --- a/docs/app/content/components/tour.mdx +++ b/docs/app/content/components/tour.mdx @@ -4,6 +4,12 @@ description: A flexible step-by-step product tour component to guide users throu preview: false --- +## Installation + +```bash +npx shadcn@latest add @kombase/tour +``` + ## Components Layout ```tsx @@ -23,7 +29,7 @@ import { TourStep, TourStepCounter, TourTitle, -} from "kombase"; +} from "@/components/tour"; export default function TourDemo() { return ( diff --git a/docs/app/content/form/form-date-picker.mdx b/docs/app/content/form/form-date-picker.mdx index 87a8394..42f7b9a 100644 --- a/docs/app/content/form/form-date-picker.mdx +++ b/docs/app/content/form/form-date-picker.mdx @@ -4,6 +4,12 @@ description: is the standard text input element. preview: false --- +## Installation + +```bash +npx shadcn@latest add @kombase/form-date-picker +``` + ## Examples ### FormDatePicker diff --git a/docs/app/content/form/form-input-group.mdx b/docs/app/content/form/form-input-group.mdx index 230e3b4..392bc69 100644 --- a/docs/app/content/form/form-input-group.mdx +++ b/docs/app/content/form/form-input-group.mdx @@ -4,6 +4,12 @@ description: renders an input equipped with an **optional right-side addon** (te preview: false --- +## Installation + +```bash +npx shadcn@latest add @kombase/form-input-group +``` + ## Examples ### FormInputGroup diff --git a/docs/app/content/form/form-input.mdx b/docs/app/content/form/form-input.mdx index 55f26b9..8571ce4 100644 --- a/docs/app/content/form/form-input.mdx +++ b/docs/app/content/form/form-input.mdx @@ -4,6 +4,12 @@ description: is the standard text input element. preview: false --- +## Installation + +```bash +npx shadcn@latest add @kombase/form-input +``` + ## Examples ### FormInput diff --git a/docs/app/content/form/form-password.mdx b/docs/app/content/form/form-password.mdx new file mode 100644 index 0000000..8e6f55f --- /dev/null +++ b/docs/app/content/form/form-password.mdx @@ -0,0 +1,23 @@ +--- +title: FormPassword +description: A password input field with a show/hide toggle button, integrated with react-hook-form. +preview: false +--- + +## Installation + +```bash +npx shadcn@latest add @kombase/form-password +``` + +## Examples + +### FormPassword + + + +## API + +### FormInputPassword + + diff --git a/docs/app/content/form/form-phone-input.mdx b/docs/app/content/form/form-phone-input.mdx new file mode 100644 index 0000000..0fb792c --- /dev/null +++ b/docs/app/content/form/form-phone-input.mdx @@ -0,0 +1,23 @@ +--- +title: FormPhoneInput +description: International phone number input with country code selector and flag icons, integrated with react-hook-form. +preview: false +--- + +## Installation + +```bash +npx shadcn@latest add @kombase/form-phone-input +``` + +## Examples + +### FormPhoneInput + + + +## API + +### FormPhoneInput + + diff --git a/docs/app/content/form/form-pick.mdx b/docs/app/content/form/form-pick.mdx index c30b486..fe736f3 100644 --- a/docs/app/content/form/form-pick.mdx +++ b/docs/app/content/form/form-pick.mdx @@ -4,6 +4,12 @@ description: renders selectable options as **clickable cards**. It natively supp preview: false --- +## Installation + +```bash +npx shadcn@latest add @kombase/form-pick +``` + ## Examples ### FormPick @@ -14,14 +20,18 @@ preview: false ### FormPick - +/> */} + +(Type table details) ### `PickOption` Type - +{/* */} + +(Type table details) ### Custom Render Slot diff --git a/docs/app/content/form/form-radio.mdx b/docs/app/content/form/form-radio.mdx index 74aae80..d2099e8 100644 --- a/docs/app/content/form/form-radio.mdx +++ b/docs/app/content/form/form-radio.mdx @@ -4,6 +4,12 @@ description: Renders a list of options with standard circular radio buttons next preview: false --- +## Installation + +```bash +npx shadcn@latest add @kombase/form-radio +``` + ## Examples ### FormRadio diff --git a/docs/app/content/form/form-search-select.mdx b/docs/app/content/form/form-search-select.mdx index fdb710e..113b7b4 100644 --- a/docs/app/content/form/form-search-select.mdx +++ b/docs/app/content/form/form-search-select.mdx @@ -4,6 +4,12 @@ description: is an advanced autocomplete component with **search filtering**, ** preview: false --- +## Installation + +```bash +npx shadcn@latest add @kombase/form-search-select +``` + ## Examples ### FormSearchSelect diff --git a/docs/app/content/form/form-textarea.mdx b/docs/app/content/form/form-textarea.mdx index 041f848..1e2f270 100644 --- a/docs/app/content/form/form-textarea.mdx +++ b/docs/app/content/form/form-textarea.mdx @@ -4,6 +4,12 @@ description: is a multi-line text input component with optional character count preview: false --- +## Installation + +```bash +npx shadcn@latest add @kombase/form-textarea +``` + ## Examples ### FormTextarea diff --git a/docs/app/content/form/form-upload.mdx b/docs/app/content/form/form-upload.mdx index 5c3920b..58685a2 100644 --- a/docs/app/content/form/form-upload.mdx +++ b/docs/app/content/form/form-upload.mdx @@ -4,6 +4,12 @@ description: wraps FileUpload to integrate file selection and validation with re preview: false --- +## Installation + +```bash +npx shadcn@latest add @kombase/form-upload +``` + ## Examples ### Form Validation diff --git a/docs/app/content/index.mdx b/docs/app/content/index.mdx index 07c3f2b..9fc5108 100644 --- a/docs/app/content/index.mdx +++ b/docs/app/content/index.mdx @@ -11,7 +11,7 @@ description: A lightweight, highly customizable UI component library with out-of ## Installation -Follow these steps to integrate `kombase` into your project: +Follow these steps to add `kombase` components to your project: @@ -22,28 +22,76 @@ Ensure you have installed and configured [Shadcn UI](https://ui.shadcn.com/docs/ -### Install kombase & Peer Dependencies +### Configure Registry (Recommended) + +To install components using the `@kombase/` alias/prefix, configure the registry in your project. + +#### Option A: Edit `components.json` (Recommended) +Add the `"registries"` block at the root level of your `components.json` file: + +```json +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/index.css", + "baseColor": "slate", + "cssVariables": true + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + }, + "registries": { + "@kombase": "https://kombase.komerce.id/r/{name}.json" + } +} +``` -To keep the bundle size lightweight and avoid dependency version conflicts in your host application, `kombase` requires a few peer dependencies. +> **Note:** The registry URL template must end with `{name}.json` to satisfy shadcn CLI v4 schema validation. -Run the following command using your preferred package manager: +#### Option B: Edit `package.json` +Alternatively, you can define the registry under the `"registries"` key at the root level of your `package.json`: -```package-install -kombase react-hook-form react-day-picker @tanstack/react-table +```json +{ + "name": "your-project", + "dependencies": { ... }, + "registries": { + "@kombase": "https://kombase.komerce.id/r/{name}.json" + } +} ``` -### Import Styles +### Add Components + +Install any kombase component directly from the registry: + +```bash +# Menggunakan alias (jika registry telah dikonfigurasi) +npx shadcn@latest add @kombase/ + +# Atau menggunakan URL langsung +npx shadcn@latest add https://kombase.komerce.id/r/.json +``` + +For example, to add the Data Table: -Import the `kombase` stylesheet inside your global CSS file (e.g., `app.css` or `globals.css`): +```bash +# Menggunakan alias +npx shadcn@latest add @kombase/data-table -```css -@import "kombase/styles"; +# Atau menggunakan URL langsung +npx shadcn@latest add https://kombase.komerce.id/r/data-table.json ``` -This tells Tailwind CSS v4 to scan the compiled bundle of `kombase` and generate the matching style utility classes according to your theme. +All dependencies (npm packages and other kombase components) are resolved and installed automatically. diff --git a/docs/app/content/types/form-password.types.ts b/docs/app/content/types/form-password.types.ts new file mode 100644 index 0000000..11fdabe --- /dev/null +++ b/docs/app/content/types/form-password.types.ts @@ -0,0 +1,53 @@ +import type { ReactNode } from 'react'; +import type { Control, FieldPath, FieldValues } from 'react-hook-form'; +import type { PasswordInput } from '@/components/password-input'; + +/** + * Props accepted by the `FormInputPassword` component. + */ +export interface FormInputPassword { + /** + * The React Hook Form `control` object returned by `useForm`. + */ + control: Control; + + /** + * Dot-notation path to the field inside the form schema. + * Fully type-safe — TypeScript will error if the path does not exist. + */ + name: FieldPath; + + /** + * Optional label rendered above the input using a `\n )}\n
\n \n \n \n {React.cloneElement(trigger, {\n children: (\n
\n {trigger.props.children}\n\n \n
\n ),\n })}\n
\n \n \n \n
\n
\n \n
\n \n );\n }}\n />\n );\n}\n", + "type": "registry:component", + "target": "@components/form/form-date-picker.tsx" + } + ], + "type": "registry:component" +} \ No newline at end of file diff --git a/docs/public/r/form-input-group.json b/docs/public/r/form-input-group.json new file mode 100644 index 0000000..3b0442c --- /dev/null +++ b/docs/public/r/form-input-group.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "form-input-group", + "title": "Form Input Group", + "description": "Input group with prefix/suffix addons, integrated with react-hook-form.", + "registryDependencies": [ + "form", + "@kombase/input-group", + "label" + ], + "files": [ + { + "path": "registry/form/form-input-group.tsx", + "content": "import type { Control, FieldPath, FieldValues } from 'react-hook-form';\nimport { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form';\nimport {\n InputGroup,\n InputGroupAddon,\n InputGroupInput,\n InputGroupText,\n} from '@/components/ui/input-group';\nimport { Label } from '@/components/ui/label';\nimport { cn } from '@/lib/utils';\n\ntype FormInputGroupProps = {\n control: Control;\n name: FieldPath;\n label?: string | React.ReactNode;\n addon?: React.ReactNode;\n inputGroupProps?: React.ComponentProps;\n layout?: 'vertical' | 'horizontal';\n className?: string;\n labelClassName?: string;\n};\n\nexport function FormInputGroup({\n control,\n name,\n label,\n addon,\n inputGroupProps,\n layout = 'vertical',\n className,\n labelClassName,\n}: FormInputGroupProps) {\n return (\n (\n \n {label && (\n \n {label}\n \n )}\n
\n \n \n \n {addon && (\n \n {addon}\n \n )}\n \n \n \n
\n \n )}\n />\n );\n}\n", + "type": "registry:component", + "target": "@components/form/form-input-group.tsx" + } + ], + "type": "registry:component" +} \ No newline at end of file diff --git a/docs/public/r/form-input.json b/docs/public/r/form-input.json new file mode 100644 index 0000000..e60fd6b --- /dev/null +++ b/docs/public/r/form-input.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "form-input", + "title": "Form Input", + "description": "Text input with label and validation, integrated with react-hook-form.", + "registryDependencies": [ + "form", + "input", + "label" + ], + "files": [ + { + "path": "registry/form/form-input.tsx", + "content": "import type React from 'react';\nimport type { Control, FieldPath, FieldValues } from 'react-hook-form';\nimport { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form';\nimport { Input } from '@/components/ui/input';\nimport { Label } from '@/components/ui/label';\nimport { cn } from '@/lib/utils';\n\ntype FormInputProps = {\n control: Control;\n name: FieldPath;\n label?: string | React.ReactNode;\n inputProps?: React.ComponentProps;\n layout?: 'vertical' | 'horizontal';\n className?: string;\n labelClassName?: string;\n prefix?: React.ReactNode;\n suffix?: React.ReactNode;\n};\n\nexport function FormInput({\n control,\n name,\n label,\n inputProps,\n layout = 'vertical',\n className,\n labelClassName,\n prefix,\n suffix,\n}: FormInputProps) {\n return (\n (\n \n {label && (\n \n {label}\n \n )}\n
\n \n
\n {prefix && (\n
{prefix}
\n )}\n\n \n\n {suffix && (\n
{suffix}
\n )}\n
\n
\n \n
\n \n )}\n />\n );\n}\n", + "type": "registry:component", + "target": "@components/form/form-input.tsx" + } + ], + "type": "registry:component" +} \ No newline at end of file diff --git a/docs/public/r/form-password.json b/docs/public/r/form-password.json new file mode 100644 index 0000000..549da96 --- /dev/null +++ b/docs/public/r/form-password.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "form-password", + "title": "Form Password", + "description": "Password input with show/hide toggle, integrated with react-hook-form.", + "registryDependencies": [ + "form", + "label", + "@kombase/password-input" + ], + "files": [ + { + "path": "registry/form/form-password.tsx", + "content": "import type React from 'react';\nimport type { Control, FieldPath, FieldValues } from 'react-hook-form';\nimport { PasswordInput } from '@/components/password-input';\nimport { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form';\nimport { Label } from '@/components/ui/label';\nimport { cn } from '@/lib/utils';\n\ntype FormInputPasswordProps = {\n control: Control;\n name: FieldPath;\n label?: string | React.ReactNode;\n inputProps?: React.ComponentProps;\n layout?: 'vertical' | 'horizontal';\n className?: string;\n labelClassName?: string;\n};\n\nexport function FormInputPassword({\n control,\n name,\n label,\n inputProps,\n layout = 'vertical',\n className,\n labelClassName,\n}: FormInputPasswordProps) {\n return (\n (\n \n {label && (\n \n {label}\n \n )}\n
\n \n \n \n \n
\n \n )}\n />\n );\n}\n", + "type": "registry:component", + "target": "@components/form/form-password.tsx" + } + ], + "type": "registry:component" +} \ No newline at end of file diff --git a/docs/public/r/form-phone-input.json b/docs/public/r/form-phone-input.json new file mode 100644 index 0000000..5bc41d3 --- /dev/null +++ b/docs/public/r/form-phone-input.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "form-phone-input", + "title": "Form Phone Input", + "description": "International phone number input with country select, integrated with react-hook-form.", + "registryDependencies": [ + "form", + "label", + "@kombase/phone-input" + ], + "files": [ + { + "path": "registry/form/form-phone-input.tsx", + "content": "import type React from 'react';\nimport type { Control, FieldPath, FieldValues } from 'react-hook-form';\nimport { PhoneInput, PhoneInputCountrySelect, PhoneInputField } from '@/components/phone-input';\nimport { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form';\nimport { Label } from '@/components/ui/label';\nimport { cn } from '@/lib/utils';\n\ntype FormPhoneInputProps = {\n control: Control;\n name: FieldPath;\n label?: string | React.ReactNode;\n phoneInputProps?: Omit<\n React.ComponentProps,\n keyof React.ComponentProps<'div'>\n >;\n phoneInputCountrySelectProps?: React.ComponentProps;\n layout?: 'vertical' | 'horizontal';\n className?: string;\n labelClassName?: string;\n isVisibleCountrySelect?: boolean;\n};\n\nexport function FormPhoneInput({\n control,\n name,\n label,\n phoneInputProps,\n phoneInputCountrySelectProps,\n layout = 'vertical',\n className,\n labelClassName,\n isVisibleCountrySelect = true,\n}: FormPhoneInputProps) {\n return (\n (\n \n {label && (\n \n {label}\n \n )}\n
\n \n \n {isVisibleCountrySelect && (\n \n )}\n \n \n \n \n
\n \n )}\n />\n );\n}\n", + "type": "registry:component", + "target": "@components/form/form-phone-input.tsx" + } + ], + "type": "registry:component" +} \ No newline at end of file diff --git a/docs/public/r/form-pick.json b/docs/public/r/form-pick.json new file mode 100644 index 0000000..f8add4e --- /dev/null +++ b/docs/public/r/form-pick.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "form-pick", + "title": "Form Pick", + "description": "Card-style single selection input, integrated with react-hook-form.", + "registryDependencies": [ + "form", + "label", + "radio-group" + ], + "files": [ + { + "path": "registry/form/form-pick.tsx", + "content": "import type * as React from 'react';\nimport type { ComponentProps } from 'react';\nimport type { Control, FieldPath, FieldPathValue, FieldValues } from 'react-hook-form';\nimport { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';\nimport { Label } from '@/components/ui/label';\nimport { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';\nimport { cn } from '@/lib/utils';\n\nexport type PickOption = {\n id: TValue;\n label: string | React.ReactNode;\n sub?: string;\n icon?: React.ElementType;\n};\n\nexport type FormPickProps = {\n control: Control;\n name: FieldPath;\n options?: readonly PickOption> & string>[];\n label?: string | React.ReactNode;\n className?: string;\n radioGroupProps?: Omit, 'value' | 'onValueChange'>;\n renderOption?: (\n option: Readonly> & string>>,\n isSelected: boolean,\n ) => React.ReactNode;\n onValueChange?: (value: FieldPathValue>) => void;\n layout?: 'vertical' | 'horizontal';\n labelClassName?: string;\n};\n\nexport function FormPick>({\n control,\n name,\n label,\n options,\n className,\n radioGroupProps,\n renderOption,\n onValueChange,\n layout = 'vertical',\n labelClassName,\n}: FormPickProps) {\n return (\n (\n \n {label && (\n \n {label}\n \n )}\n\n
\n \n {\n field.onChange(val);\n onValueChange?.(val as FieldPathValue);\n }}\n value={field.value as string}\n >\n {options?.map((opt) => {\n const isSelected = field.value === opt.id;\n const Icon = opt.icon;\n\n return (\n
\n \n\n {renderOption ? (\n // Custom render – consumer is responsible for the full card\n \n ) : (\n // Default card layout\n \n {Icon && (\n
\n \n
\n )}\n\n
\n \n {opt.label}\n \n {opt.sub && (\n \n {opt.sub}\n \n )}\n
\n \n )}\n
\n );\n })}\n \n
\n\n \n
\n \n )}\n />\n );\n}\n", + "type": "registry:component", + "target": "@components/form/form-pick.tsx" + } + ], + "type": "registry:component" +} \ No newline at end of file diff --git a/docs/public/r/form-radio.json b/docs/public/r/form-radio.json new file mode 100644 index 0000000..794a442 --- /dev/null +++ b/docs/public/r/form-radio.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "form-radio", + "title": "Form Radio", + "description": "Radio button group with label and layout options, integrated with react-hook-form.", + "registryDependencies": [ + "form", + "label", + "radio-group" + ], + "files": [ + { + "path": "registry/form/form-radio.tsx", + "content": "import type * as React from 'react';\nimport type { ComponentProps } from 'react';\nimport type { Control, FieldPath, FieldPathValue, FieldValues } from 'react-hook-form';\nimport { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form';\nimport { Label } from '@/components/ui/label';\nimport { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';\nimport { cn } from '@/lib/utils';\n\nexport type RadioOption = {\n id: TValue;\n label: string | React.ReactNode;\n description?: string;\n};\n\nexport type FormRadioProps = {\n control: Control;\n name: FieldPath;\n options?: readonly RadioOption> & string>[];\n label?: string | React.ReactNode;\n className?: string;\n radioGroupProps?: Omit, 'value' | 'onValueChange'>;\n onValueChange?: (value: FieldPathValue>) => void;\n layout?: 'vertical' | 'horizontal';\n labelClassName?: string;\n orientation?: 'vertical' | 'horizontal';\n};\n\nexport function FormRadio>({\n control,\n name,\n label,\n options,\n className,\n radioGroupProps,\n onValueChange,\n layout = 'vertical',\n labelClassName,\n orientation = 'vertical',\n}: FormRadioProps) {\n return (\n (\n \n {label && (\n \n {label}\n \n )}\n\n
\n \n {\n field.onChange(val);\n onValueChange?.(val as FieldPathValue);\n }}\n value={field.value as string}\n >\n {options?.map((opt) => {\n const optionId = `${name}-${opt.id}`;\n const hasDescription = !!opt.description;\n return (\n \n \n \n {opt.label}\n {opt.description && (\n \n {opt.description}\n \n )}\n \n
\n );\n })}\n \n \n\n \n \n \n )}\n />\n );\n}\n", + "type": "registry:component", + "target": "@components/form/form-radio.tsx" + } + ], + "type": "registry:component" +} \ No newline at end of file diff --git a/docs/public/r/form-search-select.json b/docs/public/r/form-search-select.json new file mode 100644 index 0000000..64201ba --- /dev/null +++ b/docs/public/r/form-search-select.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "form-search-select", + "title": "Form Search Select", + "description": "Searchable combobox select with async data support, integrated with react-hook-form.", + "registryDependencies": [ + "combobox", + "form", + "label" + ], + "files": [ + { + "path": "registry/form/form-search-select.tsx", + "content": "import type React from 'react';\nimport type { Control, FieldPath, FieldValues } from 'react-hook-form';\nimport { Combobox, useComboboxAnchor } from '@/components/ui/combobox';\nimport { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form';\nimport { Label } from '@/components/ui/label';\nimport { cn } from '@/lib/utils';\n\ntype FormSearchSelectProps = {\n control: Control;\n name: FieldPath;\n multiple?: boolean;\n label?: string | React.ReactNode;\n comboboxProps?: Omit<\n React.ComponentProps,\n 'value' | 'onValueChange' | 'multiple' | 'defaultValue'\n >;\n render: (props: { anchor: ReturnType }) => React.ReactNode;\n layout?: 'vertical' | 'horizontal';\n className?: string;\n labelClassName?: string;\n};\n\nexport function FormSearchSelect({\n control,\n name,\n render,\n label,\n comboboxProps,\n layout = 'vertical',\n className,\n labelClassName,\n multiple = false,\n}: FormSearchSelectProps) {\n const anchor = useComboboxAnchor();\n\n return (\n (\n \n {label && (\n \n {label}\n \n )}\n\n
\n \n \n {render({ anchor })}\n \n \n\n \n
\n \n )}\n />\n );\n}\n", + "type": "registry:component", + "target": "@components/form/form-search-select.tsx" + } + ], + "type": "registry:component" +} \ No newline at end of file diff --git a/docs/public/r/form-textarea.json b/docs/public/r/form-textarea.json new file mode 100644 index 0000000..735df4d --- /dev/null +++ b/docs/public/r/form-textarea.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "form-textarea", + "title": "Form Textarea", + "description": "Multi-line text area with character count and validation, integrated with react-hook-form.", + "registryDependencies": [ + "form", + "label", + "textarea" + ], + "files": [ + { + "path": "registry/form/form-textarea.tsx", + "content": "import type React from 'react';\nimport type { Control, FieldPath, FieldValues } from 'react-hook-form';\nimport { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form';\nimport { Label } from '@/components/ui/label';\nimport { Textarea } from '@/components/ui/textarea';\nimport { cn } from '@/lib/utils';\n\ntype FormTextareaProps = {\n control: Control;\n name: FieldPath;\n label?: string | React.ReactNode;\n showCharacterCount?: boolean;\n maxLength?: number;\n textareaProps?: Omit, 'value' | 'onChange'>;\n layout?: 'vertical' | 'horizontal';\n className?: string;\n labelClassName?: string;\n};\n\nexport function FormTextarea({\n control,\n name,\n label,\n showCharacterCount = false,\n maxLength,\n textareaProps,\n layout = 'vertical',\n className,\n labelClassName,\n}: FormTextareaProps) {\n return (\n {\n const currentLength = field.value?.length || 0;\n const isExceeded = maxLength !== undefined && currentLength > maxLength;\n\n return (\n \n {label && (\n \n {label}\n \n )}\n
\n \n \n \n {showCharacterCount && (\n \n {maxLength !== undefined ? `${currentLength}/${maxLength}` : currentLength}\n
\n )}\n \n \n \n );\n }}\n />\n );\n}\n", + "type": "registry:component", + "target": "@components/form/form-textarea.tsx" + } + ], + "type": "registry:component" +} \ No newline at end of file diff --git a/docs/public/r/form-upload.json b/docs/public/r/form-upload.json new file mode 100644 index 0000000..db73ff6 --- /dev/null +++ b/docs/public/r/form-upload.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "form-upload", + "title": "Form Upload", + "description": "File upload with drag-and-drop and progress tracking, integrated with react-hook-form.", + "registryDependencies": [ + "form", + "label", + "@kombase/file-upload" + ], + "files": [ + { + "path": "registry/form/form-upload.tsx", + "content": "import type React from 'react';\nimport type { Control, FieldPath, FieldValues } from 'react-hook-form';\nimport { FileUpload, type FileUploadProps } from '@/components/ui/file-upload';\nimport { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form';\nimport { Label } from '@/components/ui/label';\nimport { cn } from '@/lib/utils';\n\ntype FormUploadProps = {\n control: Control;\n name: FieldPath;\n label?: string | React.ReactNode;\n uploadProps?: Omit;\n layout?: 'vertical' | 'horizontal';\n className?: string;\n labelClassName?: string;\n children?: React.ReactNode;\n};\n\nexport function FormUpload({\n control,\n name,\n label,\n uploadProps,\n layout = 'vertical',\n className,\n labelClassName,\n children,\n}: FormUploadProps) {\n return (\n (\n \n {label && (\n \n {label}\n \n )}\n
\n \n \n {children}\n \n \n \n
\n \n )}\n />\n );\n}\n", + "type": "registry:component", + "target": "@components/form/form-upload.tsx" + } + ], + "type": "registry:component" +} \ No newline at end of file diff --git a/docs/public/r/form.json b/docs/public/r/form.json new file mode 100644 index 0000000..70ec636 --- /dev/null +++ b/docs/public/r/form.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "form", + "title": "Form", + "description": "Form primitives with react-hook-form integration and validation messages.", + "dependencies": [ + "@radix-ui/react-form", + "@radix-ui/react-label", + "react-hook-form" + ], + "registryDependencies": [ + "label", + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/form.tsx", + "content": "'use client';\n\nimport * as Slot from '@radix-ui/react-slot';\nimport * as React from 'react';\nimport {\n Controller,\n type ControllerProps,\n type FieldPath,\n type FieldValues,\n FormProvider,\n useFormContext,\n useFormState,\n} from 'react-hook-form';\nimport { Label } from '@/components/ui/label';\nimport { cn } from '@/lib/utils';\n\nconst Form = FormProvider;\n\ntype FormFieldContextValue<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath = FieldPath,\n> = {\n name: TName;\n};\n\nconst FormFieldContext = React.createContext({} as FormFieldContextValue);\n\nconst FormField = <\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath = FieldPath,\n>({\n ...props\n}: ControllerProps) => {\n return (\n \n \n \n );\n};\n\nconst useFormField = () => {\n const fieldContext = React.useContext(FormFieldContext);\n const itemContext = React.useContext(FormItemContext);\n const { getFieldState } = useFormContext();\n const formState = useFormState({ name: fieldContext.name });\n const fieldState = getFieldState(fieldContext.name, formState);\n\n if (!fieldContext) {\n throw new Error('useFormField should be used within ');\n }\n\n const { id } = itemContext;\n\n return {\n formDescriptionId: `${id}-form-item-description`,\n formItemId: `${id}-form-item`,\n formMessageId: `${id}-form-item-message`,\n id,\n name: fieldContext.name,\n ...fieldState,\n };\n};\n\ntype FormItemContextValue = {\n id: string;\n};\n\nconst FormItemContext = React.createContext({} as FormItemContextValue);\n\nconst FormItem = React.forwardRef>(\n ({ className, ...props }, ref) => {\n const id = React.useId();\n\n return (\n \n
\n \n );\n },\n);\nFormItem.displayName = 'FormItem';\n\nconst FormLabel = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => {\n const { error, formItemId } = useFormField();\n\n return (\n \n );\n});\nFormLabel.displayName = 'FormLabel';\n\nconst FormControl = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>((props, ref) => {\n const { error, formItemId, formDescriptionId, formMessageId } = useFormField();\n\n return (\n \n );\n});\nFormControl.displayName = 'FormControl';\n\nconst FormDescription = React.forwardRef>(\n ({ className, ...props }, ref) => {\n const { formDescriptionId } = useFormField();\n\n return (\n \n );\n },\n);\nFormDescription.displayName = 'FormDescription';\n\nconst FormMessage = React.forwardRef>(\n ({ className, ...props }, ref) => {\n const { error, formMessageId } = useFormField();\n const body = error ? String(error?.message ?? '') : props.children;\n\n if (!body) {\n return null;\n }\n\n return (\n \n {body}\n

\n );\n },\n);\nFormMessage.displayName = 'FormMessage';\n\nexport {\n Form,\n FormControl,\n FormDescription,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n useFormField,\n};\n", + "type": "registry:ui", + "target": "@ui/form.tsx" + } + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/docs/public/r/input-group.json b/docs/public/r/input-group.json new file mode 100644 index 0000000..5c7ca78 --- /dev/null +++ b/docs/public/r/input-group.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "input-group", + "title": "Input Group", + "description": "An input with prefix/suffix addons and action buttons.", + "registryDependencies": [ + "button", + "input", + "textarea", + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/input-group.tsx", + "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport * as React from 'react';\nimport { Button } from '@/components/ui/button';\nimport { Input } from '@/components/ui/input';\nimport { Textarea } from '@/components/ui/textarea';\nimport { cn } from '@/lib/utils';\n\nconst InputGroup = React.forwardRef>(\n ({ className, ...props }, ref) => {\n return (\n textarea]:h-auto',\n\n // Variants based on alignment.\n 'has-[>[data-align=inline-start]]:[&>input]:pl-2',\n 'has-[>[data-align=inline-end]]:[&>input]:pr-2',\n 'has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3',\n 'has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3',\n\n // Focus state.\n 'has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50',\n\n // Error state.\n 'has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40',\n\n className,\n )}\n data-slot=\"input-group\"\n ref={ref}\n role=\"group\"\n {...props}\n />\n );\n },\n);\nInputGroup.displayName = 'InputGroup';\n\nconst inputGroupAddonVariants = cva(\n \"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4\",\n {\n defaultVariants: {\n align: 'inline-start',\n },\n variants: {\n align: {\n 'block-end':\n 'order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5 [.border-t]:pt-3',\n 'block-start':\n 'order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5 [.border-b]:pb-3',\n 'inline-end': 'order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]',\n 'inline-start': 'order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]',\n },\n },\n },\n);\n\nconst InputGroupAddon = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<'div'> & VariantProps\n>(({ className, align = 'inline-start', ...props }, ref) => {\n return (\n {\n if ((e.target as HTMLElement).closest('button')) {\n return;\n }\n e.currentTarget.parentElement?.querySelector('input')?.focus();\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n if ((e.target as HTMLElement).closest('button')) {\n return;\n }\n if (e.key === ' ') e.preventDefault();\n e.currentTarget.parentElement?.querySelector('input')?.focus();\n }\n }}\n ref={ref}\n role=\"group\"\n {...props}\n />\n );\n});\nInputGroupAddon.displayName = 'InputGroupAddon';\n\nconst inputGroupButtonVariants = cva('flex items-center gap-2 text-sm shadow-none', {\n defaultVariants: {\n size: 'xs',\n },\n variants: {\n size: {\n 'icon-sm': 'size-8 p-0 has-[>svg]:p-0',\n 'icon-xs': 'size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0',\n sm: 'h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5',\n xs: \"h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5\",\n },\n },\n});\n\nconst InputGroupButton = React.forwardRef<\n React.ComponentRef,\n Omit, 'size'> &\n VariantProps\n>(({ className, type = 'button', variant = 'ghost', size = 'xs', ...props }, ref) => {\n return (\n \n );\n});\nInputGroupButton.displayName = 'InputGroupButton';\n\nconst InputGroupText = React.forwardRef>(\n ({ className, ...props }, ref) => {\n return (\n \n );\n },\n);\nInputGroupText.displayName = 'InputGroupText';\n\nconst InputGroupInput = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => {\n return (\n \n );\n});\nInputGroupInput.displayName = 'InputGroupInput';\n\nconst InputGroupTextarea = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => {\n return (\n \n );\n});\nInputGroupTextarea.displayName = 'InputGroupTextarea';\n\nexport {\n InputGroup,\n InputGroupAddon,\n InputGroupButton,\n InputGroupInput,\n InputGroupText,\n InputGroupTextarea,\n};\n", + "type": "registry:ui", + "target": "@ui/input-group.tsx" + } + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/docs/public/r/input.json b/docs/public/r/input.json new file mode 100644 index 0000000..9434213 --- /dev/null +++ b/docs/public/r/input.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "input", + "title": "Input", + "description": "A styled text input field.", + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/input.tsx", + "content": "'use client';\n\nimport * as React from 'react';\nimport { cn } from '@/lib/utils';\n\nconst Input = React.forwardRef>(\n ({ className, type, ...props }, ref) => {\n return (\n \n );\n },\n);\nInput.displayName = 'Input';\n\nexport { Input };\n", + "type": "registry:ui", + "target": "@ui/input.tsx" + } + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/docs/public/r/label.json b/docs/public/r/label.json new file mode 100644 index 0000000..d443f32 --- /dev/null +++ b/docs/public/r/label.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "label", + "title": "Label", + "description": "An accessible label for form controls.", + "dependencies": [ + "@radix-ui/react-label" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/label.tsx", + "content": "'use client';\n\nimport * as LabelPrimitive from '@radix-ui/react-label';\nimport * as React from 'react';\n\nimport { cn } from '@/lib/utils';\n\nconst Label = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nLabel.displayName = LabelPrimitive.Root.displayName;\n\nexport { Label };\n", + "type": "registry:ui", + "target": "@ui/label.tsx" + } + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/docs/public/r/lib-data-table.json b/docs/public/r/lib-data-table.json new file mode 100644 index 0000000..ea7507e --- /dev/null +++ b/docs/public/r/lib-data-table.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "lib-data-table", + "title": "Data Table Helpers", + "description": "Helper functions for data table filter validation and parsing.", + "dependencies": [ + "@tanstack/react-table" + ], + "registryDependencies": [ + "@kombase/data-table-config", + "@kombase/data-table-types" + ], + "files": [ + { + "path": "registry/lib/data-table.ts", + "content": "import type { Column } from '@tanstack/react-table';\nimport { dataTableConfig } from '@/components/data-table/data-table-config';\nimport type {\n ExtendedColumnFilter,\n FilterOperator,\n FilterVariant,\n} from '@/components/data-table/types';\n\nexport function getCommonPinningStyles({\n column,\n withBorder = false,\n isHeader = false,\n stickyHeader = false,\n}: {\n column: Column;\n withBorder?: boolean;\n isHeader?: boolean;\n stickyHeader?: boolean;\n}): React.CSSProperties {\n const isPinned = column.getIsPinned();\n const isLastLeftPinnedColumn = isPinned === 'left' && column.getIsLastColumn('left');\n const isFirstRightPinnedColumn = isPinned === 'right' && column.getIsFirstColumn('right');\n\n const isSticky = isPinned || (isHeader && stickyHeader);\n\n return {\n backgroundColor: 'inherit',\n boxShadow: withBorder\n ? isLastLeftPinnedColumn\n ? '-4px 0 4px -4px var(--border) inset'\n : isFirstRightPinnedColumn\n ? '4px 0 4px -4px var(--border) inset'\n : undefined\n : undefined,\n left: isPinned === 'left' ? `${column.getStart('left')}px` : undefined,\n opacity: isPinned ? 0.97 : 1,\n position: isSticky ? 'sticky' : 'relative',\n right: isPinned === 'right' ? `${column.getAfter('right')}px` : undefined,\n top: isHeader && stickyHeader ? 0 : undefined,\n width: column.getSize(),\n zIndex: isPinned\n ? isHeader && stickyHeader\n ? 30\n : 10\n : isHeader && stickyHeader\n ? 20\n : undefined,\n };\n}\n\nexport function getFilterOperators(filterVariant: FilterVariant) {\n const operatorMap: Record = {\n boolean: dataTableConfig.booleanOperators,\n date: dataTableConfig.dateOperators,\n dateRange: dataTableConfig.dateOperators,\n multiSelect: dataTableConfig.multiSelectOperators,\n number: dataTableConfig.numericOperators,\n range: dataTableConfig.numericOperators,\n select: dataTableConfig.selectOperators,\n text: dataTableConfig.textOperators,\n };\n\n return operatorMap[filterVariant] ?? dataTableConfig.textOperators;\n}\n\nexport function getDefaultFilterOperator(filterVariant: FilterVariant) {\n if (filterVariant === 'dateRange' || filterVariant === 'range') {\n return 'isBetween';\n }\n const operators = getFilterOperators(filterVariant);\n\n return operators[0]?.value ?? (filterVariant === 'text' ? 'iLike' : 'eq');\n}\n\nexport function getValidFilters(\n filters: ExtendedColumnFilter[],\n): ExtendedColumnFilter[] {\n return filters.filter(\n (filter) =>\n filter.operator === 'isEmpty' ||\n filter.operator === 'isNotEmpty' ||\n (Array.isArray(filter.value)\n ? filter.value.length > 0\n : filter.value !== '' && filter.value !== null && filter.value !== undefined),\n );\n}\n", + "type": "registry:lib", + "target": "@lib/data-table.ts" + } + ], + "type": "registry:lib" +} \ No newline at end of file diff --git a/docs/public/r/lib-date.json b/docs/public/r/lib-date.json new file mode 100644 index 0000000..362dffe --- /dev/null +++ b/docs/public/r/lib-date.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "lib-date", + "title": "Date Helpers", + "description": "Date formatting and range utilities using dayjs and react-day-picker.", + "dependencies": [ + "dayjs", + "react-day-picker" + ], + "files": [ + { + "path": "registry/lib/date.ts", + "content": "import dayjs from 'dayjs';\nimport type { DateRange } from 'react-day-picker';\n\nexport function getTimezone() {\n return Intl.DateTimeFormat().resolvedOptions().timeZone;\n}\n\ntype DateRangeParams = {\n start_date?: string;\n end_date?: string;\n};\n\n/**\n * Convert `DateRange` (react-day-picker) to a format string\n * that meets API requirements (default: `yyyy-MM-dd`).\n *\n * @param range - A DateRange object containing `from` and `to` (optional).\n * @param formatStr - The date format (default: `yyyy-MM-dd`).\n *\n * @returns An object containing:\n * - `start_date` → the format string resulting from `range.from`\n * - `end_date` → the format string resulting from `range.to`\n *\n * @remarks\n * - Will return `{}` if `range` is undefined\n * - Field will be `undefined` if `from` / `to` is not provided\n * - Safe to spread directly to object query parameters\n */\nexport const mapDateRangeToParams = (\n range?: DateRange,\n formatStr: string = 'yyyy-MM-dd',\n): DateRangeParams => {\n if (!range) return {};\n\n return {\n end_date: range.to ? dayjs(range.to).format(formatStr) : undefined,\n start_date: range.from ? dayjs(range.from).format(formatStr) : undefined,\n };\n};\n\nexport function formatDateFilterTable(\n date: Date | string | number | undefined,\n opts: Intl.DateTimeFormatOptions = {},\n) {\n if (!date) return '';\n\n try {\n return new Intl.DateTimeFormat('en-US', {\n day: opts.day ?? 'numeric',\n month: opts.month ?? 'long',\n year: opts.year ?? 'numeric',\n ...opts,\n }).format(new Date(date));\n } catch (_err) {\n return '';\n }\n}\n", + "type": "registry:lib", + "target": "@lib/date.ts" + } + ], + "type": "registry:lib" +} \ No newline at end of file diff --git a/docs/public/r/lib-pagination.json b/docs/public/r/lib-pagination.json new file mode 100644 index 0000000..5a9f10c --- /dev/null +++ b/docs/public/r/lib-pagination.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "lib-pagination", + "title": "Pagination Helpers", + "description": "Pagination range calculation utilities for data table pagination.", + "files": [ + { + "path": "registry/lib/pagination.ts", + "content": "/**\n * Generates page numbers for pagination with ellipsis\n * @param currentPage - Current page number (1-based)\n * @param totalPages - Total number of pages\n * @returns Array of page numbers and ellipsis strings\n *\n * Examples:\n * - Small dataset (≤5 pages): [1, 2, 3, 4, 5]\n * - Near beginning: [1, 2, 3, 4, \"...\", 10]\n * - In middle: [1, \"...\", 4, 5, 6, \"...\", 10]\n * - Near end: [1, \"...\", 7, 8, 9, 10]\n */\nexport function getPageNumbers(currentPage: number, totalPages: number) {\n const maxVisiblePages = 5; // Maximum number of page buttons to show\n const rangeWithDots: (number | string)[] = [];\n\n if (totalPages <= maxVisiblePages) {\n // If total pages is 5 or less, show all pages\n for (let i = 1; i <= totalPages; i++) {\n rangeWithDots.push(i);\n }\n } else {\n // Always show first page\n rangeWithDots.push(1);\n\n if (currentPage <= 3) {\n // Near the beginning: [1] [2] [3] [4] ... [10]\n for (let i = 2; i <= 4; i++) {\n rangeWithDots.push(i);\n }\n rangeWithDots.push('...', totalPages);\n } else if (currentPage >= totalPages - 2) {\n // Near the end: [1] ... [7] [8] [9] [10]\n rangeWithDots.push('...');\n for (let i = totalPages - 3; i <= totalPages; i++) {\n rangeWithDots.push(i);\n }\n } else {\n // In the middle: [1] ... [4] [5] [6] ... [10]\n rangeWithDots.push('...');\n for (let i = currentPage - 1; i <= currentPage + 1; i++) {\n rangeWithDots.push(i);\n }\n rangeWithDots.push('...', totalPages);\n }\n }\n\n return rangeWithDots;\n}\n", + "type": "registry:lib", + "target": "@lib/pagination.ts" + } + ], + "type": "registry:lib" +} \ No newline at end of file diff --git a/docs/public/r/long-text.json b/docs/public/r/long-text.json new file mode 100644 index 0000000..0ab45e7 --- /dev/null +++ b/docs/public/r/long-text.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "long-text", + "title": "Long Text", + "description": "Truncated text with tooltip or popover for viewing full content.", + "registryDependencies": [ + "popover", + "tooltip" + ], + "files": [ + { + "path": "registry/components/long-text.tsx", + "content": "'use client';\n\nimport { useRef, useState } from 'react';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';\nimport { cn } from '@/lib/utils';\n\ntype LongTextProps = {\n children: React.ReactNode;\n className?: string;\n contentClassName?: string;\n};\n\nexport function LongText({ children, className = '', contentClassName = '' }: LongTextProps) {\n const ref = useRef(null);\n const [isOverflown, setIsOverflown] = useState(false);\n\n // Use ref callback to check overflow when element is mounted\n const refCallback = (node: HTMLDivElement | null) => {\n ref.current = node;\n if (node && checkOverflow(node)) {\n queueMicrotask(() => setIsOverflown(true));\n }\n };\n\n if (!isOverflown)\n return (\n
\n {children}\n
\n );\n\n return (\n <>\n
\n \n \n \n
\n {children}\n
\n
\n \n

{children}

\n
\n
\n
\n
\n
\n \n \n
\n {children}\n
\n
\n \n

{children}

\n
\n
\n
\n \n );\n}\n\nconst checkOverflow = (textContainer: HTMLDivElement | null) => {\n if (textContainer) {\n return (\n textContainer.offsetHeight < textContainer.scrollHeight ||\n textContainer.offsetWidth < textContainer.scrollWidth\n );\n }\n return false;\n};\n", + "type": "registry:component", + "target": "@components/long-text.tsx" + } + ], + "type": "registry:component" +} \ No newline at end of file diff --git a/docs/public/r/password-input.json b/docs/public/r/password-input.json new file mode 100644 index 0000000..9fcb7c0 --- /dev/null +++ b/docs/public/r/password-input.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "password-input", + "title": "Password Input", + "description": "A password field with show/hide toggle button.", + "dependencies": [ + "lucide-react" + ], + "registryDependencies": [ + "button", + "@kombase/utils" + ], + "files": [ + { + "path": "registry/components/password-input.tsx", + "content": "import { Eye, EyeOff } from 'lucide-react';\nimport * as React from 'react';\nimport { Button } from '@/components/ui/button';\nimport { cn } from '@/lib/utils';\n\ntype PasswordInputProps = Omit, 'type'>;\n\nexport const PasswordInput = React.forwardRef(\n ({ className, disabled, ...props }, ref) => {\n const [showPassword, setShowPassword] = React.useState(false);\n\n return (\n
\n \n setShowPassword((prev) => !prev)}\n size=\"icon\"\n type=\"button\"\n variant=\"ghost\"\n >\n {showPassword ? : }\n {showPassword ? 'Hide password' : 'Show password'}\n \n
\n );\n },\n);\nPasswordInput.displayName = 'PasswordInput';\n", + "type": "registry:component", + "target": "@components/password-input.tsx" + } + ], + "type": "registry:component" +} \ No newline at end of file diff --git a/docs/public/r/phone-input.json b/docs/public/r/phone-input.json new file mode 100644 index 0000000..99cbef9 --- /dev/null +++ b/docs/public/r/phone-input.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "phone-input", + "title": "Phone Input", + "description": "International phone number input with country code selector and flag icons.", + "registryDependencies": [ + "command", + "input", + "popover", + "@kombase/component-refs", + "@kombase/visually-hidden-input", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "files": [ + { + "path": "registry/components/phone-input.tsx", + "content": "'use client';\n\nimport * as SlotPrimitive from '@radix-ui/react-slot';\nimport { Check, ChevronDown } from 'lucide-react';\nimport * as React from 'react';\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from '@/components/ui/command';\nimport { Input } from '@/components/ui/input';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';\nimport { VisuallyHiddenInput } from '@/components/visually-hidden-input';\nimport { useAsRef } from '@/hooks/use-as-ref';\nimport { useIsomorphicLayoutEffect } from '@/hooks/use-isomorphic-layout-effect';\nimport { useLazyRef } from '@/hooks/use-lazy-ref';\nimport { useComposedRefs } from '@/lib/component-refs';\nimport { cn } from '@/lib/utils';\n\nconst ROOT_NAME = 'PhoneInput';\nconst COUNTRY_SELECT_NAME = 'PhoneInputCountrySelect';\nconst FIELD_NAME = 'PhoneInputField';\n\n/**\n * @see https://github.com/mukeshsoni/country-telephone-data/blob/master/country_telephone_data.js\n * @format [iso2, dialCode]\n */\nconst COUNTRY_DATA: [string, string][] = [\n ['af', '93'],\n ['ax', '358'],\n ['al', '355'],\n ['dz', '213'],\n ['as', '1684'],\n ['ad', '376'],\n ['ao', '244'],\n ['ai', '1264'],\n ['ag', '1268'],\n ['ar', '54'],\n ['am', '374'],\n ['aw', '297'],\n ['au', '61'],\n ['at', '43'],\n ['az', '994'],\n ['bs', '1242'],\n ['bh', '973'],\n ['bd', '880'],\n ['bb', '1246'],\n ['by', '375'],\n ['be', '32'],\n ['bz', '501'],\n ['bj', '229'],\n ['bm', '1441'],\n ['bt', '975'],\n ['bo', '591'],\n ['ba', '387'],\n ['bw', '267'],\n ['br', '55'],\n ['io', '246'],\n ['vg', '1284'],\n ['bn', '673'],\n ['bg', '359'],\n ['bf', '226'],\n ['bi', '257'],\n ['kh', '855'],\n ['cm', '237'],\n ['ca', '1'],\n ['cv', '238'],\n ['bq', '599'],\n ['ky', '1345'],\n ['cf', '236'],\n ['td', '235'],\n ['cl', '56'],\n ['cn', '86'],\n ['co', '57'],\n ['km', '269'],\n ['cd', '243'],\n ['cg', '242'],\n ['ck', '682'],\n ['cr', '506'],\n ['ci', '225'],\n ['hr', '385'],\n ['cu', '53'],\n ['cw', '599'],\n ['cy', '357'],\n ['cz', '420'],\n ['dk', '45'],\n ['dj', '253'],\n ['dm', '1767'],\n ['do', '1'],\n ['ec', '593'],\n ['eg', '20'],\n ['sv', '503'],\n ['gq', '240'],\n ['er', '291'],\n ['ee', '372'],\n ['et', '251'],\n ['fk', '500'],\n ['fo', '298'],\n ['fj', '679'],\n ['fi', '358'],\n ['fr', '33'],\n ['gf', '594'],\n ['pf', '689'],\n ['ga', '241'],\n ['gm', '220'],\n ['ge', '995'],\n ['de', '49'],\n ['gh', '233'],\n ['gi', '350'],\n ['gr', '30'],\n ['gl', '299'],\n ['gd', '1473'],\n ['gp', '590'],\n ['gu', '1671'],\n ['gt', '502'],\n ['gg', '44'],\n ['gn', '224'],\n ['gw', '245'],\n ['gy', '592'],\n ['ht', '509'],\n ['hn', '504'],\n ['hk', '852'],\n ['hu', '36'],\n ['is', '354'],\n ['in', '91'],\n ['id', '62'],\n ['ir', '98'],\n ['iq', '964'],\n ['ie', '353'],\n ['im', '44'],\n ['il', '972'],\n ['it', '39'],\n ['jm', '1876'],\n ['jp', '81'],\n ['je', '44'],\n ['jo', '962'],\n ['kz', '7'],\n ['ke', '254'],\n ['ki', '686'],\n ['xk', '383'],\n ['kw', '965'],\n ['kg', '996'],\n ['la', '856'],\n ['lv', '371'],\n ['lb', '961'],\n ['ls', '266'],\n ['lr', '231'],\n ['ly', '218'],\n ['li', '423'],\n ['lt', '370'],\n ['lu', '352'],\n ['mo', '853'],\n ['mk', '389'],\n ['mg', '261'],\n ['mw', '265'],\n ['my', '60'],\n ['mv', '960'],\n ['ml', '223'],\n ['mt', '356'],\n ['mh', '692'],\n ['mq', '596'],\n ['mr', '222'],\n ['mu', '230'],\n ['mx', '52'],\n ['fm', '691'],\n ['md', '373'],\n ['mc', '377'],\n ['mn', '976'],\n ['me', '382'],\n ['ms', '1664'],\n ['ma', '212'],\n ['mz', '258'],\n ['mm', '95'],\n ['na', '264'],\n ['nr', '674'],\n ['np', '977'],\n ['nl', '31'],\n ['nc', '687'],\n ['nz', '64'],\n ['ni', '505'],\n ['ne', '227'],\n ['ng', '234'],\n ['nu', '683'],\n ['nf', '672'],\n ['kp', '850'],\n ['mp', '1670'],\n ['no', '47'],\n ['om', '968'],\n ['pk', '92'],\n ['pw', '680'],\n ['ps', '970'],\n ['pa', '507'],\n ['pg', '675'],\n ['py', '595'],\n ['pe', '51'],\n ['ph', '63'],\n ['pl', '48'],\n ['pt', '351'],\n ['pr', '1'],\n ['qa', '974'],\n ['re', '262'],\n ['ro', '40'],\n ['ru', '7'],\n ['rw', '250'],\n ['bl', '590'],\n ['sh', '290'],\n ['kn', '1869'],\n ['lc', '1758'],\n ['mf', '590'],\n ['pm', '508'],\n ['vc', '1784'],\n ['ws', '685'],\n ['sm', '378'],\n ['st', '239'],\n ['sa', '966'],\n ['sn', '221'],\n ['rs', '381'],\n ['sc', '248'],\n ['sl', '232'],\n ['sg', '65'],\n ['sx', '1721'],\n ['sk', '421'],\n ['si', '386'],\n ['sb', '677'],\n ['so', '252'],\n ['za', '27'],\n ['kr', '82'],\n ['ss', '211'],\n ['es', '34'],\n ['lk', '94'],\n ['sd', '249'],\n ['sr', '597'],\n ['sz', '268'],\n ['se', '46'],\n ['ch', '41'],\n ['sy', '963'],\n ['tw', '886'],\n ['tj', '992'],\n ['tz', '255'],\n ['th', '66'],\n ['tl', '670'],\n ['tg', '228'],\n ['tk', '690'],\n ['to', '676'],\n ['tt', '1868'],\n ['tn', '216'],\n ['tr', '90'],\n ['tm', '993'],\n ['tc', '1649'],\n ['tv', '688'],\n ['vi', '1340'],\n ['ug', '256'],\n ['ua', '380'],\n ['ae', '971'],\n ['gb', '44'],\n ['us', '1'],\n ['uy', '598'],\n ['uz', '998'],\n ['vu', '678'],\n ['va', '39'],\n ['ve', '58'],\n ['vn', '84'],\n ['wf', '681'],\n ['eh', '212'],\n ['ye', '967'],\n ['zm', '260'],\n ['zw', '263'],\n];\n\ninterface Country {\n code: string;\n name: string;\n dialCode: string;\n flag?: string;\n}\n\nfunction getCountryName(countryCode: string, locale = 'en'): string {\n try {\n const regionNames = new Intl.DisplayNames([locale], { type: 'region' });\n return regionNames.of(countryCode) ?? countryCode;\n } catch {\n return countryCode;\n }\n}\n\nfunction getFlagEmoji(countryCode: string): string {\n const codePoints = countryCode\n .toUpperCase()\n .split('')\n .map((char) => 127397 + char.charCodeAt(0));\n return String.fromCodePoint(...codePoints);\n}\n\nfunction getCountries(): Country[] {\n return COUNTRY_DATA.map(([iso2, dialCode]): Country => {\n const code = iso2.toUpperCase();\n return {\n code,\n dialCode: `+${dialCode}`,\n flag: getFlagEmoji(code),\n name: getCountryName(code),\n };\n }).sort((a, b) => a.name.localeCompare(b.name));\n}\n\nfunction detectCountryFromNumber(value: string, countries: Country[]): Country | undefined {\n if (!value?.startsWith('+')) return undefined;\n\n const digits = value.slice(1).replace(/\\D/g, '');\n if (!digits) return undefined;\n\n const sorted = [...countries].sort((a, b) => b.dialCode.length - a.dialCode.length);\n\n const matches: Country[] = [];\n for (const country of sorted) {\n const dialCode = country.dialCode.slice(1);\n if (digits.startsWith(dialCode)) {\n matches.push(country);\n }\n }\n\n if (matches.length === 0) return undefined;\n\n if (matches.length > 1 && matches[0]?.dialCode === '+1') {\n const usCountry = matches.find((c) => c.code === 'US');\n if (usCountry) return usCountry;\n }\n\n return matches[0];\n}\n\nfunction formatPhoneNumber(value: string, countries: Country[]): string {\n if (!value) return '';\n\n const normalized = value.startsWith('+') ? value : `+${value}`;\n\n const digits = normalized.slice(1).replace(/\\D/g, '');\n if (!digits) return '+';\n\n const detected = detectCountryFromNumber(`+${digits}`, countries);\n const dialCodeLength = detected ? detected.dialCode.slice(1).length : Math.min(digits.length, 3);\n\n const countryCode = digits.slice(0, dialCodeLength);\n const rest = digits.slice(dialCodeLength);\n\n let formatted = `+${countryCode}`;\n\n if (rest) {\n formatted += ' ';\n for (let i = 0; i < rest.length; i++) {\n if (i > 0 && i % 3 === 0) {\n formatted += ' ';\n }\n formatted += rest[i];\n }\n }\n\n return formatted;\n}\n\ntype RootElement = React.ComponentRef;\n\ninterface StoreState {\n value: string;\n country: string;\n open: boolean;\n startsWithPlus: boolean;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n setState: (key: K, value: StoreState[K]) => void;\n notify: () => void;\n}\n\nconst StoreContext = React.createContext(null);\n\nfunction useStoreContext(consumerName: string) {\n const context = React.useContext(StoreContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\nfunction useStore(selector: (state: StoreState) => T, ogStore?: Store | null): T {\n const contextStore = React.useContext(StoreContext);\n\n const store = ogStore ?? contextStore;\n\n if (!store) {\n throw new Error(`\\`useStore\\` must be used within \\`${ROOT_NAME}\\``);\n }\n\n const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ninterface PhoneInputContextValue {\n rootId: string;\n countries: Country[];\n placeholder: string;\n disabled?: boolean;\n readOnly?: boolean;\n required?: boolean;\n invalid?: boolean;\n showFlag: boolean;\n inputRef: React.RefObject;\n}\n\nconst PhoneInputContext = React.createContext(null);\n\nfunction usePhoneInputContext(consumerName: string) {\n const context = React.useContext(PhoneInputContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface PhoneInputProps extends React.ComponentProps<'div'> {\n defaultValue?: string;\n value?: string;\n onValueChange?: (value: string) => void;\n defaultCountry?: string;\n country?: string;\n onCountryChange?: (country: string) => void;\n countries?: Country[];\n name?: string;\n placeholder?: string;\n asChild?: boolean;\n disabled?: boolean;\n readOnly?: boolean;\n required?: boolean;\n invalid?: boolean;\n showFlag?: boolean;\n}\n\nfunction PhoneInput(props: PhoneInputProps) {\n const {\n value: valueProp,\n defaultValue,\n defaultCountry,\n country: countryProp,\n onValueChange,\n onCountryChange,\n countries = getCountries(),\n name,\n placeholder = 'Enter phone number',\n asChild,\n disabled,\n required,\n readOnly,\n invalid,\n showFlag = true,\n className,\n id,\n ref,\n ...rootProps\n } = props;\n\n const instanceId = React.useId();\n const rootId = id ?? instanceId;\n\n const inputRef = React.useRef(null);\n\n const [formTrigger, setFormTrigger] = React.useState(null);\n const composedRef = useComposedRefs(ref, (node) => setFormTrigger(node));\n const isFormControl = formTrigger ? !!formTrigger.closest('form') : true;\n\n const listenersRef = useLazyRef(() => new Set<() => void>());\n const stateRef = useLazyRef(() => {\n const initialValue = valueProp ?? defaultValue ?? '';\n const initialCountry = countryProp ?? defaultCountry ?? '';\n\n return {\n country: initialCountry,\n open: false,\n startsWithPlus: initialValue.startsWith('+'),\n value: initialValue,\n };\n });\n\n const propsRef = useAsRef({\n onCountryChange,\n onValueChange,\n });\n\n const store = React.useMemo(() => {\n return {\n getState: () => stateRef.current,\n notify: () => {\n for (const cb of listenersRef.current) {\n cb();\n }\n },\n setState: (key, value) => {\n if (Object.is(stateRef.current[key], value)) return;\n\n if (key === 'value' && typeof value === 'string') {\n stateRef.current.value = value;\n propsRef.current.onValueChange?.(value);\n } else if (key === 'country' && typeof value === 'string') {\n stateRef.current.country = value;\n propsRef.current.onCountryChange?.(value);\n } else {\n stateRef.current[key] = value;\n }\n\n store.notify();\n },\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n };\n }, [listenersRef, stateRef, propsRef]);\n\n const value = useStore((state) => state.value, store);\n const country = useStore((state) => state.country, store);\n\n useIsomorphicLayoutEffect(() => {\n if (valueProp !== undefined) {\n store.setState('value', valueProp);\n }\n }, [valueProp]);\n\n useIsomorphicLayoutEffect(() => {\n if (countryProp !== undefined) {\n store.setState('country', countryProp);\n }\n }, [countryProp]);\n\n const startsWithPlus = useStore((state) => state.startsWithPlus, store);\n\n React.useEffect(() => {\n if (!value) return;\n\n const digits = value.slice(1).replace(/\\D/g, '');\n const shouldDetect = startsWithPlus || digits.length >= 10;\n\n if (!shouldDetect) return;\n\n const detected = detectCountryFromNumber(value, countries);\n if (detected && detected.code !== country) {\n store.setState('country', detected.code);\n }\n }, [value, countries, country, store, startsWithPlus]);\n\n const contextValue = React.useMemo(\n () => ({\n countries,\n disabled,\n inputRef,\n invalid,\n placeholder,\n readOnly,\n required,\n rootId,\n showFlag,\n }),\n [rootId, countries, placeholder, disabled, required, readOnly, invalid, showFlag],\n );\n\n const RootPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n \n \n {isFormControl && (\n \n )}\n \n \n );\n}\n\ninterface PhoneInputCountrySelectProps\n extends React.ComponentProps,\n Pick, 'disabled' | 'className'> {}\n\nfunction PhoneInputCountrySelect(props: PhoneInputCountrySelectProps) {\n const {\n disabled: disabledProp,\n className,\n children,\n onOpenChange: onOpenChangeProp,\n ...popoverProps\n } = props;\n\n const { countries, inputRef, disabled, showFlag } = usePhoneInputContext(COUNTRY_SELECT_NAME);\n const store = useStoreContext(COUNTRY_SELECT_NAME);\n const country = useStore((state) => state.country);\n const open = useStore((state) => state.open);\n const onOpenChangeRef = useAsRef(onOpenChangeProp);\n\n const isDisabled = disabledProp || disabled;\n\n const countryContext = countries.find((c) => c.code === country);\n\n const onOpenChange = React.useCallback(\n (open: boolean) => {\n store.setState('open', open);\n onOpenChangeRef.current?.(open);\n },\n [store, onOpenChangeRef],\n );\n\n return (\n \n \n {!countryContext ? (\n
\n ) : (\n showFlag &&\n countryContext.flag && (\n
{countryContext.flag}
\n )\n )}\n \n \n \n \n \n \n No country found.\n \n {countries.map((c) => (\n {\n store.setState('country', c.code);\n store.setState('open', false);\n requestAnimationFrame(() => {\n inputRef.current?.focus();\n });\n }}\n value={`${c.name} ${c.dialCode} ${c.code}`}\n >\n {showFlag && c.flag && {c.flag}}\n {c.name}\n {c.dialCode}\n \n \n ))}\n \n \n \n \n \n );\n}\n\nfunction PhoneInputField(props: React.ComponentProps<'input'>) {\n const {\n onChange: onChangeProp,\n className,\n disabled: disabledProp,\n readOnly: readOnlyProp,\n required: requiredProp,\n ref,\n ...inputProps\n } = props;\n\n const { inputRef, disabled, invalid, readOnly, required, placeholder, countries } =\n usePhoneInputContext(FIELD_NAME);\n const store = useStoreContext(FIELD_NAME);\n const value = useStore((state) => state.value);\n\n const composedRef = useComposedRefs(ref, inputRef);\n\n const onChangeRef = useAsRef(onChangeProp);\n\n const isDisabled = disabledProp || disabled;\n const isReadOnly = readOnlyProp || readOnly;\n const isRequired = requiredProp || required;\n\n const onChange = React.useCallback(\n (event: React.ChangeEvent) => {\n if (isDisabled || isReadOnly) return;\n\n onChangeRef.current?.(event);\n if (event.defaultPrevented) return;\n\n const inputValue = event.target.value;\n\n const startsWithPlus = inputValue.startsWith('+');\n const digits = inputValue.replace(/\\D/g, '');\n const newValue = digits ? `+${digits}` : startsWithPlus ? '+' : '';\n store.setState('startsWithPlus', startsWithPlus);\n store.setState('value', newValue);\n },\n [store, onChangeRef, isDisabled, isReadOnly],\n );\n\n const displayValue = React.useMemo(() => {\n return formatPhoneNumber(value, countries);\n }, [value, countries]);\n\n return (\n \n );\n}\n\nexport {\n PhoneInput,\n PhoneInputCountrySelect,\n PhoneInputField,\n type PhoneInputProps,\n useStore as usePhoneInput,\n};\n", + "type": "registry:component", + "target": "@components/phone-input.tsx" + } + ], + "type": "registry:component" +} \ No newline at end of file diff --git a/docs/public/r/popover.json b/docs/public/r/popover.json new file mode 100644 index 0000000..46bc238 --- /dev/null +++ b/docs/public/r/popover.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "popover", + "title": "Popover", + "description": "A floating panel anchored to a trigger element.", + "dependencies": [ + "@radix-ui/react-popover" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/popover.tsx", + "content": "'use client';\n\nimport * as PopoverPrimitive from '@radix-ui/react-popover';\nimport * as React from 'react';\n\nimport { cn } from '@/lib/utils';\n\nconst Popover = PopoverPrimitive.Root;\n\nconst PopoverTrigger = PopoverPrimitive.Trigger;\n\nconst PopoverAnchor = PopoverPrimitive.Anchor;\n\nconst PopoverContent = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (\n \n \n \n));\nPopoverContent.displayName = PopoverPrimitive.Content.displayName;\n\nexport { Popover, PopoverAnchor, PopoverContent, PopoverTrigger };\n", + "type": "registry:ui", + "target": "@ui/popover.tsx" + } + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/docs/public/r/radio-group.json b/docs/public/r/radio-group.json new file mode 100644 index 0000000..52043ed --- /dev/null +++ b/docs/public/r/radio-group.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "radio-group", + "title": "Radio Group", + "description": "A set of radio buttons for single-option selection.", + "dependencies": [ + "@radix-ui/react-radio-group" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/radio-group.tsx", + "content": "'use client';\n\nimport * as RadioGroupPrimitive from '@radix-ui/react-radio-group';\nimport { CircleIcon } from 'lucide-react';\nimport * as React from 'react';\n\nimport { cn } from '@/lib/utils';\n\nconst RadioGroup = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => {\n return (\n \n );\n});\nRadioGroup.displayName = RadioGroupPrimitive.Root.displayName;\n\nconst RadioGroupItem = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => {\n return (\n \n \n \n \n \n );\n});\nRadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;\n\nexport { RadioGroup, RadioGroupItem };\n", + "type": "registry:ui", + "target": "@ui/radio-group.tsx" + } + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/docs/public/r/rating.json b/docs/public/r/rating.json new file mode 100644 index 0000000..07a679a --- /dev/null +++ b/docs/public/r/rating.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "rating", + "title": "Rating", + "description": "Customizable star rating component with keyboard navigation and form integration.", + "dependencies": [ + "@radix-ui/react-direction" + ], + "registryDependencies": [ + "@kombase/component-refs", + "@kombase/visually-hidden-input", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "files": [ + { + "path": "registry/components/rating.tsx", + "content": "'use client';\n\nimport * as DirectionPrimitive from '@radix-ui/react-direction';\nimport * as SlotPrimitive from '@radix-ui/react-slot';\nimport { Star } from 'lucide-react';\nimport * as React from 'react';\nimport { VisuallyHiddenInput } from '@/components/visually-hidden-input';\nimport { useAsRef } from '@/hooks/use-as-ref';\nimport { useIsomorphicLayoutEffect } from '@/hooks/use-isomorphic-layout-effect';\nimport { useLazyRef } from '@/hooks/use-lazy-ref';\nimport { useComposedRefs } from '@/lib/component-refs';\nimport { cn } from '@/lib/utils';\n\ntype Direction = 'ltr' | 'rtl';\ntype Orientation = 'horizontal' | 'vertical';\ntype ActivationMode = 'automatic' | 'manual';\ntype Size = 'default' | 'sm' | 'lg';\ntype Step = 0.5 | 1;\ntype DataState = 'full' | 'partial' | 'empty';\ntype FocusIntent = 'first' | 'last' | 'prev' | 'next';\n\ntype RootElement = React.ComponentRef;\ntype ItemElement = React.ComponentRef;\n\nconst ROOT_NAME = 'Rating';\nconst ITEM_NAME = 'RatingItem';\n\nconst ENTRY_FOCUS = 'ratingFocusGroup.onEntryFocus';\nconst EVENT_OPTIONS = { bubbles: false, cancelable: true };\n\nfunction getItemId(id: string, value: number) {\n return `${id}-item-${value}`;\n}\n\nfunction getPartialFillGradientId(id: string, step: Step) {\n return `partial-fill-gradient-${id}-${step}`;\n}\n\nconst MAP_KEY_TO_FOCUS_INTENT: Record = {\n ArrowDown: 'next',\n ArrowLeft: 'prev',\n ArrowRight: 'next',\n ArrowUp: 'prev',\n End: 'last',\n Home: 'first',\n};\n\nfunction getDirectionAwareKey(key: string, dir?: Direction) {\n if (dir !== 'rtl') return key;\n return key === 'ArrowLeft' ? 'ArrowRight' : key === 'ArrowRight' ? 'ArrowLeft' : key;\n}\n\nfunction getFocusIntent(\n event: React.KeyboardEvent,\n dir?: Direction,\n orientation?: Orientation,\n) {\n const key = getDirectionAwareKey(event.key, dir);\n if (orientation === 'horizontal' && ['ArrowUp', 'ArrowDown'].includes(key)) return undefined;\n if (orientation === 'vertical' && ['ArrowLeft', 'ArrowRight'].includes(key)) return undefined;\n return MAP_KEY_TO_FOCUS_INTENT[key];\n}\n\nfunction focusFirst(candidates: React.RefObject[], preventScroll = false) {\n const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;\n for (const candidateRef of candidates) {\n const candidate = candidateRef.current;\n if (!candidate) continue;\n if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;\n candidate.focus({ preventScroll });\n if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;\n }\n}\n\ninterface StoreState {\n value: number;\n hoveredValue: number | null;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n setState: (key: K, value: StoreState[K]) => void;\n notify: () => void;\n}\n\nconst StoreContext = React.createContext(null);\n\nfunction useStoreContext(consumerName: string) {\n const context = React.useContext(StoreContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\nfunction useStore(selector: (state: StoreState) => T, ogStore?: Store | null): T {\n const contextStore = React.useContext(StoreContext);\n\n const store = ogStore ?? contextStore;\n\n if (!store) {\n throw new Error(`\\`useStore\\` must be used within \\`${ROOT_NAME}\\``);\n }\n\n const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ninterface ItemData {\n id: string;\n ref: React.RefObject;\n value: number;\n disabled: boolean;\n}\n\ninterface RatingContextValue {\n rootId: string;\n dir: Direction;\n orientation: Orientation;\n activationMode: ActivationMode;\n size: Size;\n max: number;\n step: Step;\n clearable: boolean;\n disabled: boolean;\n readOnly: boolean;\n getAutoIndex: (instanceId: string) => number;\n}\n\nconst RatingContext = React.createContext(null);\n\nfunction useRatingContext(consumerName: string) {\n const context = React.useContext(RatingContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface FocusContextValue {\n tabStopId: string | null;\n onItemFocus: (tabStopId: string) => void;\n onItemShiftTab: () => void;\n onFocusableItemAdd: () => void;\n onFocusableItemRemove: () => void;\n onItemRegister: (item: ItemData) => void;\n onItemUnregister: (id: string) => void;\n getItems: () => ItemData[];\n}\n\nconst FocusContext = React.createContext(null);\n\nfunction useFocusContext(consumerName: string) {\n const context = React.useContext(FocusContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` must be used within \\`FocusProvider\\``);\n }\n return context;\n}\n\ninterface RatingProps extends React.ComponentProps<'div'> {\n value?: number;\n defaultValue?: number;\n onValueChange?: (value: number) => void;\n onHover?: (value: number | null) => void;\n max?: number;\n activationMode?: ActivationMode;\n dir?: Direction;\n orientation?: Orientation;\n size?: Size;\n asChild?: boolean;\n step?: Step;\n clearable?: boolean;\n disabled?: boolean;\n readOnly?: boolean;\n required?: boolean;\n name?: string;\n}\n\nfunction Rating(props: RatingProps) {\n const {\n value: valueProp,\n defaultValue = 0,\n onValueChange,\n onHover,\n onFocus: onFocusProp,\n onMouseDown: onMouseDownProp,\n dir: dirProp,\n orientation = 'horizontal',\n activationMode = 'automatic',\n size = 'default',\n max = 5,\n step = 1,\n clearable = false,\n asChild,\n disabled = false,\n readOnly = false,\n required = false,\n className,\n id,\n name,\n ref,\n ...rootProps\n } = props;\n\n const dir = DirectionPrimitive.useDirection(dirProp);\n const instanceId = React.useId();\n const rootId = id ?? instanceId;\n\n const listenersRef = useLazyRef(() => new Set<() => void>());\n const stateRef = useLazyRef(() => ({\n hoveredValue: null,\n value: valueProp ?? defaultValue,\n }));\n\n const propsRef = useAsRef({\n onFocus: onFocusProp,\n onHover,\n onMouseDown: onMouseDownProp,\n onValueChange,\n step,\n });\n\n const store = React.useMemo(() => {\n return {\n getState: () => stateRef.current,\n notify: () => {\n for (const cb of listenersRef.current) {\n cb();\n }\n },\n setState: (key, value) => {\n if (Object.is(stateRef.current[key], value)) return;\n\n if (key === 'value' && typeof value === 'number') {\n stateRef.current.value = value;\n propsRef.current.onValueChange?.(value);\n } else if (key === 'hoveredValue') {\n stateRef.current.hoveredValue = value as number | null;\n propsRef.current.onHover?.(value as number | null);\n } else {\n stateRef.current[key] = value;\n }\n\n store.notify();\n },\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n };\n }, [listenersRef, stateRef, propsRef]);\n\n useIsomorphicLayoutEffect(() => {\n if (valueProp !== undefined) {\n store.setState('value', valueProp);\n }\n }, [valueProp]);\n\n const value = useStore((state) => state.value, store);\n\n const [formTrigger, setFormTrigger] = React.useState(null);\n const composedRef = useComposedRefs(ref, (node) => setFormTrigger(node));\n const isFormControl = formTrigger ? !!formTrigger.closest('form') : true;\n\n const [tabStopId, setTabStopId] = React.useState(null);\n const [isTabbingBackOut, setIsTabbingBackOut] = React.useState(false);\n const [focusableItemCount, setFocusableItemCount] = React.useState(0);\n const isClickFocusRef = React.useRef(false);\n const itemsRef = React.useRef>(new Map());\n\n const autoIndexMapRef = React.useRef(new Map());\n const nextAutoIndexRef = React.useRef(0);\n\n const getAutoIndex = React.useCallback((instanceId: string) => {\n const existingIndex = autoIndexMapRef.current.get(instanceId);\n if (existingIndex !== undefined) {\n return existingIndex;\n }\n\n const newIndex = nextAutoIndexRef.current++;\n autoIndexMapRef.current.set(instanceId, newIndex);\n return newIndex;\n }, []);\n\n const onItemFocus = React.useCallback((tabStopId: string) => {\n setTabStopId(tabStopId);\n }, []);\n\n const onItemShiftTab = React.useCallback(() => {\n setIsTabbingBackOut(true);\n }, []);\n\n const onFocusableItemAdd = React.useCallback(() => {\n setFocusableItemCount((prevCount) => prevCount + 1);\n }, []);\n\n const onFocusableItemRemove = React.useCallback(() => {\n setFocusableItemCount((prevCount) => prevCount - 1);\n }, []);\n\n const onItemRegister = React.useCallback((item: ItemData) => {\n itemsRef.current.set(item.id, item);\n }, []);\n\n const onItemUnregister = React.useCallback((id: string) => {\n itemsRef.current.delete(id);\n }, []);\n\n const getItems = React.useCallback(() => {\n return Array.from(itemsRef.current.values())\n .filter((item) => item.ref.current)\n .sort((a, b) => {\n const elementA = a.ref.current;\n const elementB = b.ref.current;\n if (!elementA || !elementB) return 0;\n const position = elementA.compareDocumentPosition(elementB);\n if (position & Node.DOCUMENT_POSITION_FOLLOWING) {\n return -1;\n }\n if (position & Node.DOCUMENT_POSITION_PRECEDING) {\n return 1;\n }\n return 0;\n });\n }, []);\n\n const onBlur = React.useCallback(\n (event: React.FocusEvent) => {\n rootProps.onBlur?.(event);\n if (event.defaultPrevented) return;\n\n setIsTabbingBackOut(false);\n },\n [rootProps.onBlur],\n );\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent) => {\n propsRef.current.onFocus?.(event);\n if (event.defaultPrevented) return;\n\n const isKeyboardFocus = !isClickFocusRef.current;\n if (event.target === event.currentTarget && isKeyboardFocus && !isTabbingBackOut) {\n const entryFocusEvent = new CustomEvent(ENTRY_FOCUS, EVENT_OPTIONS);\n event.currentTarget.dispatchEvent(entryFocusEvent);\n\n if (!entryFocusEvent.defaultPrevented) {\n const items = Array.from(itemsRef.current.values()).filter((item) => !item.disabled);\n // For half-step ratings, find the item that represents the selected value\n // by looking for the ceiling value (e.g., 3.5 → find item with value 4)\n const selectedItem =\n propsRef.current.step < 1\n ? items.find((item) => item.value === Math.ceil(value))\n : items.find((item) => item.value === value);\n const currentItem = items.find((item) => item.id === tabStopId);\n\n const candidateItems = [selectedItem, currentItem, ...items].filter(\n Boolean,\n ) as ItemData[];\n const candidateRefs = candidateItems.map((item) => item.ref);\n focusFirst(candidateRefs, false);\n }\n }\n isClickFocusRef.current = false;\n },\n [propsRef, isTabbingBackOut, value, tabStopId],\n );\n\n const onMouseDown = React.useCallback(\n (event: React.MouseEvent) => {\n propsRef.current.onMouseDown?.(event);\n\n if (event.defaultPrevented) return;\n\n isClickFocusRef.current = true;\n },\n [propsRef],\n );\n\n const contextValue = React.useMemo(\n () => ({\n activationMode,\n clearable,\n dir,\n disabled,\n getAutoIndex,\n max,\n orientation,\n readOnly,\n rootId,\n size,\n step,\n }),\n [\n rootId,\n dir,\n orientation,\n activationMode,\n disabled,\n readOnly,\n size,\n max,\n step,\n clearable,\n getAutoIndex,\n ],\n );\n\n const focusContextValue = React.useMemo(\n () => ({\n getItems,\n onFocusableItemAdd,\n onFocusableItemRemove,\n onItemFocus,\n onItemRegister,\n onItemShiftTab,\n onItemUnregister,\n tabStopId,\n }),\n [\n tabStopId,\n onItemFocus,\n onItemShiftTab,\n onFocusableItemAdd,\n onFocusableItemRemove,\n onItemRegister,\n onItemUnregister,\n getItems,\n ],\n );\n\n const RootPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n \n \n \n \n \n \n {dir === 'rtl' ? (\n <>\n \n \n \n ) : (\n <>\n \n \n \n )}\n \n \n \n {isFormControl && (\n \n )}\n \n \n \n );\n}\n\ninterface RatingItemProps extends Omit, 'children'> {\n index?: number;\n asChild?: boolean;\n children?: React.ReactNode | ((dataState: DataState) => React.ReactNode);\n}\n\nfunction RatingItem(props: RatingItemProps) {\n const {\n index,\n asChild,\n onClick: onClickProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n onMouseDown: onMouseDownProp,\n onMouseEnter: onMouseEnterProp,\n onMouseMove: onMouseMoveProp,\n onMouseLeave: onMouseLeaveProp,\n disabled,\n className,\n children,\n ref,\n ...itemProps\n } = props;\n\n const itemRef = React.useRef(null);\n const composedRef = useComposedRefs(ref, itemRef);\n\n const context = useRatingContext(ITEM_NAME);\n\n const instanceId = React.useId();\n\n const actualIndex = React.useMemo(() => {\n if (index !== undefined) {\n return index;\n }\n\n return context.getAutoIndex(instanceId);\n }, [index, context, instanceId]);\n\n const itemValue = actualIndex + 1;\n const store = useStoreContext(ITEM_NAME);\n const focusContext = useFocusContext(ITEM_NAME);\n const value = useStore((state) => state.value);\n const hoveredValue = useStore((state) => state.hoveredValue);\n const clearable = context.clearable;\n const step = context.step;\n const activationMode = context.activationMode;\n\n const itemId = getItemId(context.rootId, itemValue);\n const isDisabled = context.disabled || disabled;\n const isReadOnly = context.readOnly;\n const isTabStop = focusContext.tabStopId === itemId;\n\n const displayValue = hoveredValue ?? value;\n const isFilled = displayValue >= itemValue;\n const isPartiallyFilled =\n step < 1 && displayValue >= itemValue - step && displayValue < itemValue;\n const isHovered = hoveredValue !== null && hoveredValue < itemValue;\n\n const isMouseClickRef = React.useRef(false);\n\n const propsRef = useAsRef({\n onClick: onClickProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n onMouseDown: onMouseDownProp,\n onMouseEnter: onMouseEnterProp,\n onMouseLeave: onMouseLeaveProp,\n onMouseMove: onMouseMoveProp,\n });\n\n useIsomorphicLayoutEffect(() => {\n focusContext.onItemRegister({\n disabled: !!isDisabled,\n id: itemId,\n ref: itemRef,\n value: itemValue,\n });\n\n if (!isDisabled) {\n focusContext.onFocusableItemAdd();\n }\n\n return () => {\n focusContext.onItemUnregister(itemId);\n if (!isDisabled) {\n focusContext.onFocusableItemRemove();\n }\n };\n }, [focusContext, itemId, itemValue, isDisabled]);\n\n const onClick = React.useCallback(\n (event: React.MouseEvent) => {\n propsRef.current.onClick?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly) {\n let newValue = itemValue;\n\n if (step < 1) {\n const rect = event.currentTarget.getBoundingClientRect();\n const clickX = event.clientX - rect.left;\n const isLeftHalf = clickX < rect.width / 2;\n\n if (context.dir === 'rtl') {\n if (!isLeftHalf) {\n newValue = itemValue - step;\n }\n } else {\n if (isLeftHalf) {\n newValue = itemValue - step;\n }\n }\n }\n\n if (clearable && value === newValue) {\n newValue = 0;\n }\n\n store.setState('value', newValue);\n }\n },\n [isDisabled, isReadOnly, clearable, step, value, itemValue, store, context.dir, propsRef],\n );\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent) => {\n propsRef.current.onFocus?.(event);\n if (event.defaultPrevented) return;\n\n focusContext.onItemFocus(itemId);\n\n const isKeyboardFocus = !isMouseClickRef.current;\n\n if (!isDisabled && !isReadOnly && activationMode !== 'manual' && isKeyboardFocus) {\n // For half-step mode, check if the current value is a half-step that belongs to this item\n // e.g., if value is 3.5 and itemValue is 4, don't change it\n const isHalfStepValue = step < 1 && value === itemValue - step;\n\n if (!isHalfStepValue) {\n const newValue = clearable && value === itemValue ? 0 : itemValue;\n store.setState('value', newValue);\n }\n }\n\n isMouseClickRef.current = false;\n },\n [\n focusContext,\n itemId,\n activationMode,\n isDisabled,\n isReadOnly,\n clearable,\n value,\n itemValue,\n step,\n store,\n propsRef,\n ],\n );\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent) => {\n propsRef.current.onKeyDown?.(event);\n if (event.defaultPrevented) return;\n\n if ((event.key === 'Enter' || event.key === ' ') && activationMode === 'manual') {\n event.preventDefault();\n if (!isDisabled && !isReadOnly && itemRef.current) {\n itemRef.current.click();\n }\n return;\n }\n\n if (event.key === 'Tab' && event.shiftKey) {\n focusContext.onItemShiftTab();\n return;\n }\n\n if (event.target !== event.currentTarget) return;\n\n const focusIntent = getFocusIntent(event, context.dir, context.orientation);\n\n if (focusIntent !== undefined) {\n if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;\n event.preventDefault();\n\n // For half-step mode, increment/decrement by step value instead of jumping to next item\n if (step < 1 && (focusIntent === 'prev' || focusIntent === 'next')) {\n if (!isDisabled && !isReadOnly) {\n let newValue = value;\n\n if (focusIntent === 'next') {\n newValue = Math.min(value + step, context.max);\n } else {\n newValue = Math.max(value - step, 0);\n }\n\n store.setState('value', newValue);\n\n // Find and focus the item that represents this value\n const items = focusContext.getItems().filter((item) => !item.disabled);\n const targetItem = items.find((item) => item.value === Math.ceil(newValue));\n if (targetItem?.ref.current) {\n queueMicrotask(() => targetItem.ref.current?.focus());\n }\n }\n return;\n }\n\n // For full-step mode or Home/End keys, use the original navigation\n const items = focusContext.getItems().filter((item) => !item.disabled);\n let candidateRefs = items.map((item) => item.ref);\n\n if (focusIntent === 'last') {\n candidateRefs.reverse();\n } else if (focusIntent === 'prev' || focusIntent === 'next') {\n if (focusIntent === 'prev') candidateRefs.reverse();\n const currentIndex = candidateRefs.findIndex(\n (ref) => ref.current === event.currentTarget,\n );\n candidateRefs = candidateRefs.slice(currentIndex + 1);\n }\n\n queueMicrotask(() => focusFirst(candidateRefs));\n }\n },\n [\n focusContext,\n context.dir,\n context.orientation,\n activationMode,\n isDisabled,\n isReadOnly,\n step,\n value,\n context.max,\n store,\n propsRef,\n ],\n );\n\n const onMouseDown = React.useCallback(\n (event: React.MouseEvent) => {\n propsRef.current.onMouseDown?.(event);\n if (event.defaultPrevented) return;\n\n isMouseClickRef.current = true;\n\n if (isDisabled) {\n event.preventDefault();\n } else {\n focusContext.onItemFocus(itemId);\n }\n },\n [focusContext, itemId, isDisabled, propsRef],\n );\n\n const onMouseEnter = React.useCallback(\n (event: React.MouseEvent) => {\n propsRef.current.onMouseEnter?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly) {\n let hoverValue = itemValue;\n\n if (step < 1) {\n const rect = event.currentTarget.getBoundingClientRect();\n const mouseX = event.clientX - rect.left;\n const isLeftHalf = mouseX < rect.width / 2;\n\n if (context.dir === 'rtl') {\n if (!isLeftHalf) {\n hoverValue = itemValue - step;\n }\n } else {\n if (isLeftHalf) {\n hoverValue = itemValue - step;\n }\n }\n }\n\n store.setState('hoveredValue', hoverValue);\n }\n },\n [isDisabled, isReadOnly, step, itemValue, store, context.dir, propsRef],\n );\n\n const onMouseLeave = React.useCallback(\n (event: React.MouseEvent) => {\n propsRef.current.onMouseLeave?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly) {\n store.setState('hoveredValue', null);\n }\n },\n [isDisabled, isReadOnly, store, propsRef],\n );\n\n const onMouseMove = React.useCallback(\n (event: React.MouseEvent) => {\n propsRef.current.onMouseMove?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly && step < 1) {\n const rect = event.currentTarget.getBoundingClientRect();\n const mouseX = event.clientX - rect.left;\n const isLeftHalf = mouseX < rect.width / 2;\n\n let hoverValue = itemValue;\n if (context.dir === 'rtl') {\n hoverValue = !isLeftHalf ? itemValue - step : itemValue;\n } else {\n hoverValue = isLeftHalf ? itemValue - step : itemValue;\n }\n\n store.setState('hoveredValue', hoverValue);\n }\n },\n [isDisabled, isReadOnly, step, itemValue, store, context.dir, propsRef],\n );\n\n const dataState: DataState = isFilled ? 'full' : isPartiallyFilled ? 'partial' : 'empty';\n\n const ItemPrimitive = asChild ? SlotPrimitive.Slot : 'button';\n\n return (\n \n {typeof children === 'function' ? children(dataState) : (children ?? )}\n \n );\n}\n\nexport { Rating, RatingItem, useStore as useRating };\n", + "type": "registry:component", + "target": "@components/rating.tsx" + } + ], + "type": "registry:component" +} \ No newline at end of file diff --git a/docs/public/r/registry.json b/docs/public/r/registry.json new file mode 100644 index 0000000..930d160 --- /dev/null +++ b/docs/public/r/registry.json @@ -0,0 +1,1437 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry.json", + "name": "kombase", + "homepage": "https://kombase.komerce.id", + "items": [ + { + "name": "alert-dialog", + "title": "Alert Dialog", + "description": "A modal dialog for critical actions that requires user acknowledgment.", + "dependencies": [ + "@radix-ui/react-alert-dialog" + ], + "registryDependencies": [ + "button" + ], + "files": [ + { + "path": "registry/ui/alert-dialog.tsx", + "type": "registry:ui", + "target": "@ui/alert-dialog.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "alert", + "title": "Alert", + "description": "Displays a callout for important information with variant styles.", + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/alert.tsx", + "type": "registry:ui", + "target": "@ui/alert.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "avatar", + "title": "Avatar", + "description": "An image element with a fallback for user profile pictures.", + "dependencies": [ + "@radix-ui/react-avatar" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/avatar.tsx", + "type": "registry:ui", + "target": "@ui/avatar.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "badge", + "title": "Badge", + "description": "A small status descriptor with multiple visual variants.", + "dependencies": [ + "class-variance-authority" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/badge.tsx", + "type": "registry:ui", + "target": "@ui/badge.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "button", + "title": "Button", + "description": "An interactive button with multiple size and variant options.", + "dependencies": [ + "@radix-ui/react-slot", + "class-variance-authority" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/button.tsx", + "type": "registry:ui", + "target": "@ui/button.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "calendar", + "title": "Calendar", + "description": "A date picker calendar with range selection, presets, and RTL support.", + "dependencies": [ + "react-day-picker", + "dayjs" + ], + "registryDependencies": [ + "button", + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/calendar.tsx", + "type": "registry:ui", + "target": "@ui/calendar.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "checkbox", + "title": "Checkbox", + "description": "A control for boolean input with indeterminate state support.", + "dependencies": [ + "@radix-ui/react-checkbox" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/checkbox.tsx", + "type": "registry:ui", + "target": "@ui/checkbox.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "collapsible", + "title": "Collapsible", + "description": "An interactive component that expands and collapses content.", + "dependencies": [ + "@radix-ui/react-collapsible" + ], + "files": [ + { + "path": "registry/ui/collapsible.tsx", + "type": "registry:ui", + "target": "@ui/collapsible.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "combobox", + "title": "Combobox", + "description": "A searchable select input with command palette integration.", + "dependencies": [ + "cmdk" + ], + "registryDependencies": [ + "popover", + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/combobox.tsx", + "type": "registry:ui", + "target": "@ui/combobox.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "command", + "title": "Command", + "description": "A command palette for searchable actions and navigation.", + "dependencies": [ + "cmdk" + ], + "registryDependencies": [ + "dialog", + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/command.tsx", + "type": "registry:ui", + "target": "@ui/command.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "debounced-input", + "title": "Debounced Input", + "description": "An input that delays onChange calls to reduce unnecessary re-renders.", + "registryDependencies": [ + "input" + ], + "files": [ + { + "path": "registry/ui/debounced-input.tsx", + "type": "registry:ui", + "target": "@ui/debounced-input.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "dialog", + "title": "Dialog", + "description": "A modal overlay for focused content or forms.", + "dependencies": [ + "@radix-ui/react-dialog" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/dialog.tsx", + "type": "registry:ui", + "target": "@ui/dialog.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "dropdown-menu", + "title": "Dropdown Menu", + "description": "A contextual menu triggered by a button click.", + "dependencies": [ + "@radix-ui/react-dropdown-menu" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/dropdown-menu.tsx", + "type": "registry:ui", + "target": "@ui/dropdown-menu.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "file-upload", + "title": "File Upload", + "description": "A drag-and-drop file upload component with validation, progress tracking, and paste support.", + "dependencies": [ + "@radix-ui/react-slot", + "lucide-react" + ], + "registryDependencies": [ + "@kombase/utils", + "@kombase/use-as-ref", + "@kombase/use-lazy-ref" + ], + "files": [ + { + "path": "registry/ui/file-upload.tsx", + "type": "registry:ui", + "target": "@ui/file-upload.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "form", + "title": "Form", + "description": "Form primitives with react-hook-form integration and validation messages.", + "dependencies": [ + "@radix-ui/react-form", + "@radix-ui/react-label", + "react-hook-form" + ], + "registryDependencies": [ + "label", + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/form.tsx", + "type": "registry:ui", + "target": "@ui/form.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "input-group", + "title": "Input Group", + "description": "An input with prefix/suffix addons and action buttons.", + "registryDependencies": [ + "button", + "input", + "textarea", + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/input-group.tsx", + "type": "registry:ui", + "target": "@ui/input-group.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "input", + "title": "Input", + "description": "A styled text input field.", + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/input.tsx", + "type": "registry:ui", + "target": "@ui/input.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "label", + "title": "Label", + "description": "An accessible label for form controls.", + "dependencies": [ + "@radix-ui/react-label" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/label.tsx", + "type": "registry:ui", + "target": "@ui/label.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "popover", + "title": "Popover", + "description": "A floating panel anchored to a trigger element.", + "dependencies": [ + "@radix-ui/react-popover" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/popover.tsx", + "type": "registry:ui", + "target": "@ui/popover.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "radio-group", + "title": "Radio Group", + "description": "A set of radio buttons for single-option selection.", + "dependencies": [ + "@radix-ui/react-radio-group" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/radio-group.tsx", + "type": "registry:ui", + "target": "@ui/radio-group.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "select", + "title": "Select", + "description": "A dropdown select input with search and grouping.", + "dependencies": [ + "@radix-ui/react-select" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/select.tsx", + "type": "registry:ui", + "target": "@ui/select.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "separator", + "title": "Separator", + "description": "A visual divider between content sections.", + "dependencies": [ + "@radix-ui/react-separator" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/separator.tsx", + "type": "registry:ui", + "target": "@ui/separator.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "skeleton", + "title": "Skeleton", + "description": "A placeholder loading indicator.", + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/skeleton.tsx", + "type": "registry:ui", + "target": "@ui/skeleton.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "slider", + "title": "Slider", + "description": "A range input for selecting numeric values.", + "dependencies": [ + "@radix-ui/react-slider" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/slider.tsx", + "type": "registry:ui", + "target": "@ui/slider.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "switch", + "title": "Switch", + "description": "A toggle for binary on/off states.", + "dependencies": [ + "@radix-ui/react-switch" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/switch.tsx", + "type": "registry:ui", + "target": "@ui/switch.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "table", + "title": "Table", + "description": "A styled HTML table with header, body, and footer.", + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/table.tsx", + "type": "registry:ui", + "target": "@ui/table.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "tabs", + "title": "Tabs", + "description": "A tabbed navigation component for switching between views.", + "dependencies": [ + "@radix-ui/react-tabs" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/tabs.tsx", + "type": "registry:ui", + "target": "@ui/tabs.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "textarea", + "title": "Textarea", + "description": "A multi-line text input field.", + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/textarea.tsx", + "type": "registry:ui", + "target": "@ui/textarea.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "tooltip", + "title": "Tooltip", + "description": "A popup that displays information on hover or focus.", + "dependencies": [ + "@radix-ui/react-tooltip" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/tooltip.tsx", + "type": "registry:ui", + "target": "@ui/tooltip.tsx" + } + ], + "type": "registry:ui" + }, + { + "name": "use-as-ref", + "title": "useAsRef", + "description": "Keeps a mutable ref synchronized with the latest value using layout effect.", + "registryDependencies": [ + "@kombase/use-isomorphic-layout-effect" + ], + "files": [ + { + "path": "registry/hooks/use-as-ref.ts", + "type": "registry:hook", + "target": "@hooks/use-as-ref.ts" + } + ], + "type": "registry:hook" + }, + { + "name": "use-callback-ref", + "title": "useCallbackRef", + "description": "Creates a stable callback ref that always calls the latest function.", + "files": [ + { + "path": "registry/hooks/use-callback-ref.ts", + "type": "registry:hook", + "target": "@hooks/use-callback-ref.ts" + } + ], + "type": "registry:hook" + }, + { + "name": "use-data-table", + "title": "useDataTable", + "description": "Hook for managing data table state, filtering, sorting, and pagination with TanStack Table.", + "dependencies": [ + "@tanstack/react-table" + ], + "registryDependencies": [ + "@kombase/data-table-config", + "@kombase/data-table-types", + "@kombase/lib-data-table" + ], + "files": [ + { + "path": "registry/hooks/use-data-table.ts", + "type": "registry:hook", + "target": "@hooks/use-data-table.ts" + } + ], + "type": "registry:hook" + }, + { + "name": "use-debounced-callback", + "title": "useDebouncedCallback", + "description": "Returns a debounced version of a callback function with configurable delay.", + "registryDependencies": [ + "@kombase/use-callback-ref" + ], + "files": [ + { + "path": "registry/hooks/use-debounced-callback.ts", + "type": "registry:hook", + "target": "@hooks/use-debounced-callback.ts" + } + ], + "type": "registry:hook" + }, + { + "name": "use-isomorphic-layout-effect", + "title": "useIsomorphicLayoutEffect", + "description": "SSR-safe wrapper around useLayoutEffect that falls back to useEffect on the server.", + "files": [ + { + "path": "registry/hooks/use-isomorphic-layout-effect.ts", + "type": "registry:hook", + "target": "@hooks/use-isomorphic-layout-effect.ts" + } + ], + "type": "registry:hook" + }, + { + "name": "use-lazy-ref", + "title": "useLazyRef", + "description": "Creates a ref that is lazily initialized on first access.", + "files": [ + { + "path": "registry/hooks/use-lazy-ref.ts", + "type": "registry:hook", + "target": "@hooks/use-lazy-ref.ts" + } + ], + "type": "registry:hook" + }, + { + "name": "utils", + "title": "Utilities", + "description": "Core utility function (cn) for merging Tailwind CSS classes.", + "dependencies": [ + "clsx", + "tailwind-merge" + ], + "files": [ + { + "path": "registry/lib/utils.ts", + "type": "registry:lib", + "target": "@lib/utils.ts" + } + ], + "type": "registry:lib" + }, + { + "name": "component-refs", + "title": "Component Refs", + "description": "Ref composition utilities for combining multiple React refs.", + "files": [ + { + "path": "registry/lib/component-refs.ts", + "type": "registry:lib", + "target": "@lib/component-refs.ts" + } + ], + "type": "registry:lib" + }, + { + "name": "lib-data-table", + "title": "Data Table Helpers", + "description": "Helper functions for data table filter validation and parsing.", + "dependencies": [ + "@tanstack/react-table" + ], + "registryDependencies": [ + "@kombase/data-table-config", + "@kombase/data-table-types" + ], + "files": [ + { + "path": "registry/lib/data-table.ts", + "type": "registry:lib", + "target": "@lib/data-table.ts" + } + ], + "type": "registry:lib" + }, + { + "name": "lib-date", + "title": "Date Helpers", + "description": "Date formatting and range utilities using dayjs and react-day-picker.", + "dependencies": [ + "dayjs", + "react-day-picker" + ], + "files": [ + { + "path": "registry/lib/date.ts", + "type": "registry:lib", + "target": "@lib/date.ts" + } + ], + "type": "registry:lib" + }, + { + "name": "lib-pagination", + "title": "Pagination Helpers", + "description": "Pagination range calculation utilities for data table pagination.", + "files": [ + { + "path": "registry/lib/pagination.ts", + "type": "registry:lib", + "target": "@lib/pagination.ts" + } + ], + "type": "registry:lib" + }, + { + "name": "filter-helper", + "title": "Filter Helper", + "description": "Serializes data table filters to backend query parameters with multiple format styles.", + "dependencies": [ + "dayjs" + ], + "registryDependencies": [ + "@kombase/data-table-types" + ], + "files": [ + { + "path": "registry/lib/filter-helper.ts", + "type": "registry:lib", + "target": "@lib/filter-helper.ts" + } + ], + "type": "registry:lib" + }, + { + "name": "data-table-config", + "title": "Data Table Config", + "description": "Configuration constants and translations for data table components.", + "files": [ + { + "path": "registry/components/data-table/data-table-config.ts", + "type": "registry:component", + "target": "@components/data-table/data-table-config.ts" + } + ], + "type": "registry:component" + }, + { + "name": "data-table-types", + "title": "Data Table Types", + "description": "TypeScript type definitions for data table column filters and options.", + "registryDependencies": [ + "@kombase/data-table-config" + ], + "files": [ + { + "path": "registry/components/data-table/types.ts", + "type": "registry:component", + "target": "@components/data-table/types.ts" + } + ], + "type": "registry:component" + }, + { + "name": "data-table-pagination", + "title": "Data Table Pagination", + "description": "Pagination controls with page size selector for data tables.", + "dependencies": [ + "@tanstack/react-table" + ], + "registryDependencies": [ + "button", + "select", + "@kombase/lib-pagination", + "@kombase/utils" + ], + "files": [ + { + "path": "registry/components/data-table/data-table-pagination.tsx", + "type": "registry:component", + "target": "@components/data-table/data-table-pagination.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "data-table-range-filter", + "title": "Data Table Range Filter", + "description": "Min/max range filter with debounced inputs for numeric columns.", + "registryDependencies": [ + "@kombase/debounced-input" + ], + "files": [ + { + "path": "registry/components/data-table/data-table-range-filter.tsx", + "type": "registry:component", + "target": "@components/data-table/data-table-range-filter.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "data-table-skeleton", + "title": "Data Table Skeleton", + "description": "Loading skeleton placeholder for data tables.", + "registryDependencies": [ + "skeleton", + "table" + ], + "files": [ + { + "path": "registry/components/data-table/data-table-skeleton.tsx", + "type": "registry:component", + "target": "@components/data-table/data-table-skeleton.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "data-table-date-filter", + "title": "Data Table Date Filter", + "description": "Date range filter with calendar presets for data table columns.", + "registryDependencies": [ + "button", + "calendar", + "popover", + "separator", + "@kombase/lib-date" + ], + "files": [ + { + "path": "registry/components/data-table/data-table-date-filter.tsx", + "type": "registry:component", + "target": "@components/data-table/data-table-date-filter.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "data-table-faceted-filter", + "title": "Data Table Faceted Filter", + "description": "Multi-select faceted filter with search for categorical columns.", + "registryDependencies": [ + "badge", + "button", + "command", + "popover", + "separator" + ], + "files": [ + { + "path": "registry/components/data-table/data-table-faceted-filter.tsx", + "type": "registry:component", + "target": "@components/data-table/data-table-faceted-filter.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "data-table-slider-filter", + "title": "Data Table Slider Filter", + "description": "Numeric range filter with slider input for data table columns.", + "registryDependencies": [ + "button", + "input", + "label", + "popover", + "separator", + "slider" + ], + "files": [ + { + "path": "registry/components/data-table/data-table-slider-filter.tsx", + "type": "registry:component", + "target": "@components/data-table/data-table-slider-filter.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "data-table-view-options", + "title": "Data Table View Options", + "description": "Column visibility toggle menu for data tables.", + "registryDependencies": [ + "button", + "command", + "popover" + ], + "files": [ + { + "path": "registry/components/data-table/data-table-view-options.tsx", + "type": "registry:component", + "target": "@components/data-table/data-table-view-options.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "data-table-toolbar", + "title": "Data Table Toolbar", + "description": "Toolbar with search, filters, and view options for data tables.", + "registryDependencies": [ + "button", + "@kombase/debounced-input", + "@kombase/data-table-date-filter", + "@kombase/data-table-faceted-filter", + "@kombase/data-table-slider-filter", + "@kombase/data-table-view-options" + ], + "files": [ + { + "path": "registry/components/data-table/data-table-toolbar.tsx", + "type": "registry:component", + "target": "@components/data-table/data-table-toolbar.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "data-table-bulk-action", + "title": "Data Table Bulk Action", + "description": "Action bar for batch operations on selected table rows.", + "registryDependencies": [ + "badge", + "button", + "separator", + "tooltip" + ], + "files": [ + { + "path": "registry/components/data-table/data-table-bulk-action.tsx", + "type": "registry:component", + "target": "@components/data-table/data-table-bulk-action.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "data-table-column-header", + "title": "Data Table Column Header", + "description": "Sortable column header with dropdown menu for data tables.", + "registryDependencies": [ + "dropdown-menu" + ], + "files": [ + { + "path": "registry/components/data-table/data-table-column-header.tsx", + "type": "registry:component", + "target": "@components/data-table/data-table-column-header.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "data-table-advance-filter", + "title": "Data Table Advanced Filter", + "description": "Advanced multi-column filter panel with custom operators and debounced input.", + "registryDependencies": [ + "@kombase/lib-data-table", + "badge", + "button", + "calendar", + "command", + "popover", + "select", + "@kombase/data-table-config", + "@kombase/data-table-date-filter", + "@kombase/data-table-range-filter" + ], + "files": [ + { + "path": "registry/components/data-table/data-table-advance-filter.tsx", + "type": "registry:component", + "target": "@components/data-table/data-table-advance-filter.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "data-table", + "title": "Data Table", + "description": "Full-featured data table with sorting, filtering, pagination, and row selection powered by TanStack Table.", + "dependencies": [ + "@tanstack/react-table" + ], + "registryDependencies": [ + "table", + "@kombase/data-table-config", + "@kombase/data-table-types", + "@kombase/data-table-pagination", + "@kombase/data-table-toolbar", + "@kombase/data-table-advance-filter", + "@kombase/data-table-bulk-action", + "@kombase/data-table-column-header", + "@kombase/data-table-skeleton", + "@kombase/lib-data-table", + "@kombase/use-data-table" + ], + "files": [ + { + "path": "registry/components/data-table/data-table.tsx", + "type": "registry:component", + "target": "@components/data-table/data-table.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "action-bar", + "title": "Action Bar", + "description": "A fixed bottom bar for contextual actions with animated entrance.", + "dependencies": [ + "@radix-ui/react-direction" + ], + "registryDependencies": [ + "button", + "@kombase/component-refs", + "@kombase/use-as-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "files": [ + { + "path": "registry/components/action-bar.tsx", + "type": "registry:component", + "target": "@components/action-bar.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "avatar-group", + "title": "Avatar Group", + "description": "Grouped avatar display with overflow counter and RTL support.", + "dependencies": [ + "@radix-ui/react-avatar" + ], + "files": [ + { + "path": "registry/components/avatar-group.tsx", + "type": "registry:component", + "target": "@components/avatar-group.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "confirm-dialog", + "title": "Confirm Dialog", + "description": "A confirmation modal with customizable actions and descriptions.", + "registryDependencies": [ + "alert-dialog", + "button" + ], + "files": [ + { + "path": "registry/components/confirm-dialog.tsx", + "type": "registry:component", + "target": "@components/confirm-dialog.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "long-text", + "title": "Long Text", + "description": "Truncated text with tooltip or popover for viewing full content.", + "registryDependencies": [ + "popover", + "tooltip" + ], + "files": [ + { + "path": "registry/components/long-text.tsx", + "type": "registry:component", + "target": "@components/long-text.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "phone-input", + "title": "Phone Input", + "description": "International phone number input with country code selector and flag icons.", + "registryDependencies": [ + "command", + "input", + "popover", + "@kombase/component-refs", + "@kombase/visually-hidden-input", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "files": [ + { + "path": "registry/components/phone-input.tsx", + "type": "registry:component", + "target": "@components/phone-input.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "rating", + "title": "Rating", + "description": "Customizable star rating component with keyboard navigation and form integration.", + "dependencies": [ + "@radix-ui/react-direction" + ], + "registryDependencies": [ + "@kombase/component-refs", + "@kombase/visually-hidden-input", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "files": [ + { + "path": "registry/components/rating.tsx", + "type": "registry:component", + "target": "@components/rating.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "stepper", + "title": "Stepper", + "description": "Multi-step progress indicator with vertical layout and form validation support.", + "dependencies": [ + "@radix-ui/react-direction" + ], + "registryDependencies": [ + "@kombase/component-refs", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "files": [ + { + "path": "registry/components/stepper.tsx", + "type": "registry:component", + "target": "@components/stepper.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "timeline", + "title": "Timeline", + "description": "Chronological event timeline with alternate positioning and horizontal layout.", + "dependencies": [ + "@radix-ui/react-direction", + "class-variance-authority" + ], + "registryDependencies": [ + "@kombase/component-refs", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "files": [ + { + "path": "registry/components/timeline.tsx", + "type": "registry:component", + "target": "@components/timeline.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "tour", + "title": "Tour", + "description": "Step-by-step onboarding tour with spotlight highlighting and floating UI.", + "dependencies": [ + "@floating-ui/react-dom", + "@radix-ui/react-direction" + ], + "registryDependencies": [ + "button", + "@kombase/component-refs", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "files": [ + { + "path": "registry/components/tour.tsx", + "type": "registry:component", + "target": "@components/tour.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "visually-hidden-input", + "title": "Visually Hidden Input", + "description": "An invisible input for form integration with custom controls.", + "files": [ + { + "path": "registry/components/visually-hidden-input.tsx", + "type": "registry:component", + "target": "@components/visually-hidden-input.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "password-input", + "title": "Password Input", + "description": "A password field with show/hide toggle button.", + "dependencies": [ + "lucide-react" + ], + "registryDependencies": [ + "button", + "@kombase/utils" + ], + "files": [ + { + "path": "registry/components/password-input.tsx", + "type": "registry:component", + "target": "@components/password-input.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "select-dropdown", + "title": "Select Dropdown", + "description": "A form-integrated select dropdown with loading state support.", + "dependencies": [ + "lucide-react" + ], + "registryDependencies": [ + "form", + "select", + "@kombase/utils" + ], + "files": [ + { + "path": "registry/components/select-dropdown.tsx", + "type": "registry:component", + "target": "@components/select-dropdown.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "form-date-picker", + "title": "Form Date Picker", + "description": "Date picker with calendar popup, integrated with react-hook-form.", + "dependencies": [ + "dayjs" + ], + "registryDependencies": [ + "calendar", + "form", + "label", + "popover" + ], + "files": [ + { + "path": "registry/form/form-date-picker.tsx", + "type": "registry:component", + "target": "@components/form/form-date-picker.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "form-input", + "title": "Form Input", + "description": "Text input with label and validation, integrated with react-hook-form.", + "registryDependencies": [ + "form", + "input", + "label" + ], + "files": [ + { + "path": "registry/form/form-input.tsx", + "type": "registry:component", + "target": "@components/form/form-input.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "form-input-group", + "title": "Form Input Group", + "description": "Input group with prefix/suffix addons, integrated with react-hook-form.", + "registryDependencies": [ + "form", + "@kombase/input-group", + "label" + ], + "files": [ + { + "path": "registry/form/form-input-group.tsx", + "type": "registry:component", + "target": "@components/form/form-input-group.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "form-password", + "title": "Form Password", + "description": "Password input with show/hide toggle, integrated with react-hook-form.", + "registryDependencies": [ + "form", + "label", + "@kombase/password-input" + ], + "files": [ + { + "path": "registry/form/form-password.tsx", + "type": "registry:component", + "target": "@components/form/form-password.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "form-phone-input", + "title": "Form Phone Input", + "description": "International phone number input with country select, integrated with react-hook-form.", + "registryDependencies": [ + "form", + "label", + "@kombase/phone-input" + ], + "files": [ + { + "path": "registry/form/form-phone-input.tsx", + "type": "registry:component", + "target": "@components/form/form-phone-input.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "form-pick", + "title": "Form Pick", + "description": "Card-style single selection input, integrated with react-hook-form.", + "registryDependencies": [ + "form", + "label", + "radio-group" + ], + "files": [ + { + "path": "registry/form/form-pick.tsx", + "type": "registry:component", + "target": "@components/form/form-pick.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "form-radio", + "title": "Form Radio", + "description": "Radio button group with label and layout options, integrated with react-hook-form.", + "registryDependencies": [ + "form", + "label", + "radio-group" + ], + "files": [ + { + "path": "registry/form/form-radio.tsx", + "type": "registry:component", + "target": "@components/form/form-radio.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "form-search-select", + "title": "Form Search Select", + "description": "Searchable combobox select with async data support, integrated with react-hook-form.", + "registryDependencies": [ + "combobox", + "form", + "label" + ], + "files": [ + { + "path": "registry/form/form-search-select.tsx", + "type": "registry:component", + "target": "@components/form/form-search-select.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "form-textarea", + "title": "Form Textarea", + "description": "Multi-line text area with character count and validation, integrated with react-hook-form.", + "registryDependencies": [ + "form", + "label", + "textarea" + ], + "files": [ + { + "path": "registry/form/form-textarea.tsx", + "type": "registry:component", + "target": "@components/form/form-textarea.tsx" + } + ], + "type": "registry:component" + }, + { + "name": "form-upload", + "title": "Form Upload", + "description": "File upload with drag-and-drop and progress tracking, integrated with react-hook-form.", + "registryDependencies": [ + "form", + "label", + "@kombase/file-upload" + ], + "files": [ + { + "path": "registry/form/form-upload.tsx", + "type": "registry:component", + "target": "@components/form/form-upload.tsx" + } + ], + "type": "registry:component" + } + ] +} \ No newline at end of file diff --git a/docs/public/r/select-dropdown.json b/docs/public/r/select-dropdown.json new file mode 100644 index 0000000..7681b2f --- /dev/null +++ b/docs/public/r/select-dropdown.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "select-dropdown", + "title": "Select Dropdown", + "description": "A form-integrated select dropdown with loading state support.", + "dependencies": [ + "lucide-react" + ], + "registryDependencies": [ + "form", + "select", + "@kombase/utils" + ], + "files": [ + { + "path": "registry/components/select-dropdown.tsx", + "content": "import { Loader } from 'lucide-react';\nimport { FormControl } from '@/components/ui/form';\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@/components/ui/select';\nimport { cn } from '@/lib/utils';\n\ntype SelectDropdownProps = {\n onValueChange?: (value: string) => void;\n defaultValue: string | undefined;\n placeholder?: string;\n isPending?: boolean;\n items: { label: string; value: string }[] | undefined;\n disabled?: boolean;\n className?: string;\n isControlled?: boolean;\n};\n\nexport function SelectDropdown({\n defaultValue,\n onValueChange,\n isPending,\n items,\n placeholder,\n disabled,\n className = '',\n isControlled = false,\n}: SelectDropdownProps) {\n const defaultState = isControlled\n ? { onValueChange, value: defaultValue }\n : { defaultValue, onValueChange };\n return (\n \n );\n}\n", + "type": "registry:component", + "target": "@components/select-dropdown.tsx" + } + ], + "type": "registry:component" +} \ No newline at end of file diff --git a/docs/public/r/select.json b/docs/public/r/select.json new file mode 100644 index 0000000..b148627 --- /dev/null +++ b/docs/public/r/select.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "select", + "title": "Select", + "description": "A dropdown select input with search and grouping.", + "dependencies": [ + "@radix-ui/react-select" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/select.tsx", + "content": "'use client';\n\nimport * as SelectPrimitive from '@radix-ui/react-select';\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';\nimport * as React from 'react';\n\nimport { cn } from '@/lib/utils';\n\nconst Select = ({ ...props }: React.ComponentProps) => (\n \n);\nSelect.displayName = 'Select';\n\nconst SelectGroup = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>((props, ref) => );\nSelectGroup.displayName = SelectPrimitive.Group.displayName;\n\nconst SelectValue = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>((props, ref) => );\nSelectValue.displayName = SelectPrimitive.Value.displayName;\n\nconst SelectTrigger = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef & {\n size?: 'sm' | 'default';\n }\n>(({ className, size = 'default', children, ...props }, ref) => (\n \n {children}\n \n \n \n \n));\nSelectTrigger.displayName = SelectPrimitive.Trigger.displayName;\n\nconst SelectContent = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>(({ className, children, position = 'item-aligned', align = 'center', ...props }, ref) => (\n \n \n \n \n {children}\n \n \n \n \n));\nSelectContent.displayName = SelectPrimitive.Content.displayName;\n\nconst SelectLabel = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nSelectLabel.displayName = SelectPrimitive.Label.displayName;\n\nconst SelectItem = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>(({ className, children, ...props }, ref) => (\n \n \n \n \n \n \n {children}\n \n));\nSelectItem.displayName = SelectPrimitive.Item.displayName;\n\nconst SelectSeparator = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nSelectSeparator.displayName = SelectPrimitive.Separator.displayName;\n\nconst SelectScrollUpButton = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n \n \n));\nSelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;\n\nconst SelectScrollDownButton = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n \n \n));\nSelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;\n\nexport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectScrollDownButton,\n SelectScrollUpButton,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n};\n", + "type": "registry:ui", + "target": "@ui/select.tsx" + } + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/docs/public/r/separator.json b/docs/public/r/separator.json new file mode 100644 index 0000000..c54caff --- /dev/null +++ b/docs/public/r/separator.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "separator", + "title": "Separator", + "description": "A visual divider between content sections.", + "dependencies": [ + "@radix-ui/react-separator" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/separator.tsx", + "content": "'use client';\n\nimport * as SeparatorPrimitive from '@radix-ui/react-separator';\nimport * as React from 'react';\nimport { cn } from '@/lib/utils';\n\nconst Separator = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => (\n \n));\nSeparator.displayName = SeparatorPrimitive.Root.displayName;\n\nexport { Separator };\n", + "type": "registry:ui", + "target": "@ui/separator.tsx" + } + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/docs/public/r/skeleton.json b/docs/public/r/skeleton.json new file mode 100644 index 0000000..ca61c3c --- /dev/null +++ b/docs/public/r/skeleton.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "skeleton", + "title": "Skeleton", + "description": "A placeholder loading indicator.", + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/skeleton.tsx", + "content": "'use client';\n\nimport * as React from 'react';\nimport { cn } from '@/lib/utils';\n\nconst Skeleton = React.forwardRef>(\n ({ className, ...props }, ref) => {\n return (\n \n );\n },\n);\nSkeleton.displayName = 'Skeleton';\n\nexport { Skeleton };\n", + "type": "registry:ui", + "target": "@ui/skeleton.tsx" + } + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/docs/public/r/slider.json b/docs/public/r/slider.json new file mode 100644 index 0000000..8c9927d --- /dev/null +++ b/docs/public/r/slider.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "slider", + "title": "Slider", + "description": "A range input for selecting numeric values.", + "dependencies": [ + "@radix-ui/react-slider" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/slider.tsx", + "content": "'use client';\n\nimport * as SliderPrimitive from '@radix-ui/react-slider';\nimport * as React from 'react';\nimport { cn } from '@/lib/utils';\n\nconst Slider = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>(({ className, defaultValue, value, min = 0, max = 100, ...props }, ref) => {\n const _values = React.useMemo(\n () => (Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max]),\n [value, defaultValue, min, max],\n );\n\n return (\n \n \n \n \n {Array.from({ length: _values.length }, (_, index) => (\n \n ))}\n \n );\n});\nSlider.displayName = SliderPrimitive.Root.displayName;\n\nexport { Slider };\n", + "type": "registry:ui", + "target": "@ui/slider.tsx" + } + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/docs/public/r/stepper.json b/docs/public/r/stepper.json new file mode 100644 index 0000000..9820c31 --- /dev/null +++ b/docs/public/r/stepper.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "stepper", + "title": "Stepper", + "description": "Multi-step progress indicator with vertical layout and form validation support.", + "dependencies": [ + "@radix-ui/react-direction" + ], + "registryDependencies": [ + "@kombase/component-refs", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "files": [ + { + "path": "registry/components/stepper.tsx", + "content": "'use client';\n\nimport * as DirectionPrimitive from '@radix-ui/react-direction';\nimport * as SlotPrimitive from '@radix-ui/react-slot';\nimport { Check } from 'lucide-react';\nimport * as React from 'react';\nimport { useAsRef } from '@/hooks/use-as-ref';\nimport { useIsomorphicLayoutEffect } from '@/hooks/use-isomorphic-layout-effect';\nimport { useLazyRef } from '@/hooks/use-lazy-ref';\nimport { useComposedRefs } from '@/lib/component-refs';\nimport { cn } from '@/lib/utils';\n\nconst ROOT_NAME = 'Stepper';\nconst LIST_NAME = 'StepperList';\nconst ITEM_NAME = 'StepperItem';\nconst TRIGGER_NAME = 'StepperTrigger';\nconst INDICATOR_NAME = 'StepperIndicator';\nconst SEPARATOR_NAME = 'StepperSeparator';\nconst TITLE_NAME = 'StepperTitle';\nconst DESCRIPTION_NAME = 'StepperDescription';\nconst CONTENT_NAME = 'StepperContent';\nconst PREV_NAME = 'StepperPrev';\nconst NEXT_NAME = 'StepperNext';\n\nconst ENTRY_FOCUS = 'stepperFocusGroup.onEntryFocus';\nconst EVENT_OPTIONS = { bubbles: false, cancelable: true };\nconst ARROW_KEYS = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'];\n\ntype Direction = 'ltr' | 'rtl';\ntype Orientation = 'horizontal' | 'vertical';\ntype NavigationDirection = 'next' | 'prev';\ntype ActivationMode = 'automatic' | 'manual';\ntype DataState = 'inactive' | 'active' | 'completed';\n\ninterface DivProps extends React.ComponentProps<'div'> {\n asChild?: boolean;\n}\ninterface ButtonProps extends React.ComponentProps<'button'> {\n asChild?: boolean;\n}\n\ntype ListElement = React.ComponentRef;\ntype TriggerElement = React.ComponentRef;\n\nfunction getId(\n id: string,\n variant: 'trigger' | 'content' | 'title' | 'description',\n value: string,\n) {\n return `${id}-${variant}-${value}`;\n}\n\ntype FocusIntent = 'first' | 'last' | 'prev' | 'next';\n\nconst MAP_KEY_TO_FOCUS_INTENT: Record = {\n ArrowDown: 'next',\n ArrowLeft: 'prev',\n ArrowRight: 'next',\n ArrowUp: 'prev',\n End: 'last',\n Home: 'first',\n PageDown: 'last',\n PageUp: 'first',\n};\n\nfunction getDirectionAwareKey(key: string, dir?: Direction) {\n if (dir !== 'rtl') return key;\n return key === 'ArrowLeft' ? 'ArrowRight' : key === 'ArrowRight' ? 'ArrowLeft' : key;\n}\n\nfunction getFocusIntent(\n event: React.KeyboardEvent,\n dir?: Direction,\n orientation?: Orientation,\n) {\n const key = getDirectionAwareKey(event.key, dir);\n if (orientation === 'horizontal' && ['ArrowUp', 'ArrowDown'].includes(key)) return undefined;\n if (orientation === 'vertical' && ['ArrowLeft', 'ArrowRight'].includes(key)) return undefined;\n return MAP_KEY_TO_FOCUS_INTENT[key];\n}\n\nfunction focusFirst(candidates: React.RefObject[], preventScroll = false) {\n const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;\n for (const candidateRef of candidates) {\n const candidate = candidateRef.current;\n if (!candidate) continue;\n if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;\n candidate.focus({ preventScroll });\n if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;\n }\n}\n\nfunction wrapArray(array: T[], startIndex: number) {\n return array.map((_, index) => array[(startIndex + index) % array.length] as T);\n}\n\nfunction getDataState(\n value: string | undefined,\n itemValue: string,\n stepState: StepState | undefined,\n steps: Map,\n variant: 'item' | 'separator' = 'item',\n): DataState {\n const stepKeys = Array.from(steps.keys());\n const currentIndex = stepKeys.indexOf(itemValue);\n\n if (stepState?.completed) return 'completed';\n\n if (value === itemValue) {\n return variant === 'separator' ? 'inactive' : 'active';\n }\n\n if (value) {\n const activeIndex = stepKeys.indexOf(value);\n\n if (activeIndex > currentIndex) return 'completed';\n }\n\n return 'inactive';\n}\n\ninterface StepState {\n value: string;\n completed: boolean;\n disabled: boolean;\n}\n\ninterface StoreState {\n steps: Map;\n value: string;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n setState: (key: K, value: StoreState[K]) => void;\n setStateWithValidation: (value: string, direction: NavigationDirection) => Promise;\n hasValidation: () => boolean;\n notify: () => void;\n addStep: (value: string, completed: boolean, disabled: boolean) => void;\n removeStep: (value: string) => void;\n setStep: (value: string, completed: boolean, disabled: boolean) => void;\n}\n\nconst StoreContext = React.createContext(null);\n\nfunction useStoreContext(consumerName: string) {\n const context = React.useContext(StoreContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\nfunction useStore(selector: (state: StoreState) => T): T {\n const store = useStoreContext('useStore');\n\n const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ninterface ItemData {\n id: string;\n ref: React.RefObject;\n value: string;\n active: boolean;\n disabled: boolean;\n}\n\ninterface StepperContextValue {\n rootId: string;\n dir: Direction;\n orientation: Orientation;\n activationMode: ActivationMode;\n disabled: boolean;\n nonInteractive: boolean;\n loop: boolean;\n}\n\nconst StepperContext = React.createContext(null);\n\nfunction useStepperContext(consumerName: string) {\n const context = React.useContext(StepperContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface StepperProps extends DivProps {\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n onValueComplete?: (value: string, completed: boolean) => void;\n onValueAdd?: (value: string) => void;\n onValueRemove?: (value: string) => void;\n onValidate?: (value: string, direction: NavigationDirection) => boolean | Promise;\n activationMode?: ActivationMode;\n dir?: Direction;\n orientation?: Orientation;\n disabled?: boolean;\n loop?: boolean;\n nonInteractive?: boolean;\n}\n\nfunction Stepper(props: StepperProps) {\n const {\n value,\n defaultValue,\n onValueChange,\n onValueComplete,\n onValueAdd,\n onValueRemove,\n onValidate,\n dir: dirProp,\n orientation = 'horizontal',\n activationMode = 'automatic',\n asChild,\n disabled = false,\n nonInteractive = false,\n loop = false,\n className,\n id,\n ...rootProps\n } = props;\n\n const listenersRef = useLazyRef(() => new Set<() => void>());\n const stateRef = useLazyRef(() => ({\n steps: new Map(),\n value: value ?? defaultValue ?? '',\n }));\n\n const propsRef = useAsRef({\n onValidate,\n onValueAdd,\n onValueChange,\n onValueComplete,\n onValueRemove,\n });\n\n const store = React.useMemo(() => {\n return {\n addStep: (value, completed, disabled) => {\n const newStep: StepState = { completed, disabled, value };\n stateRef.current.steps.set(value, newStep);\n propsRef.current.onValueAdd?.(value);\n store.notify();\n },\n getState: () => stateRef.current,\n hasValidation: () => !!propsRef.current.onValidate,\n notify: () => {\n for (const cb of listenersRef.current) {\n cb();\n }\n },\n removeStep: (value) => {\n stateRef.current.steps.delete(value);\n propsRef.current.onValueRemove?.(value);\n store.notify();\n },\n setState: (key, value) => {\n if (Object.is(stateRef.current[key], value)) return;\n\n if (key === 'value' && typeof value === 'string') {\n stateRef.current.value = value;\n propsRef.current.onValueChange?.(value);\n } else {\n stateRef.current[key] = value;\n }\n\n store.notify();\n },\n setStateWithValidation: async (value, direction) => {\n if (!propsRef.current.onValidate) {\n store.setState('value', value);\n return true;\n }\n\n try {\n const isValid = await propsRef.current.onValidate(value, direction);\n if (isValid) {\n store.setState('value', value);\n }\n return isValid;\n } catch {\n return false;\n }\n },\n setStep: (value, completed, disabled) => {\n const step = stateRef.current.steps.get(value);\n if (step) {\n const updatedStep: StepState = { ...step, completed, disabled };\n stateRef.current.steps.set(value, updatedStep);\n\n if (completed !== step.completed) {\n propsRef.current.onValueComplete?.(value, completed);\n }\n\n store.notify();\n }\n },\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n };\n }, [listenersRef, stateRef, propsRef]);\n\n useIsomorphicLayoutEffect(() => {\n if (value !== undefined) {\n store.setState('value', value);\n }\n }, [value]);\n\n const dir = DirectionPrimitive.useDirection(dirProp);\n\n const instanceId = React.useId();\n const rootId = id ?? instanceId;\n\n const contextValue = React.useMemo(\n () => ({\n activationMode,\n dir,\n disabled,\n loop,\n nonInteractive,\n orientation,\n rootId,\n }),\n [rootId, dir, orientation, activationMode, disabled, nonInteractive, loop],\n );\n\n const RootPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n \n \n \n \n );\n}\n\ninterface FocusContextValue {\n tabStopId: string | null;\n onItemFocus: (tabStopId: string) => void;\n onItemShiftTab: () => void;\n onFocusableItemAdd: () => void;\n onFocusableItemRemove: () => void;\n onItemRegister: (item: ItemData) => void;\n onItemUnregister: (id: string) => void;\n getItems: () => ItemData[];\n}\n\nconst FocusContext = React.createContext(null);\n\nfunction useFocusContext(consumerName: string) {\n const context = React.useContext(FocusContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` must be used within \\`FocusProvider\\``);\n }\n return context;\n}\n\nfunction StepperList(props: DivProps) {\n const {\n asChild,\n onBlur: onBlurProp,\n onFocus: onFocusProp,\n onMouseDown: onMouseDownProp,\n className,\n children,\n ref,\n ...listProps\n } = props;\n\n const context = useStepperContext(LIST_NAME);\n const orientation = context.orientation;\n const currentValue = useStore((state) => state.value);\n\n const propsRef = useAsRef({\n onBlur: onBlurProp,\n onFocus: onFocusProp,\n onMouseDown: onMouseDownProp,\n });\n\n const [tabStopId, setTabStopId] = React.useState(null);\n const [isTabbingBackOut, setIsTabbingBackOut] = React.useState(false);\n const [focusableItemCount, setFocusableItemCount] = React.useState(0);\n const isClickFocusRef = React.useRef(false);\n const itemsRef = React.useRef>(new Map());\n const listRef = React.useRef(null);\n const composedRef = useComposedRefs(ref, listRef);\n\n const onItemFocus = React.useCallback((tabStopId: string) => {\n setTabStopId(tabStopId);\n }, []);\n\n const onItemShiftTab = React.useCallback(() => {\n setIsTabbingBackOut(true);\n }, []);\n\n const onFocusableItemAdd = React.useCallback(() => {\n setFocusableItemCount((prevCount) => prevCount + 1);\n }, []);\n\n const onFocusableItemRemove = React.useCallback(() => {\n setFocusableItemCount((prevCount) => prevCount - 1);\n }, []);\n\n const onItemRegister = React.useCallback((item: ItemData) => {\n itemsRef.current.set(item.id, item);\n }, []);\n\n const onItemUnregister = React.useCallback((id: string) => {\n itemsRef.current.delete(id);\n }, []);\n\n const getItems = React.useCallback(() => {\n return Array.from(itemsRef.current.values())\n .filter((item) => item.ref.current)\n .sort((a, b) => {\n const elementA = a.ref.current;\n const elementB = b.ref.current;\n if (!elementA || !elementB) return 0;\n const position = elementA.compareDocumentPosition(elementB);\n if (position & Node.DOCUMENT_POSITION_FOLLOWING) {\n return -1;\n }\n if (position & Node.DOCUMENT_POSITION_PRECEDING) {\n return 1;\n }\n return 0;\n });\n }, []);\n\n const onBlur = React.useCallback(\n (event: React.FocusEvent) => {\n propsRef.current.onBlur?.(event);\n if (event.defaultPrevented) return;\n\n setIsTabbingBackOut(false);\n },\n [propsRef],\n );\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent) => {\n propsRef.current.onFocus?.(event);\n if (event.defaultPrevented) return;\n\n const isKeyboardFocus = !isClickFocusRef.current;\n if (event.target === event.currentTarget && isKeyboardFocus && !isTabbingBackOut) {\n const entryFocusEvent = new CustomEvent(ENTRY_FOCUS, EVENT_OPTIONS);\n event.currentTarget.dispatchEvent(entryFocusEvent);\n\n if (!entryFocusEvent.defaultPrevented) {\n const items = Array.from(itemsRef.current.values()).filter((item) => !item.disabled);\n const selectedItem = currentValue\n ? items.find((item) => item.value === currentValue)\n : undefined;\n const activeItem = items.find((item) => item.active);\n const currentItem = items.find((item) => item.id === tabStopId);\n\n const candidateItems = [selectedItem, activeItem, currentItem, ...items].filter(\n Boolean,\n ) as ItemData[];\n const candidateRefs = candidateItems.map((item) => item.ref);\n focusFirst(candidateRefs, false);\n }\n }\n isClickFocusRef.current = false;\n },\n [propsRef, isTabbingBackOut, currentValue, tabStopId],\n );\n\n const onMouseDown = React.useCallback(\n (event: React.MouseEvent) => {\n propsRef.current.onMouseDown?.(event);\n\n if (event.defaultPrevented) return;\n\n isClickFocusRef.current = true;\n },\n [propsRef],\n );\n\n const focusContextValue = React.useMemo(\n () => ({\n getItems,\n onFocusableItemAdd,\n onFocusableItemRemove,\n onItemFocus,\n onItemRegister,\n onItemShiftTab,\n onItemUnregister,\n tabStopId,\n }),\n [\n tabStopId,\n onItemFocus,\n onItemShiftTab,\n onFocusableItemAdd,\n onFocusableItemRemove,\n onItemRegister,\n onItemUnregister,\n getItems,\n ],\n );\n\n const ListPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n \n {children}\n \n \n );\n}\n\ninterface StepperItemContextValue {\n value: string;\n stepState: StepState | undefined;\n}\n\nconst StepperItemContext = React.createContext(null);\n\nfunction useStepperItemContext(consumerName: string) {\n const context = React.useContext(StepperItemContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ITEM_NAME}\\``);\n }\n return context;\n}\n\ninterface StepperItemProps extends DivProps {\n value: string;\n completed?: boolean;\n disabled?: boolean;\n}\n\nfunction StepperItem(props: StepperItemProps) {\n const {\n value: itemValue,\n completed = false,\n disabled = false,\n asChild,\n className,\n children,\n ref,\n ...itemProps\n } = props;\n\n const context = useStepperContext(ITEM_NAME);\n const store = useStoreContext(ITEM_NAME);\n const orientation = context.orientation;\n const value = useStore((state) => state.value);\n\n useIsomorphicLayoutEffect(() => {\n store.addStep(itemValue, completed, disabled);\n\n return () => {\n store.removeStep(itemValue);\n };\n }, [itemValue, completed, disabled]);\n\n useIsomorphicLayoutEffect(() => {\n store.setStep(itemValue, completed, disabled);\n }, [itemValue, completed, disabled]);\n\n const stepState = useStore((state) => state.steps.get(itemValue));\n const steps = useStore((state) => state.steps);\n const dataState = getDataState(value, itemValue, stepState, steps);\n\n const itemContextValue = React.useMemo(\n () => ({\n stepState,\n value: itemValue,\n }),\n [itemValue, stepState],\n );\n\n const ItemPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n \n {children}\n \n \n );\n}\n\nfunction StepperTrigger(props: ButtonProps) {\n const {\n asChild,\n onClick: onClickProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n onMouseDown: onMouseDownProp,\n disabled,\n className,\n ref,\n ...triggerProps\n } = props;\n\n const context = useStepperContext(TRIGGER_NAME);\n const itemContext = useStepperItemContext(TRIGGER_NAME);\n const itemValue = itemContext.value;\n\n const store = useStoreContext(TRIGGER_NAME);\n const focusContext = useFocusContext(TRIGGER_NAME);\n const value = useStore((state) => state.value);\n const steps = useStore((state) => state.steps);\n const stepState = useStore((state) => state.steps.get(itemValue));\n\n const propsRef = useAsRef({\n onClick: onClickProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n onMouseDown: onMouseDownProp,\n });\n\n const activationMode = context.activationMode;\n const orientation = context.orientation;\n const loop = context.loop;\n\n const stepIndex = Array.from(steps.keys()).indexOf(itemValue);\n\n const stepPosition = stepIndex + 1;\n const stepCount = steps.size;\n\n const triggerId = getId(context.rootId, 'trigger', itemValue);\n const contentId = getId(context.rootId, 'content', itemValue);\n const titleId = getId(context.rootId, 'title', itemValue);\n const descriptionId = getId(context.rootId, 'description', itemValue);\n\n const isDisabled = disabled || stepState?.disabled || context.disabled;\n const isActive = value === itemValue;\n const isTabStop = focusContext.tabStopId === triggerId;\n const dataState = getDataState(value, itemValue, stepState, steps);\n\n const triggerRef = React.useRef(null);\n const composedRef = useComposedRefs(ref, triggerRef);\n const isArrowKeyPressedRef = React.useRef(false);\n const isMouseClickRef = React.useRef(false);\n\n React.useEffect(() => {\n function onKeyDown(event: KeyboardEvent) {\n if (ARROW_KEYS.includes(event.key)) {\n isArrowKeyPressedRef.current = true;\n }\n }\n function onKeyUp() {\n isArrowKeyPressedRef.current = false;\n }\n document.addEventListener('keydown', onKeyDown);\n document.addEventListener('keyup', onKeyUp);\n return () => {\n document.removeEventListener('keydown', onKeyDown);\n document.removeEventListener('keyup', onKeyUp);\n };\n }, []);\n\n useIsomorphicLayoutEffect(() => {\n focusContext.onItemRegister({\n active: isTabStop,\n disabled: !!isDisabled,\n id: triggerId,\n ref: triggerRef,\n value: itemValue,\n });\n\n if (!isDisabled) {\n focusContext.onFocusableItemAdd();\n }\n\n return () => {\n focusContext.onItemUnregister(triggerId);\n if (!isDisabled) {\n focusContext.onFocusableItemRemove();\n }\n };\n }, [focusContext, triggerId, itemValue, isTabStop, isDisabled]);\n\n const onClick = React.useCallback(\n async (event: React.MouseEvent) => {\n propsRef.current.onClick?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !context.nonInteractive) {\n const currentStepIndex = Array.from(steps.keys()).indexOf(value ?? '');\n const targetStepIndex = Array.from(steps.keys()).indexOf(itemValue);\n const direction = targetStepIndex > currentStepIndex ? 'next' : 'prev';\n\n await store.setStateWithValidation(itemValue, direction);\n }\n },\n [isDisabled, context.nonInteractive, store, itemValue, value, steps, propsRef],\n );\n\n const onFocus = React.useCallback(\n async (event: React.FocusEvent) => {\n propsRef.current.onFocus?.(event);\n if (event.defaultPrevented) return;\n\n focusContext.onItemFocus(triggerId);\n\n const isKeyboardFocus = !isMouseClickRef.current;\n\n if (\n !isActive &&\n !isDisabled &&\n activationMode !== 'manual' &&\n !context.nonInteractive &&\n isKeyboardFocus\n ) {\n const currentStepIndex = Array.from(steps.keys()).indexOf(value || '');\n const targetStepIndex = Array.from(steps.keys()).indexOf(itemValue);\n const direction = targetStepIndex > currentStepIndex ? 'next' : 'prev';\n\n await store.setStateWithValidation(itemValue, direction);\n }\n\n isMouseClickRef.current = false;\n },\n [\n focusContext,\n triggerId,\n activationMode,\n isActive,\n isDisabled,\n context.nonInteractive,\n store,\n itemValue,\n value,\n steps,\n propsRef,\n ],\n );\n\n const onKeyDown = React.useCallback(\n async (event: React.KeyboardEvent) => {\n propsRef.current.onKeyDown?.(event);\n if (event.defaultPrevented) return;\n\n if (event.key === 'Enter' && context.nonInteractive) {\n event.preventDefault();\n return;\n }\n\n if (\n (event.key === 'Enter' || event.key === ' ') &&\n activationMode === 'manual' &&\n !context.nonInteractive\n ) {\n event.preventDefault();\n if (!isDisabled && triggerRef.current) {\n triggerRef.current.click();\n }\n return;\n }\n\n if (event.key === 'Tab' && event.shiftKey) {\n focusContext.onItemShiftTab();\n return;\n }\n\n if (event.target !== event.currentTarget) return;\n\n const focusIntent = getFocusIntent(event, context.dir, orientation);\n\n if (focusIntent !== undefined) {\n if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;\n event.preventDefault();\n\n const items = focusContext.getItems().filter((item) => !item.disabled);\n let candidateRefs = items.map((item) => item.ref);\n\n if (focusIntent === 'last') {\n candidateRefs.reverse();\n } else if (focusIntent === 'prev' || focusIntent === 'next') {\n if (focusIntent === 'prev') candidateRefs.reverse();\n const currentIndex = candidateRefs.findIndex(\n (ref) => ref.current === event.currentTarget,\n );\n candidateRefs = loop\n ? wrapArray(candidateRefs, currentIndex + 1)\n : candidateRefs.slice(currentIndex + 1);\n }\n\n if (store.hasValidation() && candidateRefs.length > 0) {\n const nextRef = candidateRefs[0];\n const nextElement = nextRef?.current;\n const nextItem = items.find((item) => item.ref.current === nextElement);\n\n if (nextItem && nextItem.value !== itemValue) {\n const currentStepIndex = Array.from(steps.keys()).indexOf(value || '');\n const targetStepIndex = Array.from(steps.keys()).indexOf(nextItem.value);\n const direction: NavigationDirection =\n targetStepIndex > currentStepIndex ? 'next' : 'prev';\n\n if (direction === 'next') {\n const isValid = await store.setStateWithValidation(nextItem.value, direction);\n if (!isValid) return;\n } else {\n store.setState('value', nextItem.value);\n }\n\n queueMicrotask(() => nextElement?.focus());\n return;\n }\n }\n\n queueMicrotask(() => focusFirst(candidateRefs));\n }\n },\n [\n focusContext,\n context.nonInteractive,\n context.dir,\n activationMode,\n orientation,\n loop,\n isDisabled,\n store,\n propsRef,\n itemValue,\n value,\n steps,\n ],\n );\n\n const onMouseDown = React.useCallback(\n (event: React.MouseEvent) => {\n propsRef.current.onMouseDown?.(event);\n if (event.defaultPrevented) return;\n\n isMouseClickRef.current = true;\n\n if (isDisabled) {\n event.preventDefault();\n } else {\n focusContext.onItemFocus(triggerId);\n }\n },\n [focusContext, triggerId, isDisabled, propsRef],\n );\n\n const TriggerPrimitive = asChild ? SlotPrimitive.Slot : 'button';\n\n return (\n \n );\n}\n\ninterface StepperIndicatorProps extends Omit {\n children?: React.ReactNode | ((dataState: DataState) => React.ReactNode);\n}\n\nfunction StepperIndicator(props: StepperIndicatorProps) {\n const { className, children, asChild, ref, ...indicatorProps } = props;\n\n const context = useStepperContext(INDICATOR_NAME);\n const itemContext = useStepperItemContext(INDICATOR_NAME);\n\n const value = useStore((state) => state.value);\n const itemValue = itemContext.value;\n const stepState = useStore((state) => state.steps.get(itemValue));\n const steps = useStore((state) => state.steps);\n\n const stepPosition = Array.from(steps.keys()).indexOf(itemValue) + 1;\n\n const dataState = getDataState(value, itemValue, stepState, steps);\n\n const IndicatorPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n {typeof children === 'function' ? (\n children(dataState)\n ) : children ? (\n children\n ) : dataState === 'completed' ? (\n \n ) : (\n stepPosition\n )}\n \n );\n}\n\ninterface StepperSeparatorProps extends DivProps {\n forceMount?: boolean;\n}\n\nfunction StepperSeparator(props: StepperSeparatorProps) {\n const { className, asChild, forceMount = false, ref, ...separatorProps } = props;\n\n const context = useStepperContext(SEPARATOR_NAME);\n const itemContext = useStepperItemContext(SEPARATOR_NAME);\n const value = useStore((state) => state.value);\n const steps = useStore((state) => state.steps);\n\n const orientation = context.orientation;\n\n const stepIndex = Array.from(steps.keys()).indexOf(itemContext.value);\n\n const isLastStep = stepIndex === steps.size - 1;\n\n if (isLastStep && !forceMount) return null;\n\n const dataState = getDataState(\n value,\n itemContext.value,\n itemContext.stepState,\n steps,\n 'separator',\n );\n\n const SeparatorPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n );\n}\n\ninterface StepperTitleProps extends React.ComponentProps<'span'> {\n asChild?: boolean;\n}\n\nfunction StepperTitle(props: StepperTitleProps) {\n const { className, asChild, ref, ...titleProps } = props;\n\n const context = useStepperContext(TITLE_NAME);\n const itemContext = useStepperItemContext(TITLE_NAME);\n\n const titleId = getId(context.rootId, 'title', itemContext.value);\n\n const TitlePrimitive = asChild ? SlotPrimitive.Slot : 'span';\n\n return (\n \n );\n}\n\ninterface StepperDescriptionProps extends React.ComponentProps<'span'> {\n asChild?: boolean;\n}\n\nfunction StepperDescription(props: StepperDescriptionProps) {\n const { className, asChild, ref, ...descriptionProps } = props;\n\n const context = useStepperContext(DESCRIPTION_NAME);\n const itemContext = useStepperItemContext(DESCRIPTION_NAME);\n\n const descriptionId = getId(context.rootId, 'description', itemContext.value);\n\n const DescriptionPrimitive = asChild ? SlotPrimitive.Slot : 'span';\n\n return (\n \n );\n}\n\ninterface StepperContentProps extends DivProps {\n value: string;\n forceMount?: boolean;\n}\n\nfunction StepperContent(props: StepperContentProps) {\n const { value: valueProp, asChild, forceMount = false, ref, className, ...contentProps } = props;\n\n const context = useStepperContext(CONTENT_NAME);\n const value = useStore((state) => state.value);\n\n const contentId = getId(context.rootId, 'content', valueProp);\n const triggerId = getId(context.rootId, 'trigger', valueProp);\n\n if (valueProp !== value && !forceMount) return null;\n\n const ContentPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n );\n}\n\nfunction StepperPrev(props: ButtonProps) {\n const { asChild, onClick: onClickProp, disabled, ...prevProps } = props;\n\n const store = useStoreContext(PREV_NAME);\n const value = useStore((state) => state.value);\n const steps = useStore((state) => state.steps);\n\n const propsRef = useAsRef({\n onClick: onClickProp,\n });\n\n const stepKeys = Array.from(steps.keys());\n const currentIndex = value ? stepKeys.indexOf(value) : -1;\n const isDisabled = disabled || currentIndex <= 0;\n\n const onClick = React.useCallback(\n async (event: React.MouseEvent) => {\n propsRef.current.onClick?.(event);\n if (event.defaultPrevented || isDisabled) return;\n\n const prevIndex = Math.max(currentIndex - 1, 0);\n const prevStepValue = stepKeys[prevIndex];\n\n if (prevStepValue) {\n store.setState('value', prevStepValue);\n }\n },\n [propsRef, isDisabled, currentIndex, stepKeys, store],\n );\n\n const PrevPrimitive = asChild ? SlotPrimitive.Slot : 'button';\n\n return (\n \n );\n}\n\nfunction StepperNext(props: ButtonProps) {\n const { asChild, onClick: onClickProp, disabled, ...nextProps } = props;\n\n const store = useStoreContext(NEXT_NAME);\n const value = useStore((state) => state.value);\n const steps = useStore((state) => state.steps);\n\n const propsRef = useAsRef({\n onClick: onClickProp,\n });\n\n const stepKeys = Array.from(steps.keys());\n const currentIndex = value ? stepKeys.indexOf(value) : -1;\n const isDisabled = disabled || currentIndex >= stepKeys.length - 1;\n\n const onClick = React.useCallback(\n async (event: React.MouseEvent) => {\n propsRef.current.onClick?.(event);\n if (event.defaultPrevented || isDisabled) return;\n\n const nextIndex = Math.min(currentIndex + 1, stepKeys.length - 1);\n const nextStepValue = stepKeys[nextIndex];\n\n if (nextStepValue) {\n await store.setStateWithValidation(nextStepValue, 'next');\n }\n },\n [propsRef, isDisabled, currentIndex, stepKeys, store],\n );\n\n const NextPrimitive = asChild ? SlotPrimitive.Slot : 'button';\n\n return (\n \n );\n}\n\nexport {\n Stepper,\n StepperContent,\n StepperDescription,\n StepperIndicator,\n StepperItem,\n StepperList,\n StepperNext,\n StepperPrev,\n type StepperProps,\n StepperSeparator,\n StepperTitle,\n StepperTrigger,\n useStore as useStepper,\n};\n", + "type": "registry:component", + "target": "@components/stepper.tsx" + } + ], + "type": "registry:component" +} \ No newline at end of file diff --git a/docs/public/r/switch.json b/docs/public/r/switch.json new file mode 100644 index 0000000..0638641 --- /dev/null +++ b/docs/public/r/switch.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "switch", + "title": "Switch", + "description": "A toggle for binary on/off states.", + "dependencies": [ + "@radix-ui/react-switch" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/switch.tsx", + "content": "'use client';\n\nimport * as SwitchPrimitive from '@radix-ui/react-switch';\nimport * as React from 'react';\n\nimport { cn } from '@/lib/utils';\n\nconst Switch = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef & {\n size?: 'sm' | 'default';\n }\n>(({ className, size = 'default', ...props }, ref) => {\n return (\n \n \n \n );\n});\nSwitch.displayName = SwitchPrimitive.Root.displayName;\n\nexport { Switch };\n", + "type": "registry:ui", + "target": "@ui/switch.tsx" + } + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/docs/public/r/table.json b/docs/public/r/table.json new file mode 100644 index 0000000..9fb1105 --- /dev/null +++ b/docs/public/r/table.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "table", + "title": "Table", + "description": "A styled HTML table with header, body, and footer.", + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/table.tsx", + "content": "'use client';\n\nimport * as React from 'react';\nimport { cn } from '@/lib/utils';\n\ntype TableProps = React.HTMLAttributes & {\n wrapperClassname?: string;\n};\n\nconst Table = React.forwardRef(\n ({ wrapperClassname, className, ...props }, ref) => (\n
\n \n \n ),\n);\nTable.displayName = 'Table';\n\nconst TableHeader = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n));\nTableHeader.displayName = 'TableHeader';\n\nconst TableBody = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n));\nTableBody.displayName = 'TableBody';\n\nconst TableFooter = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n tr]:last:border-b-0', className)}\n ref={ref}\n {...props}\n />\n));\nTableFooter.displayName = 'TableFooter';\n\nconst TableRow = React.forwardRef>(\n ({ className, ...props }, ref) => (\n \n ),\n);\nTableRow.displayName = 'TableRow';\n\nconst TableHead = React.forwardRef<\n HTMLTableCellElement,\n React.ThHTMLAttributes\n>(({ className, ...props }, ref) => (\n \n));\nTableHead.displayName = 'TableHead';\n\nconst TableCell = React.forwardRef<\n HTMLTableCellElement,\n React.TdHTMLAttributes\n>(({ className, ...props }, ref) => (\n \n));\nTableCell.displayName = 'TableCell';\n\nconst TableCaption = React.forwardRef<\n HTMLTableCaptionElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \r\n );\r\n },\r\n ...components,\r\n }}\r\n formatters={{\r\n formatMonthDropdown: (date) => date.toLocaleString(locale?.code, { month: 'short' }),\r\n ...formatters,\r\n }}\r\n locale={locale}\r\n showOutsideDays={showOutsideDays}\r\n {...props}\r\n />\r\n );\r\n}\r\n\r\nfunction CalendarDayButton({\r\n className,\r\n day,\r\n modifiers,\r\n locale,\r\n ...props\r\n}: React.ComponentProps & { locale?: Partial }) {\r\n const defaultClassNames = getDefaultClassNames();\r\n\r\n const ref = React.useRef(null);\r\n React.useEffect(() => {\r\n if (modifiers.focused) ref.current?.focus();\r\n }, [modifiers.focused]);\r\n\r\n return (\r\n span]:text-xs [&>span]:opacity-70',\r\n defaultClassNames.day,\r\n className,\r\n )}\r\n data-day={day.date.toLocaleDateString(locale?.code)}\r\n data-range-end={modifiers.range_end}\r\n data-range-middle={modifiers.range_middle}\r\n data-range-start={modifiers.range_start}\r\n data-selected-single={\r\n modifiers.selected &&\r\n !modifiers.range_start &&\r\n !modifiers.range_end &&\r\n !modifiers.range_middle\r\n }\r\n ref={ref}\r\n size=\"icon\"\r\n variant=\"ghost\"\r\n {...props}\r\n />\r\n );\r\n}\r\n\r\nexport { Calendar, CalendarDayButton };\r\n", + "path": "registry/ui/calendar.tsx", + "target": "@ui/calendar.tsx", + "type": "registry:ui" + } + ], + "name": "calendar", + "registryDependencies": ["button", "@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/checkbox.json b/public/r/checkbox.json new file mode 100644 index 0000000..ba1e38b --- /dev/null +++ b/public/r/checkbox.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-checkbox"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as CheckboxPrimitive from '@radix-ui/react-checkbox';\r\nimport { CheckIcon } from 'lucide-react';\r\nimport * as React from 'react';\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst Checkbox = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => {\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n );\r\n});\r\nCheckbox.displayName = CheckboxPrimitive.Root.displayName;\r\n\r\nexport { Checkbox };\r\n", + "path": "registry/ui/checkbox.tsx", + "target": "@ui/checkbox.tsx", + "type": "registry:ui" + } + ], + "name": "checkbox", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/collapsible.json b/public/r/collapsible.json new file mode 100644 index 0000000..8f65006 --- /dev/null +++ b/public/r/collapsible.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-collapsible"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as CollapsiblePrimitive from '@radix-ui/react-collapsible';\r\nimport * as React from 'react';\r\nimport { useEffect, useState } from 'react';\r\nimport { cn } from '@/lib/utils';\r\n\r\nexport const Collapsible = CollapsiblePrimitive.Root;\r\n\r\nexport const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;\r\n\r\nexport const CollapsibleContent = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ children, className, ...props }, ref) => {\r\n const [mounted, setMounted] = useState(false);\r\n\r\n useEffect(() => {\r\n setMounted(true);\r\n }, []);\r\n\r\n return (\r\n \r\n {children}\r\n \r\n );\r\n});\r\nCollapsibleContent.displayName = 'CollapsibleContent';\r\n\r\nexport type CollapsibleProps = CollapsiblePrimitive.CollapsibleProps;\r\nexport type CollapsibleContentProps = CollapsiblePrimitive.CollapsibleContentProps;\r\nexport type CollapsibleTriggerProps = CollapsiblePrimitive.CollapsibleTriggerProps;\r\n", + "path": "registry/ui/collapsible.tsx", + "target": "@ui/collapsible.tsx", + "type": "registry:ui" + } + ], + "name": "collapsible", + "type": "registry:ui" +} diff --git a/public/r/combobox.json b/public/r/combobox.json new file mode 100644 index 0000000..bfbe492 --- /dev/null +++ b/public/r/combobox.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["cmdk"], + "files": [ + { + "content": "'use client';\r\n\r\nimport { Combobox as ComboboxPrimitive } from '@base-ui/react';\r\nimport { CheckIcon, ChevronDownIcon, XIcon } from 'lucide-react';\r\nimport * as React from 'react';\r\nimport { Button } from '@/components/ui/button';\r\nimport {\r\n InputGroup,\r\n InputGroupAddon,\r\n InputGroupButton,\r\n InputGroupInput,\r\n} from '@/components/ui/input-group';\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst Combobox = ComboboxPrimitive.Root;\r\n\r\nfunction ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) {\r\n return ;\r\n}\r\n\r\nconst ComboboxTrigger = React.forwardRef<\r\n React.ComponentRef,\r\n ComboboxPrimitive.Trigger.Props\r\n>(({ className, children, ...props }, ref) => (\r\n \r\n {children}\r\n \r\n \r\n));\r\nComboboxTrigger.displayName = 'ComboboxTrigger';\r\n\r\nconst ComboboxClear = React.forwardRef<\r\n React.ComponentRef,\r\n ComboboxPrimitive.Clear.Props\r\n>(({ className, ...props }, ref) => (\r\n }\r\n {...props}\r\n >\r\n \r\n \r\n));\r\nComboboxClear.displayName = 'ComboboxClear';\r\n\r\nconst ComboboxInput = React.forwardRef<\r\n HTMLDivElement,\r\n ComboboxPrimitive.Input.Props & {\r\n showTrigger?: boolean;\r\n showClear?: boolean;\r\n }\r\n>(\r\n (\r\n { className, children, disabled = false, showTrigger = true, showClear = false, ...props },\r\n ref,\r\n ) => {\r\n return (\r\n \r\n } {...props} />\r\n \r\n {showTrigger && (\r\n \r\n \r\n \r\n )}\r\n {showClear && }\r\n \r\n {children}\r\n \r\n );\r\n },\r\n);\r\nComboboxInput.displayName = 'ComboboxInput';\r\n\r\nconst ComboboxContent = React.forwardRef<\r\n React.ComponentRef,\r\n ComboboxPrimitive.Popup.Props &\r\n Pick<\r\n ComboboxPrimitive.Positioner.Props,\r\n 'side' | 'align' | 'sideOffset' | 'alignOffset' | 'anchor'\r\n >\r\n>(\r\n (\r\n {\r\n className,\r\n side = 'bottom',\r\n sideOffset = 6,\r\n align = 'start',\r\n alignOffset = 0,\r\n anchor,\r\n ...props\r\n },\r\n ref,\r\n ) => {\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n );\r\n },\r\n);\r\nComboboxContent.displayName = 'ComboboxContent';\r\n\r\nconst ComboboxList = React.forwardRef<\r\n React.ComponentRef,\r\n ComboboxPrimitive.List.Props\r\n>(({ className, ...props }, ref) => {\r\n return (\r\n \r\n );\r\n});\r\nComboboxList.displayName = 'ComboboxList';\r\n\r\nconst ComboboxItem = React.forwardRef<\r\n React.ComponentRef,\r\n ComboboxPrimitive.Item.Props\r\n>(({ className, children, ...props }, ref) => {\r\n return (\r\n \r\n {children}\r\n \r\n }\r\n >\r\n \r\n \r\n \r\n );\r\n});\r\nComboboxItem.displayName = 'ComboboxItem';\r\n\r\nconst ComboboxGroup = React.forwardRef<\r\n React.ComponentRef,\r\n ComboboxPrimitive.Group.Props\r\n>(({ className, ...props }, ref) => {\r\n return (\r\n \r\n );\r\n});\r\nComboboxGroup.displayName = 'ComboboxGroup';\r\n\r\nconst ComboboxLabel = React.forwardRef<\r\n React.ComponentRef,\r\n ComboboxPrimitive.GroupLabel.Props\r\n>(({ className, ...props }, ref) => {\r\n return (\r\n \r\n );\r\n});\r\nComboboxLabel.displayName = 'ComboboxLabel';\r\n\r\nfunction ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) {\r\n return ;\r\n}\r\n\r\nconst ComboboxEmpty = React.forwardRef<\r\n React.ComponentRef,\r\n ComboboxPrimitive.Empty.Props\r\n>(({ className, ...props }, ref) => {\r\n return (\r\n \r\n );\r\n});\r\nComboboxEmpty.displayName = 'ComboboxEmpty';\r\n\r\nconst ComboboxSeparator = React.forwardRef<\r\n React.ComponentRef,\r\n ComboboxPrimitive.Separator.Props\r\n>(({ className, ...props }, ref) => {\r\n return (\r\n \r\n );\r\n});\r\nComboboxSeparator.displayName = 'ComboboxSeparator';\r\n\r\nconst ComboboxChips = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef & ComboboxPrimitive.Chips.Props\r\n>(({ className, ...props }, ref) => {\r\n return (\r\n \r\n );\r\n});\r\nComboboxChips.displayName = 'ComboboxChips';\r\n\r\nconst ComboboxChip = React.forwardRef<\r\n React.ComponentRef,\r\n ComboboxPrimitive.Chip.Props & {\r\n showRemove?: boolean;\r\n }\r\n>(({ className, children, showRemove = true, ...props }, ref) => {\r\n return (\r\n \r\n {children}\r\n {showRemove && (\r\n }\r\n >\r\n \r\n \r\n )}\r\n \r\n );\r\n});\r\nComboboxChip.displayName = 'ComboboxChip';\r\n\r\nconst ComboboxChipsInput = React.forwardRef<\r\n React.ComponentRef,\r\n ComboboxPrimitive.Input.Props\r\n>(({ className, ...props }, ref) => {\r\n return (\r\n \r\n );\r\n});\r\nComboboxChipsInput.displayName = 'ComboboxChipsInput';\r\n\r\nfunction useComboboxAnchor() {\r\n return React.useRef(null);\r\n}\r\n\r\nexport {\r\n Combobox,\r\n ComboboxChip,\r\n ComboboxChips,\r\n ComboboxChipsInput,\r\n ComboboxCollection,\r\n ComboboxContent,\r\n ComboboxEmpty,\r\n ComboboxGroup,\r\n ComboboxInput,\r\n ComboboxItem,\r\n ComboboxLabel,\r\n ComboboxList,\r\n ComboboxSeparator,\r\n ComboboxTrigger,\r\n ComboboxValue,\r\n useComboboxAnchor,\r\n};\r\n", + "path": "registry/ui/combobox.tsx", + "target": "@ui/combobox.tsx", + "type": "registry:ui" + } + ], + "name": "combobox", + "registryDependencies": ["popover", "@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/command.json b/public/r/command.json new file mode 100644 index 0000000..0c3da93 --- /dev/null +++ b/public/r/command.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["cmdk"], + "files": [ + { + "content": "'use client';\r\n\r\nimport { Command as CommandPrimitive } from 'cmdk';\r\nimport { SearchIcon } from 'lucide-react';\r\nimport * as React from 'react';\r\nimport { cn } from '@/lib/utils';\r\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';\r\n\r\nconst Command = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nCommand.displayName = 'Command';\r\n\r\nconst CommandDialog = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef & {\r\n title?: string;\r\n description?: string;\r\n showCloseButton?: boolean;\r\n className?: string;\r\n }\r\n>(\r\n (\r\n {\r\n title = 'Command Palette',\r\n description = 'Search for a command to run...',\r\n children,\r\n className,\r\n showCloseButton = true,\r\n ...props\r\n },\r\n ref,\r\n ) => {\r\n return (\r\n \r\n \r\n {title}\r\n {description}\r\n \r\n \r\n \r\n {children}\r\n \r\n \r\n \r\n );\r\n },\r\n);\r\nCommandDialog.displayName = 'CommandDialog';\r\n\r\nconst CommandInput = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n
\r\n \r\n \r\n
\r\n));\r\nCommandInput.displayName = 'CommandInput';\r\n\r\nconst CommandList = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nCommandList.displayName = 'CommandList';\r\n\r\nconst CommandEmpty = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>((props, ref) => (\r\n \r\n));\r\nCommandEmpty.displayName = 'CommandEmpty';\r\n\r\nconst CommandGroup = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nCommandGroup.displayName = 'CommandGroup';\r\n\r\nconst CommandSeparator = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nCommandSeparator.displayName = 'CommandSeparator';\r\n\r\nconst CommandItem = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nCommandItem.displayName = 'CommandItem';\r\n\r\nconst CommandShortcut = React.forwardRef>(\r\n ({ className, ...props }, ref) => (\r\n \r\n ),\r\n);\r\nCommandShortcut.displayName = 'CommandShortcut';\r\n\r\nexport {\r\n Command,\r\n CommandDialog,\r\n CommandEmpty,\r\n CommandGroup,\r\n CommandInput,\r\n CommandItem,\r\n CommandList,\r\n CommandSeparator,\r\n CommandShortcut,\r\n};\r\n", + "path": "registry/ui/command.tsx", + "target": "@ui/command.tsx", + "type": "registry:ui" + } + ], + "name": "command", + "registryDependencies": ["dialog", "@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/component-refs.json b/public/r/component-refs.json new file mode 100644 index 0000000..9e4c3bf --- /dev/null +++ b/public/r/component-refs.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "/**\r\n * @see https://github.com/radix-ui/primitives/blob/main/packages/react/compose-refs/src/compose-refs.tsx\r\n */\r\n\r\nimport * as React from 'react';\r\n\r\ntype PossibleRef = React.Ref | undefined;\r\n\r\n/**\r\n * Set a given ref to a given value\r\n * This utility takes care of different types of refs: callback refs and RefObject(s)\r\n */\r\nfunction setRef(ref: PossibleRef, value: T) {\r\n if (typeof ref === 'function') {\r\n return ref(value);\r\n }\r\n\r\n if (ref !== null && ref !== undefined) {\r\n ref.current = value;\r\n }\r\n}\r\n\r\n/**\r\n * A utility to compose multiple refs together\r\n * Accepts callback refs and RefObject(s)\r\n */\r\nfunction composeRefs(...refs: PossibleRef[]): React.RefCallback {\r\n return (node) => {\r\n let hasCleanup = false;\r\n const cleanups = refs.map((ref) => {\r\n const cleanup = setRef(ref, node);\r\n if (!hasCleanup && typeof cleanup === 'function') {\r\n hasCleanup = true;\r\n }\r\n return cleanup;\r\n });\r\n\r\n // React <19 will log an error to the console if a callback ref returns a\r\n // value. We don't use ref cleanups internally so this will only happen if a\r\n // user's ref callback returns a value, which we only expect if they are\r\n // using the cleanup functionality added in React 19.\r\n if (hasCleanup) {\r\n return () => {\r\n for (let i = 0; i < cleanups.length; i++) {\r\n const cleanup = cleanups[i];\r\n if (typeof cleanup === 'function') {\r\n cleanup();\r\n } else {\r\n setRef(refs[i], null);\r\n }\r\n }\r\n };\r\n }\r\n };\r\n}\r\n\r\n/**\r\n * A custom hook that composes multiple refs\r\n * Accepts callback refs and RefObject(s)\r\n */\r\nfunction useComposedRefs(...refs: PossibleRef[]): React.RefCallback {\r\n return React.useCallback(composeRefs(...refs), refs);\r\n}\r\n\r\nexport { composeRefs, useComposedRefs };\r\n", + "path": "registry/lib/component-refs.ts", + "target": "@lib/component-refs.ts", + "type": "registry:lib" + } + ], + "name": "component-refs", + "type": "registry:lib" +} diff --git a/public/r/confirm-dialog.json b/public/r/confirm-dialog.json new file mode 100644 index 0000000..42bd647 --- /dev/null +++ b/public/r/confirm-dialog.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import { XIcon } from 'lucide-react';\r\nimport {\r\n AlertDialog,\r\n AlertDialogCancel,\r\n AlertDialogContent,\r\n AlertDialogDescription,\r\n AlertDialogFooter,\r\n AlertDialogHeader,\r\n AlertDialogTitle,\r\n} from '@/components/ui/alert-dialog';\r\nimport { Button } from '@/components/ui/button';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype ConfirmDialogProps = {\r\n open: boolean;\r\n onOpenChange: (open: boolean) => void;\r\n onClose?: () => void;\r\n title: React.ReactNode;\r\n disabled?: boolean;\r\n desc: React.JSX.Element | string;\r\n cancelBtnText?: string;\r\n confirmText?: React.ReactNode;\r\n destructive?: boolean;\r\n isLoading?: boolean;\r\n className?: string;\r\n children?: React.ReactNode;\r\n showCloseButton?: boolean;\r\n} & (\r\n | { footer: React.ReactNode | null; form?: undefined; handleConfirm?: undefined }\r\n | { footer?: undefined; form: string; handleConfirm?: undefined }\r\n | { footer?: undefined; form?: undefined; handleConfirm: () => void }\r\n);\r\n\r\nexport function ConfirmDialog(props: ConfirmDialogProps) {\r\n const {\r\n open,\r\n onOpenChange,\r\n onClose,\r\n title,\r\n desc,\r\n children,\r\n className,\r\n confirmText,\r\n cancelBtnText,\r\n destructive,\r\n isLoading,\r\n disabled = false,\r\n form,\r\n handleConfirm,\r\n footer,\r\n showCloseButton = false,\r\n ...actions\r\n } = props;\r\n\r\n const handleOpenChange = (newOpen: boolean) => {\r\n onOpenChange(newOpen);\r\n if (!newOpen) {\r\n onClose?.();\r\n }\r\n };\r\n\r\n return (\r\n \r\n \r\n {showCloseButton && (\r\n handleOpenChange(false)}\r\n >\r\n \r\n Close\r\n \r\n )}\r\n \r\n {title}\r\n \r\n
{desc}
\r\n
\r\n
\r\n {children}\r\n {footer !== null && (\r\n \r\n {footer !== undefined ? (\r\n footer\r\n ) : (\r\n <>\r\n \r\n {cancelBtnText ?? 'Cancel'}\r\n \r\n \r\n {confirmText ?? 'Continue'}\r\n \r\n \r\n )}\r\n \r\n )}\r\n
\r\n
\r\n );\r\n}\r\n", + "path": "registry/components/confirm-dialog.tsx", + "target": "@components/confirm-dialog.tsx", + "type": "registry:component" + } + ], + "name": "confirm-dialog", + "registryDependencies": ["alert-dialog", "button"], + "type": "registry:component" +} diff --git a/public/r/data-table-advance-filter.json b/public/r/data-table-advance-filter.json new file mode 100644 index 0000000..c4cb128 --- /dev/null +++ b/public/r/data-table-advance-filter.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import type { Column, ColumnMeta, Table } from '@tanstack/react-table';\r\nimport dayjs from 'dayjs';\r\nimport { Calendar as CalendarIcon, Check, ChevronsUpDown, ListFilter, Trash2 } from 'lucide-react';\r\nimport * as React from 'react';\r\nimport type { DateRange } from 'react-day-picker';\r\nimport {\r\n getDefaultFilterOperator,\r\n getFilterOperators,\r\n getValidFilters,\r\n} from '@/lib/data-table';\r\nimport { cn } from '@/lib/utils';\r\nimport { Badge } from '@/components/ui/badge';\r\nimport { Button } from '@/components/ui/button';\r\nimport { Calendar } from '@/components/ui/calendar';\r\nimport {\r\n Command,\r\n CommandEmpty,\r\n CommandGroup,\r\n CommandInput,\r\n CommandItem,\r\n CommandList,\r\n} from '@/components/ui/command';\r\nimport { DebouncedInput } from '@/components/ui/debounced-input';\r\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';\r\nimport {\r\n Select,\r\n SelectContent,\r\n SelectGroup,\r\n SelectItem,\r\n SelectTrigger,\r\n SelectValue,\r\n} from '@/components/ui/select';\r\nimport { dataTableConfig } from './data-table-config';\r\nimport { getIsDateRange, parseAsDate, parseColumnFilterValue } from './data-table-date-filter';\r\nimport { DataTableRangeFilter } from './data-table-range-filter';\r\nimport type {\r\n DataTableTranslations,\r\n DatePreset,\r\n ExtendedColumnFilter,\r\n FilterOperator,\r\n JoinOperator,\r\n Option,\r\n} from './types';\r\n\r\nconst DEBOUNCE_MS = 300;\r\n\r\nconst DEFAULT_TRANSLATIONS = {\r\n addFilter: 'Add filter',\r\n and: 'and',\r\n enterValue: 'Enter a value...',\r\n filters: 'Filters',\r\n noFiltersApplied: 'No filters applied',\r\n operators: {\r\n eq: 'is',\r\n gt: 'is greater than',\r\n gte: 'is greater than or equal to',\r\n iLike: 'contains',\r\n inArray: 'has any of',\r\n isBetween: 'is between',\r\n isEmpty: 'is empty',\r\n isNotEmpty: 'is not empty',\r\n isRelativeToToday: 'is relative to today',\r\n lt: 'is less than',\r\n lte: 'is less than or equal to',\r\n ne: 'is not',\r\n notILike: 'does not contain',\r\n notInArray: 'has none of',\r\n },\r\n or: 'or',\r\n pickADate: 'Pick a date',\r\n resetFilters: 'Reset filters',\r\n searchFields: 'Search fields...',\r\n selected: 'selected',\r\n selectField: 'Select field',\r\n where: 'Where',\r\n};\r\n\r\nconst generateId = () => Math.random().toString(36).substring(2, 10);\r\n\r\nexport interface DataTableAdvanceFilterProps\r\n extends React.ComponentPropsWithoutRef {\r\n table: Table;\r\n translations?: DataTableTranslations;\r\n shallow?: boolean;\r\n debounceMs?: number;\r\n throttleMs?: number;\r\n disabled?: boolean;\r\n}\r\n\r\nexport function DataTableAdvanceFilter({\r\n table,\r\n translations: customTranslations,\r\n debounceMs = DEBOUNCE_MS,\r\n shallow,\r\n throttleMs,\r\n disabled,\r\n align = 'center',\r\n ...props\r\n}: DataTableAdvanceFilterProps) {\r\n const labelId = React.useId();\r\n const descriptionId = React.useId();\r\n const [open, setOpen] = React.useState(false);\r\n const addButtonRef = React.useRef(null);\r\n\r\n const translations = React.useMemo(() => {\r\n return {\r\n ...DEFAULT_TRANSLATIONS,\r\n ...customTranslations,\r\n operators: {\r\n ...DEFAULT_TRANSLATIONS.operators,\r\n ...customTranslations?.operators,\r\n },\r\n };\r\n }, [customTranslations]);\r\n\r\n const columns = React.useMemo(() => {\r\n return table\r\n .getAllColumns()\r\n .filter(\r\n (column) =>\r\n column.columnDef.enableColumnFilter !== false &&\r\n column.id !== 'select' &&\r\n column.id !== 'actions',\r\n );\r\n }, [table, table.options.columns]);\r\n\r\n // Read/write state from table meta\r\n const metaFilters = (table.options.meta as any)?.filters as\r\n | ExtendedColumnFilter[]\r\n | undefined;\r\n const metaSetFilters = (table.options.meta as any)?.setFilters as\r\n | ((val: any) => void)\r\n | undefined;\r\n const metaJoinOperator = (table.options.meta as any)?.joinOperator as JoinOperator | undefined;\r\n const metaSetJoinOperator = (table.options.meta as any)?.setJoinOperator as\r\n | ((val: JoinOperator) => void)\r\n | undefined;\r\n const metaDebounceMs = (table.options.meta as any)?.debounceMs as number | undefined;\r\n\r\n const [localFilters, setLocalFilters] = React.useState[]>([]);\r\n const [localJoinOperator, setLocalJoinOperator] = React.useState('and');\r\n\r\n // Sync local filters with controlled filters when popover is closed\r\n React.useEffect(() => {\r\n if (metaFilters !== undefined && !open) {\r\n setLocalFilters(metaFilters);\r\n }\r\n }, [open, metaFilters]);\r\n\r\n // Sync local joinOperator with controlled joinOperator when popover is closed\r\n React.useEffect(() => {\r\n if (metaJoinOperator !== undefined && !open) {\r\n setLocalJoinOperator(metaJoinOperator);\r\n }\r\n }, [open, metaJoinOperator]);\r\n\r\n const filters = localFilters;\r\n const setFilters = React.useCallback(\r\n (value: any) => {\r\n if (metaSetFilters) {\r\n setLocalFilters((prev) => {\r\n const next = typeof value === 'function' ? value(prev) : value;\r\n metaSetFilters(getValidFilters(next));\r\n return next;\r\n });\r\n } else {\r\n setLocalFilters(value);\r\n }\r\n },\r\n [metaSetFilters],\r\n );\r\n\r\n const joinOperator = localJoinOperator;\r\n const setJoinOperator = React.useCallback(\r\n (value: any) => {\r\n if (metaSetJoinOperator) {\r\n setLocalJoinOperator((prev) => {\r\n const next = typeof value === 'function' ? value(prev) : value;\r\n metaSetJoinOperator(next);\r\n return next;\r\n });\r\n } else {\r\n setLocalJoinOperator(value);\r\n }\r\n },\r\n [metaSetJoinOperator],\r\n );\r\n\r\n const activeDebounceMs = metaDebounceMs !== undefined ? metaDebounceMs : debounceMs;\r\n\r\n const onFilterAdd = React.useCallback(() => {\r\n const column = columns[0];\r\n if (!column) return;\r\n\r\n setFilters([\r\n ...filters,\r\n {\r\n filterId: generateId(),\r\n id: column.id as Extract,\r\n operator: getDefaultFilterOperator(column.columnDef.meta?.variant ?? 'text'),\r\n value: '',\r\n variant: column.columnDef.meta?.variant ?? 'text',\r\n },\r\n ]);\r\n }, [columns, filters, setFilters]);\r\n\r\n const onFilterUpdate = React.useCallback(\r\n (filterId: string, updates: Partial, 'filterId'>>) => {\r\n setFilters((prevFilters: ExtendedColumnFilter[]) => {\r\n return prevFilters.map((filter) => {\r\n if (filter.filterId === filterId) {\r\n return { ...filter, ...updates } as ExtendedColumnFilter;\r\n }\r\n return filter;\r\n });\r\n });\r\n },\r\n [setFilters],\r\n );\r\n\r\n const onFilterRemove = React.useCallback(\r\n (filterId: string) => {\r\n const updatedFilters = filters.filter((filter) => filter.filterId !== filterId);\r\n setFilters(updatedFilters);\r\n requestAnimationFrame(() => {\r\n addButtonRef.current?.focus();\r\n });\r\n },\r\n [filters, setFilters],\r\n );\r\n\r\n const onFiltersReset = React.useCallback(() => {\r\n setFilters([]);\r\n setJoinOperator('and');\r\n }, [setFilters, setJoinOperator]);\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n
\r\n

\r\n {filters.length > 0 ? translations.filters : translations.noFiltersApplied}\r\n

\r\n
\r\n {filters.length > 0 ? (\r\n
\r\n {filters.map((filter, index) => (\r\n \r\n columns={columns}\r\n debounceMs={activeDebounceMs}\r\n filter={filter}\r\n filterItemId={`filter-${filter.filterId}`}\r\n index={index}\r\n joinOperator={joinOperator}\r\n key={filter.filterId}\r\n onFilterRemove={onFilterRemove}\r\n onFilterUpdate={onFilterUpdate}\r\n setJoinOperator={setJoinOperator}\r\n translations={translations}\r\n />\r\n ))}\r\n
\r\n ) : null}\r\n
\r\n \r\n {translations.addFilter}\r\n \r\n {filters.length > 0 ? (\r\n \r\n {translations.resetFilters}\r\n \r\n ) : null}\r\n
\r\n \r\n
\r\n );\r\n}\r\n\r\ninterface DataTableFilterItemProps {\r\n filter: ExtendedColumnFilter;\r\n index: number;\r\n filterItemId: string;\r\n joinOperator: JoinOperator;\r\n setJoinOperator: (value: JoinOperator) => void;\r\n columns: Column[];\r\n onFilterUpdate: (\r\n filterId: string,\r\n updates: Partial, 'filterId'>>,\r\n ) => void;\r\n onFilterRemove: (filterId: string) => void;\r\n translations: any;\r\n debounceMs: number;\r\n}\r\n\r\nfunction DataTableFilterItem({\r\n filter,\r\n index,\r\n filterItemId,\r\n joinOperator,\r\n setJoinOperator,\r\n columns,\r\n onFilterUpdate,\r\n onFilterRemove,\r\n translations,\r\n debounceMs,\r\n}: DataTableFilterItemProps) {\r\n const [showFieldSelector, setShowFieldSelector] = React.useState(false);\r\n\r\n const column = columns.find((col) => col.id === filter.id);\r\n const columnMeta = column?.columnDef.meta;\r\n const filterOperators = React.useMemo(() => {\r\n return getFilterOperators(filter.variant);\r\n }, [filter.variant]);\r\n\r\n if (!column) return null;\r\n\r\n return (\r\n
\r\n
\r\n {index === 0 ? (\r\n {translations.where}\r\n ) : index === 1 ? (\r\n setJoinOperator(value)}\r\n value={joinOperator}\r\n >\r\n \r\n \r\n \r\n \r\n \r\n {dataTableConfig.joinOperators.map((op) => (\r\n \r\n {op === 'and' ? translations.and : translations.or}\r\n \r\n ))}\r\n \r\n \r\n \r\n ) : (\r\n \r\n {joinOperator === 'and' ? translations.and : translations.or}\r\n \r\n )}\r\n
\r\n \r\n \r\n \r\n {columnMeta?.label ?? filter.id}\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {translations.noResults ?? 'No fields found.'}\r\n \r\n {columns.map((col) => (\r\n {\r\n onFilterUpdate(filter.filterId, {\r\n id: value as Extract,\r\n operator: getDefaultFilterOperator(col.columnDef.meta?.variant ?? 'text'),\r\n value: '',\r\n variant: col.columnDef.meta?.variant ?? 'text',\r\n });\r\n setShowFieldSelector(false);\r\n }}\r\n value={col.id}\r\n >\r\n {col.columnDef.meta?.label ?? col.id}\r\n \r\n \r\n ))}\r\n \r\n \r\n \r\n \r\n \r\n \r\n onFilterUpdate(filter.filterId, {\r\n operator: value,\r\n value: value === 'isEmpty' || value === 'isNotEmpty' ? '' : filter.value,\r\n })\r\n }\r\n value={filter.operator}\r\n >\r\n \r\n
\r\n \r\n
\r\n
\r\n \r\n \r\n {filterOperators.map((op) => (\r\n \r\n {translations.operators?.[op.value] ?? op.label}\r\n \r\n ))}\r\n \r\n \r\n \r\n
\r\n {onFilterInputRender({\r\n column,\r\n columnMeta,\r\n debounceMs,\r\n filter,\r\n inputId: `${filterItemId}-input`,\r\n onFilterUpdate,\r\n translations,\r\n })}\r\n
\r\n onFilterRemove(filter.filterId)}\r\n size=\"icon\"\r\n variant=\"outline\"\r\n >\r\n \r\n \r\n
\r\n );\r\n}\r\n\r\nfunction onFilterInputRender({\r\n filter,\r\n inputId,\r\n column,\r\n columnMeta,\r\n onFilterUpdate,\r\n translations,\r\n debounceMs,\r\n}: {\r\n filter: ExtendedColumnFilter;\r\n inputId: string;\r\n column: Column;\r\n columnMeta?: ColumnMeta;\r\n onFilterUpdate: (\r\n filterId: string,\r\n updates: Partial, 'filterId'>>,\r\n ) => void;\r\n translations: any;\r\n debounceMs: number;\r\n}) {\r\n if (filter.operator === 'isEmpty' || filter.operator === 'isNotEmpty') {\r\n return (\r\n \r\n );\r\n }\r\n\r\n switch (filter.variant) {\r\n case 'text':\r\n case 'number':\r\n case 'range': {\r\n if (filter.operator === 'isBetween') {\r\n return (\r\n \r\n );\r\n }\r\n\r\n const isNumber = filter.variant === 'number' || filter.variant === 'range';\r\n\r\n return (\r\n \r\n onFilterUpdate(filter.filterId, {\r\n value: String(val),\r\n })\r\n }\r\n placeholder={columnMeta?.placeholder ?? translations.enterValue}\r\n type={isNumber ? 'number' : filter.variant}\r\n value={\r\n typeof filter.value === 'string' || typeof filter.value === 'number' ? filter.value : ''\r\n }\r\n />\r\n );\r\n }\r\n\r\n case 'boolean': {\r\n const options = columnMeta?.options ?? [\r\n { label: 'True', value: 'true' },\r\n { label: 'False', value: 'false' },\r\n ];\r\n const selectedOption = options.find((opt) => String(opt.value) === String(filter.value));\r\n const displayLabel = selectedOption?.label ?? (filter.value === 'true' ? 'True' : 'False');\r\n\r\n return (\r\n \r\n onFilterUpdate(filter.filterId, {\r\n value: val,\r\n })\r\n }\r\n value={String(filter.value)}\r\n >\r\n \r\n \r\n \r\n \r\n \r\n {options.map((opt) => (\r\n \r\n {opt.label}\r\n \r\n ))}\r\n \r\n \r\n \r\n );\r\n }\r\n\r\n case 'select':\r\n case 'multiSelect': {\r\n const multiple = filter.variant === 'multiSelect';\r\n return (\r\n {\r\n onFilterUpdate(filter.filterId, {\r\n value: val,\r\n });\r\n }}\r\n options={columnMeta?.options ?? []}\r\n placeholder={columnMeta?.placeholder}\r\n translations={translations}\r\n value={filter.value}\r\n />\r\n );\r\n }\r\n\r\n case 'date':\r\n case 'dateRange': {\r\n return (\r\n {\r\n onFilterUpdate(filter.filterId, {\r\n value: val,\r\n ...(Array.isArray(val) ? { operator: 'isBetween' } : {}),\r\n });\r\n }}\r\n operator={filter.operator}\r\n placeholder={columnMeta?.placeholder}\r\n presets={columnMeta?.presets ?? (columnMeta as any)?.preset}\r\n translations={translations}\r\n value={filter.value}\r\n />\r\n );\r\n }\r\n\r\n default:\r\n return null;\r\n }\r\n}\r\n\r\nfunction FacetedFilter({\r\n options,\r\n value,\r\n onChange,\r\n multiple,\r\n placeholder,\r\n translations,\r\n}: {\r\n options: Option[];\r\n value: string | string[];\r\n onChange: (value: string | string[]) => void;\r\n multiple: boolean;\r\n placeholder?: string;\r\n translations: any;\r\n}) {\r\n const [open, setOpen] = React.useState(false);\r\n const selectedValues = React.useMemo(() => {\r\n if (multiple) {\r\n return new Set(Array.isArray(value) ? value : []);\r\n }\r\n return new Set(typeof value === 'string' && value ? [value] : []);\r\n }, [value, multiple]);\r\n\r\n const handleSelect = (optionValue: string) => {\r\n if (multiple) {\r\n const next = new Set(selectedValues);\r\n if (next.has(optionValue)) {\r\n next.delete(optionValue);\r\n } else {\r\n next.add(optionValue);\r\n }\r\n onChange(Array.from(next));\r\n } else {\r\n onChange(optionValue);\r\n setOpen(false);\r\n }\r\n };\r\n\r\n const getLabel = (val: string) => {\r\n return options.find((o) => o.value === val)?.label ?? val;\r\n };\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n {selectedValues.size === 0\r\n ? (placeholder ?? translations.selectField)\r\n : multiple\r\n ? `${selectedValues.size} ${translations.selected || 'selected'}`\r\n : getLabel(Array.from(selectedValues)[0] ?? '')}\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {translations.noResults ?? 'No options found.'}\r\n \r\n {options.map((option) => {\r\n const isSelected = selectedValues.has(option.value);\r\n return (\r\n handleSelect(option.value)}\r\n value={option.value}\r\n >\r\n \r\n \r\n \r\n {option.icon && }\r\n {option.label}\r\n {option.count && (\r\n \r\n {option.count}\r\n \r\n )}\r\n \r\n );\r\n })}\r\n \r\n \r\n \r\n \r\n \r\n );\r\n}\r\n\r\nfunction DateFilterInput({\r\n value,\r\n onChange,\r\n operator,\r\n placeholder,\r\n translations,\r\n disabled,\r\n presets,\r\n label,\r\n}: {\r\n value: any;\r\n onChange: (value: any) => void;\r\n operator: string;\r\n placeholder?: string;\r\n translations: any;\r\n disabled?: import('react-day-picker').Matcher | import('react-day-picker').Matcher[];\r\n presets?: boolean | DatePreset[];\r\n label?: string;\r\n}) {\r\n const selectedDates = React.useMemo(() => {\r\n if (!value) {\r\n return operator === 'isBetween' ? { from: undefined, to: undefined } : [];\r\n }\r\n\r\n if (operator === 'isBetween') {\r\n const timestamps = parseColumnFilterValue(value);\r\n return {\r\n from: parseAsDate(timestamps[0]),\r\n to: parseAsDate(timestamps[1]),\r\n };\r\n }\r\n\r\n const timestamps = parseColumnFilterValue(value);\r\n const date = parseAsDate(timestamps[0]);\r\n return date ? [date] : [];\r\n }, [value, operator]);\r\n\r\n const startDate = getIsDateRange(selectedDates) ? selectedDates.from : selectedDates[0];\r\n const endDate = getIsDateRange(selectedDates) ? selectedDates.to : undefined;\r\n\r\n const displayValue = React.useMemo(() => {\r\n if (operator === 'isBetween' && startDate && endDate) {\r\n return `${dayjs(startDate).format('MMM D, YYYY')} - ${dayjs(endDate).format('MMM D, YYYY')}`;\r\n }\r\n return startDate\r\n ? dayjs(startDate).format('MMM D, YYYY')\r\n : (placeholder ?? translations.pickADate);\r\n }, [startDate, endDate, operator, placeholder, translations]);\r\n\r\n const hasPresets = Array.isArray(presets) && presets.length > 0;\r\n const resolvedPresets = React.useMemo(() => {\r\n return hasPresets ? (presets as DatePreset[]) : [];\r\n }, [hasPresets, presets]);\r\n\r\n const isPresetActive = React.useCallback(\r\n (presetValue: DateRange | [Date, Date] | (() => DateRange | [Date, Date])) => {\r\n let resolvedValue: DateRange;\r\n const val = typeof presetValue === 'function' ? presetValue() : presetValue;\r\n if (Array.isArray(val)) {\r\n resolvedValue = { from: val[0], to: val[1] };\r\n } else {\r\n resolvedValue = val;\r\n }\r\n\r\n if (!resolvedValue.from && !startDate && !resolvedValue.to && !endDate) return true;\r\n if (!resolvedValue.from || !startDate) return false;\r\n\r\n const sameFrom = dayjs(resolvedValue.from).isSame(startDate, 'day');\r\n const sameTo =\r\n resolvedValue.to && endDate\r\n ? dayjs(resolvedValue.to).isSame(endDate, 'day')\r\n : !resolvedValue.to && !endDate;\r\n\r\n return sameFrom && sameTo;\r\n },\r\n [startDate, endDate],\r\n );\r\n\r\n const handlePresetClick = React.useCallback(\r\n (presetValue: DateRange | [Date, Date] | (() => DateRange | [Date, Date])) => {\r\n let resolvedValue: DateRange;\r\n const val = typeof presetValue === 'function' ? presetValue() : presetValue;\r\n if (Array.isArray(val)) {\r\n resolvedValue = { from: val[0], to: val[1] };\r\n } else {\r\n resolvedValue = val;\r\n }\r\n\r\n const from = resolvedValue.from?.getTime();\r\n const to = resolvedValue.to?.getTime();\r\n onChange(from || to ? [from ? from.toString() : '', to ? to.toString() : ''] : undefined);\r\n },\r\n [onChange],\r\n );\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n {displayValue}\r\n \r\n \r\n \r\n {hasPresets && (\r\n
\r\n {resolvedPresets.map((preset) => (\r\n handlePresetClick(preset.value)}\r\n size=\"sm\"\r\n variant={isPresetActive(preset.value) ? 'default' : 'ghost'}\r\n >\r\n {preset.label}\r\n \r\n ))}\r\n
\r\n )}\r\n
\r\n {operator === 'isBetween' ? (\r\n {\r\n if (range?.from || range?.to) {\r\n const from = range.from?.getTime();\r\n const to = range.to?.getTime();\r\n onChange(\r\n from || to ? [from ? from.toString() : '', to ? to.toString() : ''] : undefined,\r\n );\r\n }\r\n }}\r\n selected={startDate ? { from: startDate, to: endDate } : undefined}\r\n />\r\n ) : (\r\n {\r\n if (date) {\r\n onChange(date.getTime().toString());\r\n }\r\n }}\r\n selected={startDate}\r\n />\r\n )}\r\n
\r\n \r\n
\r\n );\r\n}\r\n", + "path": "registry/components/data-table/data-table-advance-filter.tsx", + "target": "@components/data-table/data-table-advance-filter.tsx", + "type": "registry:component" + } + ], + "name": "data-table-advance-filter", + "registryDependencies": [ + "@kombase/lib-data-table", + "badge", + "button", + "calendar", + "command", + "popover", + "select", + "@kombase/data-table-config", + "@kombase/data-table-date-filter", + "@kombase/data-table-range-filter" + ], + "type": "registry:component" +} diff --git a/public/r/data-table-bulk-action.json b/public/r/data-table-bulk-action.json new file mode 100644 index 0000000..3144eef --- /dev/null +++ b/public/r/data-table-bulk-action.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import type { Table } from '@tanstack/react-table';\r\nimport { X } from 'lucide-react';\r\nimport { useEffect, useRef, useState } from 'react';\r\nimport { Badge } from '@/components/ui/badge';\r\nimport { Button } from '@/components/ui/button';\r\nimport { Separator } from '@/components/ui/separator';\r\nimport { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype DataTableBulkActionsProps = {\r\n table: Table;\r\n entityName?: string;\r\n children: React.ReactNode;\r\n className?: string;\r\n};\r\n\r\n/**\r\n * A modular toolbar for displaying bulk actions when table rows are selected.\r\n *\r\n * @template TData The type of data in the table.\r\n * @param {object} props The component props.\r\n * @param {Table} props.table The react-table instance.\r\n * @param {string} props.entityName The name of the entity being acted upon (e.g., \"task\", \"user\").\r\n * @param {React.ReactNode} props.children The action buttons to be rendered inside the toolbar.\r\n * @param {string} [props.className] Optional custom CSS class for positioning and styling.\r\n * @returns {React.ReactNode | null} The rendered component or null if no rows are selected.\r\n */\r\nexport function DataTableBulkActions({\r\n table,\r\n entityName,\r\n children,\r\n className,\r\n}: DataTableBulkActionsProps): React.ReactNode | null {\r\n const selectedRows = table.getFilteredSelectedRowModel().rows;\r\n const selectedCount = selectedRows.length;\r\n const toolbarRef = useRef(null);\r\n const [announcement, setAnnouncement] = useState('');\r\n\r\n // Announce selection changes to screen readers\r\n useEffect(() => {\r\n if (selectedCount > 0) {\r\n const message = `${selectedCount} ${entityName || 'item'}${selectedCount > 1 ? 's' : ''} selected. Bulk actions toolbar is available.`;\r\n\r\n // Use queueMicrotask to defer state update and avoid cascading renders\r\n queueMicrotask(() => {\r\n setAnnouncement(message);\r\n });\r\n\r\n // Clear announcement after a delay\r\n const timer = setTimeout(() => setAnnouncement(''), 3000);\r\n return () => clearTimeout(timer);\r\n }\r\n }, [selectedCount, entityName]);\r\n\r\n const handleClearSelection = () => {\r\n table.resetRowSelection();\r\n };\r\n\r\n const handleKeyDown = (event: React.KeyboardEvent) => {\r\n const buttons = toolbarRef.current?.querySelectorAll('button');\r\n if (!buttons) return;\r\n\r\n const activeElement = document.activeElement as HTMLButtonElement | null;\r\n const currentIndex = activeElement ? Array.from(buttons).indexOf(activeElement) : -1;\r\n\r\n switch (event.key) {\r\n case 'ArrowRight': {\r\n event.preventDefault();\r\n const nextIndex = (currentIndex + 1) % buttons.length;\r\n buttons[nextIndex]?.focus();\r\n break;\r\n }\r\n case 'ArrowLeft': {\r\n event.preventDefault();\r\n const prevIndex = currentIndex === 0 ? buttons.length - 1 : currentIndex - 1;\r\n buttons[prevIndex]?.focus();\r\n break;\r\n }\r\n case 'Home':\r\n event.preventDefault();\r\n buttons[0]?.focus();\r\n break;\r\n case 'End':\r\n event.preventDefault();\r\n buttons[buttons.length - 1]?.focus();\r\n break;\r\n case 'Escape': {\r\n // Check if the Escape key came from a dropdown trigger or content\r\n // We can't check dropdown state because Radix UI closes it before our handler runs\r\n const target = event.target as HTMLElement;\r\n const activeElement = document.activeElement as HTMLElement;\r\n\r\n // Check if the event target or currently focused element is a dropdown trigger\r\n const isFromDropdownTrigger =\r\n target?.getAttribute('data-slot') === 'dropdown-menu-trigger' ||\r\n activeElement?.getAttribute('data-slot') === 'dropdown-menu-trigger' ||\r\n target?.closest('[data-slot=\"dropdown-menu-trigger\"]') ||\r\n activeElement?.closest('[data-slot=\"dropdown-menu-trigger\"]');\r\n\r\n // Check if the focused element is inside dropdown content (which is portaled)\r\n const isFromDropdownContent =\r\n activeElement?.closest('[data-slot=\"dropdown-menu-content\"]') ||\r\n target?.closest('[data-slot=\"dropdown-menu-content\"]');\r\n\r\n if (isFromDropdownTrigger || isFromDropdownContent) {\r\n // Escape was meant for the dropdown - don't clear selection\r\n return;\r\n }\r\n\r\n // Escape was meant for the toolbar - clear selection\r\n event.preventDefault();\r\n handleClearSelection();\r\n break;\r\n }\r\n }\r\n };\r\n\r\n if (selectedCount === 0) {\r\n return null;\r\n }\r\n\r\n return (\r\n <>\r\n {/* Live region for screen reader announcements */}\r\n
\r\n {announcement}\r\n
\r\n\r\n 1 ? 's' : ''}`}\r\n className={cn(\r\n 'fixed bottom-6 left-1/2 z-50 -translate-x-1/2 rounded-xl w-max',\r\n 'transition-all delay-100 duration-300 ease-out hover:scale-105',\r\n 'focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:outline-none',\r\n className,\r\n )}\r\n onKeyDown={handleKeyDown}\r\n ref={toolbarRef}\r\n role=\"toolbar\"\r\n tabIndex={-1}\r\n >\r\n \r\n \r\n \r\n \r\n \r\n Clear selection\r\n \r\n \r\n \r\n

Clear selection (Escape)

\r\n
\r\n
\r\n\r\n \r\n\r\n {selectedCount > 0 && (\r\n
\r\n \r\n {selectedCount}\r\n \r\n {entityName && {entityName}}\r\n
\r\n )}\r\n\r\n \r\n\r\n {children}\r\n \r\n \r\n \r\n );\r\n}\r\n", + "path": "registry/components/data-table/data-table-bulk-action.tsx", + "target": "@components/data-table/data-table-bulk-action.tsx", + "type": "registry:component" + } + ], + "name": "data-table-bulk-action", + "registryDependencies": ["badge", "button", "separator", "tooltip"], + "type": "registry:component" +} diff --git a/public/r/data-table-column-header.json b/public/r/data-table-column-header.json new file mode 100644 index 0000000..b5bbcdc --- /dev/null +++ b/public/r/data-table-column-header.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import type { Column } from '@tanstack/react-table';\r\nimport { ChevronDown, ChevronsUpDown, ChevronUp, EyeOff, X } from 'lucide-react';\r\nimport {\r\n DropdownMenu,\r\n DropdownMenuCheckboxItem,\r\n DropdownMenuContent,\r\n DropdownMenuItem,\r\n DropdownMenuTrigger,\r\n} from '@/components/ui/dropdown-menu';\r\nimport { cn } from '@/lib/utils';\r\n\r\ninterface DataTableColumnHeaderProps\r\n extends React.ComponentProps {\r\n column: Column;\r\n label: React.ReactNode;\r\n}\r\n\r\nexport function DataTableColumnHeader({\r\n column,\r\n label,\r\n className,\r\n ...props\r\n}: DataTableColumnHeaderProps) {\r\n const align = column.columnDef.meta?.align;\r\n\r\n if (!column.getCanSort() && !column.getCanHide()) {\r\n return (\r\n \r\n {label}\r\n \r\n );\r\n }\r\n\r\n return (\r\n \r\n \r\n {label}\r\n {column.getCanSort() &&\r\n (column.getIsSorted() === 'desc' ? (\r\n \r\n ) : column.getIsSorted() === 'asc' ? (\r\n \r\n ) : (\r\n \r\n ))}\r\n \r\n \r\n {column.getCanSort() && (\r\n <>\r\n span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground\"\r\n onClick={() => column.toggleSorting(false)}\r\n >\r\n \r\n Asc\r\n \r\n span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground\"\r\n onClick={() => column.toggleSorting(true)}\r\n >\r\n \r\n Desc\r\n \r\n {column.getIsSorted() && (\r\n column.clearSorting()}\r\n >\r\n \r\n Reset\r\n \r\n )}\r\n \r\n )}\r\n {column.getCanHide() && (\r\n span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground\"\r\n onClick={() => column.toggleVisibility(false)}\r\n >\r\n \r\n Hide\r\n \r\n )}\r\n \r\n \r\n );\r\n}\r\n", + "path": "registry/components/data-table/data-table-column-header.tsx", + "target": "@components/data-table/data-table-column-header.tsx", + "type": "registry:component" + } + ], + "name": "data-table-column-header", + "registryDependencies": ["dropdown-menu"], + "type": "registry:component" +} diff --git a/public/r/data-table-config.json b/public/r/data-table-config.json new file mode 100644 index 0000000..61abba4 --- /dev/null +++ b/public/r/data-table-config.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "export type DataTableConfig = typeof dataTableConfig;\r\n\r\nexport const dataTableConfig = {\r\n booleanOperators: [\r\n { label: 'Is', value: 'eq' as const },\r\n { label: 'Is not', value: 'ne' as const },\r\n ],\r\n dateOperators: [\r\n { label: 'Is', value: 'eq' as const },\r\n { label: 'Is not', value: 'ne' as const },\r\n { label: 'Is before', value: 'lt' as const },\r\n { label: 'Is after', value: 'gt' as const },\r\n { label: 'Is on or before', value: 'lte' as const },\r\n { label: 'Is on or after', value: 'gte' as const },\r\n { label: 'Is between', value: 'isBetween' as const },\r\n { label: 'Is relative to today', value: 'isRelativeToToday' as const },\r\n { label: 'Is empty', value: 'isEmpty' as const },\r\n { label: 'Is not empty', value: 'isNotEmpty' as const },\r\n ],\r\n filterVariants: [\r\n 'text',\r\n 'number',\r\n 'range',\r\n 'date',\r\n 'dateRange',\r\n 'boolean',\r\n 'select',\r\n 'multiSelect',\r\n ] as const,\r\n joinOperators: ['and', 'or'] as const,\r\n multiSelectOperators: [\r\n { label: 'Has any of', value: 'inArray' as const },\r\n { label: 'Has none of', value: 'notInArray' as const },\r\n { label: 'Is empty', value: 'isEmpty' as const },\r\n { label: 'Is not empty', value: 'isNotEmpty' as const },\r\n ],\r\n numericOperators: [\r\n { label: 'Is', value: 'eq' as const },\r\n { label: 'Is not', value: 'ne' as const },\r\n { label: 'Is less than', value: 'lt' as const },\r\n { label: 'Is less than or equal to', value: 'lte' as const },\r\n { label: 'Is greater than', value: 'gt' as const },\r\n { label: 'Is greater than or equal to', value: 'gte' as const },\r\n { label: 'Is between', value: 'isBetween' as const },\r\n { label: 'Is empty', value: 'isEmpty' as const },\r\n { label: 'Is not empty', value: 'isNotEmpty' as const },\r\n ],\r\n operators: [\r\n 'iLike',\r\n 'notILike',\r\n 'eq',\r\n 'ne',\r\n 'inArray',\r\n 'notInArray',\r\n 'isEmpty',\r\n 'isNotEmpty',\r\n 'lt',\r\n 'lte',\r\n 'gt',\r\n 'gte',\r\n 'isBetween',\r\n 'isRelativeToToday',\r\n ] as const,\r\n selectOperators: [\r\n { label: 'Is', value: 'eq' as const },\r\n { label: 'Is not', value: 'ne' as const },\r\n { label: 'Is empty', value: 'isEmpty' as const },\r\n { label: 'Is not empty', value: 'isNotEmpty' as const },\r\n ],\r\n sortOrders: [\r\n { label: 'Asc', value: 'asc' as const },\r\n { label: 'Desc', value: 'desc' as const },\r\n ],\r\n textOperators: [\r\n { label: 'Contains', value: 'iLike' as const },\r\n { label: 'Does not contain', value: 'notILike' as const },\r\n { label: 'Is', value: 'eq' as const },\r\n { label: 'Is not', value: 'ne' as const },\r\n { label: 'Is empty', value: 'isEmpty' as const },\r\n { label: 'Is not empty', value: 'isNotEmpty' as const },\r\n ],\r\n};\r\n\r\nexport const idIDTranslations = {\r\n addFilter: 'Tambah filter',\r\n and: 'dan',\r\n enterValue: 'Masukkan nilai...',\r\n filters: 'Filter',\r\n noFiltersApplied: 'Tidak ada filter yang diterapkan',\r\n operators: {\r\n eq: 'adalah',\r\n gt: 'lebih dari',\r\n gte: 'lebih dari atau sama dengan',\r\n iLike: 'mengandung',\r\n inArray: 'salah satu dari',\r\n isBetween: 'di antara',\r\n isEmpty: 'kosong',\r\n isNotEmpty: 'tidak kosong',\r\n isRelativeToToday: 'relatif terhadap hari ini',\r\n lt: 'kurang dari',\r\n lte: 'kurang dari atau sama dengan',\r\n ne: 'bukan',\r\n notILike: 'tidak mengandung',\r\n notInArray: 'bukan salah satu dari',\r\n },\r\n or: 'atau',\r\n pickADate: 'Pilih tanggal',\r\n resetFilters: 'Hapus filter',\r\n searchFields: 'Cari kolom...',\r\n selected: 'terpilih',\r\n selectField: 'Pilih kolom',\r\n where: 'Di mana',\r\n};\r\n", + "path": "registry/components/data-table/data-table-config.ts", + "target": "@components/data-table/data-table-config.ts", + "type": "registry:component" + } + ], + "name": "data-table-config", + "type": "registry:component" +} diff --git a/public/r/data-table-date-filter.json b/public/r/data-table-date-filter.json new file mode 100644 index 0000000..98bbce8 --- /dev/null +++ b/public/r/data-table-date-filter.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import type { Column } from '@tanstack/react-table';\r\nimport dayjs from 'dayjs';\r\nimport { CalendarDays, XCircle } from 'lucide-react';\r\nimport * as React from 'react';\r\nimport type { DateRange } from 'react-day-picker';\r\n\r\nimport { Button } from '@/components/ui/button';\r\nimport { Calendar } from '@/components/ui/calendar';\r\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';\r\nimport { Separator } from '@/components/ui/separator';\r\nimport { formatDateFilterTable } from '@/lib/date';\r\nimport type { DatePreset } from './types';\r\n\r\ntype DateSelection = Date[] | DateRange;\r\n\r\nexport function getIsDateRange(value: DateSelection): value is DateRange {\r\n return value && typeof value === 'object' && !Array.isArray(value);\r\n}\r\n\r\nexport function parseAsDate(timestamp: number | string | undefined): Date | undefined {\r\n if (!timestamp) return undefined;\r\n const numericTimestamp = typeof timestamp === 'string' ? Number(timestamp) : timestamp;\r\n const date = new Date(numericTimestamp);\r\n return !Number.isNaN(date.getTime()) ? date : undefined;\r\n}\r\n\r\nexport function parseColumnFilterValue(value: unknown) {\r\n if (value === null || value === undefined) {\r\n return [];\r\n }\r\n\r\n if (Array.isArray(value)) {\r\n return value.map((item) => {\r\n if (typeof item === 'number' || typeof item === 'string') {\r\n return item;\r\n }\r\n return undefined;\r\n });\r\n }\r\n\r\n if (typeof value === 'string' || typeof value === 'number') {\r\n return [value];\r\n }\r\n\r\n return [];\r\n}\r\n\r\ninterface DataTableDateFilterProps {\r\n column: Column;\r\n title?: string;\r\n multiple?: boolean;\r\n presets?: boolean | DatePreset[];\r\n}\r\n\r\nexport function DataTableDateFilter({\r\n column,\r\n title,\r\n multiple,\r\n presets,\r\n}: DataTableDateFilterProps) {\r\n const [open, setOpen] = React.useState(false);\r\n const columnFilterValue = column.getFilterValue();\r\n\r\n const serializedFilterValue = React.useMemo(() => {\r\n if (columnFilterValue === undefined || columnFilterValue === null) {\r\n return '';\r\n }\r\n if (Array.isArray(columnFilterValue)) {\r\n return columnFilterValue.join(',');\r\n }\r\n return String(columnFilterValue);\r\n }, [columnFilterValue]);\r\n\r\n const selectedDates = React.useMemo(() => {\r\n if (!columnFilterValue) {\r\n return multiple ? { from: undefined, to: undefined } : [];\r\n }\r\n\r\n if (multiple) {\r\n const timestamps = parseColumnFilterValue(columnFilterValue);\r\n return {\r\n from: parseAsDate(timestamps[0]),\r\n to: parseAsDate(timestamps[1]),\r\n };\r\n }\r\n\r\n const timestamps = parseColumnFilterValue(columnFilterValue);\r\n const date = parseAsDate(timestamps[0]);\r\n return date ? [date] : [];\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [serializedFilterValue, multiple]);\r\n\r\n const onSelect = React.useCallback(\r\n (date: Date | DateRange | undefined) => {\r\n if (!date) {\r\n column.setFilterValue(undefined);\r\n return;\r\n }\r\n\r\n if (multiple && !('getTime' in date)) {\r\n const from = date.from?.getTime();\r\n const to = date.to?.getTime();\r\n column.setFilterValue(from || to ? [from, to] : undefined);\r\n } else if (!multiple && 'getTime' in date) {\r\n column.setFilterValue(date.getTime());\r\n setOpen(false);\r\n }\r\n },\r\n [column, multiple],\r\n );\r\n\r\n const onReset = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n event.stopPropagation();\r\n column.setFilterValue(undefined);\r\n setOpen(false);\r\n },\r\n [column],\r\n );\r\n\r\n const hasValue = React.useMemo(() => {\r\n if (multiple) {\r\n if (!getIsDateRange(selectedDates)) return false;\r\n return selectedDates.from || selectedDates.to;\r\n }\r\n if (!Array.isArray(selectedDates)) return false;\r\n return selectedDates.length > 0;\r\n }, [multiple, selectedDates]);\r\n\r\n const formatDateRange = React.useCallback((range: DateRange) => {\r\n if (!range.from && !range.to) return '';\r\n if (range.from && range.to) {\r\n return `${formatDateFilterTable(range.from)} - ${formatDateFilterTable(range.to)}`;\r\n }\r\n return formatDateFilterTable(range.from ?? range.to);\r\n }, []);\r\n\r\n const label = React.useMemo(() => {\r\n if (multiple) {\r\n if (!getIsDateRange(selectedDates)) return null;\r\n\r\n const hasSelectedDates = selectedDates.from || selectedDates.to;\r\n const dateText = hasSelectedDates ? formatDateRange(selectedDates) : 'Select date range';\r\n\r\n return (\r\n \r\n {title}\r\n {hasSelectedDates && (\r\n <>\r\n \r\n {dateText}\r\n \r\n )}\r\n \r\n );\r\n }\r\n\r\n if (getIsDateRange(selectedDates)) return null;\r\n\r\n const hasSelectedDate = selectedDates.length > 0;\r\n const dateText = hasSelectedDate ? formatDateFilterTable(selectedDates[0]) : 'Select date';\r\n\r\n return (\r\n \r\n {title}\r\n {hasSelectedDate && (\r\n <>\r\n \r\n {dateText}\r\n \r\n )}\r\n \r\n );\r\n }, [selectedDates, multiple, formatDateRange, title]);\r\n\r\n const disabled = column.columnDef.meta?.disabled;\r\n\r\n const resolvedPresetsOption =\r\n presets ?? column.columnDef.meta?.presets ?? (column.columnDef.meta as any)?.preset;\r\n const hasPresets =\r\n multiple && Array.isArray(resolvedPresetsOption) && resolvedPresetsOption.length > 0;\r\n const resolvedPresets = React.useMemo(() => {\r\n return hasPresets ? (resolvedPresetsOption as DatePreset[]) : [];\r\n }, [hasPresets, resolvedPresetsOption]);\r\n\r\n const isPresetActive = React.useCallback(\r\n (presetValue: DateRange | [Date, Date] | (() => DateRange | [Date, Date])) => {\r\n let resolvedValue: DateRange;\r\n const val = typeof presetValue === 'function' ? presetValue() : presetValue;\r\n if (Array.isArray(val)) {\r\n resolvedValue = { from: val[0], to: val[1] };\r\n } else {\r\n resolvedValue = val;\r\n }\r\n\r\n const current = selectedDates;\r\n if (!getIsDateRange(current)) return false;\r\n\r\n if (!resolvedValue.from && !current.from && !resolvedValue.to && !current.to) return true;\r\n if (!resolvedValue.from || !current.from) return false;\r\n\r\n const sameFrom = dayjs(resolvedValue.from).isSame(current.from, 'day');\r\n const sameTo =\r\n resolvedValue.to && current.to\r\n ? dayjs(resolvedValue.to).isSame(current.to, 'day')\r\n : !resolvedValue.to && !current.to;\r\n\r\n return sameFrom && sameTo;\r\n },\r\n [selectedDates],\r\n );\r\n\r\n const handlePresetClick = React.useCallback(\r\n (presetValue: DateRange | [Date, Date] | (() => DateRange | [Date, Date])) => {\r\n let resolvedValue: DateRange;\r\n const val = typeof presetValue === 'function' ? presetValue() : presetValue;\r\n if (Array.isArray(val)) {\r\n resolvedValue = { from: val[0], to: val[1] };\r\n } else {\r\n resolvedValue = val;\r\n }\r\n\r\n const from = resolvedValue.from?.getTime();\r\n const to = resolvedValue.to?.getTime();\r\n column.setFilterValue(from || to ? [from, to] : undefined);\r\n setOpen(false);\r\n },\r\n [column],\r\n );\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n {hasPresets && (\r\n
\r\n {resolvedPresets.map((preset) => (\r\n handlePresetClick(preset.value)}\r\n size=\"sm\"\r\n variant={isPresetActive(preset.value) ? 'default' : 'ghost'}\r\n >\r\n {preset.label}\r\n \r\n ))}\r\n
\r\n )}\r\n
\r\n {multiple ? (\r\n \r\n ) : (\r\n \r\n )}\r\n
\r\n \r\n
\r\n );\r\n}\r\n", + "path": "registry/components/data-table/data-table-date-filter.tsx", + "target": "@components/data-table/data-table-date-filter.tsx", + "type": "registry:component" + } + ], + "name": "data-table-date-filter", + "registryDependencies": ["button", "calendar", "popover", "separator", "@kombase/lib-date"], + "type": "registry:component" +} diff --git a/public/r/data-table-faceted-filter.json b/public/r/data-table-faceted-filter.json new file mode 100644 index 0000000..a26b871 --- /dev/null +++ b/public/r/data-table-faceted-filter.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import type { Column } from '@tanstack/react-table';\r\nimport { Check, Loader, PlusCircle, XCircle } from 'lucide-react';\r\nimport * as React from 'react';\r\nimport { Badge } from '@/components/ui/badge';\r\nimport { Button } from '@/components/ui/button';\r\nimport {\r\n Command,\r\n CommandEmpty,\r\n CommandGroup,\r\n CommandInput,\r\n CommandItem,\r\n CommandList,\r\n CommandSeparator,\r\n} from '@/components/ui/command';\r\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';\r\nimport { Separator } from '@/components/ui/separator';\r\nimport { cn } from '@/lib/utils';\r\nimport type { Option } from './types';\r\n\r\ninterface DataTableFacetedFilterProps {\r\n column?: Column;\r\n title?: string;\r\n options: Option[];\r\n multiple?: boolean;\r\n loading?: boolean;\r\n}\r\n\r\nexport function DataTableFacetedFilter({\r\n column,\r\n title,\r\n options,\r\n multiple,\r\n loading = false,\r\n}: DataTableFacetedFilterProps) {\r\n const [open, setOpen] = React.useState(false);\r\n\r\n const columnFilterValue = column?.getFilterValue();\r\n const selectedValues = new Set(Array.isArray(columnFilterValue) ? columnFilterValue : []);\r\n\r\n const onItemSelect = React.useCallback(\r\n (option: Option, isSelected: boolean) => {\r\n if (!column) return;\r\n\r\n if (multiple) {\r\n const newSelectedValues = new Set(selectedValues);\r\n if (isSelected) {\r\n newSelectedValues.delete(option.value);\r\n } else {\r\n newSelectedValues.add(option.value);\r\n }\r\n const filterValues = Array.from(newSelectedValues);\r\n column.setFilterValue(filterValues.length ? filterValues : undefined);\r\n } else {\r\n column.setFilterValue(isSelected ? undefined : [option.value]);\r\n setOpen(false);\r\n }\r\n },\r\n [column, multiple, selectedValues],\r\n );\r\n\r\n const onReset = React.useCallback(\r\n (event?: React.MouseEvent) => {\r\n event?.stopPropagation();\r\n column?.setFilterValue(undefined);\r\n },\r\n [column],\r\n );\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {loading ? (\r\n
\r\n \r\n Loading...\r\n
\r\n ) : (\r\n <>\r\n No results found.\r\n \r\n {options.map((option) => {\r\n const isSelected = selectedValues.has(option.value);\r\n\r\n return (\r\n onItemSelect(option, isSelected)}\r\n >\r\n \r\n \r\n \r\n {option.icon && }\r\n {option.label}\r\n {option.count && (\r\n {option.count}\r\n )}\r\n \r\n );\r\n })}\r\n \r\n {selectedValues.size > 0 && (\r\n <>\r\n \r\n \r\n onReset()}\r\n >\r\n Clear filters\r\n \r\n \r\n \r\n )}\r\n \r\n )}\r\n
\r\n
\r\n
\r\n
\r\n );\r\n}\r\n", + "path": "registry/components/data-table/data-table-faceted-filter.tsx", + "target": "@components/data-table/data-table-faceted-filter.tsx", + "type": "registry:component" + } + ], + "name": "data-table-faceted-filter", + "registryDependencies": ["badge", "button", "command", "popover", "separator"], + "type": "registry:component" +} diff --git a/public/r/data-table-pagination.json b/public/r/data-table-pagination.json new file mode 100644 index 0000000..9abc5bf --- /dev/null +++ b/public/r/data-table-pagination.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@tanstack/react-table"], + "files": [ + { + "content": "import type { Table } from '@tanstack/react-table';\r\nimport { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';\r\nimport { Button } from '@/components/ui/button';\r\nimport {\r\n Select,\r\n SelectContent,\r\n SelectItem,\r\n SelectTrigger,\r\n SelectValue,\r\n} from '@/components/ui/select';\r\nimport { getPageNumbers } from '@/lib/pagination';\r\nimport { cn } from '@/lib/utils';\r\n\r\ninterface DataTablePaginationProps extends React.ComponentProps<'div'> {\r\n table: Table;\r\n pageSizeOptions?: number[];\r\n showPageNumbers?: boolean;\r\n rowsPerPageText?: React.ReactNode | null;\r\n pageText?: ((page: number, total: number) => React.ReactNode) | null;\r\n}\r\n\r\nexport function DataTablePagination({\r\n table,\r\n pageSizeOptions = [10, 20, 30, 40, 50],\r\n className,\r\n showPageNumbers = true,\r\n rowsPerPageText = 'Rows per page',\r\n pageText,\r\n ...props\r\n}: DataTablePaginationProps) {\r\n const currentPage = table.getState().pagination.pageIndex + 1;\r\n const totalPages = table.getPageCount();\r\n const pageNumbers = getPageNumbers(currentPage, totalPages);\r\n\r\n return (\r\n \r\n
\r\n {\r\n table.setPageSize(Number(value));\r\n }}\r\n value={`${table.getState().pagination.pageSize}`}\r\n >\r\n \r\n \r\n \r\n \r\n {pageSizeOptions.map((pageSize) => (\r\n \r\n {pageSize}\r\n \r\n ))}\r\n \r\n \r\n {showPageNumbers && rowsPerPageText !== null && (\r\n

{rowsPerPageText}

\r\n )}\r\n
\r\n
\r\n {showPageNumbers && pageText !== null && (\r\n
\r\n {pageText ? pageText(currentPage, totalPages) : `Page ${currentPage} of ${totalPages}`}\r\n
\r\n )}\r\n
\r\n table.setPageIndex(0)}\r\n variant=\"outline\"\r\n >\r\n Go to first page\r\n \r\n \r\n table.previousPage()}\r\n variant=\"outline\"\r\n >\r\n Go to previous page\r\n \r\n \r\n\r\n {/* Page number buttons */}\r\n {pageNumbers.map((pageNumber, index) => (\r\n
\r\n {pageNumber === '...' ? (\r\n ...\r\n ) : (\r\n table.setPageIndex((pageNumber as number) - 1)}\r\n variant={currentPage === pageNumber ? 'default' : 'outline'}\r\n >\r\n Go to page {pageNumber}\r\n {pageNumber}\r\n \r\n )}\r\n
\r\n ))}\r\n\r\n table.nextPage()}\r\n variant=\"outline\"\r\n >\r\n Go to next page\r\n \r\n \r\n table.setPageIndex(table.getPageCount() - 1)}\r\n variant=\"outline\"\r\n >\r\n Go to last page\r\n \r\n \r\n
\r\n
\r\n \r\n );\r\n}\r\n", + "path": "registry/components/data-table/data-table-pagination.tsx", + "target": "@components/data-table/data-table-pagination.tsx", + "type": "registry:component" + } + ], + "name": "data-table-pagination", + "registryDependencies": ["button", "select", "@kombase/lib-pagination", "@kombase/utils"], + "type": "registry:component" +} diff --git a/public/r/data-table-range-filter.json b/public/r/data-table-range-filter.json new file mode 100644 index 0000000..9508377 --- /dev/null +++ b/public/r/data-table-range-filter.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import type { Column } from '@tanstack/react-table';\r\nimport * as React from 'react';\r\nimport { cn } from '@/lib/utils';\r\nimport { DebouncedInput } from '@/components/ui/debounced-input';\r\nimport type { ExtendedColumnFilter } from './types';\r\n\r\ninterface DataTableRangeFilterProps extends React.ComponentProps<'div'> {\r\n filter: ExtendedColumnFilter;\r\n column: Column;\r\n inputId: string;\r\n onFilterUpdate: (\r\n filterId: string,\r\n updates: Partial, 'filterId'>>,\r\n ) => void;\r\n debounceMs?: number;\r\n}\r\n\r\nexport function DataTableRangeFilter({\r\n filter,\r\n column,\r\n inputId,\r\n onFilterUpdate,\r\n className,\r\n debounceMs = 300,\r\n ...props\r\n}: DataTableRangeFilterProps) {\r\n const meta = column.columnDef.meta;\r\n\r\n const [min, max] = React.useMemo(() => {\r\n const range = column.columnDef.meta?.range;\r\n if (range) return range;\r\n\r\n const values = column.getFacetedMinMaxValues();\r\n if (!values) return [0, 100];\r\n\r\n return [values[0], values[1]];\r\n }, [column]);\r\n\r\n const formatValue = React.useCallback((value: string | number | undefined) => {\r\n if (value === undefined || value === '') return '';\r\n const numValue = Number(value);\r\n return Number.isNaN(numValue)\r\n ? ''\r\n : numValue.toLocaleString(undefined, {\r\n maximumFractionDigits: 0,\r\n });\r\n }, []);\r\n\r\n const value = React.useMemo((): [string, string] => {\r\n if (Array.isArray(filter.value)) {\r\n return [formatValue(filter.value[0]), formatValue(filter.value[1])];\r\n }\r\n return [formatValue(filter.value), ''];\r\n }, [filter.value, formatValue]);\r\n\r\n const onRangeValueChange = React.useCallback(\r\n (value: string, isMin?: boolean) => {\r\n const numValue = Number(value);\r\n const currentValues = Array.isArray(filter.value) ? filter.value : ['', ''];\r\n const otherValue = isMin ? (currentValues[1] ?? '') : (currentValues[0] ?? '');\r\n\r\n if (\r\n value === '' ||\r\n (!Number.isNaN(numValue) &&\r\n (isMin\r\n ? numValue >= min && numValue <= (Number(otherValue) || max)\r\n : numValue <= max && numValue >= (Number(otherValue) || min)))\r\n ) {\r\n onFilterUpdate(filter.filterId, {\r\n value: isMin ? [value, otherValue] : [otherValue, value],\r\n });\r\n }\r\n },\r\n [filter.filterId, filter.value, min, max, onFilterUpdate],\r\n );\r\n\r\n return (\r\n
\r\n onRangeValueChange(String(val), true)}\r\n placeholder={min.toString()}\r\n type=\"number\"\r\n value={value[0]}\r\n />\r\n to\r\n onRangeValueChange(String(val))}\r\n placeholder={max.toString()}\r\n type=\"number\"\r\n value={value[1]}\r\n />\r\n
\r\n );\r\n}\r\n", + "path": "registry/components/data-table/data-table-range-filter.tsx", + "target": "@components/data-table/data-table-range-filter.tsx", + "type": "registry:component" + } + ], + "name": "data-table-range-filter", + "registryDependencies": ["debounced-input"], + "type": "registry:component" +} diff --git a/public/r/data-table-skeleton.json b/public/r/data-table-skeleton.json new file mode 100644 index 0000000..cb619be --- /dev/null +++ b/public/r/data-table-skeleton.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import { Skeleton } from '@/components/ui/skeleton';\r\nimport {\r\n Table,\r\n TableBody,\r\n TableCell,\r\n TableHead,\r\n TableHeader,\r\n TableRow,\r\n} from '@/components/ui/table';\r\nimport { cn } from '@/lib/utils';\r\n\r\ninterface DataTableSkeletonProps extends React.ComponentProps<'div'> {\r\n columnCount: number;\r\n rowCount?: number;\r\n filterCount?: number;\r\n cellWidths?: string[];\r\n withViewOptions?: boolean;\r\n withPagination?: boolean;\r\n shrinkZero?: boolean;\r\n}\r\n\r\nexport function DataTableSkeleton({\r\n columnCount,\r\n rowCount = 10,\r\n filterCount = 0,\r\n cellWidths = ['auto'],\r\n withViewOptions = true,\r\n withPagination = true,\r\n shrinkZero = false,\r\n className,\r\n ...props\r\n}: DataTableSkeletonProps) {\r\n const cozyCellWidths = Array.from(\r\n { length: columnCount },\r\n (_, index) => cellWidths[index % cellWidths.length] ?? 'auto',\r\n );\r\n\r\n return (\r\n
\r\n
\r\n
\r\n {filterCount > 0\r\n ? Array.from({ length: filterCount }).map((_, i) => (\r\n \r\n ))\r\n : null}\r\n
\r\n {withViewOptions ?
\r\n
\r\n
\n));\nTableCaption.displayName = 'TableCaption';\n\nexport { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow };\n", + "type": "registry:ui", + "target": "@ui/table.tsx" + } + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/docs/public/r/tabs.json b/docs/public/r/tabs.json new file mode 100644 index 0000000..761dcef --- /dev/null +++ b/docs/public/r/tabs.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "tabs", + "title": "Tabs", + "description": "A tabbed navigation component for switching between views.", + "dependencies": [ + "@radix-ui/react-tabs" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/tabs.tsx", + "content": "'use client';\n\nimport * as TabsPrimitive from '@radix-ui/react-tabs';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport * as React from 'react';\n\nimport { cn } from '@/lib/utils';\n\nconst Tabs = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>(({ className, orientation = 'horizontal', ...props }, ref) => (\n \n));\nTabs.displayName = TabsPrimitive.Root.displayName;\n\nconst tabsListVariants = cva(\n 'group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground data-[variant=line]:rounded-none group-data-[orientation=horizontal]/tabs:h-9 group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col',\n {\n defaultVariants: {\n variant: 'default',\n },\n variants: {\n variant: {\n default: 'bg-muted',\n line: 'gap-1 bg-transparent',\n },\n },\n },\n);\n\nconst TabsList = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef & VariantProps\n>(({ className, variant = 'default', ...props }, ref) => (\n \n));\nTabsList.displayName = TabsPrimitive.List.displayName;\n\nconst TabsTrigger = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nTabsTrigger.displayName = TabsPrimitive.Trigger.displayName;\n\nconst TabsContent = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nTabsContent.displayName = TabsPrimitive.Content.displayName;\n\nexport { Tabs, TabsContent, TabsList, TabsTrigger, tabsListVariants };\n", + "type": "registry:ui", + "target": "@ui/tabs.tsx" + } + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/docs/public/r/textarea.json b/docs/public/r/textarea.json new file mode 100644 index 0000000..bd59f0d --- /dev/null +++ b/docs/public/r/textarea.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "textarea", + "title": "Textarea", + "description": "A multi-line text input field.", + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/textarea.tsx", + "content": "'use client';\n\nimport * as React from 'react';\nimport { cn } from '@/lib/utils';\n\nconst Textarea = React.forwardRef>(\n ({ className, ...props }, ref) => {\n return (\n \n );\n },\n);\nTextarea.displayName = 'Textarea';\n\nexport { Textarea };\n", + "type": "registry:ui", + "target": "@ui/textarea.tsx" + } + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/docs/public/r/timeline.json b/docs/public/r/timeline.json new file mode 100644 index 0000000..21c6d39 --- /dev/null +++ b/docs/public/r/timeline.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "timeline", + "title": "Timeline", + "description": "Chronological event timeline with alternate positioning and horizontal layout.", + "dependencies": [ + "@radix-ui/react-direction", + "class-variance-authority" + ], + "registryDependencies": [ + "@kombase/component-refs", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "files": [ + { + "path": "registry/components/timeline.tsx", + "content": "'use client';\n\nimport * as DirectionPrimitive from '@radix-ui/react-direction';\nimport * as SlotPrimitive from '@radix-ui/react-slot';\nimport { cva } from 'class-variance-authority';\nimport * as React from 'react';\nimport { useIsomorphicLayoutEffect } from '@/hooks/use-isomorphic-layout-effect';\nimport { useLazyRef } from '@/hooks/use-lazy-ref';\nimport { useComposedRefs } from '@/lib/component-refs';\nimport { cn } from '@/lib/utils';\n\ntype Direction = 'ltr' | 'rtl';\ntype Orientation = 'vertical' | 'horizontal';\ntype Variant = 'default' | 'alternate';\ntype Status = 'completed' | 'active' | 'pending';\n\ninterface DivProps extends React.ComponentProps<'div'> {\n asChild?: boolean;\n}\n\ntype ItemElement = React.ComponentRef;\n\nconst ROOT_NAME = 'Timeline';\nconst ITEM_NAME = 'TimelineItem';\nconst DOT_NAME = 'TimelineDot';\nconst CONNECTOR_NAME = 'TimelineConnector';\nconst CONTENT_NAME = 'TimelineContent';\n\nfunction getItemStatus(itemIndex: number, activeIndex?: number): Status {\n if (activeIndex === undefined) return 'pending';\n if (itemIndex < activeIndex) return 'completed';\n if (itemIndex === activeIndex) return 'active';\n return 'pending';\n}\n\nfunction getSortedEntries(entries: [string, React.RefObject][]) {\n return entries.sort((a, b) => {\n const elementA = a[1].current;\n const elementB = b[1].current;\n if (!elementA || !elementB) return 0;\n const position = elementA.compareDocumentPosition(elementB);\n if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;\n if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;\n return 0;\n });\n}\n\nfunction useStore(selector: (store: Store) => T): T {\n const store = React.useContext(StoreContext);\n if (!store) {\n throw new Error(`\\`useStore\\` must be used within \\`${ROOT_NAME}\\``);\n }\n\n const getSnapshot = React.useCallback(() => selector(store), [store, selector]);\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ninterface StoreState {\n items: Map>;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n notify: () => void;\n onItemRegister: (id: string, ref: React.RefObject) => void;\n onItemUnregister: (id: string) => void;\n getNextItemStatus: (id: string, activeIndex?: number) => Status | undefined;\n getItemIndex: (id: string) => number;\n}\n\nconst StoreContext = React.createContext(null);\n\nfunction useStoreContext(consumerName: string) {\n const context = React.useContext(StoreContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface TimelineContextValue {\n dir: Direction;\n orientation: Orientation;\n variant: Variant;\n activeIndex?: number;\n}\n\nconst TimelineContext = React.createContext(null);\n\nfunction useTimelineContext(consumerName: string) {\n const context = React.useContext(TimelineContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\nconst timelineVariants = cva(\n 'relative flex [--timeline-connector-thickness:0.125rem] [--timeline-dot-size:0.875rem]',\n {\n compoundVariants: [\n {\n class: 'gap-6',\n orientation: 'vertical',\n variant: 'default',\n },\n {\n class: 'gap-8',\n orientation: 'horizontal',\n variant: 'default',\n },\n {\n class: 'relative w-full gap-3',\n orientation: 'vertical',\n variant: 'alternate',\n },\n {\n class: 'items-center gap-4',\n orientation: 'horizontal',\n variant: 'alternate',\n },\n ],\n defaultVariants: {\n orientation: 'vertical',\n variant: 'default',\n },\n variants: {\n orientation: {\n horizontal: 'flex-row items-start',\n vertical: 'flex-col',\n },\n variant: {\n alternate: '',\n default: '',\n },\n },\n },\n);\n\ninterface TimelineProps extends DivProps {\n dir?: Direction;\n orientation?: Orientation;\n variant?: Variant;\n activeIndex?: number;\n}\n\nfunction Timeline(props: TimelineProps) {\n const {\n orientation = 'vertical',\n variant = 'default',\n dir: dirProp,\n activeIndex,\n asChild,\n className,\n ...rootProps\n } = props;\n\n const dir = DirectionPrimitive.useDirection(dirProp);\n\n const listenersRef = useLazyRef(() => new Set<() => void>());\n const stateRef = useLazyRef(() => ({\n items: new Map(),\n }));\n\n const store = React.useMemo(() => {\n return {\n getItemIndex: (id: string) => {\n const entries = Array.from(stateRef.current.items.entries());\n const sortedEntries = getSortedEntries(entries);\n return sortedEntries.findIndex(([key]) => key === id);\n },\n getNextItemStatus: (id: string, activeIndex?: number) => {\n const entries = Array.from(stateRef.current.items.entries());\n const sortedEntries = getSortedEntries(entries);\n\n const currentIndex = sortedEntries.findIndex(([key]) => key === id);\n if (currentIndex === -1 || currentIndex === sortedEntries.length - 1) {\n return undefined;\n }\n\n const nextItemIndex = currentIndex + 1;\n return getItemStatus(nextItemIndex, activeIndex);\n },\n getState: () => stateRef.current,\n notify: () => {\n for (const cb of listenersRef.current) {\n cb();\n }\n },\n onItemRegister: (id: string, ref: React.RefObject) => {\n stateRef.current.items.set(id, ref);\n store.notify();\n },\n onItemUnregister: (id: string) => {\n stateRef.current.items.delete(id);\n store.notify();\n },\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n };\n }, [listenersRef, stateRef]);\n\n const contextValue = React.useMemo(\n () => ({\n activeIndex,\n dir,\n orientation,\n variant,\n }),\n [dir, orientation, variant, activeIndex],\n );\n\n const RootPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n \n \n \n \n );\n}\n\ninterface TimelineItemContextValue {\n id: string;\n status: Status;\n isAlternateRight: boolean;\n}\n\nconst TimelineItemContext = React.createContext(null);\n\nfunction useTimelineItemContext(consumerName: string) {\n const context = React.useContext(TimelineItemContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ITEM_NAME}\\``);\n }\n return context;\n}\n\nconst timelineItemVariants = cva('relative flex', {\n compoundVariants: [\n {\n class: 'gap-3 pb-8 last:pb-0',\n orientation: 'vertical',\n variant: 'default',\n },\n {\n class: 'flex-col gap-3',\n orientation: 'horizontal',\n variant: 'default',\n },\n {\n class: 'w-1/2 gap-3 pr-6 pb-12 last:pb-0',\n isAlternateRight: false,\n orientation: 'vertical',\n variant: 'alternate',\n },\n {\n class: 'ml-auto w-1/2 flex-row-reverse gap-3 pb-12 pl-6 last:pb-0',\n isAlternateRight: true,\n orientation: 'vertical',\n variant: 'alternate',\n },\n {\n class: 'grid min-w-0 grid-rows-[1fr_auto_1fr] gap-3',\n orientation: 'horizontal',\n variant: 'alternate',\n },\n ],\n defaultVariants: {\n isAlternateRight: false,\n orientation: 'vertical',\n variant: 'default',\n },\n variants: {\n isAlternateRight: {\n false: '',\n true: '',\n },\n orientation: {\n horizontal: '',\n vertical: '',\n },\n variant: {\n alternate: '',\n default: '',\n },\n },\n});\n\nfunction TimelineItem(props: DivProps) {\n const { asChild, className, id, ref, ...itemProps } = props;\n\n const { dir, orientation, variant, activeIndex } = useTimelineContext(ITEM_NAME);\n const store = useStoreContext(ITEM_NAME);\n\n const instanceId = React.useId();\n const itemId = id ?? instanceId;\n const itemRef = React.useRef(null);\n const composedRef = useComposedRefs(ref, itemRef);\n\n const itemIndex = useStore((state) => state.getItemIndex(itemId));\n\n const status = React.useMemo(() => {\n return getItemStatus(itemIndex, activeIndex);\n }, [activeIndex, itemIndex]);\n\n useIsomorphicLayoutEffect(() => {\n store.onItemRegister(itemId, itemRef);\n return () => {\n store.onItemUnregister(itemId);\n };\n }, [id, store]);\n\n const isAlternateRight = variant === 'alternate' && itemIndex % 2 === 1;\n\n const itemContextValue = React.useMemo(\n () => ({ id: itemId, isAlternateRight, status }),\n [itemId, status, isAlternateRight],\n );\n\n const ItemPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n \n \n );\n}\n\nconst timelineContentVariants = cva('flex-1', {\n compoundVariants: [\n {\n class: 'text-right',\n isAlternateRight: false,\n orientation: 'vertical',\n variant: 'alternate',\n },\n {\n class: 'row-start-3 pt-2',\n isAlternateRight: false,\n orientation: 'horizontal',\n variant: 'alternate',\n },\n {\n class: 'row-start-1 pb-2',\n isAlternateRight: true,\n orientation: 'horizontal',\n variant: 'alternate',\n },\n ],\n defaultVariants: {\n isAlternateRight: false,\n orientation: 'vertical',\n variant: 'default',\n },\n variants: {\n isAlternateRight: {\n false: '',\n true: '',\n },\n orientation: {\n horizontal: '',\n vertical: '',\n },\n variant: {\n alternate: '',\n default: '',\n },\n },\n});\n\nfunction TimelineContent(props: DivProps) {\n const { asChild, className, ...contentProps } = props;\n\n const { variant, orientation } = useTimelineContext(CONTENT_NAME);\n const { status, isAlternateRight } = useTimelineItemContext(CONTENT_NAME);\n\n const ContentPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n );\n}\n\nconst timelineDotVariants = cva(\n 'relative z-10 flex size-[var(--timeline-dot-size)] shrink-0 items-center justify-center rounded-full border-2 bg-background',\n {\n compoundVariants: [\n {\n class:\n 'absolute -right-[calc(var(--timeline-dot-size)/2-var(--timeline-connector-thickness)/2)] bg-background',\n isAlternateRight: false,\n orientation: 'vertical',\n variant: 'alternate',\n },\n {\n class:\n 'absolute -left-[calc(var(--timeline-dot-size)/2-var(--timeline-connector-thickness)/2)] bg-background',\n isAlternateRight: true,\n orientation: 'vertical',\n variant: 'alternate',\n },\n {\n class: 'row-start-2 bg-background',\n orientation: 'horizontal',\n variant: 'alternate',\n },\n {\n class: 'bg-background',\n status: 'completed',\n variant: 'alternate',\n },\n {\n class: 'bg-background',\n status: 'active',\n variant: 'alternate',\n },\n ],\n defaultVariants: {\n isAlternateRight: false,\n orientation: 'vertical',\n status: 'pending',\n variant: 'default',\n },\n variants: {\n isAlternateRight: {\n false: '',\n true: '',\n },\n orientation: {\n horizontal: '',\n vertical: '',\n },\n status: {\n active: 'border-primary',\n completed: 'border-primary',\n pending: 'border-border',\n },\n variant: {\n alternate: '',\n default: '',\n },\n },\n },\n);\n\nfunction TimelineDot(props: DivProps) {\n const { asChild, className, ...dotProps } = props;\n\n const { orientation, variant } = useTimelineContext(DOT_NAME);\n const { status, isAlternateRight } = useTimelineItemContext(DOT_NAME);\n\n const DotPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n );\n}\n\nconst timelineConnectorVariants = cva('absolute z-0', {\n compoundVariants: [\n {\n class:\n 'start-[calc(var(--timeline-dot-size)/2-var(--timeline-connector-thickness)/2)] top-3 h-[calc(100%+0.5rem)] w-[var(--timeline-connector-thickness)]',\n orientation: 'vertical',\n variant: 'default',\n },\n {\n class:\n 'start-3 top-[calc(var(--timeline-dot-size)/2-var(--timeline-connector-thickness)/2)] h-[var(--timeline-connector-thickness)] w-[calc(100%+0.5rem)]',\n orientation: 'horizontal',\n variant: 'default',\n },\n {\n class:\n 'top-2 -right-[calc(var(--timeline-connector-thickness)/2)] h-full w-[var(--timeline-connector-thickness)]',\n isAlternateRight: false,\n orientation: 'vertical',\n variant: 'alternate',\n },\n {\n class:\n 'top-2 -left-[calc(var(--timeline-connector-thickness)/2)] h-full w-[var(--timeline-connector-thickness)]',\n isAlternateRight: true,\n orientation: 'vertical',\n variant: 'alternate',\n },\n {\n class:\n 'top-[calc(var(--timeline-dot-size)/2-var(--timeline-connector-thickness)/2)] left-3 row-start-2 h-[var(--timeline-connector-thickness)] w-[calc(100%+0.5rem)]',\n orientation: 'horizontal',\n variant: 'alternate',\n },\n ],\n defaultVariants: {\n isAlternateRight: false,\n isCompleted: false,\n orientation: 'vertical',\n variant: 'default',\n },\n variants: {\n isAlternateRight: {\n false: '',\n true: '',\n },\n isCompleted: {\n false: 'bg-border',\n true: 'bg-primary',\n },\n orientation: {\n horizontal: '',\n vertical: '',\n },\n variant: {\n alternate: '',\n default: '',\n },\n },\n});\n\ninterface TimelineConnectorProps extends DivProps {\n forceMount?: boolean;\n}\n\nfunction TimelineConnector(props: TimelineConnectorProps) {\n const { asChild, forceMount, className, ...connectorProps } = props;\n\n const { orientation, variant, activeIndex } = useTimelineContext(CONNECTOR_NAME);\n const { id, status, isAlternateRight } = useTimelineItemContext(CONNECTOR_NAME);\n\n const nextItemStatus = useStore((state) => state.getNextItemStatus(id, activeIndex));\n\n const isLastItem = nextItemStatus === undefined;\n\n if (!forceMount && isLastItem) return null;\n\n const isConnectorCompleted = nextItemStatus === 'completed' || nextItemStatus === 'active';\n\n const ConnectorPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n );\n}\n\nfunction TimelineHeader(props: DivProps) {\n const { asChild, className, ...headerProps } = props;\n\n const HeaderPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n );\n}\n\nfunction TimelineTitle(props: DivProps) {\n const { asChild, className, ...titleProps } = props;\n\n const TitlePrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n );\n}\n\nfunction TimelineDescription(props: DivProps) {\n const { asChild, className, ...descriptionProps } = props;\n\n const DescriptionPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n );\n}\n\ninterface TimelineTimeProps extends React.ComponentProps<'time'> {\n asChild?: boolean;\n}\n\nfunction TimelineTime(props: TimelineTimeProps) {\n const { asChild, className, ...timeProps } = props;\n\n const TimePrimitive = asChild ? SlotPrimitive.Slot : 'time';\n\n return (\n \n );\n}\n\nexport {\n Timeline,\n TimelineConnector,\n TimelineContent,\n TimelineDescription,\n TimelineDot,\n TimelineHeader,\n TimelineItem,\n type TimelineProps,\n TimelineTime,\n TimelineTitle,\n};\n", + "type": "registry:component", + "target": "@components/timeline.tsx" + } + ], + "type": "registry:component" +} \ No newline at end of file diff --git a/docs/public/r/tooltip.json b/docs/public/r/tooltip.json new file mode 100644 index 0000000..f7100f7 --- /dev/null +++ b/docs/public/r/tooltip.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "tooltip", + "title": "Tooltip", + "description": "A popup that displays information on hover or focus.", + "dependencies": [ + "@radix-ui/react-tooltip" + ], + "registryDependencies": [ + "@kombase/utils" + ], + "files": [ + { + "path": "registry/ui/tooltip.tsx", + "content": "'use client';\n\nimport * as TooltipPrimitive from '@radix-ui/react-tooltip';\nimport * as React from 'react';\n\nimport { cn } from '@/lib/utils';\n\nconst TooltipProvider = ({\n delayDuration = 0,\n ...props\n}: React.ComponentProps) => (\n \n);\nTooltipProvider.displayName = 'TooltipProvider';\n\nconst Tooltip = ({ ...props }: React.ComponentProps) => (\n \n);\nTooltip.displayName = 'Tooltip';\n\nconst TooltipTrigger = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>((props, ref) => );\nTooltipTrigger.displayName = TooltipPrimitive.Trigger.displayName;\n\nconst TooltipContent = React.forwardRef<\n React.ComponentRef,\n React.ComponentPropsWithoutRef\n>(({ className, sideOffset = 0, children, ...props }, ref) => (\n \n \n {children}\n \n \n \n));\nTooltipContent.displayName = TooltipPrimitive.Content.displayName;\n\nexport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };\n", + "type": "registry:ui", + "target": "@ui/tooltip.tsx" + } + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/docs/public/r/tour.json b/docs/public/r/tour.json new file mode 100644 index 0000000..9a7d92d --- /dev/null +++ b/docs/public/r/tour.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "tour", + "title": "Tour", + "description": "Step-by-step onboarding tour with spotlight highlighting and floating UI.", + "dependencies": [ + "@floating-ui/react-dom", + "@radix-ui/react-direction" + ], + "registryDependencies": [ + "button", + "@kombase/component-refs", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "files": [ + { + "path": "registry/components/tour.tsx", + "content": "import {\n autoUpdate,\n flip,\n hide,\n limitShift,\n type Middleware,\n offset,\n arrow as onArrow,\n type Placement,\n shift,\n useFloating,\n} from '@floating-ui/react-dom';\nimport * as DirectionPrimitive from '@radix-ui/react-direction';\nimport * as SlotPrimitive from '@radix-ui/react-slot';\nimport { ChevronLeft, ChevronRight, X } from 'lucide-react';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { Button } from '@/components/ui/button';\nimport { useAsRef } from '@/hooks/use-as-ref';\nimport { useIsomorphicLayoutEffect } from '@/hooks/use-isomorphic-layout-effect';\nimport { useLazyRef } from '@/hooks/use-lazy-ref';\nimport { useComposedRefs } from '@/lib/component-refs';\nimport { cn } from '@/lib/utils';\n\nconst ROOT_NAME = 'Tour';\nconst PORTAL_NAME = 'TourPortal';\nconst STEP_NAME = 'TourStep';\nconst ARROW_NAME = 'TourArrow';\nconst HEADER_NAME = 'TourHeader';\nconst TITLE_NAME = 'TourTitle';\nconst DESCRIPTION_NAME = 'TourDescription';\nconst CLOSE_NAME = 'TourClose';\nconst PREV_NAME = 'TourPrev';\nconst NEXT_NAME = 'TourNext';\nconst SKIP_NAME = 'TourSkip';\nconst FOOTER_NAME = 'TourFooter';\n\nconst POINTER_DOWN_OUTSIDE = 'tour.pointerDownOutside';\nconst INTERACT_OUTSIDE = 'tour.interactOutside';\nconst OPEN_AUTO_FOCUS = 'tour.openAutoFocus';\nconst CLOSE_AUTO_FOCUS = 'tour.closeAutoFocus';\nconst EVENT_OPTIONS = { bubbles: false, cancelable: true };\n\nconst SIDE_OPTIONS = ['top', 'right', 'bottom', 'left'] as const;\nconst ALIGN_OPTIONS = ['start', 'center', 'end'] as const;\n\nconst DEFAULT_ALIGN_OFFSET = 0;\nconst DEFAULT_SIDE_OFFSET = 16;\nconst DEFAULT_SPOTLIGHT_PADDING = 4;\n\ntype Side = (typeof SIDE_OPTIONS)[number];\ntype Align = (typeof ALIGN_OPTIONS)[number];\ntype Direction = 'ltr' | 'rtl';\n\ninterface ScrollOffset {\n top?: number;\n bottom?: number;\n left?: number;\n right?: number;\n}\n\ntype Boundary = Element | null;\n\ninterface DivProps extends React.ComponentProps<'div'> {\n asChild?: boolean;\n}\n\ntype StepElement = React.ComponentRef;\ntype CloseElement = React.ComponentRef;\ntype PrevElement = React.ComponentRef;\ntype NextElement = React.ComponentRef;\ntype SkipElement = React.ComponentRef;\ntype FooterElement = React.ComponentRef;\n\nconst OPPOSITE_SIDE: Record = {\n bottom: 'top',\n left: 'right',\n right: 'left',\n top: 'bottom',\n};\n\n/**\n * @see https://github.com/radix-ui/primitives/blob/main/packages/react/focus-guards/src/focus-guards.tsx\n */\nlet focusGuardCount = 0;\n\nfunction createFocusGuard() {\n const element = document.createElement('span');\n element.setAttribute('data-tour-focus-guard', '');\n element.tabIndex = 0;\n element.style.outline = 'none';\n element.style.opacity = '0';\n element.style.position = 'fixed';\n element.style.pointerEvents = 'none';\n return element;\n}\n\nfunction useFocusGuards() {\n React.useEffect(() => {\n const edgeGuards = document.querySelectorAll('[data-tour-focus-guard]');\n document.body.insertAdjacentElement('afterbegin', edgeGuards[0] ?? createFocusGuard());\n document.body.insertAdjacentElement('beforeend', edgeGuards[1] ?? createFocusGuard());\n focusGuardCount++;\n\n return () => {\n if (focusGuardCount === 1) {\n const guards = document.querySelectorAll('[data-tour-focus-guard]');\n for (const node of guards) {\n node.remove();\n }\n }\n focusGuardCount--;\n };\n }, []);\n}\n\nfunction useFocusTrap(\n containerRef: React.RefObject,\n enabled: boolean,\n tourOpen: boolean,\n onOpenAutoFocus?: (event: OpenAutoFocusEvent) => void,\n onCloseAutoFocus?: (event: CloseAutoFocusEvent) => void,\n) {\n const lastFocusedElementRef = React.useRef(null);\n const onOpenAutoFocusRef = useAsRef(onOpenAutoFocus);\n const onCloseAutoFocusRef = useAsRef(onCloseAutoFocus);\n const tourOpenRef = useAsRef(tourOpen);\n\n React.useEffect(() => {\n if (!enabled) return;\n\n const container = containerRef.current;\n if (!container) return;\n\n const previouslyFocusedElement = document.activeElement as HTMLElement | null;\n\n function getTabbableCandidates() {\n if (!container) return [];\n\n const nodes: HTMLElement[] = [];\n const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {\n acceptNode: (node: Element) => {\n const element = node as HTMLElement;\n const isHiddenInput =\n element.tagName === 'INPUT' && (element as HTMLInputElement).type === 'hidden';\n if (element.hidden || isHiddenInput) return NodeFilter.FILTER_SKIP;\n return element.tabIndex >= 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;\n },\n });\n while (walker.nextNode()) {\n nodes.push(walker.currentNode as HTMLElement);\n }\n return nodes;\n }\n\n function getTabbableEdges() {\n const candidates = getTabbableCandidates();\n const first = candidates[0];\n const last = candidates[candidates.length - 1];\n return [first, last] as const;\n }\n\n function onFocusIn(event: FocusEvent) {\n if (!container) return;\n\n const target = event.target as HTMLElement | null;\n if (container.contains(target)) {\n lastFocusedElementRef.current = target;\n } else {\n const elementToFocus = lastFocusedElementRef.current ?? getTabbableCandidates()[0];\n elementToFocus?.focus({ preventScroll: true });\n }\n }\n\n function onKeyDown(event: KeyboardEvent) {\n if (event.key !== 'Tab' || event.altKey || event.ctrlKey || event.metaKey) return;\n\n const [first, last] = getTabbableEdges();\n const hasTabbableElements = first && last;\n\n if (!hasTabbableElements) {\n if (document.activeElement === container) event.preventDefault();\n return;\n }\n\n if (!event.shiftKey && document.activeElement === last) {\n event.preventDefault();\n first?.focus({ preventScroll: true });\n } else if (event.shiftKey && document.activeElement === first) {\n event.preventDefault();\n last?.focus({ preventScroll: true });\n }\n }\n\n const openAutoFocusEvent = new CustomEvent(OPEN_AUTO_FOCUS, EVENT_OPTIONS);\n if (onOpenAutoFocusRef.current) {\n container.addEventListener(OPEN_AUTO_FOCUS, onOpenAutoFocusRef.current as EventListener, {\n once: true,\n });\n }\n container.dispatchEvent(openAutoFocusEvent);\n\n if (!openAutoFocusEvent.defaultPrevented) {\n const tabbableCandidates = getTabbableCandidates();\n if (tabbableCandidates.length > 0) {\n tabbableCandidates[0]?.focus({ preventScroll: true });\n } else {\n container.focus({ preventScroll: true });\n }\n }\n\n document.addEventListener('focusin', onFocusIn);\n container.addEventListener('keydown', onKeyDown);\n\n return () => {\n document.removeEventListener('focusin', onFocusIn);\n container.removeEventListener('keydown', onKeyDown);\n\n if (!tourOpenRef.current) {\n setTimeout(() => {\n const closeAutoFocusEvent = new CustomEvent(CLOSE_AUTO_FOCUS, EVENT_OPTIONS);\n if (onCloseAutoFocusRef.current) {\n container.addEventListener(\n CLOSE_AUTO_FOCUS,\n onCloseAutoFocusRef.current as EventListener,\n { once: true },\n );\n }\n container.dispatchEvent(closeAutoFocusEvent);\n\n if (!closeAutoFocusEvent.defaultPrevented) {\n if (previouslyFocusedElement && document.body.contains(previouslyFocusedElement)) {\n previouslyFocusedElement.focus({ preventScroll: true });\n }\n }\n\n if (onCloseAutoFocusRef.current) {\n container.removeEventListener(\n CLOSE_AUTO_FOCUS,\n onCloseAutoFocusRef.current as EventListener,\n );\n }\n }, 0);\n }\n };\n }, [containerRef, enabled, onOpenAutoFocusRef, onCloseAutoFocusRef, tourOpenRef]);\n}\n\nfunction getDataState(open: boolean) {\n return open ? 'open' : 'closed';\n}\n\ninterface StepData {\n target: string | React.RefObject | HTMLElement;\n align?: Align;\n alignOffset?: number;\n side?: Side;\n sideOffset?: number;\n collisionBoundary?: Boundary | Boundary[];\n collisionPadding?: number | Partial>;\n arrowPadding?: number;\n sticky?: 'partial' | 'always';\n hideWhenDetached?: boolean;\n avoidCollisions?: boolean;\n onStepEnter?: () => void;\n onStepLeave?: () => void;\n required?: boolean;\n}\n\ninterface StoreState {\n open: boolean;\n value: number;\n steps: StepData[];\n maskPath: string;\n spotlightRect: { x: number; y: number; width: number; height: number } | null;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n setState: (key: K, value: StoreState[K], opts?: unknown) => void;\n notify: () => void;\n addStep: (stepData: StepData) => { id: string; index: number };\n removeStep: (id: string) => void;\n}\n\nfunction useStore(selector: (state: StoreState) => T, ogStore?: Store | null): T {\n const contextStore = React.useContext(StoreContext);\n\n const store = ogStore ?? contextStore;\n\n if (!store) {\n throw new Error(`\\`useStore\\` must be used within \\`${ROOT_NAME}\\``);\n }\n\n const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\nfunction getTargetElement(\n target: string | React.RefObject | HTMLElement,\n): HTMLElement | null {\n if (typeof target === 'string') {\n return document.querySelector(target);\n }\n if (target && 'current' in target) {\n return target.current;\n }\n if (target instanceof HTMLElement) {\n return target;\n }\n return null;\n}\n\nfunction getDefaultScrollBehavior(): ScrollBehavior {\n if (typeof window === 'undefined') return 'smooth';\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth';\n}\n\nfunction onScrollToElement(\n element: HTMLElement,\n scrollBehavior: ScrollBehavior = getDefaultScrollBehavior(),\n scrollOffset?: ScrollOffset,\n) {\n const offset: Required = {\n bottom: 100,\n left: 0,\n right: 0,\n top: 100,\n ...scrollOffset,\n };\n const rect = element.getBoundingClientRect();\n const viewportHeight = window.innerHeight;\n const viewportWidth = window.innerWidth;\n\n const isInViewport =\n rect.top >= offset.top &&\n rect.bottom <= viewportHeight - offset.bottom &&\n rect.left >= offset.left &&\n rect.right <= viewportWidth - offset.right;\n\n if (!isInViewport) {\n const elementTop = rect.top + window.scrollY;\n const scrollTop = elementTop - offset.top;\n\n window.scrollTo({\n behavior: scrollBehavior,\n top: Math.max(0, scrollTop),\n });\n }\n}\n\nfunction getSideAndAlignFromPlacement(placement: Placement): [Side, Align] {\n const [side, align = 'center'] = placement.split('-') as [Side, Align?];\n return [side, align];\n}\n\nfunction getPlacement(side: Side, align: Align): Placement {\n if (align === 'center') {\n return side as Placement;\n }\n return `${side}-${align}` as Placement;\n}\n\nfunction updateMask(\n store: Store,\n targetElement: HTMLElement,\n padding: number = DEFAULT_SPOTLIGHT_PADDING,\n) {\n const clientRect = targetElement.getBoundingClientRect();\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n\n const x = Math.max(0, clientRect.left - padding);\n const y = Math.max(0, clientRect.top - padding);\n const width = Math.min(viewportWidth - x, clientRect.width + padding * 2);\n const height = Math.min(viewportHeight - y, clientRect.height + padding * 2);\n\n const path = `polygon(0% 0%, 0% 100%, ${x}px 100%, ${x}px ${y}px, ${x + width}px ${y}px, ${x + width}px ${y + height}px, ${x}px ${y + height}px, ${x}px 100%, 100% 100%, 100% 0%)`;\n store.setState('maskPath', path);\n store.setState('spotlightRect', { height, width, x, y });\n}\n\nconst StoreContext = React.createContext(null);\n\nfunction useStoreContext(consumerName: string) {\n const context = React.useContext(StoreContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface TourContextValue {\n dir: Direction;\n alignOffset: number;\n sideOffset: number;\n spotlightPadding: number;\n dismissible: boolean;\n modal: boolean;\n stepFooter?: React.ReactElement;\n onPointerDownOutside?: (event: PointerDownOutsideEvent) => void;\n onInteractOutside?: (event: InteractOutsideEvent) => void;\n onOpenAutoFocus?: (event: OpenAutoFocusEvent) => void;\n onCloseAutoFocus?: (event: CloseAutoFocusEvent) => void;\n}\n\nconst TourContext = React.createContext(null);\n\nfunction useTourContext(consumerName: string) {\n const context = React.useContext(TourContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface StepContextValue {\n arrowX?: number;\n arrowY?: number;\n placedAlign: Align;\n placedSide: Side;\n shouldHideArrow: boolean;\n onArrowChange: (arrow: HTMLSpanElement | null) => void;\n onFooterChange: (footer: FooterElement | null) => void;\n}\n\nconst StepContext = React.createContext(null);\n\nfunction useStepContext(consumerName: string) {\n const context = React.useContext(StepContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` must be used within \\`${STEP_NAME}\\``);\n }\n return context;\n}\n\nconst DefaultFooterContext = React.createContext(false);\n\ninterface PortalContextValue {\n portal: HTMLElement | null;\n onPortalChange: (node: HTMLElement | null) => void;\n}\n\nconst PortalContext = React.createContext(null);\n\nfunction usePortalContext(consumerName: string) {\n const context = React.useContext(PortalContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\nfunction useScrollLock(enabled: boolean) {\n React.useEffect(() => {\n if (!enabled) return;\n\n const originalStyle = window.getComputedStyle(document.body).overflow;\n const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;\n\n document.body.style.overflow = 'hidden';\n if (scrollbarWidth > 0) {\n document.body.style.paddingRight = `${scrollbarWidth}px`;\n }\n\n return () => {\n document.body.style.overflow = originalStyle;\n document.body.style.paddingRight = '';\n };\n }, [enabled]);\n}\n\ntype PointerDownOutsideEvent = CustomEvent<{ originalEvent: PointerEvent }>;\ntype InteractOutsideEvent = CustomEvent<{\n originalEvent: PointerEvent | FocusEvent;\n}>;\ntype OpenAutoFocusEvent = CustomEvent>;\ntype CloseAutoFocusEvent = CustomEvent>;\n\ninterface TourProps extends DivProps {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n value?: number;\n defaultValue?: number;\n onValueChange?: (step: number) => void;\n onComplete?: () => void;\n onSkip?: () => void;\n onEscapeKeyDown?: (event: KeyboardEvent) => void;\n onPointerDownOutside?: (event: PointerDownOutsideEvent) => void;\n onInteractOutside?: (event: InteractOutsideEvent) => void;\n onOpenAutoFocus?: (event: OpenAutoFocusEvent) => void;\n onCloseAutoFocus?: (event: CloseAutoFocusEvent) => void;\n dir?: Direction;\n alignOffset?: number;\n sideOffset?: number;\n spotlightPadding?: number;\n autoScroll?: boolean;\n scrollBehavior?: ScrollBehavior;\n scrollOffset?: ScrollOffset;\n dismissible?: boolean;\n modal?: boolean;\n stepFooter?: React.ReactElement;\n}\n\nfunction Tour(props: TourProps) {\n const {\n open: openProp,\n defaultOpen = false,\n onOpenChange,\n value: valueProp,\n defaultValue = 0,\n onValueChange,\n onComplete,\n onSkip,\n autoScroll = true,\n scrollBehavior = getDefaultScrollBehavior(),\n scrollOffset,\n onEscapeKeyDown,\n onPointerDownOutside,\n onInteractOutside,\n onOpenAutoFocus,\n onCloseAutoFocus,\n dir: dirProp,\n alignOffset = DEFAULT_ALIGN_OFFSET,\n sideOffset = DEFAULT_SIDE_OFFSET,\n spotlightPadding = DEFAULT_SPOTLIGHT_PADDING,\n dismissible = true,\n modal = true,\n stepFooter,\n asChild,\n ...rootProps\n } = props;\n\n const dir = DirectionPrimitive.useDirection(dirProp);\n\n const [portal, setPortal] = React.useState(null);\n const prevOpenRef = React.useRef(undefined);\n const previouslyFocusedElementRef = React.useRef(null);\n\n const stateRef = useLazyRef(() => ({\n maskPath: '',\n open: openProp ?? defaultOpen,\n spotlightRect: null,\n steps: [],\n value: valueProp ?? defaultValue,\n }));\n const listenersRef = useLazyRef void>>(() => new Set());\n const stepIdsMapRef = useLazyRef>(() => new Map());\n const stepIdCounterRef = useLazyRef(() => ({ current: 0 }));\n const propsRef = useAsRef({\n autoScroll,\n onCloseAutoFocus,\n onComplete,\n onEscapeKeyDown,\n onOpenChange,\n onSkip,\n onValueChange,\n scrollBehavior,\n scrollOffset,\n valueProp,\n });\n\n const store: Store = React.useMemo(\n () => ({\n addStep: (stepData) => {\n const id = `step-${stepIdCounterRef.current.current++}`;\n const index = stateRef.current.steps.length;\n stepIdsMapRef.current.set(id, index);\n stateRef.current.steps = [...stateRef.current.steps, stepData];\n store.notify();\n return { id, index };\n },\n getState: () => {\n return stateRef.current;\n },\n notify: () => {\n listenersRef.current.forEach((l) => {\n l();\n });\n },\n removeStep: (id) => {\n const index = stepIdsMapRef.current.get(id);\n if (index === undefined) return;\n\n stateRef.current.steps = stateRef.current.steps.filter((_, i) => i !== index);\n\n stepIdsMapRef.current.delete(id);\n\n for (const [stepId, stepIndex] of stepIdsMapRef.current.entries()) {\n if (stepIndex > index) {\n stepIdsMapRef.current.set(stepId, stepIndex - 1);\n }\n }\n\n store.notify();\n },\n setState: (key, value) => {\n if (Object.is(stateRef.current[key], value)) return;\n stateRef.current[key] = value;\n\n if (key === 'open' && typeof value === 'boolean') {\n propsRef.current.onOpenChange?.(value);\n\n if (value) {\n if (stateRef.current.steps.length > 0) {\n if (stateRef.current.value >= stateRef.current.steps.length) {\n store.setState('value', 0);\n }\n }\n } else {\n if (stateRef.current.value < (stateRef.current.steps.length || 0) - 1) {\n propsRef.current.onSkip?.();\n }\n }\n } else if (key === 'value' && typeof value === 'number') {\n const prevStep = stateRef.current.steps[stateRef.current.value];\n const nextStep = stateRef.current.steps[value];\n\n prevStep?.onStepLeave?.();\n nextStep?.onStepEnter?.();\n\n if (value >= stateRef.current.steps.length) {\n propsRef.current.onComplete?.();\n\n if (propsRef.current.valueProp !== undefined) {\n propsRef.current.onValueChange?.(value);\n }\n\n store.setState('open', false);\n return;\n }\n\n if (propsRef.current.valueProp !== undefined) {\n propsRef.current.onValueChange?.(value);\n return;\n }\n\n propsRef.current.onValueChange?.(value);\n\n if (nextStep && propsRef.current.autoScroll) {\n const targetElement = getTargetElement(nextStep.target);\n if (targetElement) {\n onScrollToElement(\n targetElement,\n propsRef.current.scrollBehavior,\n propsRef.current.scrollOffset,\n );\n }\n }\n }\n\n store.notify();\n },\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n }),\n [stateRef, listenersRef, stepIdsMapRef, stepIdCounterRef, propsRef],\n );\n\n const open = useStore((state) => state.open, store);\n\n React.useEffect(() => {\n function onKeyDown(event: KeyboardEvent) {\n if (open && event.key === 'Escape') {\n if (propsRef.current.onEscapeKeyDown) {\n propsRef.current.onEscapeKeyDown(event);\n if (event.defaultPrevented) return;\n }\n store.setState('open', false);\n }\n }\n\n document.addEventListener('keydown', onKeyDown);\n return () => document.removeEventListener('keydown', onKeyDown);\n }, [store, open, propsRef]);\n\n useIsomorphicLayoutEffect(() => {\n const wasOpen = prevOpenRef.current;\n\n if (open && !wasOpen) {\n previouslyFocusedElementRef.current = document.activeElement as HTMLElement | null;\n } else if (!open && wasOpen) {\n setTimeout(() => {\n const container = portal ?? document.body;\n const closeAutoFocusEvent = new CustomEvent(CLOSE_AUTO_FOCUS, EVENT_OPTIONS);\n\n if (propsRef.current.onCloseAutoFocus) {\n container.addEventListener(\n CLOSE_AUTO_FOCUS,\n propsRef.current.onCloseAutoFocus as EventListener,\n { once: true },\n );\n }\n container.dispatchEvent(closeAutoFocusEvent);\n\n if (!closeAutoFocusEvent.defaultPrevented) {\n const elementToFocus = previouslyFocusedElementRef.current;\n if (elementToFocus && document.body.contains(elementToFocus)) {\n elementToFocus.focus({ preventScroll: true });\n }\n }\n\n previouslyFocusedElementRef.current = null;\n }, 0);\n }\n\n prevOpenRef.current = open;\n }, [open, portal, propsRef]);\n\n useIsomorphicLayoutEffect(() => {\n if (openProp !== undefined) {\n store.setState('open', openProp);\n }\n }, [openProp, store]);\n\n useIsomorphicLayoutEffect(() => {\n if (valueProp !== undefined) {\n store.setState('value', valueProp);\n }\n }, [valueProp, store]);\n\n const contextValue = React.useMemo(\n () => ({\n alignOffset,\n dir,\n dismissible,\n modal,\n onCloseAutoFocus,\n onInteractOutside,\n onOpenAutoFocus,\n onPointerDownOutside,\n sideOffset,\n spotlightPadding,\n stepFooter,\n }),\n [\n dir,\n alignOffset,\n sideOffset,\n spotlightPadding,\n dismissible,\n modal,\n stepFooter,\n onPointerDownOutside,\n onInteractOutside,\n onOpenAutoFocus,\n onCloseAutoFocus,\n ],\n );\n\n const portalContextValue = React.useMemo(\n () => ({\n onPortalChange: setPortal,\n portal,\n }),\n [portal],\n );\n\n useScrollLock(open && modal);\n\n const RootPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n \n \n \n \n \n \n );\n}\n\ninterface TourStepProps extends DivProps {\n target: string | React.RefObject | HTMLElement;\n side?: Side;\n sideOffset?: number;\n align?: Align;\n alignOffset?: number;\n collisionBoundary?: Boundary | Boundary[];\n collisionPadding?: number | Partial>;\n arrowPadding?: number;\n sticky?: 'partial' | 'always';\n hideWhenDetached?: boolean;\n avoidCollisions?: boolean;\n required?: boolean;\n forceMount?: boolean;\n onStepEnter?: () => void;\n onStepLeave?: () => void;\n}\n\nfunction TourStep(props: TourStepProps) {\n const {\n target,\n side = 'bottom',\n sideOffset,\n align = 'center',\n alignOffset,\n collisionBoundary = [],\n collisionPadding = 0,\n arrowPadding = 0,\n sticky = 'partial',\n hideWhenDetached = false,\n avoidCollisions = true,\n required = false,\n forceMount = false,\n onStepEnter,\n onStepLeave,\n onPointerDownCapture: onPointerDownCaptureProp,\n onFocusCapture: onFocusCaptureProp,\n onBlurCapture: onBlurCaptureProp,\n children,\n className,\n style,\n asChild,\n ...stepProps\n } = props;\n\n const store = useStoreContext(STEP_NAME);\n\n const [arrow, setArrow] = React.useState(null);\n const [footer, setFooter] = React.useState(null);\n\n const stepRef = React.useRef(null);\n const stepIdRef = React.useRef('');\n const stepOrderRef = React.useRef(-1);\n const isPointerInsideReactTreeRef = React.useRef(false);\n const isFocusInsideReactTreeRef = React.useRef(false);\n\n const open = useStore((state) => state.open);\n const value = useStore((state) => state.value);\n const steps = useStore((state) => state.steps);\n const context = useTourContext(STEP_NAME);\n\n const resolvedSideOffset = sideOffset ?? context.sideOffset;\n const resolvedAlignOffset = alignOffset ?? context.alignOffset;\n\n useIsomorphicLayoutEffect(() => {\n const { id, index } = store.addStep({\n align,\n alignOffset: resolvedAlignOffset,\n arrowPadding,\n avoidCollisions,\n collisionBoundary,\n collisionPadding,\n hideWhenDetached,\n onStepEnter,\n onStepLeave,\n required,\n side,\n sideOffset: resolvedSideOffset,\n sticky,\n target,\n });\n stepIdRef.current = id;\n stepOrderRef.current = index;\n\n return () => {\n store.removeStep(stepIdRef.current);\n };\n }, [\n target,\n side,\n resolvedSideOffset,\n align,\n resolvedAlignOffset,\n collisionPadding,\n arrowPadding,\n sticky,\n hideWhenDetached,\n avoidCollisions,\n required,\n onStepEnter,\n onStepLeave,\n store,\n ]);\n\n const stepData = steps[value];\n const targetElement = stepData ? getTargetElement(stepData.target) : null;\n\n const isCurrentStep = stepOrderRef.current === value;\n\n const middleware = React.useMemo(() => {\n if (!stepData) return [];\n\n const mainAxisOffset = stepData.sideOffset ?? resolvedSideOffset;\n const crossAxisOffset = stepData.alignOffset ?? resolvedAlignOffset;\n\n const padding =\n typeof stepData.collisionPadding === 'number'\n ? stepData.collisionPadding\n : {\n bottom: stepData.collisionPadding?.bottom ?? 0,\n left: stepData.collisionPadding?.left ?? 0,\n right: stepData.collisionPadding?.right ?? 0,\n top: stepData.collisionPadding?.top ?? 0,\n };\n\n const boundary = Array.isArray(stepData.collisionBoundary)\n ? stepData.collisionBoundary\n : stepData.collisionBoundary\n ? [stepData.collisionBoundary]\n : [];\n const hasExplicitBoundaries = boundary.length > 0;\n\n const detectOverflowOptions = {\n altBoundary: hasExplicitBoundaries,\n boundary: boundary.filter((b): b is Element => b !== null),\n padding,\n };\n\n return [\n offset({\n alignmentAxis: crossAxisOffset,\n mainAxis: mainAxisOffset,\n }),\n stepData.avoidCollisions &&\n shift({\n crossAxis: false,\n limiter: stepData.sticky === 'partial' ? limitShift() : undefined,\n mainAxis: true,\n ...detectOverflowOptions,\n }),\n stepData.avoidCollisions && flip({ ...detectOverflowOptions }),\n arrow && onArrow({ element: arrow, padding: stepData.arrowPadding }),\n stepData.hideWhenDetached &&\n hide({\n strategy: 'referenceHidden',\n ...detectOverflowOptions,\n }),\n ].filter(Boolean) as Middleware[];\n }, [stepData, resolvedSideOffset, resolvedAlignOffset, arrow]);\n\n const placement = getPlacement(stepData?.side ?? side, stepData?.align ?? align);\n\n const {\n refs,\n floatingStyles,\n placement: finalPlacement,\n middlewareData,\n } = useFloating({\n elements: {\n reference: targetElement,\n },\n middleware,\n placement,\n strategy: 'fixed',\n whileElementsMounted: autoUpdate,\n });\n\n const composedRef = useComposedRefs(refs.setFloating, stepRef);\n\n const [placedSide, placedAlign] = getSideAndAlignFromPlacement(finalPlacement);\n\n const arrowX = middlewareData.arrow?.x;\n const arrowY = middlewareData.arrow?.y;\n const cannotCenterArrow = middlewareData.arrow?.centerOffset !== 0;\n const isHidden = hideWhenDetached && middlewareData.hide?.referenceHidden;\n\n const stepContextValue = React.useMemo(\n () => ({\n arrowX,\n arrowY,\n onArrowChange: setArrow,\n onFooterChange: setFooter,\n placedAlign,\n placedSide,\n shouldHideArrow: cannotCenterArrow,\n }),\n [arrowX, arrowY, placedSide, placedAlign, cannotCenterArrow],\n );\n\n React.useEffect(() => {\n if (open && targetElement && isCurrentStep) {\n updateMask(store, targetElement, context.spotlightPadding);\n\n let rafId: number | null = null;\n\n function onResize() {\n if (targetElement) {\n updateMask(store, targetElement, context.spotlightPadding);\n }\n }\n\n function onScroll() {\n if (rafId !== null) return;\n rafId = requestAnimationFrame(() => {\n if (targetElement) {\n updateMask(store, targetElement, context.spotlightPadding);\n }\n rafId = null;\n });\n }\n\n window.addEventListener('resize', onResize);\n window.addEventListener('scroll', onScroll, { passive: true });\n return () => {\n window.removeEventListener('resize', onResize);\n window.removeEventListener('scroll', onScroll);\n if (rafId !== null) {\n cancelAnimationFrame(rafId);\n }\n };\n }\n }, [open, targetElement, isCurrentStep, store, context.spotlightPadding]);\n\n React.useEffect(() => {\n if (!open || !isCurrentStep) return;\n\n const stepElement = stepRef.current;\n if (!stepElement) return;\n\n const ownerDocument = stepElement.ownerDocument;\n\n function onPointerDown(event: PointerEvent) {\n if (event.target && !isPointerInsideReactTreeRef.current) {\n const pointerDownOutsideEvent = new CustomEvent(POINTER_DOWN_OUTSIDE, {\n ...EVENT_OPTIONS,\n detail: { originalEvent: event },\n });\n\n context.onPointerDownOutside?.(pointerDownOutsideEvent);\n\n const interactOutsideEvent = new CustomEvent(INTERACT_OUTSIDE, {\n ...EVENT_OPTIONS,\n detail: { originalEvent: event },\n });\n context.onInteractOutside?.(interactOutsideEvent);\n\n if (\n !pointerDownOutsideEvent.defaultPrevented &&\n !interactOutsideEvent.defaultPrevented &&\n context.dismissible\n ) {\n store.setState('open', false);\n }\n }\n\n isPointerInsideReactTreeRef.current = false;\n }\n\n const timerId = window.setTimeout(() => {\n ownerDocument.addEventListener('pointerdown', onPointerDown);\n }, 0);\n\n return () => {\n window.clearTimeout(timerId);\n ownerDocument.removeEventListener('pointerdown', onPointerDown);\n };\n }, [open, isCurrentStep, store, context]);\n\n React.useEffect(() => {\n if (!open || !isCurrentStep) return;\n\n const stepElement = stepRef.current;\n if (!stepElement) return;\n\n const ownerDocument = stepElement.ownerDocument;\n\n function onFocusIn(event: FocusEvent) {\n const target = event.target as HTMLElement;\n\n const isFocusInStep = stepElement?.contains(target);\n const isFocusInTarget = targetElement?.contains(target);\n\n if (\n event.target &&\n !isFocusInsideReactTreeRef.current &&\n !isFocusInStep &&\n !isFocusInTarget\n ) {\n const interactOutsideEvent = new CustomEvent(INTERACT_OUTSIDE, {\n ...EVENT_OPTIONS,\n detail: { originalEvent: event },\n });\n\n context.onInteractOutside?.(interactOutsideEvent);\n\n if (!interactOutsideEvent.defaultPrevented && context.dismissible) {\n store.setState('open', false);\n }\n }\n }\n\n ownerDocument.addEventListener('focusin', onFocusIn);\n\n return () => {\n ownerDocument.removeEventListener('focusin', onFocusIn);\n };\n }, [open, isCurrentStep, store, context, targetElement]);\n\n const onPointerDownCapture = React.useCallback(\n (event: React.PointerEvent) => {\n onPointerDownCaptureProp?.(event);\n isPointerInsideReactTreeRef.current = true;\n },\n [onPointerDownCaptureProp],\n );\n\n const onFocusCapture = React.useCallback(\n (event: React.FocusEvent) => {\n onFocusCaptureProp?.(event);\n isFocusInsideReactTreeRef.current = true;\n },\n [onFocusCaptureProp],\n );\n\n const onBlurCapture = React.useCallback(\n (event: React.FocusEvent) => {\n onBlurCaptureProp?.(event);\n isFocusInsideReactTreeRef.current = false;\n },\n [onBlurCaptureProp],\n );\n\n React.useEffect(() => {\n if (!open || !isCurrentStep || !targetElement) return;\n\n function onTargetPointerDownCapture() {\n isPointerInsideReactTreeRef.current = true;\n }\n\n function onTargetFocusCapture() {\n isFocusInsideReactTreeRef.current = true;\n }\n\n function onTargetBlurCapture() {\n isFocusInsideReactTreeRef.current = false;\n }\n\n targetElement.addEventListener('pointerdown', onTargetPointerDownCapture, true);\n targetElement.addEventListener('focus', onTargetFocusCapture, true);\n targetElement.addEventListener('blur', onTargetBlurCapture, true);\n\n return () => {\n targetElement.removeEventListener('pointerdown', onTargetPointerDownCapture, true);\n targetElement.removeEventListener('focus', onTargetFocusCapture, true);\n targetElement.removeEventListener('blur', onTargetBlurCapture, true);\n };\n }, [open, isCurrentStep, targetElement]);\n\n useFocusGuards();\n useFocusTrap(\n stepRef,\n open && isCurrentStep,\n open,\n context.onOpenAutoFocus,\n context.onCloseAutoFocus,\n );\n\n if (!open || !stepData || (!targetElement && !forceMount) || !isCurrentStep) {\n return null;\n }\n\n const StepPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n \n {children}\n {!footer && (\n \n {context.stepFooter}\n \n )}\n \n \n );\n}\n\ninterface TourSpotlightProps extends DivProps {\n forceMount?: boolean;\n}\n\nfunction TourSpotlight(props: TourSpotlightProps) {\n const { asChild, className, style, forceMount = false, ...backdropProps } = props;\n\n const open = useStore((state) => state.open);\n const maskPath = useStore((state) => state.maskPath);\n\n if (!open && !forceMount) return null;\n\n const SpotlightPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n );\n}\n\ninterface TourSpotlightRingProps extends DivProps {\n forceMount?: boolean;\n}\n\nfunction TourSpotlightRing(props: TourSpotlightRingProps) {\n const { asChild, className, style, forceMount = false, ...ringProps } = props;\n\n const open = useStore((state) => state.open);\n const spotlightRect = useStore((state) => state.spotlightRect);\n\n if (!open && !forceMount) return null;\n if (!spotlightRect) return null;\n\n const RingPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n );\n}\n\ninterface TourPortalProps {\n children?: React.ReactNode;\n container?: HTMLElement | null;\n}\n\nfunction TourPortal(props: TourPortalProps) {\n const { children, container } = props;\n\n const portalContext = usePortalContext(PORTAL_NAME);\n\n const [mounted, setMounted] = React.useState(false);\n\n useIsomorphicLayoutEffect(() => {\n setMounted(true);\n\n const node = container ?? document.body;\n\n portalContext?.onPortalChange(node);\n return () => {\n portalContext?.onPortalChange(null);\n };\n }, [container, portalContext]);\n\n if (!mounted) return null;\n\n const portalContainer = container ?? portalContext?.portal ?? document.body;\n\n return ReactDOM.createPortal(children, portalContainer);\n}\n\ninterface TourArrowProps extends React.ComponentProps<'svg'> {\n width?: number;\n height?: number;\n asChild?: boolean;\n}\n\nfunction TourArrow(props: TourArrowProps) {\n const { width = 10, height = 5, className, children, asChild, ...arrowProps } = props;\n\n const stepContext = useStepContext(ARROW_NAME);\n const baseSide = OPPOSITE_SIDE[stepContext.placedSide];\n\n return (\n \n \n {asChild ? children : }\n \n \n );\n}\n\nfunction TourHeader(props: DivProps) {\n const { asChild, className, ...headerProps } = props;\n\n const context = useTourContext(HEADER_NAME);\n\n const HeaderPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n );\n}\n\nfunction TourTitle(props: DivProps) {\n const { asChild, className, ...titleProps } = props;\n\n const context = useTourContext(TITLE_NAME);\n\n const TitlePrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n );\n}\n\nfunction TourDescription(props: DivProps) {\n const { asChild, className, ...descriptionProps } = props;\n\n const context = useTourContext(DESCRIPTION_NAME);\n\n const DescriptionPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n );\n}\n\ninterface TourCloseProps extends React.ComponentProps<'button'> {\n asChild?: boolean;\n}\n\nfunction TourClose(props: TourCloseProps) {\n const { asChild, className, onClick: onClickProp, ...closeButtonProps } = props;\n\n const store = useStoreContext(CLOSE_NAME);\n\n const onClick = React.useCallback(\n (event: React.MouseEvent) => {\n onClickProp?.(event);\n if (event.defaultPrevented) return;\n\n store.setState('open', false);\n },\n [store, onClickProp],\n );\n\n const ClosePrimitive = asChild ? SlotPrimitive.Slot : 'button';\n\n return (\n \n \n \n );\n}\n\nfunction TourPrev(props: React.ComponentProps) {\n const { children, onClick: onClickProp, ...prevButtonProps } = props;\n\n const store = useStoreContext(PREV_NAME);\n const value = useStore((state) => state.value);\n\n const onClick = React.useCallback(\n (event: React.MouseEvent) => {\n onClickProp?.(event);\n if (event.defaultPrevented) return;\n\n if (value > 0) {\n store.setState('value', value - 1);\n }\n },\n [value, store, onClickProp],\n );\n\n return (\n \n {children ?? (\n <>\n \n Previous\n \n )}\n \n );\n}\n\nfunction TourNext(props: React.ComponentProps) {\n const { children, onClick: onClickProp, ...nextButtonProps } = props;\n const store = useStoreContext(NEXT_NAME);\n const value = useStore((state) => state.value);\n const steps = useStore((state) => state.steps);\n\n const isLastStep = value === steps.length - 1;\n\n const onClick = React.useCallback(\n (event: React.MouseEvent) => {\n onClickProp?.(event);\n if (event.defaultPrevented) return;\n\n store.setState('value', value + 1);\n },\n [value, store, onClickProp],\n );\n\n return (\n \n {children ?? (\n <>\n {isLastStep ? 'Finish' : 'Next'}\n {!isLastStep && }\n \n )}\n \n );\n}\n\nfunction TourSkip(props: React.ComponentProps) {\n const { children, onClick: onClickProp, ...skipButtonProps } = props;\n\n const store = useStoreContext(SKIP_NAME);\n\n const onClick = React.useCallback(\n (event: React.MouseEvent) => {\n onClickProp?.(event);\n if (event.defaultPrevented) return;\n\n store.setState('open', false);\n },\n [store, onClickProp],\n );\n\n return (\n \n {children ?? 'Skip'}\n \n );\n}\n\ninterface TourStepCounterProps extends DivProps {\n format?: (current: number, total: number) => string;\n}\n\nfunction TourStepCounter(props: TourStepCounterProps) {\n const {\n format = (current, total) => `${current} / ${total}`,\n asChild,\n className,\n children,\n ...stepCounterProps\n } = props;\n\n const value = useStore((state) => state.value);\n const steps = useStore((state) => state.steps);\n\n const StepCounterPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n {children ?? format(value + 1, steps.length)}\n \n );\n}\n\nfunction TourFooter(props: DivProps) {\n const { asChild, className, ref, ...footerProps } = props;\n\n const stepContext = useStepContext(FOOTER_NAME);\n const hasDefaultFooter = React.useContext(DefaultFooterContext);\n const context = useTourContext(FOOTER_NAME);\n\n const composedRef = useComposedRefs(\n ref,\n hasDefaultFooter ? undefined : stepContext.onFooterChange,\n );\n\n const FooterPrimitive = asChild ? SlotPrimitive.Slot : 'div';\n\n return (\n \n );\n}\n\nexport {\n Tour,\n TourArrow,\n TourClose,\n TourDescription,\n TourFooter,\n TourHeader,\n TourNext,\n TourPortal,\n TourPrev,\n type TourProps,\n TourSkip,\n TourSpotlight,\n TourSpotlightRing,\n TourStep,\n TourStepCounter,\n TourTitle,\n};\n", + "type": "registry:component", + "target": "@components/tour.tsx" + } + ], + "type": "registry:component" +} \ No newline at end of file diff --git a/docs/public/r/use-as-ref.json b/docs/public/r/use-as-ref.json new file mode 100644 index 0000000..1bd1ce9 --- /dev/null +++ b/docs/public/r/use-as-ref.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "use-as-ref", + "title": "useAsRef", + "description": "Keeps a mutable ref synchronized with the latest value using layout effect.", + "registryDependencies": [ + "@kombase/use-isomorphic-layout-effect" + ], + "files": [ + { + "path": "registry/hooks/use-as-ref.ts", + "content": "import * as React from 'react';\n\nimport { useIsomorphicLayoutEffect } from '@/hooks/use-isomorphic-layout-effect';\n\nfunction useAsRef(props: T) {\n const ref = React.useRef(props);\n\n useIsomorphicLayoutEffect(() => {\n ref.current = props;\n });\n\n return ref;\n}\n\nexport { useAsRef };\n", + "type": "registry:hook", + "target": "@hooks/use-as-ref.ts" + } + ], + "type": "registry:hook" +} \ No newline at end of file diff --git a/docs/public/r/use-callback-ref.json b/docs/public/r/use-callback-ref.json new file mode 100644 index 0000000..975b5eb --- /dev/null +++ b/docs/public/r/use-callback-ref.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "use-callback-ref", + "title": "useCallbackRef", + "description": "Creates a stable callback ref that always calls the latest function.", + "files": [ + { + "path": "registry/hooks/use-callback-ref.ts", + "content": "import * as React from 'react';\n\n/**\n * @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-callback-ref/src/useCallbackRef.tsx\n */\n\n/**\n * A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a\n * prop or avoid re-executing effects when passed as a dependency\n */\nfunction useCallbackRef unknown>(callback: T | undefined): T {\n const callbackRef = React.useRef(callback);\n\n React.useEffect(() => {\n callbackRef.current = callback;\n });\n\n // https://github.com/facebook/react/issues/19240\n return React.useMemo(() => ((...args) => callbackRef.current?.(...args)) as T, []);\n}\n\nexport { useCallbackRef };\n", + "type": "registry:hook", + "target": "@hooks/use-callback-ref.ts" + } + ], + "type": "registry:hook" +} \ No newline at end of file diff --git a/docs/public/r/use-data-table.json b/docs/public/r/use-data-table.json new file mode 100644 index 0000000..ff4a4e1 --- /dev/null +++ b/docs/public/r/use-data-table.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "use-data-table", + "title": "useDataTable", + "description": "Hook for managing data table state, filtering, sorting, and pagination with TanStack Table.", + "dependencies": [ + "@tanstack/react-table" + ], + "registryDependencies": [ + "@kombase/data-table-config", + "@kombase/data-table-types", + "@kombase/lib-data-table" + ], + "files": [ + { + "path": "registry/hooks/use-data-table.ts", + "content": "import {\n type ColumnFiltersState,\n getCoreRowModel,\n getFacetedMinMaxValues,\n getFacetedRowModel,\n getFacetedUniqueValues,\n getFilteredRowModel,\n getPaginationRowModel,\n getSortedRowModel,\n type PaginationState,\n type RowSelectionState,\n type SortingState,\n type TableOptions,\n type TableState,\n type Updater,\n useReactTable,\n type VisibilityState,\n} from '@tanstack/react-table';\nimport dayjs from 'dayjs';\nimport * as React from 'react';\nimport type {\n ExtendedColumnFilter,\n ExtendedColumnSort,\n JoinOperator,\n} from '@/components/data-table/types';\nimport { getValidFilters } from '@/lib/data-table';\n\nconst DEBOUNCE_MS = 300;\n\ninterface UseDataTableProps\n extends Omit<\n TableOptions,\n | 'state'\n | 'pageCount'\n | 'getCoreRowModel'\n | 'manualFiltering'\n | 'manualPagination'\n | 'manualSorting'\n >,\n Required, 'pageCount'>> {\n initialState?: Omit, 'sorting'> & {\n sorting?: ExtendedColumnSort[];\n };\n debounceMs?: number;\n enableAdvancedFilter?: boolean;\n isAdvanceFilter?: boolean;\n page?: number;\n perPage?: number;\n onPageChange?: (page: number) => void;\n onPerPageChange?: (perPage: number) => void;\n manualFiltering?: boolean;\n filters?: ExtendedColumnFilter[];\n setFilters?: React.Dispatch[]>>;\n joinOperator?: JoinOperator;\n setJoinOperator?: React.Dispatch>;\n filterValues?: Record;\n onFilterValuesChange?: (updates: Record) => void;\n}\n\nexport function useDataTable(props: UseDataTableProps) {\n const {\n columns,\n pageCount = -1,\n initialState,\n debounceMs = DEBOUNCE_MS,\n enableAdvancedFilter = false,\n isAdvanceFilter = false,\n page: controlledPage,\n perPage: controlledPerPage,\n onPageChange,\n onPerPageChange,\n manualFiltering: controlledManualFiltering,\n filters: controlledFilters,\n setFilters: controlledSetFilters,\n joinOperator: controlledJoinOperator,\n setJoinOperator: controlledSetJoinOperator,\n ...tableProps\n } = props;\n\n const isAdvanced = enableAdvancedFilter || isAdvanceFilter;\n\n const [localFilters, setLocalFilters] = React.useState[]>(\n initialState?.columnFilters\n ? initialState.columnFilters.map((cf) => ({\n filterId: Math.random().toString(36).substring(7),\n id: cf.id as Extract,\n operator: 'eq',\n value: cf.value as string | string[],\n variant: 'text',\n }))\n : [],\n );\n const [localJoinOperator, setLocalJoinOperator] = React.useState('and');\n\n const filters = controlledFilters !== undefined ? controlledFilters : localFilters;\n const setFilters = controlledSetFilters !== undefined ? controlledSetFilters : setLocalFilters;\n\n const joinOperator =\n controlledJoinOperator !== undefined ? controlledJoinOperator : localJoinOperator;\n const setJoinOperator =\n controlledSetJoinOperator !== undefined ? controlledSetJoinOperator : setLocalJoinOperator;\n\n const [rowSelection, setRowSelection] = React.useState(\n initialState?.rowSelection ?? {},\n );\n const [columnVisibility, setColumnVisibility] = React.useState(\n initialState?.columnVisibility ?? {},\n );\n\n const [internalPage, setInternalPage] = React.useState(\n initialState?.pagination?.pageIndex !== undefined ? initialState.pagination.pageIndex + 1 : 1,\n );\n const [internalPerPage, setInternalPerPage] = React.useState(\n initialState?.pagination?.pageSize ?? 10,\n );\n\n const page = controlledPage ?? internalPage;\n const perPage = controlledPerPage ?? internalPerPage;\n\n const pagination: PaginationState = React.useMemo(() => {\n return {\n pageIndex: page - 1, // zero-based index -> one-based index\n pageSize: perPage,\n };\n }, [page, perPage]);\n\n const onPaginationChange = React.useCallback(\n (updaterOrValue: Updater) => {\n if (typeof updaterOrValue === 'function') {\n const newPagination = updaterOrValue(pagination);\n const newPage = newPagination.pageIndex + 1;\n\n if (controlledPage === undefined) setInternalPage(newPage);\n if (controlledPerPage === undefined) setInternalPerPage(newPagination.pageSize);\n\n onPageChange?.(newPage);\n onPerPageChange?.(newPagination.pageSize);\n } else {\n const newPage = updaterOrValue.pageIndex + 1;\n if (controlledPage === undefined) setInternalPage(newPage);\n if (controlledPerPage === undefined) setInternalPerPage(updaterOrValue.pageSize);\n\n onPageChange?.(newPage);\n onPerPageChange?.(updaterOrValue.pageSize);\n }\n },\n [pagination, controlledPage, controlledPerPage, onPageChange, onPerPageChange],\n );\n\n const [sorting, setSorting] = React.useState[]>(\n initialState?.sorting ?? [],\n );\n\n const onSortingChange = React.useCallback(\n (updaterOrValue: Updater) => {\n if (typeof updaterOrValue === 'function') {\n const newSorting = updaterOrValue(sorting);\n setSorting(newSorting as ExtendedColumnSort[]);\n } else {\n setSorting(updaterOrValue as ExtendedColumnSort[]);\n }\n },\n [sorting],\n );\n\n const [localColumnFilters, setLocalColumnFilters] = React.useState(\n initialState?.columnFilters ?? [],\n );\n\n const isControlled = props.filterValues !== undefined;\n\n const columnFilters = React.useMemo(() => {\n if (isControlled) {\n return Object.entries(props.filterValues || {})\n .map(([id, value]) => ({ id, value }))\n .filter(\n (f) =>\n f.value !== null &&\n f.value !== undefined &&\n f.value !== '' &&\n (Array.isArray(f.value) ? f.value.length > 0 : true),\n ) as ColumnFiltersState;\n }\n return localColumnFilters;\n }, [isControlled, props.filterValues, localColumnFilters]);\n\n const onColumnFiltersChange = React.useCallback(\n (updaterOrValue: Updater) => {\n if (isAdvanced) return;\n\n const next =\n typeof updaterOrValue === 'function' ? updaterOrValue(columnFilters) : updaterOrValue;\n\n if (isControlled && props.onFilterValuesChange) {\n const updates: Record = {};\n next.forEach((f) => {\n updates[f.id] = f.value;\n });\n\n columnFilters.forEach((prevF) => {\n if (!next.some((f) => f.id === prevF.id)) {\n updates[prevF.id] = null;\n }\n });\n\n props.onFilterValuesChange(updates);\n } else {\n setLocalColumnFilters(next);\n }\n },\n [columnFilters, isControlled, props.onFilterValuesChange, isAdvanced],\n );\n\n const table = useReactTable({\n ...tableProps,\n columns,\n defaultColumn: {\n ...tableProps.defaultColumn,\n enableColumnFilter: false,\n },\n enableRowSelection: true,\n getCoreRowModel: getCoreRowModel(),\n getFacetedMinMaxValues: getFacetedMinMaxValues(),\n getFacetedRowModel: getFacetedRowModel(),\n getFacetedUniqueValues: getFacetedUniqueValues(),\n getFilteredRowModel: getFilteredRowModel(),\n getPaginationRowModel: getPaginationRowModel(),\n getSortedRowModel: getSortedRowModel(),\n globalFilterFn: (row, _columnId, filterValue) => {\n if (filterValue && typeof filterValue === 'object' && 'filters' in filterValue) {\n return matchRow(row, filterValue.filters, filterValue.joinOperator || 'and');\n }\n return true;\n },\n initialState,\n manualFiltering:\n controlledManualFiltering !== undefined ? controlledManualFiltering : !isAdvanced,\n manualPagination: true,\n manualSorting: true,\n meta: {\n ...tableProps.meta,\n debounceMs,\n filters,\n isAdvanceFilter: isAdvanced,\n joinOperator,\n setFilters,\n setJoinOperator,\n },\n onColumnFiltersChange,\n onColumnVisibilityChange: setColumnVisibility,\n onPaginationChange,\n onRowSelectionChange: setRowSelection,\n onSortingChange,\n pageCount,\n state: {\n columnFilters,\n columnVisibility,\n globalFilter: isAdvanced ? { filters, joinOperator } : undefined,\n pagination,\n rowSelection,\n sorting,\n },\n });\n\n return {\n table,\n };\n}\n\nfunction matchRow(\n row: any,\n filters: ExtendedColumnFilter[],\n joinOperator: JoinOperator,\n): boolean {\n const validFilters = getValidFilters(filters);\n if (validFilters.length === 0) return true;\n\n const results = validFilters.map((filter) => {\n const cellValue = row.getValue(filter.id);\n const filterValue = filter.value;\n const operator = filter.operator;\n\n if (operator === 'isEmpty') {\n return cellValue === null || cellValue === undefined || cellValue === '';\n }\n if (operator === 'isNotEmpty') {\n return cellValue !== null && cellValue !== undefined && cellValue !== '';\n }\n\n if (filter.variant === 'boolean') {\n return String(cellValue) === String(filterValue);\n }\n\n if (filter.variant === 'date' || filter.variant === 'dateRange') {\n if (!cellValue) return false;\n const cellDate = dayjs(cellValue);\n if (!cellDate.isValid()) return false;\n\n if (operator === 'isBetween') {\n if (!Array.isArray(filterValue) || filterValue.length < 2) return false;\n const start = dayjs(Number(filterValue[0]));\n const end = dayjs(Number(filterValue[1]));\n return cellDate.isAfter(start.startOf('day')) && cellDate.isBefore(end.endOf('day'));\n }\n\n const cmpDate = dayjs(Number(filterValue));\n if (!cmpDate.isValid()) return false;\n\n switch (operator) {\n case 'eq':\n return cellDate.isSame(cmpDate, 'day');\n case 'ne':\n return !cellDate.isSame(cmpDate, 'day');\n case 'lt':\n return cellDate.isBefore(cmpDate, 'day');\n case 'lte':\n return cellDate.isBefore(cmpDate, 'day') || cellDate.isSame(cmpDate, 'day');\n case 'gt':\n return cellDate.isAfter(cmpDate, 'day');\n case 'gte':\n return cellDate.isAfter(cmpDate, 'day') || cellDate.isSame(cmpDate, 'day');\n default:\n return false;\n }\n }\n\n if (filter.variant === 'select' || filter.variant === 'multiSelect') {\n const selected = Array.isArray(filterValue) ? filterValue : [filterValue].filter(Boolean);\n if (selected.length === 0) return true;\n const valStr = String(cellValue).toLowerCase();\n\n if (operator === 'notInArray') {\n return !selected.some((v) => String(v).toLowerCase() === valStr);\n }\n return selected.some((v) => String(v).toLowerCase() === valStr);\n }\n\n if (filter.variant === 'number' || filter.variant === 'range') {\n if (operator === 'isBetween') {\n if (!Array.isArray(filterValue) || filterValue.length < 2) return false;\n const val = Number(cellValue);\n return val >= Number(filterValue[0]) && val <= Number(filterValue[1]);\n }\n const val = Number(cellValue);\n const filterNum = Number(filterValue);\n switch (operator) {\n case 'eq':\n return val === filterNum;\n case 'ne':\n return val !== filterNum;\n case 'lt':\n return val < filterNum;\n case 'lte':\n return val <= filterNum;\n case 'gt':\n return val > filterNum;\n case 'gte':\n return val >= filterNum;\n default:\n return false;\n }\n }\n\n const strCellValue = String(cellValue).toLowerCase();\n const strFilterValue = String(filterValue).toLowerCase();\n\n switch (operator) {\n case 'iLike':\n return strCellValue.includes(strFilterValue);\n case 'notILike':\n return !strCellValue.includes(strFilterValue);\n case 'eq':\n return strCellValue === strFilterValue;\n case 'ne':\n return strCellValue !== strFilterValue;\n default:\n return false;\n }\n });\n\n if (joinOperator === 'or') {\n return results.some(Boolean);\n }\n return results.every(Boolean);\n}\n", + "type": "registry:hook", + "target": "@hooks/use-data-table.ts" + } + ], + "type": "registry:hook" +} \ No newline at end of file diff --git a/docs/public/r/use-debounced-callback.json b/docs/public/r/use-debounced-callback.json new file mode 100644 index 0000000..c35b045 --- /dev/null +++ b/docs/public/r/use-debounced-callback.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "use-debounced-callback", + "title": "useDebouncedCallback", + "description": "Returns a debounced version of a callback function with configurable delay.", + "registryDependencies": [ + "@kombase/use-callback-ref" + ], + "files": [ + { + "path": "registry/hooks/use-debounced-callback.ts", + "content": "import * as React from 'react';\n\nimport { useCallbackRef } from '@/hooks/use-callback-ref';\n\nexport function useDebouncedCallback unknown>(\n callback: T,\n delay: number,\n) {\n const handleCallback = useCallbackRef(callback);\n const debounceTimerRef = React.useRef(0);\n React.useEffect(() => () => window.clearTimeout(debounceTimerRef.current), []);\n\n const setValue = React.useCallback(\n (...args: Parameters) => {\n window.clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = window.setTimeout(() => handleCallback(...args), delay);\n },\n [handleCallback, delay],\n );\n\n return setValue;\n}\n", + "type": "registry:hook", + "target": "@hooks/use-debounced-callback.ts" + } + ], + "type": "registry:hook" +} \ No newline at end of file diff --git a/docs/public/r/use-isomorphic-layout-effect.json b/docs/public/r/use-isomorphic-layout-effect.json new file mode 100644 index 0000000..adaf06c --- /dev/null +++ b/docs/public/r/use-isomorphic-layout-effect.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "use-isomorphic-layout-effect", + "title": "useIsomorphicLayoutEffect", + "description": "SSR-safe wrapper around useLayoutEffect that falls back to useEffect on the server.", + "files": [ + { + "path": "registry/hooks/use-isomorphic-layout-effect.ts", + "content": "import * as React from 'react';\n\nconst useIsomorphicLayoutEffect =\n typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;\n\nexport { useIsomorphicLayoutEffect };\n", + "type": "registry:hook", + "target": "@hooks/use-isomorphic-layout-effect.ts" + } + ], + "type": "registry:hook" +} \ No newline at end of file diff --git a/docs/public/r/use-lazy-ref.json b/docs/public/r/use-lazy-ref.json new file mode 100644 index 0000000..f211c61 --- /dev/null +++ b/docs/public/r/use-lazy-ref.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "use-lazy-ref", + "title": "useLazyRef", + "description": "Creates a ref that is lazily initialized on first access.", + "files": [ + { + "path": "registry/hooks/use-lazy-ref.ts", + "content": "import * as React from 'react';\n\nfunction useLazyRef(fn: () => T) {\n const ref = React.useRef(null);\n\n if (ref.current === null) {\n ref.current = fn();\n }\n\n return ref as React.RefObject;\n}\n\nexport { useLazyRef };\n", + "type": "registry:hook", + "target": "@hooks/use-lazy-ref.ts" + } + ], + "type": "registry:hook" +} \ No newline at end of file diff --git a/docs/public/r/utils.json b/docs/public/r/utils.json new file mode 100644 index 0000000..41d7675 --- /dev/null +++ b/docs/public/r/utils.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "utils", + "title": "Utilities", + "description": "Core utility function (cn) for merging Tailwind CSS classes.", + "dependencies": [ + "clsx", + "tailwind-merge" + ], + "files": [ + { + "path": "registry/lib/utils.ts", + "content": "import { type ClassValue, clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n", + "type": "registry:lib", + "target": "@lib/utils.ts" + } + ], + "type": "registry:lib" +} \ No newline at end of file diff --git a/docs/public/r/visually-hidden-input.json b/docs/public/r/visually-hidden-input.json new file mode 100644 index 0000000..f56a946 --- /dev/null +++ b/docs/public/r/visually-hidden-input.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "visually-hidden-input", + "title": "Visually Hidden Input", + "description": "An invisible input for form integration with custom controls.", + "files": [ + { + "path": "registry/components/visually-hidden-input.tsx", + "content": "import * as React from 'react';\n\ntype InputValue = string[] | string;\n\ninterface VisuallyHiddenInputProps\n extends Omit, 'value' | 'checked' | 'onReset'> {\n value?: T;\n checked?: boolean;\n control: HTMLElement | null;\n bubbles?: boolean;\n}\n\nfunction VisuallyHiddenInput(props: VisuallyHiddenInputProps) {\n const { control, value, checked, bubbles = true, type = 'hidden', style, ...inputProps } = props;\n\n const isCheckInput = React.useMemo(\n () => type === 'checkbox' || type === 'radio' || type === 'switch',\n [type],\n );\n const inputRef = React.useRef(null);\n\n const prevValueRef = React.useRef<{\n value: T | boolean | undefined;\n previous: T | boolean | undefined;\n }>({\n previous: isCheckInput ? checked : value,\n value: isCheckInput ? checked : value,\n });\n\n const prevValue = React.useMemo(() => {\n const currentValue = isCheckInput ? checked : value;\n if (prevValueRef.current.value !== currentValue) {\n prevValueRef.current.previous = prevValueRef.current.value;\n prevValueRef.current.value = currentValue;\n }\n return prevValueRef.current.previous;\n }, [isCheckInput, value, checked]);\n\n const [controlSize, setControlSize] = React.useState<{\n width?: number;\n height?: number;\n }>({});\n\n React.useLayoutEffect(() => {\n if (!control) {\n setControlSize({});\n return;\n }\n\n setControlSize({\n height: control.offsetHeight,\n width: control.offsetWidth,\n });\n\n if (typeof window === 'undefined') return;\n\n const resizeObserver = new ResizeObserver((entries) => {\n if (!Array.isArray(entries) || !entries.length) return;\n\n const entry = entries[0];\n if (!entry) return;\n\n let width: number;\n let height: number;\n\n if ('borderBoxSize' in entry) {\n const borderSizeEntry = entry.borderBoxSize;\n const borderSize = Array.isArray(borderSizeEntry) ? borderSizeEntry[0] : borderSizeEntry;\n width = borderSize.inlineSize;\n height = borderSize.blockSize;\n } else {\n width = control.offsetWidth;\n height = control.offsetHeight;\n }\n\n setControlSize({ height, width });\n });\n\n resizeObserver.observe(control, { box: 'border-box' });\n return () => {\n resizeObserver.disconnect();\n };\n }, [control]);\n\n React.useEffect(() => {\n const input = inputRef.current;\n if (!input) return;\n\n const inputProto = window.HTMLInputElement.prototype;\n const propertyKey = isCheckInput ? 'checked' : 'value';\n const eventType = isCheckInput ? 'click' : 'input';\n const currentValue = isCheckInput ? checked : value;\n\n const serializedCurrentValue = isCheckInput\n ? checked\n : typeof value === 'object' && value !== null\n ? JSON.stringify(value)\n : value;\n\n const descriptor = Object.getOwnPropertyDescriptor(inputProto, propertyKey);\n\n const setter = descriptor?.set;\n\n if (prevValue !== currentValue && setter) {\n const event = new Event(eventType, { bubbles });\n setter.call(input, serializedCurrentValue);\n input.dispatchEvent(event);\n }\n }, [prevValue, value, checked, bubbles, isCheckInput]);\n\n const composedStyle = React.useMemo(() => {\n return {\n ...style,\n ...(controlSize.width !== undefined && controlSize.height !== undefined ? controlSize : {}),\n border: 0,\n clip: 'rect(0 0 0 0)',\n clipPath: 'inset(50%)',\n height: '1px',\n margin: '-1px',\n overflow: 'hidden',\n padding: 0,\n position: 'absolute',\n whiteSpace: 'nowrap',\n width: '1px',\n };\n }, [style, controlSize]);\n\n return (\n \n );\n}\n\nexport { VisuallyHiddenInput };\n", + "type": "registry:component", + "target": "@components/visually-hidden-input.tsx" + } + ], + "type": "registry:component" +} \ No newline at end of file diff --git a/docs/tsconfig.json b/docs/tsconfig.json index 6aa4a4d..1c71452 100644 --- a/docs/tsconfig.json +++ b/docs/tsconfig.json @@ -8,7 +8,33 @@ "noEmit": true, "paths": { "@/*": ["./app/*"], - "@/components/ui/*": ["../packages/src/components/ui/*"], + "@/components/action-bar": ["../registry/components/action-bar.tsx"], + "@/components/avatar-group": ["../registry/components/avatar-group.tsx"], + "@/components/confirm-dialog": ["../registry/components/confirm-dialog.tsx"], + "@/components/data-table/*": ["../registry/components/data-table/*"], + "@/components/form/*": ["../registry/form/*"], + "@/components/long-text": ["../registry/components/long-text.tsx"], + "@/components/password-input": ["../registry/components/password-input.tsx"], + "@/components/phone-input": ["../registry/components/phone-input.tsx"], + "@/components/rating": ["../registry/components/rating.tsx"], + "@/components/select-dropdown": ["../registry/components/select-dropdown.tsx"], + "@/components/stepper": ["../registry/components/stepper.tsx"], + "@/components/timeline": ["../registry/components/timeline.tsx"], + "@/components/tour": ["../registry/components/tour.tsx"], + "@/components/ui/*": ["../registry/ui/*"], + "@/components/visually-hidden-input": ["../registry/components/visually-hidden-input.tsx"], + "@/hooks/use-as-ref": ["../registry/hooks/use-as-ref.ts"], + "@/hooks/use-callback-ref": ["../registry/hooks/use-callback-ref.ts"], + "@/hooks/use-data-table": ["../registry/hooks/use-data-table.ts"], + "@/hooks/use-debounced-callback": ["../registry/hooks/use-debounced-callback.ts"], + "@/hooks/use-isomorphic-layout-effect": ["../registry/hooks/use-isomorphic-layout-effect.ts"], + "@/hooks/use-lazy-ref": ["../registry/hooks/use-lazy-ref.ts"], + "@/lib/component-refs": ["../registry/lib/component-refs.ts"], + "@/lib/data-table": ["../registry/lib/data-table.ts"], + "@/lib/date": ["../registry/lib/date.ts"], + "@/lib/filter-helper": ["../registry/lib/filter-helper.ts"], + "@/lib/pagination": ["../registry/lib/pagination.ts"], + "@/lib/utils": ["../registry/lib/utils.ts"], "collections/*": ["./.source/*"] }, "resolveJsonModule": true, diff --git a/package.json b/package.json index a865f77..162cfa2 100644 --- a/package.json +++ b/package.json @@ -3,18 +3,15 @@ "private": true, "scripts": { "prepare": "husky", - "dev:kombase": "pnpm -F kombase dev", - "build:kombase": "pnpm -F kombase build", "dev:docs": "pnpm -F docs dev", - "build": "pnpm -F docs build && pnpm -F kombase build", - "build:docs": "pnpm -F docs build", + "build": "pnpm build:registry && pnpm -F docs build", + "build:docs": "pnpm build:registry && pnpm -F docs build", + "build:registry": "npx shadcn@latest build -o docs/public/r", "list-packages": "pnpm m ls --json --depth -1", - "docs": "pnpm -F kombase docs", - "validate": "pnpm format && pnpm lint:check && pnpm build:kombase && pnpm build:docs", - "typecheck": "pnpm -F kombase typecheck", + "validate": "pnpm format && pnpm lint:check && pnpm build:docs", "changeset": "changeset", "version-packages": "changeset version", - "release": "pnpm build:kombase && changeset publish", + "release": "changeset publish", "format": "biome check --write", "format:check": "biome check", "lint:check": "biome lint .", @@ -25,8 +22,42 @@ "@changesets/cli": "^2.31.0", "@commitlint/cli": "^20.5.3", "@commitlint/config-conventional": "^20.5.3", + "@base-ui/react": "^1.4.1", + "radix-ui": "^1.6.0", + "@floating-ui/react-dom": "^2.1.8", + "@radix-ui/react-alert-dialog": "^1.1.16", + "@radix-ui/react-avatar": "^1.1.12", + "@radix-ui/react-checkbox": "^1.3.4", + "@radix-ui/react-collapsible": "^1.1.13", + "@radix-ui/react-dialog": "^1.1.16", + "@radix-ui/react-direction": "^1.1.2", + "@radix-ui/react-dropdown-menu": "^2.1.17", + "@radix-ui/react-form": "^0.1.9", + "@radix-ui/react-label": "^2.1.9", + "@radix-ui/react-popover": "^1.1.2", + "@radix-ui/react-radio-group": "^1.4.0", + "@radix-ui/react-select": "^2.3.0", + "@radix-ui/react-separator": "^1.1.9", + "@radix-ui/react-slider": "^1.4.0", + "@radix-ui/react-slot": "^1.2.5", + "@radix-ui/react-switch": "^1.3.0", + "@radix-ui/react-tabs": "^1.1.14", + "@radix-ui/react-tooltip": "^1.1.8", + "@tanstack/react-table": "^8.21.3", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "dayjs": "^1.11.20", "husky": "^9.1.7", "lint-staged": "^16.4.0", + "lucide-react": "^0.545.0", + "react-day-picker": "^9.14.0", + "react-hook-form": "^7.74.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "tailwind-merge": "^3.5.0", "typescript": "^5.4.5" }, "pnpm": { diff --git a/packages/.gitignore b/packages/.gitignore deleted file mode 100644 index de4d1f0..0000000 --- a/packages/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -dist -node_modules diff --git a/packages/CHANGELOG.md b/packages/CHANGELOG.md deleted file mode 100644 index 93d83d7..0000000 --- a/packages/CHANGELOG.md +++ /dev/null @@ -1,130 +0,0 @@ -# Kombase - -## 2.0.1 - -### Patch Changes - -- style: update popover component to use semantic color tokens - -## 2.0.0 - -### Major Changes - -- A summary of features, optimizations, and refactorings implemented across the codebase. - - *** - - Introduced a highly modular, headless/styled file upload system with React/Radix and React Hook Form support. - - **Interactive Area**: Support for drag-and-drop, click-to-trigger, paste events, and keyboard interactions. - - **File Validation**: Integrated size validation, accepted MIME-type check, maximum files threshold, and custom validation callbacks (`onFileValidate`). - - **Upload Progress & Cache**: Asynchronous upload handler (`onUpload`) with progress reporting per-file, success/error status management, and memory-safe URL object cleanup. - - **Form Hook Integration**: Created `FormUpload` component wrapping `FileUpload` with full `react-hook-form` controller compatibility. - - **Demos & Docs**: - - Added complete markdown guides (`file-upload.mdx`, `form-upload.mdx`). - - Created 5 copy-pasteable demos under `docs/app/examples/` (Chat Input, Circular Progress, Direct Upload, Fill Progress, Form Integration). - - *** - - Major refactoring of the filtering hook and calendar inputs to enable controlled filters and custom backend serializations. - - **Filter Parameter Serializer (`filter-helper.ts`)**: - - Implemented `resolveFiltersToFlatParams` supporting multiple serialization styles for backends: `flat`, `suffix`, `django`, `nested`, `prefix`, and `postgrest` (e.g. `status=eq.active`). - - Implemented `mapDateFilterToParams` to serialize single date operators (`eq`, `lt`, `lte`, `gt`, `gte`), ranges (`isBetween`), and relative today dates into clean parameters. - - **Controlled Filter State (`use-data-table.ts`)**: - - Refactored `useDataTable` to support fully controlled external filters via `filterValues` and `onFilterValuesChange`, eliminating synchronization issues. - - **Date Preset Support**: - - Date filter calendars in both standard and advanced filters now dynamically render sidebar presets configured via column metadata. - - **Calendar UI Cleanup (`calendar.tsx`)**: - - Replaced Tailwind grid widths with flexible custom property spacing (`--cell-size` and `--cell-radius`). - - Added RTL flip support for navigation chevrons and refactored modifiers styling. - - **Toolbar Additions (`data-table-toolbar.tsx`)**: - - Added support for `boolean` filter variant. - - Added layout alignment property (`align: 'start' | 'center' | 'end'`). - - Prevented manual "Reset" button showing when advanced filters side sheet is active. - - **DebouncedInput Optimization**: - - Wrapped `onChange` prop in a mutable ref to prevent resetting timer intervals when parent callbacks are re-created. - - **Translations**: - - Added Indonesian translation `selected: "terpilih"` to the core translations bundle. - -## 1.3.4 - -### Patch Changes - -- - Fixed a TypeScript type mismatch error in `DataTableRangeFilter` under `"noUncheckedIndexedAccess": true` by typing the values array as a fixed `[string, string]` tuple. - - Resolved a bug in `DataTableToolbar` where children were duplicated on both the left and right sides. Added a new `actions` prop for custom right-side buttons/actions and updated the layout/docs examples. - - Simplified manual filtering assignment in `useDataTable` hook to resolve a Biome lint warning. - - Implement advanced data table filtering with custom operators and debounced input components - -## 1.3.3 - -### Patch Changes - -- fix: add alignment and custom class support to DataTable and register improve skill - -## 1.3.2 - -### Patch Changes - -- feat(data-table): enhance date filtering with presets and improve loading states - -## 1.3.1 - -### Patch Changes - -- add infinite scroll support, sticky headers, and customizable empty states to DataTable component. - -## 1.3.0 - -### Minor Changes - -- Refactored component exports, package dependencies, and added Tailwind CSS v4 support: - - Added new `FormPick` and `FormRadio` components (replacing `FormRadioGroup`). - - Removed deprecated `FormSwitch`, `FormRadioGroup`, and `Status` components. - - Migrated from monolithic `radix-ui` dependency to granular `@radix-ui/*` package dependencies. - - Configured Tailwind CSS v4 source scanning support via `@import "kombase/styles"`. - - Updated peer dependencies for React 18/19, `@tanstack/react-table`, and `react-day-picker`. - -## 1.2.2 - -### Patch Changes - -- fix: resolve package types resolution for ESM/CJS consumers under node16/nodenext module resolution - -## 1.2.1 - -### Patch Changes - -- - **fix(datepicker)**: Fixed selected date reactivity in `FormDatePicker` by correctly passing binding props (`selected`, `onSelect`) to the form control. Updated grid styling to support `react-day-picker` v10. - - **feat(date-utility)**: Replaced `date-fns` with `dayjs` for lighter and more consistent date handling across components. - - **feat(build)**: Shifted `react-hook-form`, `react-day-picker`, and `@tanstack/react-table` to `peerDependencies` to avoid duplicate bundling in consuming projects. - - **feat(build)**: Enhanced compilation output by enabling minification, code-splitting, and tree-shaking in `tsup` configuration. - - **feat(demo)**: Integrated `goey-toast` notifications upon successful form submission across demo forms (inputs, radios, selects, switches, and textareas). - - **docs**: Refined and clarified introductory content and installation instructions. - -## 1.2.0 - -### Minor Changes - -- **Tour Component**: Added a highly interactive and flexible step-by-step onboarding tour component (`Tour`, `TourStep`, `TourSpotlight`, `TourClose`, `TourNext`, etc.) built on top of `@floating-ui/react-dom`. -- **Avatar Group Component**: Added a new component to display collections of avatars with support for truncation, overflow counters, RTL layouts, and custom icons. -- **Rating Component**: Added a customizable star-rating component featuring controlled state management, custom themes, and seamless form integration. -- **Timeline Component**: Added a chronological timeline component supporting alternate positioning, horizontal layouts, custom dot indicators, and RTL options. -- **Stepper Component**: Added a progress steps component for multi-step flows, complete with vertical layout options and form validation examples. -- **Status Component**: Added a status badge/indicator component for clear visual feedback. -- **Goey Toaster Component**: Added an animated, liquid-like gooey toast notification component integrated directly into the core layout. -- **Form Components Directory Restructuring**: Moved all form-related MDX documentation files (`form-input`, `form-textarea`, `form-switch`, etc.) into a dedicated `content/form/` subdirectory. -- **Hooks Relocation**: Moved the `useLazyRef` hook to `packages/src/components/hooks/use-lazy-ref.ts` for a cleaner package architecture and optimized reference management. -- **Biome Linter**: Updated the Biome schema version and optimized formatting configurations. -- **Shared Types**: Added global shared type definitions (e.g., `EmptyProps`) in `docs/app/types.ts`. -- **Dependency Upgrades**: Cleaned up and updated package dependencies, and regenerated lockfile configurations. - -## 1.1.0 - -### Minor Changes - -- 25b80a3: feat: introduce new FormDatePicker, LongText, PhoneInput, ConfirmDialog, FormSearchSelect and other UI components, alongside massive type safety improvements for forms - -## 1.1.0 - -### Minor Changes - -- Initial release of the Kombase UI library. This version includes a core set of React components built with Radix UI, TanStack Table, and Tailwind CSS v4. It also features a centralized documentation system with automated changelog generation and standardized development workflows using Changesets. diff --git a/packages/README.md b/packages/README.md deleted file mode 100644 index 087a77c..0000000 --- a/packages/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# 🚀 Kombase UI - -Welcome to **Kombase UI**! 👋 This is the core UI component library of the Kombase ecosystem. Built with React, Tailwind CSS, and a component-driven design philosophy, it's crafted with love, sweat, and sleepless nights to make your developer life a little more peaceful and drama-free. 🧘‍♂️✨ - -## 📦 Installation - -Ready to make your UI shine? Install the package via your favorite package manager: - -```bash -npm install kombase -# or -pnpm add kombase -# or -yarn add kombase -``` - -## 📖 Documentation - -We don't just build components; we also write amazing docs for them! 💅 -For comprehensive documentation, interactive component previews, and usage guidelines, head over to our magical documentation site: - -👉 **[kombase](https://kombase.komerce.id)** - -## 📝 Contributing - -Got a brilliant idea or found a bug? We'd love your help! Since Kombase is part of an awesome monorepo, we maintain high discipline with **Husky**, **Commitlint**, and **Changesets**. - -Make sure your commit messages follow the _Conventional Commits_ standard, or our automated robots will politely reject them! 🤖 - -## ⚖️ License - -MIT License - ---- - -_Built with ❤️, lots of water, and prayers by the Kombase team._ diff --git a/packages/components.json b/packages/components.json deleted file mode 100644 index aace982..0000000 --- a/packages/components.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "aliases": { - "components": "@/components", - "hooks": "@/hooks", - "lib": "@/lib", - "ui": "@/components/ui", - "utils": "@/lib/utils" - }, - "iconLibrary": "lucide", - "menuAccent": "subtle", - "menuColor": "default", - "registries": { - "@diceui": "https://diceui.com/r/{style}/{name}.json" - }, - "rsc": false, - "rtl": false, - "style": "new-york", - "tailwind": { - "baseColor": "slate", - "config": "", - "css": "src/index.css", - "cssVariables": true, - "prefix": "" - }, - "tsx": true -} diff --git a/packages/kombase.css b/packages/kombase.css deleted file mode 100644 index a1290ef..0000000 --- a/packages/kombase.css +++ /dev/null @@ -1,15 +0,0 @@ -/** - * kombase - Tailwind CSS v4 integration - * - * Import this file once in your project's main CSS to enable - * Tailwind utility class generation for all kombase components. - * - * @example - * In your global CSS file: - * @import "kombase/styles"; - * - * This @source directive tells Tailwind CSS v4 to scan the - * kombase compiled bundle for class names, so all component - * styles are generated using your project's own Tailwind theme. - */ -@source "./dist"; diff --git a/packages/package.json b/packages/package.json deleted file mode 100644 index 7b5de2b..0000000 --- a/packages/package.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "name": "kombase", - "version": "2.0.1", - "description": "A lightweight, highly customizable UI component library with out-of-the-box form validation and data table integrations.", - "main": "./dist/index.cjs", - "type": "module", - "module": "./dist/index.js", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "import": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - }, - "require": { - "types": "./dist/index.d.cts", - "default": "./dist/index.cjs" - } - }, - "./styles": "./kombase.css" - }, - "files": [ - "dist", - "kombase.css" - ], - "scripts": { - "build": "tsup", - "dev": "tsup --watch", - "typecheck": "tsc --noEmit" - }, - "publishConfig": { - "access": "public" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/fykom/kombase.git", - "directory": "packages" - }, - "homepage": "https://github.com/fykom/kombase#readme", - "bugs": { - "url": "https://github.com/fykom/kombase/issues" - }, - "keywords": [ - "react", - "ui", - "components", - "tailwind", - "shadcn", - "form", - "design-system" - ], - "license": "MIT", - "sideEffects": false, - "peerDependencies": { - "@base-ui/react": "^1.4.1", - "@radix-ui/react-popover": "^1.x", - "@radix-ui/react-tooltip": "^1.x", - "@tanstack/react-table": "^8.21.3", - "react": "^18 || ^19", - "react-day-picker": "^10.0.1", - "react-dom": "^18 || ^19", - "react-hook-form": "^7.53.0", - "tailwindcss": "^4.0.0" - }, - "dependencies": { - "@floating-ui/react-dom": "^2.1.8", - "@radix-ui/react-alert-dialog": "^1.1.16", - "@radix-ui/react-avatar": "^1.1.12", - "@radix-ui/react-checkbox": "^1.3.4", - "@radix-ui/react-collapsible": "^1.1.13", - "@radix-ui/react-dialog": "^1.1.16", - "@radix-ui/react-direction": "^1.1.2", - "@radix-ui/react-dropdown-menu": "^2.1.17", - "@radix-ui/react-form": "^0.1.9", - "@radix-ui/react-label": "^2.1.9", - "@radix-ui/react-radio-group": "^1.4.0", - "@radix-ui/react-select": "^2.3.0", - "@radix-ui/react-separator": "^1.1.9", - "@radix-ui/react-slider": "^1.4.0", - "@radix-ui/react-slot": "^1.2.5", - "@radix-ui/react-switch": "^1.3.0", - "@radix-ui/react-tabs": "^1.1.14", - "class-variance-authority": "^0.7.0", - "clsx": "^2.1.1", - "cmdk": "^1.1.1", - "dayjs": "^1.11.20", - "lucide-react": "^1.8.0", - "radix-ui": "^1.6.0", - "tailwind-merge": "^2.5.2" - }, - "devDependencies": { - "@base-ui/react": "^1.4.1", - "@tanstack/react-table": "^8.21.3", - "@types/node": "^25.7.0", - "@types/react": "^19.0.0", - "@types/react-dom": "^19.0.0", - "react-day-picker": "^10.0.1", - "react-hook-form": "^7.53.0", - "tsup": "^8.2.4", - "typescript": "^6.0.3", - "vitest": "^4.1.6" - } -} diff --git a/packages/src/index.ts b/packages/src/index.ts deleted file mode 100644 index 0d26839..0000000 --- a/packages/src/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -export * from './components/data-table/data-table'; -export * from './components/data-table/data-table-advance-filter'; -export * from './components/data-table/data-table-bulk-action'; -export * from './components/data-table/data-table-column-header'; -export * from './components/data-table/data-table-config'; -export * from './components/data-table/data-table-pagination'; -export * from './components/data-table/data-table-skeleton'; -export * from './components/data-table/data-table-toolbar'; -export * from './components/data-table/types'; -export * from './components/dynamic/action-bar'; -export * from './components/dynamic/avatar-group'; -export * from './components/dynamic/confirm-dialog'; -export * from './components/dynamic/long-text'; -export * from './components/dynamic/phone-input'; -export * from './components/dynamic/rating'; -export * from './components/dynamic/stepper'; -export * from './components/dynamic/timeline'; -export * from './components/dynamic/tour'; -export * from './components/form/form-date-picker'; -export * from './components/form/form-input'; -export * from './components/form/form-input-group'; -export * from './components/form/form-password'; -export * from './components/form/form-phone-input'; -export * from './components/form/form-pick'; -export * from './components/form/form-radio'; -export * from './components/form/form-seach-select'; -export * from './components/form/form-textarea'; -export * from './components/form/form-upload'; -export * from './components/hooks/use-data-table'; -export * from './components/ui/file-upload'; -export * from './lib/filter-helper'; diff --git a/packages/tsup.config.ts b/packages/tsup.config.ts deleted file mode 100644 index a956255..0000000 --- a/packages/tsup.config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { defineConfig } from 'tsup'; -import pkg from './package.json'; - -export default defineConfig({ - clean: true, - dts: true, - entry: ['src/index.ts'], - external: [ - ...Object.keys(pkg.dependencies || {}), - ...Object.keys(pkg.peerDependencies || {}), - 'fsevents', - ], - format: ['cjs', 'esm'], - minify: true, - splitting: true, - treeshake: true, -}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4678be8..7713240 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: devDependencies: + '@base-ui/react': + specifier: ^1.4.1 + version: 1.6.0(@date-fns/tz@1.5.0)(@types/react@19.2.15)(date-fns@4.3.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@biomejs/biome': specifier: ^2.4.12 version: 2.4.15 @@ -20,21 +23,126 @@ importers: '@commitlint/config-conventional': specifier: ^20.5.3 version: 20.5.3 + '@floating-ui/react-dom': + specifier: ^2.1.8 + version: 2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-alert-dialog': + specifier: ^1.1.16 + version: 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-avatar': + specifier: ^1.1.12 + version: 1.2.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-checkbox': + specifier: ^1.3.4 + version: 1.3.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collapsible': + specifier: ^1.1.13 + version: 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dialog': + specifier: ^1.1.16 + version: 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-direction': + specifier: ^1.1.2 + version: 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.17 + version: 2.1.18(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-form': + specifier: ^0.1.9 + version: 0.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-label': + specifier: ^2.1.9 + version: 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popover': + specifier: ^1.1.2 + version: 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-radio-group': + specifier: ^1.4.0 + version: 1.4.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-select': + specifier: ^2.3.0 + version: 2.3.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-separator': + specifier: ^1.1.9 + version: 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slider': + specifier: ^1.4.0 + version: 1.4.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': + specifier: ^1.2.5 + version: 1.3.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-switch': + specifier: ^1.3.0 + version: 1.3.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-tabs': + specifier: ^1.1.14 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-tooltip': + specifier: ^1.1.8 + version: 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@tanstack/react-table': + specifier: ^8.21.3 + version: 8.21.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@types/react': + specifier: ^19.0.0 + version: 19.2.15 + '@types/react-dom': + specifier: ^19.0.0 + version: 19.2.3(@types/react@19.2.15) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + cmdk: + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + dayjs: + specifier: ^1.11.20 + version: 1.11.20 husky: specifier: ^9.1.7 version: 9.1.7 lint-staged: specifier: ^16.4.0 version: 16.4.0 + lucide-react: + specifier: ^0.545.0 + version: 0.545.0(react@19.2.6) + radix-ui: + specifier: ^1.6.0 + version: 1.6.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: + specifier: ^19.2.6 + version: 19.2.6 + react-day-picker: + specifier: ^9.14.0 + version: 9.14.0(react@19.2.6) + react-dom: + specifier: ^19.2.6 + version: 19.2.6(react@19.2.6) + react-hook-form: + specifier: ^7.74.0 + version: 7.76.1(react@19.2.6) + tailwind-merge: + specifier: ^3.5.0 + version: 3.6.0 typescript: specifier: ^5.4.5 version: 5.9.3 docs: dependencies: + '@base-ui/react': + specifier: ^1.4.1 + version: 1.6.0(@date-fns/tz@1.5.0)(@types/react@19.2.15)(date-fns@4.3.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@faker-js/faker': specifier: ^10.4.0 version: 10.4.0 + '@floating-ui/react-dom': + specifier: ^2.1.8 + version: 2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@fontsource-variable/noto-sans': specifier: ^5.2.10 version: 5.2.10 @@ -44,6 +152,60 @@ importers: '@hookform/resolvers': specifier: ^5.2.2 version: 5.4.0(react-hook-form@7.76.1(react@19.2.6)) + '@radix-ui/react-alert-dialog': + specifier: ^1.1.16 + version: 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-avatar': + specifier: ^1.1.12 + version: 1.2.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-checkbox': + specifier: ^1.3.4 + version: 1.3.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collapsible': + specifier: ^1.1.13 + version: 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dialog': + specifier: ^1.1.16 + version: 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-direction': + specifier: ^1.1.2 + version: 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.17 + version: 2.1.18(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-form': + specifier: ^0.1.9 + version: 0.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-label': + specifier: ^2.1.9 + version: 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popover': + specifier: ^1.1.2 + version: 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-radio-group': + specifier: ^1.4.0 + version: 1.4.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-select': + specifier: ^2.3.0 + version: 2.3.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-separator': + specifier: ^1.1.9 + version: 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slider': + specifier: ^1.4.0 + version: 1.4.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': + specifier: ^1.2.5 + version: 1.3.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-switch': + specifier: ^1.3.0 + version: 1.3.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-tabs': + specifier: ^1.1.14 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-tooltip': + specifier: ^1.1.8 + version: 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@react-router/node': specifier: 7.15.0 version: 7.15.0(react-router@7.15.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(typescript@5.9.3) @@ -62,6 +224,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + cmdk: + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) dayjs: specifier: ^1.11.20 version: 1.11.20 @@ -83,15 +248,15 @@ importers: isbot: specifier: ^5.1.36 version: 5.1.40 - kombase: - specifier: workspace:* - version: link:../packages lucide-react: specifier: ^0.545.0 version: 0.545.0(react@19.2.6) octokit: specifier: ^5.0.5 version: 5.0.5 + radix-ui: + specifier: ^1.6.0 + version: 1.6.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: specifier: ^19.2.6 version: 19.2.6 @@ -148,129 +313,6 @@ importers: specifier: ^8.0.3 version: 8.0.14(@types/node@22.19.19)(esbuild@0.28.0)(jiti@2.7.0)(yaml@2.9.0) - packages: - - dependencies: - '@floating-ui/react-dom': - specifier: ^2.1.8 - version: 2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-alert-dialog': - specifier: ^1.1.16 - version: 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-avatar': - specifier: ^1.1.12 - version: 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-checkbox': - specifier: ^1.3.4 - version: 1.3.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-collapsible': - specifier: ^1.1.13 - version: 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-dialog': - specifier: ^1.1.16 - version: 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-direction': - specifier: ^1.1.2 - version: 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dropdown-menu': - specifier: ^2.1.17 - version: 2.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-form': - specifier: ^0.1.9 - version: 0.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-label': - specifier: ^2.1.9 - version: 2.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-popover': - specifier: ^1.x - version: 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-radio-group': - specifier: ^1.4.0 - version: 1.4.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-select': - specifier: ^2.3.0 - version: 2.3.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-separator': - specifier: ^1.1.9 - version: 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slider': - specifier: ^1.4.0 - version: 1.4.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slot': - specifier: ^1.2.5 - version: 1.2.5(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-switch': - specifier: ^1.3.0 - version: 1.3.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-tabs': - specifier: ^1.1.14 - version: 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-tooltip': - specifier: ^1.x - version: 1.2.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - class-variance-authority: - specifier: ^0.7.0 - version: 0.7.1 - clsx: - specifier: ^2.1.1 - version: 2.1.1 - cmdk: - specifier: ^1.1.1 - version: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - dayjs: - specifier: ^1.11.20 - version: 1.11.20 - lucide-react: - specifier: ^1.8.0 - version: 1.16.0(react@19.2.6) - radix-ui: - specifier: ^1.6.0 - version: 1.6.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - react: - specifier: ^18 || ^19 - version: 19.2.6 - react-dom: - specifier: ^18 || ^19 - version: 19.2.6(react@19.2.6) - tailwind-merge: - specifier: ^2.5.2 - version: 2.6.1 - tailwindcss: - specifier: ^4.0.0 - version: 4.3.0 - - devDependencies: - '@base-ui/react': - specifier: ^1.4.1 - version: 1.5.0(@date-fns/tz@1.5.0)(@types/react@19.2.15)(date-fns@4.3.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@tanstack/react-table': - specifier: ^8.21.3 - version: 8.21.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@types/node': - specifier: ^25.7.0 - version: 25.9.1 - '@types/react': - specifier: ^19.0.0 - version: 19.2.15 - '@types/react-dom': - specifier: ^19.0.0 - version: 19.2.3(@types/react@19.2.15) - react-day-picker: - specifier: ^10.0.1 - version: 10.0.1(@types/react@19.2.15)(react@19.2.6) - react-hook-form: - specifier: ^7.53.0 - version: 7.76.1(react@19.2.6) - tsup: - specifier: ^8.2.4 - version: 8.5.1(jiti@2.7.0)(postcss@8.5.15)(typescript@6.0.3)(yaml@2.9.0) - typescript: - specifier: ^6.0.3 - version: 6.0.3 - vitest: - specifier: ^4.1.6 - version: 4.1.7(@types/node@25.9.1)(msw@2.14.6(@types/node@25.9.1)(typescript@6.0.3))(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) - packages: '@babel/code-frame@7.29.0': @@ -406,8 +448,8 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} - '@base-ui/react@1.5.0': - resolution: {integrity: sha512-z1gSAlced1yY+iM+mHDEtIkD8UI3Ebs52MuBPxvV6f5hRutk+xvCH/wuB7hDqDzK9JG5FoMz5nhrqtSs1wjt1A==} + '@base-ui/react@1.6.0': + resolution: {integrity: sha512-/jzjTWJYXhRFO45Bev9lc3cHbmjzCMpUqbMZ2AgKy/z25mY9B6shGSNcXcjQar9n5doM0KYW1W8fcFv2jZBuMw==} engines: {node: '>=14.0.0'} peerDependencies: '@date-fns/tz': ^1.2.0 @@ -423,8 +465,8 @@ packages: date-fns: optional: true - '@base-ui/utils@0.2.9': - resolution: {integrity: sha512-x/PDDCYzoqPpjrdyb3VcyylTI2IjUXEtYDGi5foh7KsnmNJIIaVwA2GLgDH1dps1GgXiJbA60hM+AyuTfQzIvw==} + '@base-ui/utils@0.3.1': + resolution: {integrity: sha512-gFFiltORVmW/N6IILTGxizP3PBpVpysqML1ALY5Vk0mH+7faVkCknOU31goYHN5Aoek2dkjxva1XOD2Ce9WuIg==} peerDependencies: '@types/react': ^17 || ^18 || ^19 react: ^17 || ^18 || ^19 @@ -455,28 +497,24 @@ packages: engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - libc: [musl] '@biomejs/cli-linux-arm64@2.4.15': resolution: {integrity: sha512-owaAMZD/T4LrD0ELNCk0Km3qrRHuM0X6EAyVE1FSqGY0rbLoiDLrO4Us2tllm6cAeB2Ioa9C2C08NZPdr8+0Ug==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - libc: [glibc] '@biomejs/cli-linux-x64-musl@2.4.15': resolution: {integrity: sha512-CNq/9W38SYSH023lfcQ4KKU8K0YX8T//FZUhcgtMMRABDojx5XsMV7jlweAvGSl389wJQB29Qo6Zb/a+jdvt+w==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - libc: [musl] '@biomejs/cli-linux-x64@2.4.15': resolution: {integrity: sha512-0jj7THz12GbUOLmMibktK6DZjqz2zV64KFxyBtcFTKPiiOIY0a7vns1elpO1dERvxpsZ5ik0oFfz0oGwFde1+g==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - libc: [glibc] '@biomejs/cli-win32-arm64@2.4.15': resolution: {integrity: sha512-ouhkYdlhp/1GghEJPdWwD/Vi3gQ1nFxuSpMolWsbq3Lsq3QUR4jl6UdhhscdCugKU5vOEuMiJhvKj66O0OCq+w==} @@ -1261,8 +1299,11 @@ packages: '@radix-ui/primitive@1.1.4': resolution: {integrity: sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ==} - '@radix-ui/react-accessible-icon@1.1.10': - resolution: {integrity: sha512-TraSwZUqTcVbiDV2/RXzAXC7aeVVXchq0daPFZE7zAxYFaMzjOUggLOfQH9KFLgRizuwVKZO/crveV1eeO3/ZQ==} + '@radix-ui/primitive@1.1.6': + resolution: {integrity: sha512-w9hl+724uYEgCGR3bhuRepjBtrNB/6gkhCnAf58Ke+SLbHPPQqVZZB59z60roB+5H+nh3nWTcdJhQdFMEydWmw==} + + '@radix-ui/react-accessible-icon@1.1.12': + resolution: {integrity: sha512-Y0zhCQ/XUdTom5hAxvE8RlXqR4hZmKGK6g2//LfgHmb88PJFOpXSh9B/7FlfYXezVY5FKGjRYWCYz5FXxZ9WZQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1287,8 +1328,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-accordion@1.2.14': - resolution: {integrity: sha512-iE8YB9nmTBH8zd73ofBISZ8JCzgMoMkATJr7qDwa6u5F1+7mTM81V6fa71jgZ65rpjVpecDf1vSnwIFP9Ly1zw==} + '@radix-ui/react-accordion@1.2.17': + resolution: {integrity: sha512-l3Dmp+qPPc3SqT8+SPnxIgoWBEU2MMBxcQ7BsoRgak2UT75xY83SFvFcrUkUAWukOV3LFF+BQ9aBIFtZsIG8yQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1300,8 +1341,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-alert-dialog@1.1.16': - resolution: {integrity: sha512-vPaIgo0mxYlvcFaM9jB2Uot9TjGXMuAPEvrc6BOLeV+I5U8s1dkIoouYaa6lmSfc5SPMo5x5djOTOTvaigdGMQ==} + '@radix-ui/react-alert-dialog@1.1.17': + resolution: {integrity: sha512-563ygGeyWPrxyVCNp7OV4rE2aIXhFPknpFyo4wbDlcyMMPZ6ySh+zC5WTvY0ZFLgPTg/QB6tA8PyDQyJ2b4cPg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1313,8 +1354,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-alert-dialog@1.1.17': - resolution: {integrity: sha512-563ygGeyWPrxyVCNp7OV4rE2aIXhFPknpFyo4wbDlcyMMPZ6ySh+zC5WTvY0ZFLgPTg/QB6tA8PyDQyJ2b4cPg==} + '@radix-ui/react-alert-dialog@1.1.20': + resolution: {integrity: sha512-Ft1W+jPqSh5BKfSTe4dpq6UYQKKQJ5Tvq3wfux+WVlg7nPwFK/3pIlHTb3Rbe+b/tNurx8YGXD9em91ujmgwuQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1339,8 +1380,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-arrow@1.1.9': - resolution: {integrity: sha512-yqHW5WQ/cTpU/un7dqqIKNy2iRU8BC0JB78PEzTfCCYvZu1U6W9KwObAniMk9nhSfyotKPQTYaUD/HB0f5muig==} + '@radix-ui/react-arrow@1.1.12': + resolution: {integrity: sha512-ltXCE0glRomMZ9+u10d9o1Go+edqa1aLxufH59JRNNM3Yz1uvaeNWSaS1HeVh1X64agtdBG5JA1W1I6ySqWiwA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1352,8 +1393,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-aspect-ratio@1.1.10': - resolution: {integrity: sha512-kbI7NrqhDeuytYrq7JjAsoXczvL8wgj2tc1MyaYWm+50bMKHCHQtVWCryslx4cCpmCTTkBcwQckE4CmmGV2haQ==} + '@radix-ui/react-aspect-ratio@1.1.12': + resolution: {integrity: sha512-Sok2IBJxA1XO4pU3ldzZMwUBMumIt64EY8zOUlVq5CdS+i0FrEbajVslfDB+YGWLMsrjY2kZQB0DgkrZXLZvcg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1365,8 +1406,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-avatar@1.1.12': - resolution: {integrity: sha512-NQCQyWC7QrDPhjMn8hUqFeU0lUrprIgm1AyMgLbzuQJibNnatdc3SSMo3/UGFu/eUkJUU1cEcKCnyhXTQzq6tA==} + '@radix-ui/react-avatar@1.2.0': + resolution: {integrity: sha512-am/CwltXtmtdtP+5FbYblYDnMa/zuKcMJP1i3/SJMDXXfj2mG+BTqLH2wucqeyyiQMursUtg/5cK+Nh2pCaSOA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1378,8 +1419,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-avatar@1.2.0': - resolution: {integrity: sha512-am/CwltXtmtdtP+5FbYblYDnMa/zuKcMJP1i3/SJMDXXfj2mG+BTqLH2wucqeyyiQMursUtg/5cK+Nh2pCaSOA==} + '@radix-ui/react-avatar@1.2.3': + resolution: {integrity: sha512-peavtnApRB1tABx42tHw+rPU83GSg5tXicMYO/Xi1/lqNcRsF6jkr6L7Njo7gj4q/xtDRDKBkqJvbMtoOMYWtA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1391,8 +1432,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-checkbox@1.3.4': - resolution: {integrity: sha512-m3JmIOAX5ZzZ6VPjxEU2dbTOhoHi0nT5riwcDwe8idocsWf4a5DXJLDtZ6LfJwMBx7W+A2b7kp2TgPEKtaiF6A==} + '@radix-ui/react-checkbox@1.3.5': + resolution: {integrity: sha512-pREzrmNnVwGvYaBoM64huTRK7B3lrTRuwj8A9nwhPiEtMb+yudiWh6zWAqEtP0Dzd5+iBa1Ki7V1pCxV8ExMdA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1404,8 +1445,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-checkbox@1.3.5': - resolution: {integrity: sha512-pREzrmNnVwGvYaBoM64huTRK7B3lrTRuwj8A9nwhPiEtMb+yudiWh6zWAqEtP0Dzd5+iBa1Ki7V1pCxV8ExMdA==} + '@radix-ui/react-checkbox@1.3.8': + resolution: {integrity: sha512-wfN60IGuxynWK7rP4Ks2p7u9G7gqirzkAiFptuzVbsR1ot2/K+PavNUAtxiKxyRfLOvSbVfvvm9m3rFqLEXz7A==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1430,8 +1471,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-collapsible@1.1.13': - resolution: {integrity: sha512-F0s8+p2XNpfc3k02zBfB0jPWbkHVG162+p7BdUMyJ2308QMqZ+oaclX+FAzKFovgL5OqRU+Rvy6f/vbdlJVaqA==} + '@radix-ui/react-collapsible@1.1.14': + resolution: {integrity: sha512-9bT+FvifX1FK2Mj6UEsTdyu0cN3JaA3KdfhaBao+ONrYFy/pyOy3TU1TNw7iOk1o+0hOEq67RojlUUmoFGwxyA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1443,8 +1484,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-collapsible@1.1.14': - resolution: {integrity: sha512-9bT+FvifX1FK2Mj6UEsTdyu0cN3JaA3KdfhaBao+ONrYFy/pyOy3TU1TNw7iOk1o+0hOEq67RojlUUmoFGwxyA==} + '@radix-ui/react-collapsible@1.1.17': + resolution: {integrity: sha512-DJgqGsNXa0df3ifz9PFNgvgj/bzIu5QTVWCt5nQWaUkM6y0EarUv4QG4s6mCoeQdOIyVOT/Q1osFuEGub2TDXQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1469,8 +1510,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-collection@1.1.7': - resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + '@radix-ui/react-collection@1.1.12': + resolution: {integrity: sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1482,8 +1523,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-collection@1.1.9': - resolution: {integrity: sha512-zuSVi7ziP7uQRqc+yGxsKJfNkdyHv3ZKDaHe0gzg4dRgws96TPKWIiz84tVHP4GEcEl8bC0mdt17NkcxaJHmaQ==} + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1513,8 +1554,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-context-menu@2.3.1': - resolution: {integrity: sha512-XbrxS68W5dyiE4fAb96yvJwSVU5x66B20A99sD5Mk3xSWK/LqeOnx6TZnim1KieMjXS/CTFq8reOAjWxas2G8Q==} + '@radix-ui/react-context-menu@2.3.4': + resolution: {integrity: sha512-eO9tkvHvo4dNwb+lytEcKWjy8c8To+ttLwNt0f9XzzsVFIaspqt3i1/c0JaaksxBB5G//zPo9CCgn39huWQyBA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1544,21 +1585,17 @@ packages: '@types/react': optional: true - '@radix-ui/react-dialog@1.1.15': - resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + '@radix-ui/react-context@1.2.0': + resolution: {integrity: sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==} 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-dialog@1.1.16': - resolution: {integrity: sha512-l9ok83YBclEZhbjgzt76Hw733e6cvRKPNgO6GJ/IETlufXG9p+fRu2wlvpImQvR6xdJ8h7J8J2DBvsPEiEsKMw==} + '@radix-ui/react-dialog@1.1.17': + resolution: {integrity: sha512-TDTYmpdq8dI2+Xgvgj9AJ8Ghqq+Eph/TRVEdaFQPDItIY+6QSkU7MJMeevw1568Yw/2Ijz8BTphPSP2XejKphw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1570,8 +1607,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-dialog@1.1.17': - resolution: {integrity: sha512-TDTYmpdq8dI2+Xgvgj9AJ8Ghqq+Eph/TRVEdaFQPDItIY+6QSkU7MJMeevw1568Yw/2Ijz8BTphPSP2XejKphw==} + '@radix-ui/react-dialog@1.1.20': + resolution: {integrity: sha512-cngVJcvK0yMvR7wICJpv+1uW3Qw4T7QM5sdbb+oE/lxOdTdvF00oaRpWUjVgmjyXe3J+xh7eZyXZlVF3g2g59g==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1614,19 +1651,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-dismissable-layer@1.1.12': - resolution: {integrity: sha512-MhoruH6xEzsbvOmo4TNgMfmtvRGyDZw4MDSdf4ybMHfezjqwzv6hyd4lsMzBp8K9Sn6sGzCF62x1I7BYUECXOg==} - 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-dismissable-layer@1.1.13': resolution: {integrity: sha512-2v+zNAWWe0ySxgC0D0yeXMPQ23xZVgXZTerTz+JKlmdRj6gfTqmCcR29jb6d290DezXPGgruHWDX/vYUebtErg==} peerDependencies: @@ -1640,8 +1664,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-dropdown-menu@2.1.17': - resolution: {integrity: sha512-S6b3Jm57sY5EdDyOMLkacbB0qMnKhy1RCKZCt795ZkmtUOAvojYIZ5p7dXHIh5Cyr3jCLLI5/g64V3FKLudZmw==} + '@radix-ui/react-dismissable-layer@1.1.16': + resolution: {integrity: sha512-t45h68IjFx0ccBnPJqk0X6ecv69LkCFWd6DNCFQX56mUnVEXZbNOLCH/u9fHlAjFZ1RrFdl8/m4zev7B7NyhXQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1666,14 +1690,18 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-focus-guards@1.1.3': - resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + '@radix-ui/react-dropdown-menu@2.1.21': + resolution: {integrity: sha512-gavFM1iWLmWdxWNdGJHVeWeSQul5WE/0pxfvWWt1QnD71hyyujyMCDVacqBomaSOjdxwDzYB+Ng4+MxOvrFB1A==} 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.4': resolution: {integrity: sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==} @@ -1697,21 +1725,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-focus-scope@1.1.7': - resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} - 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-scope@1.1.9': - resolution: {integrity: sha512-9Se8t+Zry+1rEOL7Y6l/4ANYU/TOtAtf8O2fKdwLltcaMcm6kOqYGbzO4tMFQ0bvzO920pRAoHpFZ4W85S3keQ==} + '@radix-ui/react-focus-scope@1.1.13': + resolution: {integrity: sha512-dE04aPEuP9rvKKT0d0KjSOtTEYNg6bmCYFsoSJpfC+y91Hic28ZfDCGgv6aJ+2Kw/LBXYipMZpyqVj/OD3Z8Gg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1736,8 +1751,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-form@0.1.9': - resolution: {integrity: sha512-eTPyThIKDacJ3mJDvYwf/PSmsEYlOyA2Qcb+aGyWwYv+P5w57VPUkMVA2XJ9z0Du2KBY1HoHQzhPV9iYL/r4hg==} + '@radix-ui/react-form@0.1.13': + resolution: {integrity: sha512-PopvWqiutoZh5TJXk9EV9Wh+khbp+LQ+A0H4uHocIjVcKIi6gMlBy4sAaW15thwUSc6PrR8J62nB2uM+htqrcg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1749,8 +1764,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-hover-card@1.1.17': - resolution: {integrity: sha512-GjZQIEANVkuuWeztlKz6QEHe31ZX2iDfHzcTMCQVZXC0JyQrgfKWSC+LOOEw6aVV64zyjzobIzSA4AU4eKWrHA==} + '@radix-ui/react-hover-card@1.1.20': + resolution: {integrity: sha512-UPmdiR8NsngWjG/y9mClzFg+Rbbpy8u0p0SKM+t7mfH4V07TiLsuylqR0RhJiRibopsawoTtMQudm/TxwHWa9w==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1793,21 +1808,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-label@2.1.9': - resolution: {integrity: sha512-rDoTeMbCwRVcnmo7NGT9IlPo1yXmEI+xc1URP3oeewwZEV4mdTp1dYUhYbQdo4D1q2SjKVvv4N1gNY77QAQtjA==} - 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-menu@2.1.17': - resolution: {integrity: sha512-fmbNnFyf+JYCN0DhhWnEdUTDnZD1mXaPQWivdsPIb8oOSbARfD3LIQJbLCG8a8QLCwoMxiJ7GVPIFcC8Dw8v2Q==} + '@radix-ui/react-label@2.1.12': + resolution: {integrity: sha512-dxioNQ7VOrYKKWJIxMRmJPDSWQN0gNCUy3zaqUSBwsuFAiFzI0yLGJr2q3ml07k/HlOk55N8KEfwa1ZgfprJ3w==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1832,8 +1834,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-menubar@1.1.18': - resolution: {integrity: sha512-hX7EGx/oFq6DPY27GQuP/2wP48GHf5LG6r06VgNJlG+znmDS8OfopZcRcGly3L4lsB9FqpmLx6JQSE9P3BUpyw==} + '@radix-ui/react-menu@2.1.21': + resolution: {integrity: sha512-2BHtaJHvvoWTECyrja1mOjN6z2dWdpeHL6b8PxqZYgex8J8xakT2KAchpZIaMwNPauIRHH/VlPJYhSSKe8lz2g==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1845,8 +1847,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-navigation-menu@1.2.14': - resolution: {integrity: sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==} + '@radix-ui/react-menubar@1.1.21': + resolution: {integrity: sha512-uQONG1qM4D8FSEt0xRs5yDpzeSWggf8lOKqHa84NvqoVoc1qJ6XN+gdkrJuQCyEY55793gLlQI3wjgWO5A/Oqg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1858,8 +1860,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-navigation-menu@1.2.16': - resolution: {integrity: sha512-nJ0SkrSQgudyYhMiYeHA1ayLVuduEJCFLan1RZZN7c9kqzzCFLaU9kuy81uNtqzweM9YaQPgWzxi9MwQ9jZ04g==} + '@radix-ui/react-navigation-menu@1.2.14': + resolution: {integrity: sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1871,8 +1873,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-one-time-password-field@0.1.10': - resolution: {integrity: sha512-GHkcJ+WVj91At+OvUVTD4R3W0/wxw9t/sG5xFUBYXaCbtWiooZX5Md376QjJqgH4VsVyXrbVNHO2O4NYcmjfVg==} + '@radix-ui/react-navigation-menu@1.2.19': + resolution: {integrity: sha512-58OVQUrpWx/zGVV3lxGUyAtjX4n0305Z8xIdUAq2QlFO2m2hd1eBS4x1yIVtV8bzCQJja0TJttWcwiPI6y6tmw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1884,8 +1886,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-password-toggle-field@0.1.5': - resolution: {integrity: sha512-fVuA82u0b/fClpbEJv8yp1nU9eSvoSEOERsU/hhf3FXGPIvkmE7oEaHEu8poowoXO39/Va7zq2E0TUcYr1dBRg==} + '@radix-ui/react-one-time-password-field@0.1.13': + resolution: {integrity: sha512-reLtbZtEBsMcqXkjd/wOga4e8t9uxzFHdX9W/j/ZfGznTNJxLGjRrDNGnGOOWcBazMH1BI/b7Cx+hblSWSD7aw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1897,8 +1899,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-popover@1.1.16': - resolution: {integrity: sha512-8brVpAU5Uq7Bh0c8EFc4ZTf2JJTYn0o+1L+CUJB3UYIOkTjKGMgoHvduylrahdmNlr3DfH0rFq2DrbNZXgaspw==} + '@radix-ui/react-password-toggle-field@0.1.8': + resolution: {integrity: sha512-NH9puF7Es5Loh8vFELm+SyayzV27nyBw8kiP/uD9wbkwgq359FfbkKEvccrNk75z0LiSqC4REWk1iL9xdeWJkQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1923,8 +1925,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-popper@1.3.0': - resolution: {integrity: sha512-9PB589e1aWZbrlFUHdz6WiPCL+xLZHQFX7oibqG/6Q0SwOkxDyQX9W/cyPa+sAPPKuC8cpLCpRczE5a/1DiwVQ==} + '@radix-ui/react-popover@1.1.20': + resolution: {integrity: sha512-/PYqbsyuDkNj+IxMcRx71qNt6GelnuNulMwdCV7AtFEhUyK6XkbwreEN6CCLydMeTiDozBV4uv5aF5d12dDH7w==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1949,8 +1951,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-portal@1.1.11': - resolution: {integrity: sha512-UEytdjgEh2tJGgD/gZK4FUx6t1rNIlM3U0DENhSrG7I75FGm1DnaDuVUWF1pWAWUwGmn1sCJ1VGHn8LhN1aTOw==} + '@radix-ui/react-popper@1.3.4': + resolution: {integrity: sha512-PXnCa3XgTQk0FegMctxgqJXtFLZe4IFJdbUkB7jKSCKEpb6utEO4S9Vog/pkyCfEPdzM331gvE4xpztmBAfMng==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1975,8 +1977,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-portal@1.1.9': - resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + '@radix-ui/react-portal@1.1.14': + resolution: {integrity: sha512-REwjAGPMa3J9oyDE4cuWkZbwnCbbyky66NurquQklXMSDn67cl6oGFx2gO7KZhPtFNbNw9xTWNrti3VIhgluYw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2014,21 +2016,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-primitive@2.1.3': - resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} - 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.4': - resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + '@radix-ui/react-presence@1.1.8': + resolution: {integrity: sha512-0hhyrQdXMaATgq4ammLG9+iPqsXxzZkgTSIxdrJHdfLnXO4Uo5L7BoO3/Xf0AEaettadGZWGGJMw6ujzQvIpGA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2040,8 +2029,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-primitive@2.1.5': - resolution: {integrity: sha512-zifXeB8Y88qCYx8PLZ5oQb32KwZub+s925mMoZsBBq9KUQqWKkREubTfs6ASjRPPBe7Jt9O8OHH89+95VG+grA==} + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2066,8 +2055,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-progress@1.1.10': - resolution: {integrity: sha512-JYzEg60lk79PwKM27WZyKd7PW8O4OM5jOaFfRPfOyeXmMw7tLJh5kSj+CEjVTehszuwml/AdCzPGMXBTGf4BBw==} + '@radix-ui/react-primitive@2.1.7': + resolution: {integrity: sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2079,8 +2068,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-radio-group@1.4.0': - resolution: {integrity: sha512-eHdV5bLx9sH+tBnbDjkIBdvQEH/c6MEtQYhTbxkaDK9qsIFFLtmJYEQFVdwhnruWotLfQmIuWEL/J+L3utE8rQ==} + '@radix-ui/react-progress@1.1.13': + resolution: {integrity: sha512-1dUdKDd63Tz9FfbTw20MVr28ohG4v7HOJ1dsavGBBPBS3KGzLOyLKiMJAC1OdgiY18nTSHpD4fULGK5gsLY/ww==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2105,8 +2094,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-roving-focus@1.1.11': - resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + '@radix-ui/react-radio-group@1.4.4': + resolution: {integrity: sha512-OpbUmp/korY+tjEQmHwGyQ+QQ3LBlCPC70z03Q/NSqGaHf2EijuwpjQPnswrH6cZLWyT2J6FmB+kzRoMUtPBig==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2118,8 +2107,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-roving-focus@1.1.12': - resolution: {integrity: sha512-FvgPt1bRmg8Xt2QpF7NUZW3dE0ZQHGm41dAdgT2J2GJPoIXz+9Em3NobAxf4fupcxhgHu03E5CRiU2MWvObXyg==} + '@radix-ui/react-roving-focus@1.1.13': + resolution: {integrity: sha512-9gkwneI0guf8JDmrFxPjJF6Ozzgioyw+/lonYNCwefS9ZHA05er0BVHiXr+LbWGHxUfczvMY6G1oiZZi1VzjRw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2131,8 +2120,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-roving-focus@1.1.13': - resolution: {integrity: sha512-9gkwneI0guf8JDmrFxPjJF6Ozzgioyw+/lonYNCwefS9ZHA05er0BVHiXr+LbWGHxUfczvMY6G1oiZZi1VzjRw==} + '@radix-ui/react-roving-focus@1.1.16': + resolution: {integrity: sha512-w7lLsTSd3940vFYEshKkHw+NGf7H0QDJPHYsy8NRjDCVbO6ZdKW1X/xoJSYHZtttnrdZiYqbN2O/2uHGB0zasw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2157,8 +2146,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-scroll-area@1.2.12': - resolution: {integrity: sha512-xuafVzQiTCLsyEjakowTdG3OgTXsmO7IdCiO77otIa+z44xoLNs9Do5eg7POFumIOCjtG6djfm6RKUKpUa/csA==} + '@radix-ui/react-scroll-area@1.2.15': + resolution: {integrity: sha512-JVBHNfTBbGd9hhq/xZZOgmVnBCXhLs8PJJ8vMzgwI0pLZNsKckW9pkoqHyxokUCt1hoxbwDNvF9DItEeZsG68g==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2170,8 +2159,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-select@2.3.0': - resolution: {integrity: sha512-mENc7WpJvJcW8hlMpzfFcHcEhTvYS5JMBmi9HVC1Q00uhBwML086MHYUV8QQdQv6lcu0Wg8dzd1RB8AFADcG/g==} + '@radix-ui/react-select@2.3.1': + resolution: {integrity: sha512-w6eDvY78LE9ZUiNnXCA1QVK8RYN7k9galFv09kjVydJqBAgHd7Y9A6h0UJ/6DCZNGZMZrB2ohcSW1Bo9d8+wWA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2183,8 +2172,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-select@2.3.1': - resolution: {integrity: sha512-w6eDvY78LE9ZUiNnXCA1QVK8RYN7k9galFv09kjVydJqBAgHd7Y9A6h0UJ/6DCZNGZMZrB2ohcSW1Bo9d8+wWA==} + '@radix-ui/react-select@2.3.4': + resolution: {integrity: sha512-E2JxqAvaTUEhWtBptWo02g8FnLYPymv9ahEvW/cZQPPV4ySeyo0M8n3sXccsLUAIfMbexnfXt91qF7UjTbTMMg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2209,8 +2198,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-separator@1.1.9': - resolution: {integrity: sha512-gvgW+JV/Mbjj6darztTetnmElpQEzZrXpJvfj+dOxNAxiyHEAyUvEjjl4zxblvmjmKmi3jfPoy7ZdxzCuUBJSA==} + '@radix-ui/react-separator@1.1.12': + resolution: {integrity: sha512-2hezgFBBR5jU3S9L9bIZ9Uag6LnvxuFBNsLCfTR8qx+NshuvFmpL4C72+5zMS3Z6UgHNSU1thOw2UaBBPEDpsQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2222,8 +2211,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-slider@1.4.0': - resolution: {integrity: sha512-RHcPlLOThRJM51DSIC33ZnpDEBYhyEFroVWkd2P54PGGjkmAt14RboYUU9E1MFst666zFHM0tGtWvMjSOtU1pw==} + '@radix-ui/react-slider@1.4.1': + resolution: {integrity: sha512-r91WSpQucNGFKAIxT8FT0H0zyjd5tJlqObLp7LOMV4z49KoDCwjy01w3vDOU4e1wxhF9IgjYco7SB6byOW7Buw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2235,8 +2224,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-slider@1.4.1': - resolution: {integrity: sha512-r91WSpQucNGFKAIxT8FT0H0zyjd5tJlqObLp7LOMV4z49KoDCwjy01w3vDOU4e1wxhF9IgjYco7SB6byOW7Buw==} + '@radix-ui/react-slider@1.4.4': + resolution: {integrity: sha512-8dUytW34KoJaB22ctfP7hqUCuyYa8xn2w7H8kCneeOtS5oM7UBivcnZtR8P4kPYMgdoAZlkMhE9/qkYZ5MlRzQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2257,24 +2246,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-slot@1.2.4': - resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-slot@1.2.5': - resolution: {integrity: sha512-rCMO3QsIVKv5JTY5CVbo2MvO77SpEqqYc8AvRE7OWqRDOIqAKjsp+DrmnY9uc8NPdxB5E2z47HTYGeE2+NTptg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-slot@1.3.0': resolution: {integrity: sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==} peerDependencies: @@ -2284,19 +2255,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-switch@1.3.0': - resolution: {integrity: sha512-GP1EZwhoZO/GGnhM1P5/2Vpm8iN8EnngyU0oezn2l78kN8tj25pyrvjIaT7azBhK615KSt+P2w39y57YV5jVkA==} - 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-switch@1.3.1': resolution: {integrity: sha512-55bQtCnOB0BohomSHi6qvQXpJEEqUGDm6hRrM0Bph5OXwhSegqkd8IqgBAQkM1IlgUlWZIxpxRcpOEfRIgimyw==} peerDependencies: @@ -2310,8 +2268,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-tabs@1.1.13': - resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} + '@radix-ui/react-switch@1.3.4': + resolution: {integrity: sha512-7iGMj1SfZBAc6xRiy0Y3Wr/v52viQeDhOmaM3fNRyNf2nbYooZA3kKoEDGLPtrDv8JitpzcueqdZLusVMojLdQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2323,8 +2281,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-tabs@1.1.14': - resolution: {integrity: sha512-D5jwp9JNuwDeCw3CYD2Fz+sSHo0droQjC8u75dJHe4aWr5q6yBiXZU+hurXnKudRgEpUkD5TsI6bjHPo5ThUxA==} + '@radix-ui/react-tabs@1.1.15': + resolution: {integrity: sha512-kxc9gI6/HfcU4nfMMVS3AmQK414kbU1IE6UCJmMmxjhO3cRPXOyYnmvyKD+ODt7q56nRq9l7Wovi6uaGwKgMlg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2336,8 +2294,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-tabs@1.1.15': - resolution: {integrity: sha512-kxc9gI6/HfcU4nfMMVS3AmQK414kbU1IE6UCJmMmxjhO3cRPXOyYnmvyKD+ODt7q56nRq9l7Wovi6uaGwKgMlg==} + '@radix-ui/react-tabs@1.1.18': + resolution: {integrity: sha512-1zq2XkQkK/KfbZn84edytYpOLquhNalra5LXc3NAMKhNRSGtyXqjMv6OyC9jlSuNKpqvQtsb57WKoICNk1v/sQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2349,8 +2307,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-toast@1.2.17': - resolution: {integrity: sha512-uL4kyyWy000pPL43fGGCV5qT6ZchCWEQZOSlkYiPwPt8Hy1iW38RjeptIvz1/SZesrW6Vn58Ct3sV7tfEfiAbw==} + '@radix-ui/react-toast@1.2.20': + resolution: {integrity: sha512-S28OtO1IvYSpWfaUBtiYCTTwRLF8doafj+a+uQw8rc8dLINS52uuG3CIPCeZc3Jfdb/S7o7HhlQxLoXlIYRu6g==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2362,8 +2320,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-toggle-group@1.1.13': - resolution: {integrity: sha512-Xb9PLtlvU66F36LiKba6dFswu6V2mDkgidO4fNSbQHQwmZ9ObxMIO17MN/LJ4aWJecVuSVLAHPZjyeMzJrgeiA==} + '@radix-ui/react-toggle-group@1.1.16': + resolution: {integrity: sha512-uil+A0Um3LaZQJkMap4nIg0VgqWc0j3iNU4AXf9a/zHOgPHNYWfVk5WVsG2296Y8HLv1bxiN7uQJblHc1+00tw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2375,8 +2333,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-toggle@1.1.12': - resolution: {integrity: sha512-AsAVsYNZIlRBsci7BhE+QyQeKd1h6TffJYt+lF0QQkd5OpQ3klfIByPsCb4G0h/Fq6PJwh1FYNluzBFYzhk4+w==} + '@radix-ui/react-toggle@1.1.15': + resolution: {integrity: sha512-tyCejFjhJ51UKFVIG8jh9nTdRIsFPxrgrI4IdlxuJeP+AKTfTko+0gBueyBFLHqsyE71Aj9PKHjMnG+YRPyKhA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2388,8 +2346,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-toolbar@1.1.13': - resolution: {integrity: sha512-Za1l4f6fzTkGgz/iynAMN8iaqiKff2wm2/QwiLmHPtDQreWEBrvSimgQFIekxMUdRPhILM7xdIXxuS/o/DGZag==} + '@radix-ui/react-toolbar@1.1.16': + resolution: {integrity: sha512-ZnvUAH+ftoRYzUzFQ8gqKnQ1lUFYb3amguGu+BXpfjvLIkjmXCcHCJlQeBLBlJCOtGNVtP+wHrZaUCC/zYKQMg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2414,8 +2372,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-tooltip@1.2.9': - resolution: {integrity: sha512-u6F9MmTtBSLkiXNVDrtB/yPCZarM9smNswC24YYLV/M+bth6J3Gs3vlJezEoFwKZvPvxhCpUYdUnOsNG/0XOlA==} + '@radix-ui/react-tooltip@1.2.13': + resolution: {integrity: sha512-56XPNYGMnGBcPyiBTaEXB7IGPybbsdNkFgSv90SCrHkXnu2Av1HhsyZMegzXlTu/QHA3V6/l22GZCv9iEoiqmQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2463,6 +2421,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-controllable-state@1.2.4': + resolution: {integrity: sha512-cx2DixxmSfjCcEoRvDvy1NLd6SWK94XFcEEOZUcharUlXbmahFQGKCfwdKZL2ub34iIwOPOEFVF80xb+yfLYiA==} + 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: @@ -2499,6 +2466,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-escape-keydown@1.1.3': + resolution: {integrity: sha512-3wEkMiPHXha/2VadZ68rYBcmYnPINVGl4Y3gtcM7fKRjANk0OscK+cdqBgUWdozb7YJxsh0vefM7vgAMHXOjqg==} + 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-is-hydrated@0.1.1': resolution: {integrity: sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==} peerDependencies: @@ -2575,8 +2551,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-visually-hidden@1.2.5': - resolution: {integrity: sha512-tPcHNI3FajdDBFpl/Ez1m2WL0ufJqBKyHxMDBvKitopamK36WwBGOMicuMEZKkM5Wce41QxUyv6BsiqfrWBiGg==} + '@radix-ui/react-visually-hidden@1.2.6': + resolution: {integrity: sha512-jCE0WljWifTI4niIMCll06kGpsJTAPiZVU9H4WR1N6qW7At9ystHbN7dDB+we2xH535roFHj7qKS+RGj0FMDWQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2588,8 +2564,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-visually-hidden@1.2.6': - resolution: {integrity: sha512-jCE0WljWifTI4niIMCll06kGpsJTAPiZVU9H4WR1N6qW7At9ystHbN7dDB+we2xH535roFHj7qKS+RGj0FMDWQ==} + '@radix-ui/react-visually-hidden@1.2.8': + resolution: {integrity: sha512-FjsQEpkNBJJYiPSat6jh2LGKLPX2jAoDVS3AZSBNX3cOUoEGhw/f+z2FCY8Cf1NkoYIbytJ1f4mlWPQpR+MjVg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2694,42 +2670,36 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.2': resolution: {integrity: sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.0.2': resolution: {integrity: sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.2': resolution: {integrity: sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.2': resolution: {integrity: sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.2': resolution: {integrity: sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.2': resolution: {integrity: sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==} @@ -2791,79 +2761,66 @@ packages: resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.60.4': resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.60.4': resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.60.4': resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.60.4': resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.60.4': resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==} cpu: [loong64] os: [linux] - libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.60.4': resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.60.4': resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==} cpu: [ppc64] os: [linux] - libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.60.4': resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.60.4': resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.60.4': resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.60.4': resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.60.4': resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-openbsd-x64@4.60.4': resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==} @@ -2989,28 +2946,24 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.3.0': resolution: {integrity: sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] - libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.3.0': resolution: {integrity: sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==} engines: {node: '>= 20'} cpu: [x64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.3.0': resolution: {integrity: sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==} engines: {node: '>= 20'} cpu: [x64] os: [linux] - libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.3.0': resolution: {integrity: sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==} @@ -3068,15 +3021,9 @@ packages: '@types/aws-lambda@8.10.161': resolution: {integrity: sha512-rUYdp+MQwSFocxIOcSsYSF3YYYC/uUpMbCY/mbO21vGqfrEYvNSoPyKYDj6RhXXpPfS0KstW9RwG3qXh9sL7FQ==} - '@types/chai@5.2.3': - resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - '@types/debug@4.1.13': resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} @@ -3133,35 +3080,6 @@ packages: '@ungap/structured-clone@1.3.1': resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} - '@vitest/expect@4.1.7': - resolution: {integrity: sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==} - - '@vitest/mocker@4.1.7': - resolution: {integrity: sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==} - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/pretty-format@4.1.7': - resolution: {integrity: sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==} - - '@vitest/runner@4.1.7': - resolution: {integrity: sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==} - - '@vitest/snapshot@4.1.7': - resolution: {integrity: sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==} - - '@vitest/spy@4.1.7': - resolution: {integrity: sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==} - - '@vitest/utils@4.1.7': - resolution: {integrity: sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==} - accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -3219,9 +3137,6 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} @@ -3245,10 +3160,6 @@ packages: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - ast-types@0.16.1: resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} engines: {node: '>=4'} @@ -3314,12 +3225,6 @@ packages: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} - bundle-require@5.1.0: - resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - peerDependencies: - esbuild: '>=0.18' - bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -3346,10 +3251,6 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - chai@6.2.2: - resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} - engines: {node: '>=18'} - chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} @@ -3437,10 +3338,6 @@ packages: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - compare-func@2.0.0: resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} @@ -3455,16 +3352,9 @@ packages: compute-scroll-into-view@3.1.1: resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} - confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - confbox@0.2.4: resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} - engines: {node: ^14.18.0 || >=16.10.0} - content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -3702,9 +3592,6 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - es-module-lexer@2.1.0: - resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} - es-object-atoms@1.1.2: resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} engines: {node: '>= 0.4'} @@ -3795,10 +3682,6 @@ packages: resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} engines: {node: '>=6'} - expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} - engines: {node: '>=12.0.0'} - express-rate-limit@8.5.2: resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==} engines: {node: '>= 16'} @@ -3880,9 +3763,6 @@ packages: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} - fix-dts-default-cjs-exports@1.0.1: - resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} - formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -4377,10 +4257,6 @@ packages: jose@6.2.3: resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} - joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} - js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -4463,28 +4339,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} @@ -4502,10 +4374,6 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} - lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} - engines: {node: '>=14'} - lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -4518,10 +4386,6 @@ packages: resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} engines: {node: '>=20.0.0'} - load-tsconfig@0.2.5: - resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -4793,9 +4657,6 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - mlly@1.8.2: - resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} - morgan@1.10.1: resolution: {integrity: sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==} engines: {node: '>= 0.8.0'} @@ -4844,9 +4705,6 @@ packages: resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} engines: {node: ^20.17.0 || >=22.9.0} - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoid@3.3.12: resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -4903,9 +4761,6 @@ packages: resolution: {integrity: sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==} engines: {node: '>= 10'} - obug@2.1.1: - resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} - octokit@5.0.5: resolution: {integrity: sha512-4+/OFSqOjoyULo7eN7EA97DE0Xydj/PW5aIckxqQIoFjFwqXKuFCvXUJObyJfBF9Khu4RL/jlDRI9FPaMGfPnw==} engines: {node: '>= 20'} @@ -5051,38 +4906,13 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} - pirates@4.0.7: - resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} - engines: {node: '>= 6'} - pkce-challenge@5.0.1: resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} engines: {node: '>=16.20.0'} - pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - pkg-types@2.3.1: resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} - postcss-load-config@6.0.1: - resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} - engines: {node: '>= 18'} - peerDependencies: - jiti: '>=1.21.0' - postcss: '>=8.0.9' - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - jiti: - optional: true - postcss: - optional: true - tsx: - optional: true - yaml: - optional: true - postcss-selector-parser@7.1.1: resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} engines: {node: '>=4'} @@ -5130,8 +4960,8 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - radix-ui@1.6.0: - resolution: {integrity: sha512-EUEC70O03EgxWMP5aoqfBZ6iLC5bczFagGy7zhSYRt8o5DP7IWNiP3ywetse3L9b8843ExB0OGWZvgbYVJuNeg==} + radix-ui@1.6.4: + resolution: {integrity: sha512-Kpgb9sx08toOydBK42//0N3MqIPlqjHcY39CYuGG8+7DrF6+NTfAnc3o+f1kvoKzG6cI56ri7Z45XEBQqG1QqQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -5155,16 +4985,6 @@ packages: resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} engines: {node: '>= 0.10'} - react-day-picker@10.0.1: - resolution: {integrity: sha512-eNh6BlwcYInWaJtRv18mXQ06Ys/H6rdTZAnTaSdOYJuTpwP1JMCHNd1FDRadA+gbeinq+psdULN5Xnowy9mV8w==} - engines: {node: '>=18'} - peerDependencies: - '@types/react': '>=16.8.0' - react: '>=16.8.0' - peerDependenciesMeta: - '@types/react': - optional: true - react-day-picker@9.14.0: resolution: {integrity: sha512-tBaoDWjPwe0M5pGrum4H0SR6Lyk+BO9oHnp9JbKpGKW2mlraNPgP9BMfsg5pWpwrssARmeqk7YBl2oXutZTaHA==} engines: {node: '>=18'} @@ -5428,9 +5248,6 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -5483,16 +5300,10 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - std-env@4.1.0: - resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} - stdin-discarder@0.2.2: resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} engines: {node: '>=18'} @@ -5549,18 +5360,10 @@ packages: style-to-object@1.0.14: resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} - sucrase@3.35.1: - resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - tagged-tag@1.0.0: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} - tailwind-merge@2.6.1: - resolution: {integrity: sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==} - tailwind-merge@3.6.0: resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} @@ -5575,22 +5378,9 @@ packages: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - tinyexec@1.2.2: resolution: {integrity: sha512-M/Q0B2cp4K7kynaT/vnED1j8TlLY+Pp7C6Wl2bl/7u/F0mUVwdyOpwomQb8JpYLitHUssAJRmLZdMCGsrx7i+g==} engines: {node: '>=18'} @@ -5599,10 +5389,6 @@ packages: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} - tinyrainbow@3.1.0: - resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} - engines: {node: '>=14.0.0'} - tldts-core@7.1.1: resolution: {integrity: sha512-v9zYcyFEAJBeyG7g4+y/HFL9i2cHqpV+9cHohNZIhA6xjO2MSVgijFgx6quQaRBDzM5FT8fs5NPjsNITOhlCzg==} @@ -5626,19 +5412,12 @@ packages: resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} engines: {node: '>=16'} - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - ts-morph@26.0.0: resolution: {integrity: sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==} @@ -5652,25 +5431,6 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsup@8.5.1: - resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - '@microsoft/api-extractor': ^7.36.0 - '@swc/core': ^1 - postcss: ^8.4.12 - typescript: '>=4.5.0' - peerDependenciesMeta: - '@microsoft/api-extractor': - optional: true - '@swc/core': - optional: true - postcss: - optional: true - typescript: - optional: true - tw-animate-css@1.4.0: resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} @@ -5691,14 +5451,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} - engines: {node: '>=14.17'} - hasBin: true - - ufo@1.6.4: - resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} - undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -5905,47 +5657,6 @@ packages: yaml: optional: true - vitest@4.1.7: - resolution: {integrity: sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.7 - '@vitest/browser-preview': 4.1.7 - '@vitest/browser-webdriverio': 4.1.7 - '@vitest/coverage-istanbul': 4.1.7 - '@vitest/coverage-v8': 4.1.7 - '@vitest/ui': 4.1.7 - happy-dom: '*' - jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@opentelemetry/api': - optional: true - '@types/node': - optional: true - '@vitest/browser-playwright': - optional: true - '@vitest/browser-preview': - optional: true - '@vitest/browser-webdriverio': - optional: true - '@vitest/coverage-istanbul': - optional: true - '@vitest/coverage-v8': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} @@ -5963,11 +5674,6 @@ packages: engines: {node: ^16.13.0 || >=18.0.0} hasBin: true - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -6215,10 +5921,10 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@base-ui/react@1.5.0(@date-fns/tz@1.5.0)(@types/react@19.2.15)(date-fns@4.3.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@base-ui/react@1.6.0(@date-fns/tz@1.5.0)(@types/react@19.2.15)(date-fns@4.3.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@babel/runtime': 7.29.2 - '@base-ui/utils': 0.2.9(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@base-ui/utils': 0.3.1(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@floating-ui/react-dom': 2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@floating-ui/utils': 0.2.11 react: 19.2.6 @@ -6229,7 +5935,7 @@ snapshots: '@types/react': 19.2.15 date-fns: 4.3.0 - '@base-ui/utils@0.2.9(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@base-ui/utils@0.3.1(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@babel/runtime': 7.29.2 '@floating-ui/utils': 0.2.11 @@ -6774,14 +6480,6 @@ snapshots: optionalDependencies: '@types/node': 22.19.19 - '@inquirer/confirm@6.0.13(@types/node@25.9.1)': - dependencies: - '@inquirer/core': 11.1.10(@types/node@25.9.1) - '@inquirer/type': 4.0.5(@types/node@25.9.1) - optionalDependencies: - '@types/node': 25.9.1 - optional: true - '@inquirer/core@11.1.10(@types/node@22.19.19)': dependencies: '@inquirer/ansi': 2.0.5 @@ -6794,19 +6492,6 @@ snapshots: optionalDependencies: '@types/node': 22.19.19 - '@inquirer/core@11.1.10(@types/node@25.9.1)': - dependencies: - '@inquirer/ansi': 2.0.5 - '@inquirer/figures': 2.0.5 - '@inquirer/type': 4.0.5(@types/node@25.9.1) - cli-width: 4.1.0 - fast-wrap-ansi: 0.2.2 - mute-stream: 3.0.0 - signal-exit: 4.1.0 - optionalDependencies: - '@types/node': 25.9.1 - optional: true - '@inquirer/external-editor@1.0.3(@types/node@25.9.1)': dependencies: chardet: 2.1.1 @@ -6820,11 +6505,6 @@ snapshots: optionalDependencies: '@types/node': 22.19.19 - '@inquirer/type@4.0.5(@types/node@25.9.1)': - optionalDependencies: - '@types/node': 25.9.1 - optional: true - '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -7122,9 +6802,11 @@ snapshots: '@radix-ui/primitive@1.1.4': {} - '@radix-ui/react-accessible-icon@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/primitive@1.1.6': {} + + '@radix-ui/react-accessible-icon@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-visually-hidden': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -7148,44 +6830,43 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-accordion@1.2.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-accordion@1.2.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collapsible': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-collection': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/primitive': 1.1.6 + '@radix-ui/react-collapsible': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collection': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-alert-dialog@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-alert-dialog@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.4 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dialog': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slot': 1.2.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dialog': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-alert-dialog@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-alert-dialog@1.1.20(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 + '@radix-ui/primitive': 1.1.6 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dialog': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dialog': 1.1.20(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -7201,28 +6882,28 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-arrow@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-arrow@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-aspect-ratio@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-aspect-ratio@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-avatar@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-avatar@1.2.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-is-hydrated': 0.1.1(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) @@ -7232,10 +6913,11 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-avatar@1.2.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-avatar@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/primitive': 1.1.6 + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-is-hydrated': 0.1.1(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) @@ -7245,13 +6927,13 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-checkbox@1.3.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-checkbox@1.3.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.4 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-previous': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-size': 1.1.2(@types/react@19.2.15)(react@19.2.6) @@ -7261,15 +6943,14 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-checkbox@1.3.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-checkbox@1.3.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 + '@radix-ui/primitive': 1.1.6 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-previous': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-size': 1.1.2(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) @@ -7293,14 +6974,14 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-collapsible@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-collapsible@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.4 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 @@ -7309,15 +6990,15 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-collapsible@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-collapsible@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 + '@radix-ui/primitive': 1.1.6 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) @@ -7337,24 +7018,24 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-collection@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-collection@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slot': 1.2.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -7373,13 +7054,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.15 - '@radix-ui/react-context-menu@2.3.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-context-menu@2.3.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-menu': 2.1.18(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/primitive': 1.1.6 + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-menu': 2.1.21(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -7398,41 +7079,25 @@ snapshots: optionalDependencies: '@types/react': 19.2.15 - '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-context@1.2.0(@types/react@19.2.15)(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) - aria-hidden: 1.2.6 react: 19.2.6 - react-dom: 19.2.6(react@19.2.6) - react-remove-scroll: 2.7.2(@types/react@19.2.15)(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 - '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-dialog@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-dialog@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.4 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-focus-scope': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-portal': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slot': 1.2.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) aria-hidden: 1.2.6 react: 19.2.6 @@ -7442,20 +7107,21 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-dialog@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-dialog@1.1.20(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 + '@radix-ui/primitive': 1.1.6 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-focus-scope': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-portal': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-slot': 1.3.0(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) aria-hidden: 1.2.6 react: 19.2.6 react-dom: 19.2.6(react@19.2.6) @@ -7489,19 +7155,6 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-dismissable-layer@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': - dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-escape-keydown': 1.1.2(@types/react@19.2.15)(react@19.2.6) - react: 19.2.6 - react-dom: 19.2.6(react@19.2.6) - optionalDependencies: - '@types/react': 19.2.15 - '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-dismissable-layer@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.4 @@ -7515,15 +7168,13 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-dropdown-menu@2.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-dismissable-layer@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 + '@radix-ui/primitive': 1.1.6 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-menu': 2.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -7545,11 +7196,20 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.15)(react@19.2.6)': + '@radix-ui/react-dropdown-menu@2.1.21(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: + '@radix-ui/primitive': 1.1.6 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-menu': 2.1.21(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) '@radix-ui/react-focus-guards@1.1.4(@types/react@19.2.15)(react@19.2.6)': dependencies: @@ -7568,21 +7228,10 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) - react: 19.2.6 - react-dom: 19.2.6(react@19.2.6) - optionalDependencies: - '@types/react': 19.2.15 - '@types/react-dom': 19.2.3(@types/react@19.2.15) - - '@radix-ui/react-focus-scope@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-focus-scope@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) @@ -7604,31 +7253,31 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-form@0.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-form@0.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 + '@radix-ui/primitive': 1.1.6 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-label': 2.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-label': 2.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-hover-card@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-hover-card@1.1.20(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 + '@radix-ui/primitive': 1.1.6 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-popper': 1.3.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-portal': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popper': 1.3.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -7658,32 +7307,32 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-label@2.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-label@2.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-menu@2.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-menu@2.1.18(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collection': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collection': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-focus-scope': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-popper': 1.3.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-portal': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popper': 1.3.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-roving-focus': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slot': 1.2.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.15)(react@19.2.6) aria-hidden: 1.2.6 react: 19.2.6 @@ -7693,22 +7342,22 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-menu@2.1.18(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-menu@2.1.21(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collection': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/primitive': 1.1.6 + '@radix-ui/react-collection': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-focus-scope': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-popper': 1.3.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-portal': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-roving-focus': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popper': 1.3.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-slot': 1.3.0(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.15)(react@19.2.6) aria-hidden: 1.2.6 @@ -7719,18 +7368,18 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-menubar@1.1.18(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-menubar@1.1.21(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collection': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/primitive': 1.1.6 + '@radix-ui/react-collection': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-menu': 2.1.18(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-roving-focus': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-menu': 2.1.21(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -7759,39 +7408,39 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-navigation-menu@1.2.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-navigation-menu@1.2.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collection': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/primitive': 1.1.6 + '@radix-ui/react-collection': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-previous': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-visually-hidden': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-one-time-password-field@0.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-one-time-password-field@0.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/number': 1.1.2 - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collection': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/primitive': 1.1.6 + '@radix-ui/react-collection': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-roving-focus': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-is-hydrated': 0.1.1(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) @@ -7801,14 +7450,14 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-password-toggle-field@0.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-password-toggle-field@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 + '@radix-ui/primitive': 1.1.6 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-is-hydrated': 0.1.1(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 @@ -7817,20 +7466,20 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-popover@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-popover@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.4 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-focus-scope': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-popper': 1.3.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-portal': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popper': 1.3.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slot': 1.2.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) aria-hidden: 1.2.6 react: 19.2.6 @@ -7840,21 +7489,21 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-popover@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-popover@1.1.20(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 + '@radix-ui/primitive': 1.1.6 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-focus-scope': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-popper': 1.3.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-portal': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popper': 1.3.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-slot': 1.3.0(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) aria-hidden: 1.2.6 react: 19.2.6 react-dom: 19.2.6(react@19.2.6) @@ -7863,13 +7512,13 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-popper@1.3.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-popper@1.3.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@floating-ui/react-dom': 2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-arrow': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-arrow': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-rect': 1.1.2(@types/react@19.2.15)(react@19.2.6) @@ -7881,13 +7530,13 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-popper@1.3.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-popper@1.3.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@floating-ui/react-dom': 2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-arrow': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-arrow': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-rect': 1.1.2(@types/react@19.2.15)(react@19.2.6) @@ -7899,16 +7548,6 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-portal@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': - dependencies: - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) - react: 19.2.6 - react-dom: 19.2.6(react@19.2.6) - optionalDependencies: - '@types/react': 19.2.15 - '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-portal@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -7919,10 +7558,10 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-portal@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -7948,27 +7587,18 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': - dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) - react: 19.2.6 - react-dom: 19.2.6(react@19.2.6) - optionalDependencies: - '@types/react': 19.2.15 - '@types/react-dom': 19.2.3(@types/react@19.2.15) - - '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-presence@1.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-slot': 1.2.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-primitive@2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-slot': 1.2.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -7984,28 +7614,19 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-progress@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-primitive@2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-radio-group@1.4.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-progress@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-roving-focus': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-previous': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-size': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -8030,32 +7651,32 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-radio-group@1.4.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/primitive': 1.1.6 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.2(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-roving-focus@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-roving-focus@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collection': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collection': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 @@ -8064,17 +7685,19 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-roving-focus@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-roving-focus@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collection': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/primitive': 1.1.6 + '@radix-ui/react-collection': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-is-hydrated': 0.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -8098,15 +7721,15 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-scroll-area@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-scroll-area@1.2.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/number': 1.1.2 - '@radix-ui/primitive': 1.1.4 + '@radix-ui/primitive': 1.1.6 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 @@ -8115,28 +7738,28 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-select@2.3.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-select@2.3.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/number': 1.1.2 '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collection': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collection': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-focus-scope': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-popper': 1.3.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-portal': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popper': 1.3.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slot': 1.2.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-previous': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-visually-hidden': 1.2.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) aria-hidden: 1.2.6 react: 19.2.6 react-dom: 19.2.6(react@19.2.6) @@ -8145,28 +7768,28 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-select@2.3.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-select@2.3.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/number': 1.1.2 - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collection': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/primitive': 1.1.6 + '@radix-ui/react-collection': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-focus-scope': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-popper': 1.3.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-portal': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popper': 1.3.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-slot': 1.3.0(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-previous': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-visually-hidden': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) aria-hidden: 1.2.6 react: 19.2.6 react-dom: 19.2.6(react@19.2.6) @@ -8184,24 +7807,24 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-separator@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-separator@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-slider@1.4.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-slider@1.4.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/number': 1.1.2 '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collection': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collection': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-previous': 1.1.2(@types/react@19.2.15)(react@19.2.6) @@ -8212,16 +7835,16 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-slider@1.4.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-slider@1.4.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/number': 1.1.2 - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collection': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/primitive': 1.1.6 + '@radix-ui/react-collection': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-previous': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-size': 1.1.2(@types/react@19.2.15)(react@19.2.6) @@ -8238,41 +7861,12 @@ snapshots: optionalDependencies: '@types/react': 19.2.15 - '@radix-ui/react-slot@1.2.4(@types/react@19.2.15)(react@19.2.6)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - react: 19.2.6 - optionalDependencies: - '@types/react': 19.2.15 - - '@radix-ui/react-slot@1.2.5(@types/react@19.2.15)(react@19.2.6)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - react: 19.2.6 - optionalDependencies: - '@types/react': 19.2.15 - - '@radix-ui/react-slot@1.3.0(@types/react@19.2.15)(react@19.2.6)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - react: 19.2.6 - optionalDependencies: - '@types/react': 19.2.15 - - '@radix-ui/react-switch@1.3.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-slot@1.3.0(@types/react@19.2.15)(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-previous': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-size': 1.1.2(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 - react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 - '@types/react-dom': 19.2.3(@types/react@19.2.15) '@radix-ui/react-switch@1.3.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: @@ -8289,31 +7883,29 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-switch@1.3.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/primitive': 1.1.6 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.2(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-tabs@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-tabs@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.4 '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-roving-focus': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) @@ -8321,77 +7913,77 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-tabs@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-tabs@1.1.18(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/primitive': 1.1.6 + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-roving-focus': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-toast@1.2.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-toast@1.2.20(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collection': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/primitive': 1.1.6 + '@radix-ui/react-collection': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-portal': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-visually-hidden': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-toggle-group@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-toggle-group@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/primitive': 1.1.6 + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-roving-focus': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-toggle': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toggle': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-toggle@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-toggle@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/primitive': 1.1.6 + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-toolbar@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-toolbar@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/primitive': 1.1.6 + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-roving-focus': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-separator': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-toggle-group': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-separator': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toggle-group': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -8418,20 +8010,21 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-tooltip@1.2.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-tooltip@1.2.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.4 + '@radix-ui/primitive': 1.1.6 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-popper': 1.3.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-portal': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slot': 1.2.5(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-visually-hidden': 1.2.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popper': 1.3.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -8466,6 +8059,15 @@ snapshots: optionalDependencies: '@types/react': 19.2.15 + '@radix-ui/react-use-controllable-state@1.2.4(@types/react@19.2.15)(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.6 + '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.15)(react@19.2.6)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) @@ -8494,6 +8096,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.15 + '@radix-ui/react-use-escape-keydown@1.1.3(@types/react@19.2.15)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + '@radix-ui/react-use-is-hydrated@0.1.1(@types/react@19.2.15)(react@19.2.6)': dependencies: react: 19.2.6 @@ -8547,18 +8156,18 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-visually-hidden@1.2.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-visually-hidden@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-visually-hidden@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-visually-hidden@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -8926,17 +8535,10 @@ snapshots: '@types/aws-lambda@8.10.161': {} - '@types/chai@5.2.3': - dependencies: - '@types/deep-eql': 4.0.2 - assertion-error: 2.0.1 - '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 - '@types/deep-eql@4.0.2': {} - '@types/estree-jsx@1.0.5': dependencies: '@types/estree': 1.0.9 @@ -8989,48 +8591,6 @@ snapshots: '@ungap/structured-clone@1.3.1': {} - '@vitest/expect@4.1.7': - dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.7 - '@vitest/utils': 4.1.7 - chai: 6.2.2 - tinyrainbow: 3.1.0 - - '@vitest/mocker@4.1.7(msw@2.14.6(@types/node@25.9.1)(typescript@6.0.3))(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))': - dependencies: - '@vitest/spy': 4.1.7 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - msw: 2.14.6(@types/node@25.9.1)(typescript@6.0.3) - vite: 8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) - - '@vitest/pretty-format@4.1.7': - dependencies: - tinyrainbow: 3.1.0 - - '@vitest/runner@4.1.7': - dependencies: - '@vitest/utils': 4.1.7 - pathe: 2.0.3 - - '@vitest/snapshot@4.1.7': - dependencies: - '@vitest/pretty-format': 4.1.7 - '@vitest/utils': 4.1.7 - magic-string: 0.30.21 - pathe: 2.0.3 - - '@vitest/spy@4.1.7': {} - - '@vitest/utils@4.1.7': - dependencies: - '@vitest/pretty-format': 4.1.7 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 - accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -9076,8 +8636,6 @@ snapshots: ansi-styles@6.2.3: {} - any-promise@1.3.0: {} - arg@5.0.2: {} argparse@1.0.10: @@ -9096,8 +8654,6 @@ snapshots: array-union@2.1.0: {} - assertion-error@2.0.1: {} - ast-types@0.16.1: dependencies: tslib: 2.8.1 @@ -9184,11 +8740,6 @@ snapshots: dependencies: run-applescript: 7.1.0 - bundle-require@5.1.0(esbuild@0.27.7): - dependencies: - esbuild: 0.27.7 - load-tsconfig: 0.2.5 - bytes@3.1.2: {} cac@6.7.14: {} @@ -9209,8 +8760,6 @@ snapshots: ccount@2.0.1: {} - chai@6.2.2: {} - chalk@5.6.2: {} character-entities-html4@2.1.0: {} @@ -9258,10 +8807,10 @@ snapshots: cmdk@1.1.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dialog': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dialog': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) transitivePeerDependencies: @@ -9286,8 +8835,6 @@ snapshots: commander@14.0.3: {} - commander@4.1.1: {} - compare-func@2.0.0: dependencies: array-ify: 1.0.0 @@ -9311,12 +8858,8 @@ snapshots: compute-scroll-into-view@3.1.1: {} - confbox@0.1.8: {} - confbox@0.2.4: {} - consola@3.4.2: {} - content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -9491,8 +9034,6 @@ snapshots: es-module-lexer@1.7.0: {} - es-module-lexer@2.1.0: {} - es-object-atoms@1.1.2: dependencies: es-errors: 1.3.0 @@ -9655,8 +9196,6 @@ snapshots: exit-hook@2.2.1: {} - expect-type@1.3.0: {} - express-rate-limit@8.5.2(express@5.2.1): dependencies: express: 5.2.1 @@ -9810,12 +9349,6 @@ snapshots: locate-path: 5.0.0 path-exists: 4.0.0 - fix-dts-default-cjs-exports@1.0.1: - dependencies: - magic-string: 0.30.21 - mlly: 1.8.2 - rollup: 4.60.4 - formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -9945,15 +9478,15 @@ snapshots: dependencies: '@fumadocs/tailwind': 0.0.5(@tailwindcss/oxide@4.3.0)(tailwindcss@4.3.0) '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collapsible': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dialog': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-popover': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popover': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slot': 1.2.5(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-tabs': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) class-variance-authority: 0.7.1 fumadocs-core: 16.9.1(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.15)(lucide-react@0.545.0(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react-router@7.15.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(zod@4.4.3) lucide-react: 1.16.0(react@19.2.6) @@ -10309,8 +9842,6 @@ snapshots: jose@6.2.3: {} - joycon@3.1.1: {} - js-tokens@4.0.0: {} js-yaml@3.14.2: @@ -10397,8 +9928,6 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 - lilconfig@3.1.3: {} - lines-and-columns@1.2.4: {} lint-staged@16.4.0: @@ -10419,8 +9948,6 @@ snapshots: rfdc: 1.4.1 wrap-ansi: 9.0.2 - load-tsconfig@0.2.5: {} - locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -10938,13 +10465,6 @@ snapshots: minimist@1.2.8: {} - mlly@1.8.2: - dependencies: - acorn: 8.16.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.4 - morgan@1.10.1: dependencies: basic-auth: 2.0.1 @@ -11000,40 +10520,8 @@ snapshots: transitivePeerDependencies: - '@types/node' - msw@2.14.6(@types/node@25.9.1)(typescript@6.0.3): - dependencies: - '@inquirer/confirm': 6.0.13(@types/node@25.9.1) - '@mswjs/interceptors': 0.41.9 - '@open-draft/deferred-promise': 3.0.0 - '@types/statuses': 2.0.6 - cookie: 1.1.1 - graphql: 16.14.0 - headers-polyfill: 5.0.1 - is-node-process: 1.2.0 - outvariant: 1.4.3 - path-to-regexp: 6.3.0 - picocolors: 1.1.1 - rettime: 0.11.11 - statuses: 2.0.2 - strict-event-emitter: 0.5.1 - tough-cookie: 6.0.1 - type-fest: 5.6.0 - until-async: 3.0.2 - yargs: 17.7.2 - optionalDependencies: - typescript: 6.0.3 - transitivePeerDependencies: - - '@types/node' - optional: true - mute-stream@3.0.0: {} - mz@2.7.0: - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - nanoid@3.3.12: {} negotiator@0.6.3: {} @@ -11072,8 +10560,6 @@ snapshots: object-treeify@1.1.33: {} - obug@2.1.1: {} - octokit@5.0.5: dependencies: '@octokit/app': 16.1.2 @@ -11222,30 +10708,14 @@ snapshots: pify@4.0.1: {} - pirates@4.0.7: {} - pkce-challenge@5.0.1: {} - pkg-types@1.3.1: - dependencies: - confbox: 0.1.8 - mlly: 1.8.2 - pathe: 2.0.3 - pkg-types@2.3.1: dependencies: confbox: 0.2.4 exsolve: 1.0.8 pathe: 2.0.3 - postcss-load-config@6.0.1(jiti@2.7.0)(postcss@8.5.15)(yaml@2.9.0): - dependencies: - lilconfig: 3.1.3 - optionalDependencies: - jiti: 2.7.0 - postcss: 8.5.15 - yaml: 2.9.0 - postcss-selector-parser@7.1.1: dependencies: cssesc: 3.0.0 @@ -11287,63 +10757,63 @@ snapshots: queue-microtask@1.2.3: {} - radix-ui@1.6.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): - dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-accessible-icon': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-accordion': 1.2.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-alert-dialog': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-arrow': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-aspect-ratio': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-avatar': 1.2.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-checkbox': 1.3.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-collapsible': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-collection': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + radix-ui@1.6.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + dependencies: + '@radix-ui/primitive': 1.1.6 + '@radix-ui/react-accessible-icon': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-accordion': 1.2.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-alert-dialog': 1.1.20(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-arrow': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-aspect-ratio': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-avatar': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-checkbox': 1.3.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collapsible': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collection': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context-menu': 2.3.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-dialog': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context-menu': 2.3.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dialog': 1.1.20(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-dropdown-menu': 2.1.18(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dropdown-menu': 2.1.21(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-focus-scope': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-form': 0.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-hover-card': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-label': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-menu': 2.1.18(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-menubar': 1.1.18(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-navigation-menu': 1.2.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-one-time-password-field': 0.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-password-toggle-field': 0.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-popover': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-popper': 1.3.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-portal': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-progress': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-radio-group': 1.4.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-roving-focus': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-scroll-area': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-select': 2.3.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-separator': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slider': 1.4.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-form': 0.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-hover-card': 1.1.20(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-label': 2.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-menu': 2.1.21(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-menubar': 1.1.21(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-navigation-menu': 1.2.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-one-time-password-field': 0.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-password-toggle-field': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popover': 1.1.20(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popper': 1.3.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-progress': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-radio-group': 1.4.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-scroll-area': 1.2.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-select': 2.3.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-separator': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slider': 1.4.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-slot': 1.3.0(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-switch': 1.3.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-tabs': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-toast': 1.2.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-toggle': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-toggle-group': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-toolbar': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-tooltip': 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-switch': 1.3.4(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-tabs': 1.1.18(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toast': 1.2.20(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toggle': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toggle-group': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toolbar': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-tooltip': 1.2.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-escape-keydown': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-escape-keydown': 1.1.3(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-is-hydrated': 0.1.1(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) '@radix-ui/react-use-size': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-visually-hidden': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -11366,14 +10836,6 @@ snapshots: iconv-lite: 0.7.2 unpipe: 1.0.0 - react-day-picker@10.0.1(@types/react@19.2.15)(react@19.2.6): - dependencies: - '@date-fns/tz': 1.5.0 - date-fns: 4.3.0 - react: 19.2.6 - optionalDependencies: - '@types/react': 19.2.15 - react-day-picker@9.14.0(react@19.2.6): dependencies: '@date-fns/tz': 1.5.0 @@ -11803,8 +11265,6 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 - siginfo@2.0.0: {} - signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -11848,12 +11308,8 @@ snapshots: sprintf-js@1.0.3: {} - stackback@0.0.2: {} - statuses@2.0.2: {} - std-env@4.1.0: {} - stdin-discarder@0.2.2: {} strict-event-emitter@0.5.1: {} @@ -11910,20 +11366,8 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - sucrase@3.35.1: - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - commander: 4.1.1 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.7 - tinyglobby: 0.2.16 - ts-interface-checker: 0.1.13 - tagged-tag@1.0.0: {} - tailwind-merge@2.6.1: {} - tailwind-merge@3.6.0: {} tailwindcss@4.3.0: {} @@ -11932,20 +11376,8 @@ snapshots: term-size@2.2.1: {} - thenify-all@1.6.0: - dependencies: - thenify: 3.3.1 - - thenify@3.3.1: - dependencies: - any-promise: 1.3.0 - tiny-invariant@1.3.3: {} - tinybench@2.9.0: {} - - tinyexec@0.3.2: {} - tinyexec@1.2.2: {} tinyglobby@0.2.16: @@ -11953,8 +11385,6 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 - tinyrainbow@3.1.0: {} - tldts-core@7.1.1: {} tldts@7.1.1: @@ -11973,14 +11403,10 @@ snapshots: dependencies: tldts: 7.1.1 - tree-kill@1.2.2: {} - trim-lines@3.0.1: {} trough@2.2.0: {} - ts-interface-checker@0.1.13: {} - ts-morph@26.0.0: dependencies: '@ts-morph/common': 0.27.0 @@ -11999,34 +11425,6 @@ snapshots: tslib@2.8.1: {} - tsup@8.5.1(jiti@2.7.0)(postcss@8.5.15)(typescript@6.0.3)(yaml@2.9.0): - dependencies: - bundle-require: 5.1.0(esbuild@0.27.7) - cac: 6.7.14 - chokidar: 4.0.3 - consola: 3.4.2 - debug: 4.4.3 - esbuild: 0.27.7 - fix-dts-default-cjs-exports: 1.0.1 - joycon: 3.1.1 - picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@2.7.0)(postcss@8.5.15)(yaml@2.9.0) - resolve-from: 5.0.0 - rollup: 4.60.4 - source-map: 0.7.6 - sucrase: 3.35.1 - tinyexec: 0.3.2 - tinyglobby: 0.2.16 - tree-kill: 1.2.2 - optionalDependencies: - postcss: 8.5.15 - typescript: 6.0.3 - transitivePeerDependencies: - - jiti - - supports-color - - tsx - - yaml - tw-animate-css@1.4.0: {} type-fest@5.6.0: @@ -12046,10 +11444,6 @@ snapshots: typescript@5.9.3: {} - typescript@6.0.3: {} - - ufo@1.6.4: {} - undici-types@6.21.0: {} undici-types@7.24.6: {} @@ -12212,47 +11606,6 @@ snapshots: jiti: 2.7.0 yaml: 2.9.0 - vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0): - dependencies: - lightningcss: 1.32.0 - picomatch: 4.0.4 - postcss: 8.5.15 - rolldown: 1.0.2 - tinyglobby: 0.2.16 - optionalDependencies: - '@types/node': 25.9.1 - esbuild: 0.27.7 - fsevents: 2.3.3 - jiti: 2.7.0 - yaml: 2.9.0 - - vitest@4.1.7(@types/node@25.9.1)(msw@2.14.6(@types/node@25.9.1)(typescript@6.0.3))(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)): - dependencies: - '@vitest/expect': 4.1.7 - '@vitest/mocker': 4.1.7(msw@2.14.6(@types/node@25.9.1)(typescript@6.0.3))(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.7 - '@vitest/runner': 4.1.7 - '@vitest/snapshot': 4.1.7 - '@vitest/spy': 4.1.7 - '@vitest/utils': 4.1.7 - es-module-lexer: 2.1.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 4.1.0 - tinybench: 2.9.0 - tinyexec: 1.2.2 - tinyglobby: 0.2.16 - tinyrainbow: 3.1.0 - vite: 8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 25.9.1 - transitivePeerDependencies: - - msw - web-namespaces@2.0.1: {} web-streams-polyfill@3.3.3: {} @@ -12265,11 +11618,6 @@ snapshots: dependencies: isexe: 3.1.5 - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 28ec63f..886c7f4 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,4 +1,3 @@ packages: - - 'packages' - 'docs' neverBuiltDependencies: [] diff --git a/public/r/action-bar.json b/public/r/action-bar.json new file mode 100644 index 0000000..09cc4ca --- /dev/null +++ b/public/r/action-bar.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "'use client';\r\n\r\nimport * as DirectionPrimitive from '@radix-ui/react-direction';\r\nimport * as SlotPrimitive from '@radix-ui/react-slot';\r\nimport * as React from 'react';\r\nimport * as ReactDOM from 'react-dom';\r\nimport { Button } from '@/components/ui/button';\r\nimport { useComposedRefs } from '@/lib/component-refs';\r\nimport { cn } from '@/lib/utils';\r\nimport { useAsRef } from '@/hooks/use-as-ref';\r\nimport { useIsomorphicLayoutEffect } from '@/hooks/use-isomorphic-layout-effect';\r\n\r\nconst ROOT_NAME = 'ActionBar';\r\nconst GROUP_NAME = 'ActionBarGroup';\r\nconst ITEM_NAME = 'ActionBarItem';\r\nconst CLOSE_NAME = 'ActionBarClose';\r\nconst SEPARATOR_NAME = 'ActionBarSeparator';\r\nconst ITEM_SELECT = 'actionbar.itemSelect';\r\nconst ENTRY_FOCUS = 'actionbarFocusGroup.onEntryFocus';\r\nconst EVENT_OPTIONS = { bubbles: false, cancelable: true };\r\n\r\ntype Direction = 'ltr' | 'rtl';\r\ntype Orientation = 'horizontal' | 'vertical';\r\n\r\ninterface DivProps extends React.ComponentProps<'div'> {\r\n asChild?: boolean;\r\n}\r\n\r\ntype RootElement = React.ComponentRef;\r\ntype ItemElement = React.ComponentRef;\r\ntype CloseElement = React.ComponentRef;\r\n\r\nfunction focusFirst(candidates: React.RefObject[], preventScroll = false) {\r\n const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;\r\n for (const candidateRef of candidates) {\r\n const candidate = candidateRef.current;\r\n if (!candidate) continue;\r\n if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;\r\n candidate.focus({ preventScroll });\r\n if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;\r\n }\r\n}\r\n\r\nfunction wrapArray(array: T[], startIndex: number) {\r\n return array.map((_, index) => array[(startIndex + index) % array.length] as T);\r\n}\r\n\r\nfunction getDirectionAwareKey(key: string, dir?: Direction) {\r\n if (dir !== 'rtl') return key;\r\n return key === 'ArrowLeft' ? 'ArrowRight' : key === 'ArrowRight' ? 'ArrowLeft' : key;\r\n}\r\n\r\ninterface ItemData {\r\n id: string;\r\n ref: React.RefObject;\r\n disabled: boolean;\r\n}\r\n\r\ninterface ActionBarContextValue {\r\n onOpenChange?: (open: boolean) => void;\r\n dir: Direction;\r\n orientation: Orientation;\r\n loop: boolean;\r\n}\r\n\r\nconst ActionBarContext = React.createContext(null);\r\n\r\nfunction useActionBarContext(consumerName: string) {\r\n const context = React.useContext(ActionBarContext);\r\n if (!context) {\r\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\r\n }\r\n return context;\r\n}\r\n\r\ninterface FocusContextValue {\r\n tabStopId: string | null;\r\n onItemFocus: (tabStopId: string) => void;\r\n onItemShiftTab: () => void;\r\n onFocusableItemAdd: () => void;\r\n onFocusableItemRemove: () => void;\r\n onItemRegister: (item: ItemData) => void;\r\n onItemUnregister: (id: string) => void;\r\n getItems: () => ItemData[];\r\n}\r\n\r\nconst FocusContext = React.createContext(null);\r\n\r\nfunction useFocusContext(consumerName: string) {\r\n const context = React.useContext(FocusContext);\r\n if (!context) {\r\n throw new Error(`\\`${consumerName}\\` must be used within \\`FocusProvider\\``);\r\n }\r\n return context;\r\n}\r\n\r\ninterface ActionBarProps extends DivProps {\r\n open?: boolean;\r\n onOpenChange?: (open: boolean) => void;\r\n onEscapeKeyDown?: (event: KeyboardEvent) => void;\r\n align?: 'start' | 'center' | 'end';\r\n alignOffset?: number;\r\n side?: 'top' | 'bottom';\r\n sideOffset?: number;\r\n portalContainer?: Element | DocumentFragment | null;\r\n dir?: Direction;\r\n orientation?: Orientation;\r\n loop?: boolean;\r\n}\r\n\r\nfunction ActionBar(props: ActionBarProps) {\r\n const {\r\n open = false,\r\n onOpenChange,\r\n onEscapeKeyDown,\r\n side = 'bottom',\r\n alignOffset = 0,\r\n align = 'center',\r\n sideOffset = 16,\r\n portalContainer: portalContainerProp,\r\n dir: dirProp,\r\n orientation = 'horizontal',\r\n loop = true,\r\n className,\r\n style,\r\n ref,\r\n asChild,\r\n ...rootProps\r\n } = props;\r\n\r\n const [mounted, setMounted] = React.useState(false);\r\n\r\n const rootRef = React.useRef(null);\r\n const composedRef = useComposedRefs(ref, rootRef);\r\n\r\n const propsRef = useAsRef({\r\n onEscapeKeyDown,\r\n onOpenChange,\r\n });\r\n\r\n const dir = DirectionPrimitive.useDirection(dirProp);\r\n\r\n React.useLayoutEffect(() => {\r\n setMounted(true);\r\n }, []);\r\n\r\n React.useEffect(() => {\r\n if (!open) return;\r\n\r\n const ownerDocument = rootRef.current?.ownerDocument ?? document;\r\n\r\n function onKeyDown(event: KeyboardEvent) {\r\n if (event.key === 'Escape') {\r\n propsRef.current.onEscapeKeyDown?.(event);\r\n if (!event.defaultPrevented) {\r\n propsRef.current.onOpenChange?.(false);\r\n }\r\n }\r\n }\r\n\r\n ownerDocument.addEventListener('keydown', onKeyDown);\r\n return () => ownerDocument.removeEventListener('keydown', onKeyDown);\r\n }, [open, propsRef]);\r\n\r\n const contextValue = React.useMemo(\r\n () => ({\r\n dir,\r\n loop,\r\n onOpenChange,\r\n orientation,\r\n }),\r\n [onOpenChange, dir, orientation, loop],\r\n );\r\n\r\n const portalContainer = portalContainerProp ?? (mounted ? globalThis.document?.body : null);\r\n\r\n if (!portalContainer || !open) return null;\r\n\r\n const RootPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n {ReactDOM.createPortal(\r\n ,\r\n portalContainer,\r\n )}\r\n \r\n );\r\n}\r\n\r\nfunction ActionBarSelection(props: DivProps) {\r\n const { className, asChild, ...selectionProps } = props;\r\n\r\n const SelectionPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\nfunction ActionBarGroup(props: DivProps) {\r\n const {\r\n onBlur: onBlurProp,\r\n onFocus: onFocusProp,\r\n onMouseDown: onMouseDownProp,\r\n className,\r\n asChild,\r\n ref,\r\n ...groupProps\r\n } = props;\r\n\r\n const [tabStopId, setTabStopId] = React.useState(null);\r\n const [isTabbingBackOut, setIsTabbingBackOut] = React.useState(false);\r\n const [focusableItemCount, setFocusableItemCount] = React.useState(0);\r\n\r\n const groupRef = React.useRef(null);\r\n const composedRef = useComposedRefs(ref, groupRef);\r\n const isClickFocusRef = React.useRef(false);\r\n const itemsRef = React.useRef>(new Map());\r\n\r\n const { dir, orientation } = useActionBarContext(GROUP_NAME);\r\n\r\n const onItemFocus = React.useCallback((tabStopId: string) => {\r\n setTabStopId(tabStopId);\r\n }, []);\r\n\r\n const onItemShiftTab = React.useCallback(() => {\r\n setIsTabbingBackOut(true);\r\n }, []);\r\n\r\n const onFocusableItemAdd = React.useCallback(() => {\r\n setFocusableItemCount((prevCount) => prevCount + 1);\r\n }, []);\r\n\r\n const onFocusableItemRemove = React.useCallback(() => {\r\n setFocusableItemCount((prevCount) => prevCount - 1);\r\n }, []);\r\n\r\n const onItemRegister = React.useCallback((item: ItemData) => {\r\n itemsRef.current.set(item.id, item);\r\n }, []);\r\n\r\n const onItemUnregister = React.useCallback((id: string) => {\r\n itemsRef.current.delete(id);\r\n }, []);\r\n\r\n const getItems = React.useCallback(() => {\r\n return Array.from(itemsRef.current.values())\r\n .filter((item) => item.ref.current)\r\n .sort((a, b) => {\r\n const elementA = a.ref.current;\r\n const elementB = b.ref.current;\r\n if (!elementA || !elementB) return 0;\r\n const position = elementA.compareDocumentPosition(elementB);\r\n if (position & Node.DOCUMENT_POSITION_FOLLOWING) {\r\n return -1;\r\n }\r\n if (position & Node.DOCUMENT_POSITION_PRECEDING) {\r\n return 1;\r\n }\r\n return 0;\r\n });\r\n }, []);\r\n\r\n const onBlur = React.useCallback(\r\n (event: React.FocusEvent) => {\r\n onBlurProp?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n setIsTabbingBackOut(false);\r\n },\r\n [onBlurProp],\r\n );\r\n\r\n const onFocus = React.useCallback(\r\n (event: React.FocusEvent) => {\r\n onFocusProp?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n const isKeyboardFocus = !isClickFocusRef.current;\r\n if (event.target === event.currentTarget && isKeyboardFocus && !isTabbingBackOut) {\r\n const entryFocusEvent = new CustomEvent(ENTRY_FOCUS, EVENT_OPTIONS);\r\n event.currentTarget.dispatchEvent(entryFocusEvent);\r\n\r\n if (!entryFocusEvent.defaultPrevented) {\r\n const items = Array.from(itemsRef.current.values()).filter((item) => !item.disabled);\r\n const currentItem = items.find((item) => item.id === tabStopId);\r\n\r\n const candidateItems = [currentItem, ...items].filter(Boolean) as ItemData[];\r\n const candidateRefs = candidateItems.map((item) => item.ref);\r\n focusFirst(candidateRefs, false);\r\n }\r\n }\r\n isClickFocusRef.current = false;\r\n },\r\n [onFocusProp, isTabbingBackOut, tabStopId],\r\n );\r\n\r\n const onMouseDown = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n onMouseDownProp?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n isClickFocusRef.current = true;\r\n },\r\n [onMouseDownProp],\r\n );\r\n\r\n const focusContextValue = React.useMemo(\r\n () => ({\r\n getItems,\r\n onFocusableItemAdd,\r\n onFocusableItemRemove,\r\n onItemFocus,\r\n onItemRegister,\r\n onItemShiftTab,\r\n onItemUnregister,\r\n tabStopId,\r\n }),\r\n [\r\n tabStopId,\r\n onItemFocus,\r\n onItemShiftTab,\r\n onFocusableItemAdd,\r\n onFocusableItemRemove,\r\n onItemRegister,\r\n onItemUnregister,\r\n getItems,\r\n ],\r\n );\r\n\r\n const GroupPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n \r\n \r\n );\r\n}\r\n\r\ninterface ActionBarItemProps extends Omit, 'onSelect'> {\r\n onSelect?: (event: Event) => void;\r\n}\r\n\r\nfunction ActionBarItem(props: ActionBarItemProps) {\r\n const {\r\n onSelect,\r\n onClick: onClickProp,\r\n onFocus: onFocusProp,\r\n onKeyDown: onKeyDownProp,\r\n onMouseDown: onMouseDownProp,\r\n className,\r\n disabled,\r\n ref,\r\n ...itemProps\r\n } = props;\r\n\r\n const itemRef = React.useRef(null);\r\n const composedRef = useComposedRefs(ref, itemRef);\r\n const isMouseClickRef = React.useRef(false);\r\n\r\n const { onOpenChange, dir, orientation, loop } = useActionBarContext(ITEM_NAME);\r\n const focusContext = useFocusContext(ITEM_NAME);\r\n\r\n const itemId = React.useId();\r\n const isTabStop = focusContext.tabStopId === itemId;\r\n\r\n useIsomorphicLayoutEffect(() => {\r\n focusContext.onItemRegister({\r\n disabled: !!disabled,\r\n id: itemId,\r\n ref: itemRef,\r\n });\r\n\r\n if (!disabled) {\r\n focusContext.onFocusableItemAdd();\r\n }\r\n\r\n return () => {\r\n focusContext.onItemUnregister(itemId);\r\n if (!disabled) {\r\n focusContext.onFocusableItemRemove();\r\n }\r\n };\r\n }, [focusContext, itemId, disabled]);\r\n\r\n const onClick = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n onClickProp?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n const item = itemRef.current;\r\n if (!item) return;\r\n\r\n const itemSelectEvent = new CustomEvent(ITEM_SELECT, {\r\n bubbles: true,\r\n cancelable: true,\r\n });\r\n\r\n item.addEventListener(ITEM_SELECT, (event) => onSelect?.(event), {\r\n once: true,\r\n });\r\n\r\n item.dispatchEvent(itemSelectEvent);\r\n\r\n if (!itemSelectEvent.defaultPrevented) {\r\n onOpenChange?.(false);\r\n }\r\n },\r\n [onClickProp, onOpenChange, onSelect],\r\n );\r\n\r\n const onFocus = React.useCallback(\r\n (event: React.FocusEvent) => {\r\n onFocusProp?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n focusContext.onItemFocus(itemId);\r\n isMouseClickRef.current = false;\r\n },\r\n [onFocusProp, focusContext, itemId],\r\n );\r\n\r\n const onKeyDown = React.useCallback(\r\n (event: React.KeyboardEvent) => {\r\n onKeyDownProp?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n if (event.key === 'Tab' && event.shiftKey) {\r\n focusContext.onItemShiftTab();\r\n return;\r\n }\r\n\r\n if (event.target !== event.currentTarget) return;\r\n\r\n const key = getDirectionAwareKey(event.key, dir);\r\n let focusIntent: 'first' | 'last' | 'prev' | 'next' | undefined;\r\n\r\n if (orientation === 'horizontal') {\r\n if (key === 'ArrowLeft') focusIntent = 'prev';\r\n else if (key === 'ArrowRight') focusIntent = 'next';\r\n else if (key === 'Home') focusIntent = 'first';\r\n else if (key === 'End') focusIntent = 'last';\r\n } else {\r\n if (key === 'ArrowUp') focusIntent = 'prev';\r\n else if (key === 'ArrowDown') focusIntent = 'next';\r\n else if (key === 'Home') focusIntent = 'first';\r\n else if (key === 'End') focusIntent = 'last';\r\n }\r\n\r\n if (focusIntent !== undefined) {\r\n if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;\r\n event.preventDefault();\r\n\r\n const items = focusContext.getItems().filter((item) => !item.disabled);\r\n let candidateRefs = items.map((item) => item.ref);\r\n\r\n if (focusIntent === 'last') {\r\n candidateRefs.reverse();\r\n } else if (focusIntent === 'prev' || focusIntent === 'next') {\r\n if (focusIntent === 'prev') candidateRefs.reverse();\r\n const currentIndex = candidateRefs.findIndex(\r\n (ref) => ref.current === event.currentTarget,\r\n );\r\n candidateRefs = loop\r\n ? wrapArray(candidateRefs, currentIndex + 1)\r\n : candidateRefs.slice(currentIndex + 1);\r\n }\r\n\r\n queueMicrotask(() => focusFirst(candidateRefs));\r\n }\r\n },\r\n [onKeyDownProp, focusContext, dir, orientation, loop],\r\n );\r\n\r\n const onMouseDown = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n onMouseDownProp?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n isMouseClickRef.current = true;\r\n\r\n if (disabled) {\r\n event.preventDefault();\r\n } else {\r\n focusContext.onItemFocus(itemId);\r\n }\r\n },\r\n [onMouseDownProp, focusContext, itemId, disabled],\r\n );\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\ninterface ActionBarCloseProps extends React.ComponentProps<'button'> {\r\n asChild?: boolean;\r\n}\r\n\r\nfunction ActionBarClose(props: ActionBarCloseProps) {\r\n const { asChild, className, onClick, ...closeProps } = props;\r\n\r\n const { onOpenChange } = useActionBarContext(CLOSE_NAME);\r\n\r\n const onCloseClick = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n onClick?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n onOpenChange?.(false);\r\n },\r\n [onOpenChange, onClick],\r\n );\r\n\r\n const ClosePrimitive = asChild ? SlotPrimitive.Slot : 'button';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\ninterface ActionBarSeparatorProps extends DivProps {\r\n orientation?: Orientation;\r\n}\r\n\r\nfunction ActionBarSeparator(props: ActionBarSeparatorProps) {\r\n const { orientation: orientationProp, asChild, className, ...separatorProps } = props;\r\n\r\n const context = useActionBarContext(SEPARATOR_NAME);\r\n const orientation = orientationProp ?? context.orientation;\r\n\r\n const SeparatorPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\nexport {\r\n ActionBar,\r\n ActionBarClose,\r\n ActionBarGroup,\r\n ActionBarItem,\r\n type ActionBarProps,\r\n ActionBarSelection,\r\n ActionBarSeparator,\r\n};\r\n", + "path": "registry/components/action-bar.tsx", + "target": "@components/action-bar.tsx", + "type": "registry:component" + } + ], + "name": "action-bar", + "registryDependencies": [ + "button", + "@kombase/component-refs", + "@kombase/use-as-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "type": "registry:component" +} diff --git a/public/r/alert-dialog.json b/public/r/alert-dialog.json new file mode 100644 index 0000000..da1076e --- /dev/null +++ b/public/r/alert-dialog.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-alert-dialog"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';\r\nimport React from 'react';\r\nimport { cn } from '@/lib/utils';\r\nimport { buttonVariants } from '@/components/ui/button';\r\n\r\nconst AlertDialog = ({ ...props }: React.ComponentProps) => (\r\n \r\n);\r\nAlertDialog.displayName = 'AlertDialog';\r\n\r\nconst AlertDialogTrigger = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>((props, ref) => (\r\n \r\n));\r\nAlertDialogTrigger.displayName = AlertDialogPrimitive.Trigger.displayName;\r\n\r\nconst AlertDialogPortal = ({\r\n ...props\r\n}: React.ComponentProps) => (\r\n \r\n);\r\nAlertDialogPortal.displayName = 'AlertDialogPortal';\r\n\r\nconst AlertDialogOverlay = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nAlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;\r\n\r\nconst AlertDialogContent = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n \r\n \r\n \r\n));\r\nAlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;\r\n\r\nconst AlertDialogHeader = React.forwardRef>(\r\n ({ className, ...props }, ref) => (\r\n \r\n ),\r\n);\r\nAlertDialogHeader.displayName = 'AlertDialogHeader';\r\n\r\nconst AlertDialogFooter = React.forwardRef>(\r\n ({ className, ...props }, ref) => (\r\n \r\n ),\r\n);\r\nAlertDialogFooter.displayName = 'AlertDialogFooter';\r\n\r\nconst AlertDialogTitle = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nAlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;\r\n\r\nconst AlertDialogDescription = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nAlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;\r\n\r\nconst AlertDialogAction = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nAlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;\r\n\r\nconst AlertDialogCancel = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nAlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;\r\n\r\nexport {\r\n AlertDialog,\r\n AlertDialogAction,\r\n AlertDialogCancel,\r\n AlertDialogContent,\r\n AlertDialogDescription,\r\n AlertDialogFooter,\r\n AlertDialogHeader,\r\n AlertDialogOverlay,\r\n AlertDialogPortal,\r\n AlertDialogTitle,\r\n AlertDialogTrigger,\r\n};\r\n", + "path": "registry/ui/alert-dialog.tsx", + "target": "@ui/alert-dialog.tsx", + "type": "registry:ui" + } + ], + "name": "alert-dialog", + "registryDependencies": ["button"], + "type": "registry:ui" +} diff --git a/public/r/alert.json b/public/r/alert.json new file mode 100644 index 0000000..bec70b0 --- /dev/null +++ b/public/r/alert.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "'use client';\r\n\r\nimport { cva, type VariantProps } from 'class-variance-authority';\r\nimport * as React from 'react';\r\n\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst alertVariants = cva(\r\n \"group/alert relative grid w-full gap-1 border bg-background px-4 py-3 text-left text-sm after:absolute after:-inset-y-px after:-left-px after:w-0.5 has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4\",\r\n {\r\n defaultVariants: {\r\n variant: 'default',\r\n },\r\n variants: {\r\n variant: {\r\n default: 'bg-card text-card-foreground after:bg-foreground',\r\n destructive:\r\n 'bg-card text-destructive after:bg-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current',\r\n },\r\n },\r\n },\r\n);\r\n\r\nconst Alert = React.forwardRef<\r\n HTMLDivElement,\r\n React.ComponentPropsWithoutRef<'div'> & VariantProps\r\n>(({ className, variant, ...props }, ref) => (\r\n \r\n));\r\nAlert.displayName = 'Alert';\r\n\r\nconst AlertTitle = React.forwardRef>(\r\n ({ className, ...props }, ref) => (\r\n svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground',\r\n className,\r\n )}\r\n data-slot=\"alert-title\"\r\n ref={ref}\r\n {...props}\r\n />\r\n ),\r\n);\r\nAlertTitle.displayName = 'AlertTitle';\r\n\r\nconst AlertDescription = React.forwardRef>(\r\n ({ className, ...props }, ref) => (\r\n \r\n ),\r\n);\r\nAlertDescription.displayName = 'AlertDescription';\r\n\r\nconst AlertAction = React.forwardRef>(\r\n ({ className, ...props }, ref) => (\r\n \r\n ),\r\n);\r\nAlertAction.displayName = 'AlertAction';\r\n\r\nexport { Alert, AlertAction, AlertDescription, AlertTitle };\r\n", + "path": "registry/ui/alert.tsx", + "target": "@ui/alert.tsx", + "type": "registry:ui" + } + ], + "name": "alert", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/avatar-group.json b/public/r/avatar-group.json new file mode 100644 index 0000000..6fe0ade --- /dev/null +++ b/public/r/avatar-group.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-avatar"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as SlotPrimitive from '@radix-ui/react-slot';\r\nimport { cva, type VariantProps } from 'class-variance-authority';\r\nimport * as React from 'react';\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst avatarGroupVariants = cva('flex items-center', {\r\n compoundVariants: [\r\n {\r\n className: '-space-x-1',\r\n dir: 'ltr',\r\n orientation: 'horizontal',\r\n },\r\n {\r\n className: 'flex-row-reverse -space-x-1 space-x-reverse',\r\n dir: 'rtl',\r\n orientation: 'horizontal',\r\n },\r\n {\r\n className: '-space-y-1',\r\n dir: 'ltr',\r\n orientation: 'vertical',\r\n },\r\n {\r\n className: 'flex-col-reverse -space-y-1 space-y-reverse',\r\n dir: 'rtl',\r\n orientation: 'vertical',\r\n },\r\n ],\r\n defaultVariants: {\r\n dir: 'ltr',\r\n orientation: 'horizontal',\r\n },\r\n variants: {\r\n dir: {\r\n ltr: '',\r\n rtl: '',\r\n },\r\n orientation: {\r\n horizontal: 'flex-row',\r\n vertical: 'flex-col',\r\n },\r\n },\r\n});\r\n\r\ninterface AvatarGroupProps\r\n extends Omit, 'dir'>,\r\n VariantProps {\r\n size?: number;\r\n max?: number;\r\n asChild?: boolean;\r\n reverse?: boolean;\r\n renderOverflow?: (count: number) => React.ReactNode;\r\n}\r\n\r\nfunction AvatarGroup(props: AvatarGroupProps) {\r\n const {\r\n orientation = 'horizontal',\r\n dir = 'ltr',\r\n size = 40,\r\n max,\r\n asChild,\r\n reverse = false,\r\n renderOverflow,\r\n className,\r\n children,\r\n ...rootProps\r\n } = props;\r\n\r\n const childrenArray = React.Children.toArray(children).filter(React.isValidElement);\r\n const itemCount = childrenArray.length;\r\n const shouldTruncate = max && itemCount > max;\r\n const visibleItems = shouldTruncate ? childrenArray.slice(0, max - 1) : childrenArray;\r\n const overflowCount = shouldTruncate ? itemCount - (max - 1) : 0;\r\n const totalRenderedItems = shouldTruncate ? max : itemCount;\r\n\r\n const RootPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n {visibleItems.map((child, index) => (\r\n \r\n ))}\r\n {shouldTruncate && (\r\n \r\n +{overflowCount}\r\n \r\n )\r\n }\r\n dir={dir}\r\n index={visibleItems.length}\r\n itemCount={totalRenderedItems}\r\n key=\"overflow\"\r\n orientation={orientation}\r\n reverse={reverse}\r\n size={size}\r\n />\r\n )}\r\n \r\n );\r\n}\r\n\r\ninterface AvatarGroupItemProps\r\n extends Omit, 'dir'>,\r\n VariantProps {\r\n child: React.ReactNode;\r\n index: number;\r\n itemCount: number;\r\n size: number;\r\n reverse: boolean;\r\n}\r\n\r\nfunction AvatarGroupItem(props: AvatarGroupItemProps) {\r\n const {\r\n child,\r\n index,\r\n size,\r\n orientation,\r\n dir = 'ltr',\r\n reverse = false,\r\n itemCount,\r\n className,\r\n style,\r\n ...itemProps\r\n } = props;\r\n\r\n const maskStyle = React.useMemo(() => {\r\n let maskImage = '';\r\n\r\n let shouldMask = false;\r\n\r\n if (orientation === 'vertical' && dir === 'rtl' && reverse) {\r\n shouldMask = index !== itemCount - 1;\r\n } else {\r\n shouldMask = reverse ? index < itemCount - 1 : index > 0;\r\n }\r\n\r\n if (shouldMask) {\r\n const maskRadius = size / 2;\r\n const maskOffset = size / 4 + size / 10;\r\n\r\n if (orientation === 'vertical') {\r\n if (dir === 'ltr') {\r\n if (reverse) {\r\n maskImage = `radial-gradient(circle ${maskRadius}px at 50% ${size + maskOffset}px, transparent 99%, white 100%)`;\r\n } else {\r\n maskImage = `radial-gradient(circle ${maskRadius}px at 50% -${maskOffset}px, transparent 99%, white 100%)`;\r\n }\r\n } else {\r\n if (reverse) {\r\n maskImage = `radial-gradient(circle ${maskRadius}px at 50% -${maskOffset}px, transparent 99%, white 100%)`;\r\n } else {\r\n maskImage = `radial-gradient(circle ${maskRadius}px at 50% ${size + maskOffset}px, transparent 99%, white 100%)`;\r\n }\r\n }\r\n } else {\r\n if (dir === 'ltr') {\r\n if (reverse) {\r\n maskImage = `radial-gradient(circle ${maskRadius}px at ${size + maskOffset}px 50%, transparent 99%, white 100%)`;\r\n } else {\r\n maskImage = `radial-gradient(circle ${maskRadius}px at -${maskOffset}px 50%, transparent 99%, white 100%)`;\r\n }\r\n } else {\r\n if (reverse) {\r\n maskImage = `radial-gradient(circle ${maskRadius}px at -${maskOffset}px 50%, transparent 99%, white 100%)`;\r\n } else {\r\n maskImage = `radial-gradient(circle ${maskRadius}px at ${size + maskOffset}px 50%, transparent 99%, white 100%)`;\r\n }\r\n }\r\n }\r\n }\r\n\r\n return {\r\n height: size,\r\n maskImage,\r\n width: size,\r\n };\r\n }, [size, index, orientation, dir, reverse, itemCount]);\r\n\r\n return (\r\n \r\n {child}\r\n \r\n );\r\n}\r\n\r\nexport { AvatarGroup };\r\n", + "path": "registry/components/avatar-group.tsx", + "target": "@components/avatar-group.tsx", + "type": "registry:component" + } + ], + "name": "avatar-group", + "type": "registry:component" +} diff --git a/public/r/avatar.json b/public/r/avatar.json new file mode 100644 index 0000000..4bf4cfb --- /dev/null +++ b/public/r/avatar.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-avatar"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as AvatarPrimitive from '@radix-ui/react-avatar';\r\nimport * as React from 'react';\r\n\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst Avatar = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef & {\r\n size?: 'default' | 'sm' | 'lg';\r\n }\r\n>(({ className, size = 'default', ...props }, ref) => (\r\n \r\n));\r\nAvatar.displayName = AvatarPrimitive.Root.displayName;\r\n\r\nconst AvatarImage = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nAvatarImage.displayName = AvatarPrimitive.Image.displayName;\r\n\r\nconst AvatarFallback = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nAvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;\r\n\r\nconst AvatarBadge = React.forwardRef>(\r\n ({ className, ...props }, ref) => (\r\n svg]:hidden',\r\n 'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',\r\n 'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',\r\n className,\r\n )}\r\n data-slot=\"avatar-badge\"\r\n ref={ref}\r\n {...props}\r\n />\r\n ),\r\n);\r\nAvatarBadge.displayName = 'AvatarBadge';\r\n\r\nconst AvatarGroup = React.forwardRef>(\r\n ({ className, ...props }, ref) => (\r\n \r\n ),\r\n);\r\nAvatarGroup.displayName = 'AvatarGroup';\r\n\r\nconst AvatarGroupCount = React.forwardRef>(\r\n ({ className, ...props }, ref) => (\r\n svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3',\r\n className,\r\n )}\r\n data-slot=\"avatar-group-count\"\r\n ref={ref}\r\n {...props}\r\n />\r\n ),\r\n);\r\nAvatarGroupCount.displayName = 'AvatarGroupCount';\r\n\r\nexport { Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage };\r\n", + "path": "registry/ui/avatar.tsx", + "target": "@ui/avatar.tsx", + "type": "registry:ui" + } + ], + "name": "avatar", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/badge.json b/public/r/badge.json new file mode 100644 index 0000000..fefcfc4 --- /dev/null +++ b/public/r/badge.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["class-variance-authority"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as Slot from '@radix-ui/react-slot';\r\nimport { cva, type VariantProps } from 'class-variance-authority';\r\nimport * as React from 'react';\r\n\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst badgeVariants = cva(\r\n 'inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3',\r\n {\r\n defaultVariants: {\r\n variant: 'default',\r\n },\r\n variants: {\r\n variant: {\r\n default: 'bg-primary text-primary-foreground [a&]:hover:bg-primary/90',\r\n destructive:\r\n 'bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90',\r\n ghost: '[a&]:hover:bg-accent [a&]:hover:text-accent-foreground',\r\n link: 'text-primary underline-offset-4 [a&]:hover:underline',\r\n outline:\r\n 'border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',\r\n secondary: 'bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',\r\n },\r\n },\r\n },\r\n);\r\n\r\nconst Badge = React.forwardRef<\r\n HTMLSpanElement,\r\n React.ComponentPropsWithoutRef<'span'> &\r\n VariantProps & { asChild?: boolean }\r\n>(({ className, variant = 'default', asChild = false, ...props }, ref) => {\r\n const Comp = asChild ? Slot.Root : 'span';\r\n\r\n return (\r\n \r\n );\r\n});\r\nBadge.displayName = 'Badge';\r\n\r\nexport { Badge, badgeVariants };\r\n", + "path": "registry/ui/badge.tsx", + "target": "@ui/badge.tsx", + "type": "registry:ui" + } + ], + "name": "badge", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/button.json b/public/r/button.json new file mode 100644 index 0000000..348ad27 --- /dev/null +++ b/public/r/button.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-slot", "class-variance-authority"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as Slot from '@radix-ui/react-slot';\r\nimport { cva, type VariantProps } from 'class-variance-authority';\r\nimport * as React from 'react';\r\n\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst buttonVariants = cva(\r\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive cursor-pointer\",\r\n {\r\n defaultVariants: {\r\n size: 'default',\r\n variant: 'default',\r\n },\r\n variants: {\r\n size: {\r\n default: 'h-9 px-4 py-2 has-[>svg]:px-3',\r\n icon: 'size-9',\r\n 'icon-lg': 'size-10',\r\n 'icon-sm': 'size-8',\r\n 'icon-xs': \"size-6 rounded-md [&_svg:not([class*='size-'])]:size-3\",\r\n lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',\r\n sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',\r\n xs: \"h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3\",\r\n },\r\n variant: {\r\n default: 'bg-primary text-white hover:bg-primary/90',\r\n destructive:\r\n 'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',\r\n ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',\r\n link: 'text-primary underline-offset-4 hover:underline',\r\n outline:\r\n 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',\r\n secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',\r\n },\r\n },\r\n },\r\n);\r\n\r\nconst Button = React.forwardRef<\r\n HTMLButtonElement,\r\n React.ComponentProps<'button'> &\r\n VariantProps & {\r\n asChild?: boolean;\r\n }\r\n>(({ className, variant = 'default', size = 'default', asChild = false, ...props }, ref) => {\r\n const Comp = asChild ? Slot.Root : 'button';\r\n\r\n return (\r\n \r\n );\r\n});\r\n\r\nButton.displayName = 'Button';\r\n\r\nexport { Button, buttonVariants };\r\n", + "path": "registry/ui/button.tsx", + "target": "@ui/button.tsx", + "type": "registry:ui" + } + ], + "name": "button", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/calendar.json b/public/r/calendar.json new file mode 100644 index 0000000..c37f1c2 --- /dev/null +++ b/public/r/calendar.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["react-day-picker", "dayjs"], + "files": [ + { + "content": "'use client';\r\n\r\nimport { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';\r\nimport * as React from 'react';\r\nimport { type DayButton, DayPicker, getDefaultClassNames, type Locale } from 'react-day-picker';\r\nimport { Button, buttonVariants } from '@/components/ui/button';\r\nimport { cn } from '@/lib/utils';\r\n\r\nfunction Calendar({\r\n className,\r\n classNames,\r\n showOutsideDays = true,\r\n captionLayout = 'label',\r\n buttonVariant = 'ghost',\r\n locale,\r\n formatters,\r\n components,\r\n ...props\r\n}: React.ComponentProps & {\r\n buttonVariant?: React.ComponentProps['variant'];\r\n}) {\r\n const defaultClassNames = getDefaultClassNames();\r\n\r\n return (\r\n svg]:rotate-180`,\r\n String.raw`rtl:**:[.rdp-button\\_previous>svg]:rotate-180`,\r\n className,\r\n )}\r\n classNames={{\r\n button_next: cn(\r\n buttonVariants({ variant: buttonVariant }),\r\n 'size-(--cell-size) p-0 select-none aria-disabled:opacity-50',\r\n defaultClassNames.button_next,\r\n ),\r\n button_previous: cn(\r\n buttonVariants({ variant: buttonVariant }),\r\n 'size-(--cell-size) p-0 select-none aria-disabled:opacity-50',\r\n defaultClassNames.button_previous,\r\n ),\r\n caption_label: cn(\r\n 'font-medium select-none',\r\n captionLayout === 'label'\r\n ? 'cn-calendar-caption text-sm'\r\n : 'cn-calendar-caption-label flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground',\r\n defaultClassNames.caption_label,\r\n ),\r\n day: cn(\r\n 'group/day relative aspect-square h-full w-full rounded-(--cell-radius) p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)',\r\n props.showWeekNumber\r\n ? '[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)'\r\n : '[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)',\r\n defaultClassNames.day,\r\n ),\r\n disabled: cn('text-muted-foreground opacity-50', defaultClassNames.disabled),\r\n dropdown: cn('absolute inset-0 bg-popover opacity-0', defaultClassNames.dropdown),\r\n dropdown_root: cn(\r\n 'cn-calendar-dropdown-root relative rounded-(--cell-radius)',\r\n defaultClassNames.dropdown_root,\r\n ),\r\n dropdowns: cn(\r\n 'flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium',\r\n defaultClassNames.dropdowns,\r\n ),\r\n hidden: cn('invisible', defaultClassNames.hidden),\r\n month: cn('flex w-full flex-col gap-4', defaultClassNames.month),\r\n month_caption: cn(\r\n 'flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)',\r\n defaultClassNames.month_caption,\r\n ),\r\n month_grid: cn('w-full border-collapse', defaultClassNames.month_grid),\r\n months: cn('relative flex flex-col gap-4 md:flex-row', defaultClassNames.months),\r\n nav: cn(\r\n 'absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1',\r\n defaultClassNames.nav,\r\n ),\r\n outside: cn(\r\n 'text-muted-foreground aria-selected:text-muted-foreground',\r\n defaultClassNames.outside,\r\n ),\r\n range_end: cn(\r\n 'relative isolate z-0 rounded-r-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-muted',\r\n defaultClassNames.range_end,\r\n ),\r\n range_middle: cn('rounded-none', defaultClassNames.range_middle),\r\n range_start: cn(\r\n 'relative isolate z-0 rounded-l-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-muted',\r\n defaultClassNames.range_start,\r\n ),\r\n root: cn('w-fit', defaultClassNames.root),\r\n today: cn(\r\n 'rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none',\r\n defaultClassNames.today,\r\n ),\r\n week: cn('mt-2 flex w-full', defaultClassNames.week),\r\n week_number: cn(\r\n 'text-[0.8rem] text-muted-foreground select-none',\r\n defaultClassNames.week_number,\r\n ),\r\n week_number_header: cn('w-(--cell-size) select-none', defaultClassNames.week_number_header),\r\n weekday: cn(\r\n 'flex-1 rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none',\r\n defaultClassNames.weekday,\r\n ),\r\n weekdays: cn('flex', defaultClassNames.weekdays),\r\n ...classNames,\r\n }}\r\n components={{\r\n Chevron: ({ className, orientation, ...props }) => {\r\n if (orientation === 'left') {\r\n return ;\r\n }\r\n\r\n if (orientation === 'right') {\r\n return ;\r\n }\r\n\r\n return ;\r\n },\r\n DayButton: ({ ...props }) => ,\r\n Root: ({ className, rootRef, ...props }) => {\r\n return
;\r\n },\r\n WeekNumber: ({ children, ...props }) => {\r\n return (\r\n
\r\n
\r\n {children}\r\n
\r\n
\r\n \r\n {Array.from({ length: 1 }).map((_, i) => (\r\n \r\n {Array.from({ length: columnCount }).map((_, j) => (\r\n \r\n \r\n \r\n ))}\r\n \r\n ))}\r\n \r\n \r\n {Array.from({ length: rowCount }).map((_, i) => (\r\n \r\n {Array.from({ length: columnCount }).map((_, j) => (\r\n \r\n \r\n \r\n ))}\r\n \r\n ))}\r\n \r\n
\r\n
\r\n {withPagination ? (\r\n
\r\n \r\n
\r\n
\r\n \r\n \r\n
\r\n
\r\n \r\n
\r\n
\r\n \r\n \r\n \r\n \r\n
\r\n
\r\n
\r\n ) : null}\r\n
\r\n );\r\n}\r\n", + "path": "registry/components/data-table/data-table-skeleton.tsx", + "target": "@components/data-table/data-table-skeleton.tsx", + "type": "registry:component" + } + ], + "name": "data-table-skeleton", + "registryDependencies": ["skeleton", "table"], + "type": "registry:component" +} diff --git a/public/r/data-table-slider-filter.json b/public/r/data-table-slider-filter.json new file mode 100644 index 0000000..ee45575 --- /dev/null +++ b/public/r/data-table-slider-filter.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import type { Column } from '@tanstack/react-table';\r\nimport { PlusCircle, XCircle } from 'lucide-react';\r\nimport * as React from 'react';\r\nimport { Button } from '@/components/ui/button';\r\nimport { Input } from '@/components/ui/input';\r\nimport { Label } from '@/components/ui/label';\r\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';\r\nimport { Separator } from '@/components/ui/separator';\r\nimport { Slider } from '@/components/ui/slider';\r\nimport { cn } from '@/lib/utils';\r\n\r\ninterface Range {\r\n min: number;\r\n max: number;\r\n}\r\n\r\ntype RangeValue = [number, number];\r\n\r\nfunction getIsValidRange(value: unknown): value is RangeValue {\r\n return (\r\n Array.isArray(value) &&\r\n value.length === 2 &&\r\n typeof value[0] === 'number' &&\r\n typeof value[1] === 'number'\r\n );\r\n}\r\n\r\nfunction parseValuesAsNumbers(value: unknown): RangeValue | undefined {\r\n if (\r\n Array.isArray(value) &&\r\n value.length === 2 &&\r\n value.every((v) => (typeof v === 'string' || typeof v === 'number') && !Number.isNaN(v))\r\n ) {\r\n return [Number(value[0]), Number(value[1])];\r\n }\r\n\r\n return undefined;\r\n}\r\n\r\ninterface DataTableSliderFilterProps {\r\n column: Column;\r\n title?: string;\r\n}\r\n\r\nexport function DataTableSliderFilter({ column, title }: DataTableSliderFilterProps) {\r\n const id = React.useId();\r\n\r\n const columnFilterValue = parseValuesAsNumbers(column.getFilterValue());\r\n\r\n const defaultRange = column.columnDef.meta?.range;\r\n const unit = column.columnDef.meta?.unit;\r\n\r\n const { min, max, step } = React.useMemo(() => {\r\n let minValue = 0;\r\n let maxValue = 100;\r\n\r\n if (defaultRange && getIsValidRange(defaultRange)) {\r\n [minValue, maxValue] = defaultRange;\r\n } else {\r\n const values = column.getFacetedMinMaxValues();\r\n if (values && Array.isArray(values) && values.length === 2) {\r\n const [facetMinValue, facetMaxValue] = values;\r\n if (typeof facetMinValue === 'number' && typeof facetMaxValue === 'number') {\r\n minValue = facetMinValue;\r\n maxValue = facetMaxValue;\r\n }\r\n }\r\n }\r\n\r\n const rangeSize = maxValue - minValue;\r\n const step =\r\n rangeSize <= 20\r\n ? 1\r\n : rangeSize <= 100\r\n ? Math.ceil(rangeSize / 20)\r\n : Math.ceil(rangeSize / 50);\r\n\r\n return { max: maxValue, min: minValue, step };\r\n }, [column, defaultRange]);\r\n\r\n const range = React.useMemo((): RangeValue => {\r\n return columnFilterValue ?? [min, max];\r\n }, [columnFilterValue, min, max]);\r\n\r\n const formatValue = React.useCallback((value: number) => {\r\n return value.toLocaleString(undefined, { maximumFractionDigits: 0 });\r\n }, []);\r\n\r\n const onFromInputChange = React.useCallback(\r\n (event: React.ChangeEvent) => {\r\n const numValue = Number(event.target.value);\r\n if (!Number.isNaN(numValue) && numValue >= min && numValue <= range[1]) {\r\n column.setFilterValue([numValue, range[1]]);\r\n }\r\n },\r\n [column, min, range],\r\n );\r\n\r\n const onToInputChange = React.useCallback(\r\n (event: React.ChangeEvent) => {\r\n const numValue = Number(event.target.value);\r\n if (!Number.isNaN(numValue) && numValue <= max && numValue >= range[0]) {\r\n column.setFilterValue([range[0], numValue]);\r\n }\r\n },\r\n [column, max, range],\r\n );\r\n\r\n const onSliderValueChange = React.useCallback(\r\n (value: RangeValue) => {\r\n if (Array.isArray(value) && value.length === 2) {\r\n column.setFilterValue(value);\r\n }\r\n },\r\n [column],\r\n );\r\n\r\n const onReset = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n if (event.target instanceof HTMLDivElement) {\r\n event.stopPropagation();\r\n }\r\n column.setFilterValue(undefined);\r\n },\r\n [column],\r\n );\r\n\r\n return (\r\n \r\n \r\n
\r\n ) : (\r\n \r\n )}\r\n {title}\r\n {columnFilterValue ? (\r\n <>\r\n \r\n {formatValue(columnFilterValue[0])} - {formatValue(columnFilterValue[1])}\r\n {unit ? ` ${unit}` : ''}\r\n \r\n ) : null}\r\n \r\n \r\n \r\n
\r\n

\r\n {title}\r\n

\r\n
\r\n \r\n
\r\n \r\n {unit && (\r\n \r\n {unit}\r\n \r\n )}\r\n
\r\n \r\n
\r\n \r\n {unit && (\r\n \r\n {unit}\r\n \r\n )}\r\n
\r\n
\r\n \r\n \r\n
\r\n \r\n
\r\n \r\n );\r\n}\r\n", + "path": "registry/components/data-table/data-table-slider-filter.tsx", + "target": "@components/data-table/data-table-slider-filter.tsx", + "type": "registry:component" + } + ], + "name": "data-table-slider-filter", + "registryDependencies": ["button", "input", "label", "popover", "separator", "slider"], + "type": "registry:component" +} diff --git a/public/r/data-table-toolbar.json b/public/r/data-table-toolbar.json new file mode 100644 index 0000000..27f845d --- /dev/null +++ b/public/r/data-table-toolbar.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import type { Column, Table } from '@tanstack/react-table';\r\nimport { X } from 'lucide-react';\r\nimport * as React from 'react';\r\n\r\nimport { DataTableDateFilter } from '@/components/data-table/data-table-date-filter';\r\nimport { DataTableFacetedFilter } from '@/components/data-table/data-table-faceted-filter';\r\nimport { DataTableSliderFilter } from '@/components/data-table/data-table-slider-filter';\r\nimport { DataTableViewOptions } from '@/components/data-table/data-table-view-options';\r\nimport { Button } from '@/components/ui/button';\r\nimport { DebouncedInput } from '@/components/ui/debounced-input';\r\nimport { cn } from '@/lib/utils';\r\n\r\ninterface DataTableToolbarProps extends React.ComponentProps<'div'> {\r\n table: Table;\r\n viewOptions?: boolean;\r\n hideFilter?: boolean;\r\n actions?: React.ReactNode;\r\n align?: 'start' | 'center' | 'end';\r\n}\r\n\r\nexport function DataTableToolbar({\r\n table,\r\n children,\r\n actions,\r\n className,\r\n viewOptions = true,\r\n hideFilter = false,\r\n align = 'start',\r\n ...props\r\n}: DataTableToolbarProps) {\r\n const advancedFilters = (table.options.meta as any)?.filters as any[] | undefined;\r\n const setAdvancedFilters = (table.options.meta as any)?.setFilters as\r\n | ((val: any) => void)\r\n | undefined;\r\n const isAdvancedActive = (table.options.meta as any)?.isAdvanceFilter ?? false;\r\n\r\n const isFiltered = isAdvancedActive\r\n ? advancedFilters && advancedFilters.length > 0\r\n : table.getState().columnFilters.length > 0;\r\n\r\n const columns = React.useMemo(\r\n () => table.getAllColumns().filter((column) => column.getCanFilter()),\r\n [table, table.options.columns],\r\n );\r\n\r\n const onReset = React.useCallback(() => {\r\n if (isAdvancedActive && setAdvancedFilters) {\r\n setAdvancedFilters([]);\r\n } else {\r\n table.resetColumnFilters();\r\n }\r\n }, [table, isAdvancedActive, setAdvancedFilters]);\r\n\r\n return (\r\n \r\n \r\n {!hideFilter &&\r\n columns.map((column) => (\r\n \r\n ))}\r\n {children}\r\n {isFiltered && !isAdvancedActive && (\r\n \r\n \r\n Reset\r\n \r\n )}\r\n \r\n
\r\n {actions}\r\n {viewOptions && }\r\n
\r\n \r\n );\r\n}\r\ninterface DataTableToolbarFilterProps {\r\n column: Column;\r\n table: Table;\r\n}\r\n\r\nfunction DataTableToolbarFilter({ column, table }: DataTableToolbarFilterProps) {\r\n {\r\n const columnMeta = column.columnDef.meta;\r\n const debounceMs = (table.options.meta as any)?.debounceMs ?? 300;\r\n\r\n const onFilterRender = React.useCallback(() => {\r\n if (!columnMeta?.variant) return null;\r\n\r\n switch (columnMeta.variant) {\r\n case 'text':\r\n return (\r\n column.setFilterValue(val)}\r\n placeholder={columnMeta.placeholder ?? columnMeta.label}\r\n value={(column.getFilterValue() as string) ?? ''}\r\n />\r\n );\r\n\r\n case 'number':\r\n return (\r\n
\r\n column.setFilterValue(val)}\r\n placeholder={columnMeta.placeholder ?? columnMeta.label}\r\n type=\"number\"\r\n value={(column.getFilterValue() as string) ?? ''}\r\n />\r\n {columnMeta.unit && (\r\n \r\n {columnMeta.unit}\r\n \r\n )}\r\n
\r\n );\r\n\r\n case 'range':\r\n return ;\r\n\r\n case 'date':\r\n case 'dateRange':\r\n return (\r\n \r\n );\r\n\r\n case 'boolean':\r\n return (\r\n \r\n );\r\n\r\n case 'select':\r\n case 'multiSelect':\r\n return (\r\n \r\n );\r\n\r\n default:\r\n return null;\r\n }\r\n }, [column, columnMeta]);\r\n\r\n return onFilterRender();\r\n }\r\n}\r\n", + "path": "registry/components/data-table/data-table-toolbar.tsx", + "target": "@components/data-table/data-table-toolbar.tsx", + "type": "registry:component" + } + ], + "name": "data-table-toolbar", + "registryDependencies": [ + "button", + "debounced-input", + "@kombase/data-table-date-filter", + "@kombase/data-table-faceted-filter", + "@kombase/data-table-slider-filter", + "@kombase/data-table-view-options" + ], + "type": "registry:component" +} diff --git a/public/r/data-table-types.json b/public/r/data-table-types.json new file mode 100644 index 0000000..b93cbbe --- /dev/null +++ b/public/r/data-table-types.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import type {\r\n ColumnSort,\r\n Row,\r\n RowData,\r\n ColumnDef as TanStackColumnDef,\r\n} from '@tanstack/react-table';\r\nimport type { DateRange } from 'react-day-picker';\r\nimport type { DataTableConfig } from './data-table-config';\r\n\r\nexport interface DatePreset {\r\n label: string;\r\n value: DateRange | [Date, Date] | (() => DateRange | [Date, Date]);\r\n}\r\n\r\nexport interface BaseColumnMeta {\r\n label?: string;\r\n icon?: React.FC>;\r\n align?: 'left' | 'center' | 'right';\r\n thClass?: string;\r\n cellClass?: string;\r\n}\r\n\r\nexport type ColumnFilterMeta =\r\n | {\r\n variant?: 'text';\r\n placeholder?: string;\r\n }\r\n | {\r\n variant: 'number';\r\n placeholder?: string;\r\n unit?: string;\r\n }\r\n | {\r\n variant: 'range';\r\n range?: [number, number];\r\n unit?: string;\r\n }\r\n | {\r\n variant: 'date';\r\n disabled?: import('react-day-picker').Matcher | import('react-day-picker').Matcher[];\r\n }\r\n | {\r\n variant: 'dateRange';\r\n presets?: boolean | DatePreset[];\r\n disabled?: import('react-day-picker').Matcher | import('react-day-picker').Matcher[];\r\n }\r\n | {\r\n variant: 'boolean';\r\n options?: Option[];\r\n }\r\n | {\r\n variant: 'select';\r\n options?: Option[];\r\n loading?: boolean;\r\n }\r\n | {\r\n variant: 'multiSelect';\r\n options?: Option[];\r\n loading?: boolean;\r\n };\r\n\r\nexport type ColumnMetaUnion = BaseColumnMeta & ColumnFilterMeta;\r\n\r\ntype DistributiveOmit = T extends any ? Omit : never;\r\n\r\nexport type ColumnDef = DistributiveOmit<\r\n TanStackColumnDef,\r\n 'meta'\r\n> & {\r\n meta?: ColumnMetaUnion;\r\n};\r\n\r\ndeclare module '@tanstack/react-table' {\r\n interface ColumnMeta {\r\n label?: string;\r\n placeholder?: string;\r\n variant?: FilterVariant;\r\n options?: Option[];\r\n range?: [number, number];\r\n unit?: string;\r\n icon?: React.FC>;\r\n loading?: boolean;\r\n disabled?: import('react-day-picker').Matcher | import('react-day-picker').Matcher[];\r\n presets?: boolean | DatePreset[];\r\n align?: 'left' | 'center' | 'right';\r\n thClass?: string;\r\n cellClass?: string;\r\n }\r\n}\r\n\r\nexport type FilterOperator = DataTableConfig['operators'][number];\r\nexport type FilterVariant = DataTableConfig['filterVariants'][number];\r\nexport type JoinOperator = DataTableConfig['joinOperators'][number];\r\n\r\nexport interface FilterItemSchema {\r\n filterId: string;\r\n id: string;\r\n operator: FilterOperator;\r\n value: any;\r\n variant: FilterVariant;\r\n}\r\n\r\nexport interface Option {\r\n label: string;\r\n value: string;\r\n count?: number;\r\n icon?: React.FC>;\r\n}\r\n\r\nexport interface ExtendedColumnSort extends Omit {\r\n id: Extract;\r\n}\r\n\r\nexport interface ExtendedColumnFilter extends FilterItemSchema {\r\n id: Extract;\r\n}\r\n\r\nexport interface DataTableRowAction {\r\n row: Row;\r\n variant: 'update' | 'delete';\r\n}\r\n\r\nexport interface DataTableTranslations {\r\n where?: string;\r\n and?: string;\r\n or?: string;\r\n addFilter?: string;\r\n resetFilters?: string;\r\n filters?: string;\r\n noFiltersApplied?: string;\r\n selectField?: string;\r\n searchFields?: string;\r\n pickADate?: string;\r\n enterValue?: string;\r\n selected?: string;\r\n operators?: {\r\n eq?: string;\r\n ne?: string;\r\n iLike?: string;\r\n notILike?: string;\r\n lt?: string;\r\n lte?: string;\r\n gt?: string;\r\n gte?: string;\r\n isEmpty?: string;\r\n isNotEmpty?: string;\r\n isBetween?: string;\r\n inArray?: string;\r\n notInArray?: string;\r\n isRelativeToToday?: string;\r\n };\r\n}\r\n", + "path": "registry/components/data-table/types.ts", + "target": "@components/data-table/types.ts", + "type": "registry:component" + } + ], + "name": "data-table-types", + "registryDependencies": ["@kombase/data-table-config"], + "type": "registry:component" +} diff --git a/public/r/data-table-view-options.json b/public/r/data-table-view-options.json new file mode 100644 index 0000000..4c34d76 --- /dev/null +++ b/public/r/data-table-view-options.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import type { Table } from '@tanstack/react-table';\r\nimport { Check, Settings2 } from 'lucide-react';\r\nimport * as React from 'react';\r\nimport { Button } from '@/components/ui/button';\r\nimport {\r\n Command,\r\n CommandEmpty,\r\n CommandGroup,\r\n CommandInput,\r\n CommandItem,\r\n CommandList,\r\n} from '@/components/ui/command';\r\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';\r\nimport { cn } from '@/lib/utils';\r\n\r\ninterface DataTableViewOptionsProps extends React.ComponentProps {\r\n table: Table;\r\n disabled?: boolean;\r\n}\r\n\r\nexport function DataTableViewOptions({\r\n table,\r\n disabled,\r\n ...props\r\n}: DataTableViewOptionsProps) {\r\n const columns = React.useMemo(\r\n () =>\r\n table\r\n .getAllColumns()\r\n .filter((column) => typeof column.accessorFn !== 'undefined' && column.getCanHide()),\r\n [table, table.options.columns],\r\n );\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n View\r\n \r\n \r\n \r\n \r\n \r\n \r\n No columns found.\r\n \r\n {columns.map((column) => (\r\n column.toggleVisibility(!column.getIsVisible())}\r\n >\r\n {column.columnDef.meta?.label ?? column.id}\r\n \r\n \r\n ))}\r\n \r\n \r\n \r\n \r\n \r\n );\r\n}\r\n", + "path": "registry/components/data-table/data-table-view-options.tsx", + "target": "@components/data-table/data-table-view-options.tsx", + "type": "registry:component" + } + ], + "name": "data-table-view-options", + "registryDependencies": ["button", "command", "popover"], + "type": "registry:component" +} diff --git a/public/r/data-table.json b/public/r/data-table.json new file mode 100644 index 0000000..92577c1 --- /dev/null +++ b/public/r/data-table.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@tanstack/react-table"], + "files": [ + { + "content": "import { flexRender, type Table as TanstackTable } from '@tanstack/react-table';\r\nimport { Loader } from 'lucide-react';\r\nimport * as React from 'react';\r\nimport {\r\n Table,\r\n TableBody,\r\n TableCell,\r\n TableHead,\r\n TableHeader,\r\n TableRow,\r\n} from '@/components/ui/table';\r\nimport { getCommonPinningStyles } from '@/lib/data-table';\r\nimport { cn } from '@/lib/utils';\r\n\r\ninterface DataTableProps extends React.ComponentProps<'div'> {\r\n table: TanstackTable;\r\n actionBar?: React.ReactNode;\r\n wrapperClassname?: string;\r\n emptyState?: React.ReactNode;\r\n stickyHeader?: boolean;\r\n onEndReached?: () => void;\r\n isLoadingMore?: boolean;\r\n loading?: boolean | React.ReactNode;\r\n endReachedThreshold?: number;\r\n}\r\n\r\nexport function DataTable({\r\n table,\r\n actionBar,\r\n children,\r\n className,\r\n wrapperClassname,\r\n emptyState,\r\n stickyHeader,\r\n onEndReached,\r\n isLoadingMore,\r\n loading,\r\n endReachedThreshold = 100,\r\n ...props\r\n}: DataTableProps) {\r\n const tableRef = React.useRef(null);\r\n\r\n const isFetchingMore = isLoadingMore;\r\n\r\n React.useEffect(() => {\r\n if (!onEndReached) return;\r\n\r\n const scrollContainer = tableRef.current?.parentElement;\r\n if (!scrollContainer) return;\r\n\r\n let isFetchingLocal = false;\r\n\r\n const handleScroll = () => {\r\n const { scrollTop, scrollHeight, clientHeight } = scrollContainer;\r\n if (scrollHeight - scrollTop - clientHeight < endReachedThreshold) {\r\n if (!isFetchingLocal && !isFetchingMore && !loading) {\r\n isFetchingLocal = true;\r\n onEndReached();\r\n }\r\n } else {\r\n isFetchingLocal = false;\r\n }\r\n };\r\n\r\n scrollContainer.addEventListener('scroll', handleScroll);\r\n return () => {\r\n scrollContainer.removeEventListener('scroll', handleScroll);\r\n };\r\n }, [onEndReached, endReachedThreshold, isFetchingMore, loading]);\r\n\r\n return (\r\n \r\n {children}\r\n
\r\n \r\n \r\n {table.getHeaderGroups().map((headerGroup) => (\r\n \r\n {headerGroup.headers.map((header) => (\r\n \r\n {header.isPlaceholder\r\n ? null\r\n : flexRender(header.column.columnDef.header, header.getContext())}\r\n \r\n ))}\r\n \r\n ))}\r\n \r\n \r\n {loading ? (\r\n \r\n \r\n
\r\n {typeof loading === 'boolean' ? (\r\n \r\n ) : (\r\n loading\r\n )}\r\n
\r\n
\r\n
\r\n ) : table.getRowModel()?.rows?.length ? (\r\n table.getRowModel().rows.map((row) => (\r\n \r\n {row.getVisibleCells().map((cell) => (\r\n \r\n {flexRender(cell.column.columnDef.cell, cell.getContext())}\r\n \r\n ))}\r\n \r\n ))\r\n ) : (\r\n \r\n \r\n {emptyState ?? 'No results.'}\r\n \r\n \r\n )}\r\n
\r\n \r\n
\r\n \r\n );\r\n}\r\n", + "path": "registry/components/data-table/data-table.tsx", + "target": "@components/data-table/data-table.tsx", + "type": "registry:component" + } + ], + "name": "data-table", + "registryDependencies": [ + "table", + "@kombase/data-table-config", + "@kombase/data-table-types", + "@kombase/data-table-pagination", + "@kombase/data-table-toolbar", + "@kombase/data-table-advance-filter", + "@kombase/data-table-bulk-action", + "@kombase/data-table-column-header", + "@kombase/data-table-skeleton", + "@kombase/lib-data-table", + "@kombase/use-data-table" + ], + "type": "registry:component" +} diff --git a/public/r/debounced-input.json b/public/r/debounced-input.json new file mode 100644 index 0000000..a573716 --- /dev/null +++ b/public/r/debounced-input.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import * as React from 'react';\r\nimport { Input } from '@/components/ui/input';\r\n\r\nexport interface DebouncedInputProps\r\n extends Omit, 'onChange' | 'value'> {\r\n value: string | number;\r\n onChange: (value: string | number) => void;\r\n debounce?: number;\r\n}\r\n\r\nexport function DebouncedInput({\r\n value: initialValue,\r\n onChange,\r\n debounce = 300,\r\n ...props\r\n}: DebouncedInputProps) {\r\n const [value, setValue] = React.useState(initialValue);\r\n\r\n const onChangeRef = React.useRef(onChange);\r\n React.useEffect(() => {\r\n onChangeRef.current = onChange;\r\n }, [onChange]);\r\n\r\n React.useEffect(() => {\r\n setValue(initialValue);\r\n }, [initialValue]);\r\n\r\n React.useEffect(() => {\r\n const timeout = setTimeout(() => {\r\n onChangeRef.current(value);\r\n }, debounce);\r\n\r\n return () => clearTimeout(timeout);\r\n }, [value, debounce]);\r\n\r\n return setValue(e.target.value)} value={value} />;\r\n}\r\n", + "path": "registry/ui/debounced-input.tsx", + "target": "@ui/debounced-input.tsx", + "type": "registry:ui" + } + ], + "name": "debounced-input", + "registryDependencies": ["input"], + "type": "registry:ui" +} diff --git a/public/r/dialog.json b/public/r/dialog.json new file mode 100644 index 0000000..962b374 --- /dev/null +++ b/public/r/dialog.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-dialog"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as DialogPrimitive from '@radix-ui/react-dialog';\r\nimport { XIcon } from 'lucide-react';\r\nimport * as React from 'react';\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst Dialog = ({ ...props }: React.ComponentProps) => (\r\n \r\n);\r\nDialog.displayName = 'Dialog';\r\n\r\nconst DialogTrigger = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>((props, ref) => );\r\nDialogTrigger.displayName = DialogPrimitive.Trigger.displayName;\r\n\r\nconst DialogPortal = ({ ...props }: React.ComponentProps) => (\r\n \r\n);\r\nDialogPortal.displayName = 'DialogPortal';\r\n\r\nconst DialogClose = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>((props, ref) => );\r\nDialogClose.displayName = DialogPrimitive.Close.displayName;\r\n\r\nconst DialogOverlay = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName;\r\n\r\nconst DialogContent = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef & {\r\n showCloseButton?: boolean;\r\n }\r\n>(({ className, children, showCloseButton = true, ...props }, ref) => (\r\n \r\n \r\n \r\n {children}\r\n {showCloseButton && (\r\n \r\n \r\n Close\r\n \r\n )}\r\n \r\n \r\n));\r\nDialogContent.displayName = DialogPrimitive.Content.displayName;\r\n\r\nconst DialogHeader = React.forwardRef>(\r\n ({ className, ...props }, ref) => (\r\n \r\n ),\r\n);\r\nDialogHeader.displayName = 'DialogHeader';\r\n\r\nconst DialogFooter = React.forwardRef>(\r\n ({ className, ...props }, ref) => (\r\n \r\n ),\r\n);\r\nDialogFooter.displayName = 'DialogFooter';\r\n\r\nconst DialogTitle = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nDialogTitle.displayName = DialogPrimitive.Title.displayName;\r\n\r\nconst DialogDescription = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nDialogDescription.displayName = DialogPrimitive.Description.displayName;\r\n\r\nexport {\r\n Dialog,\r\n DialogClose,\r\n DialogContent,\r\n DialogDescription,\r\n DialogFooter,\r\n DialogHeader,\r\n DialogOverlay,\r\n DialogPortal,\r\n DialogTitle,\r\n DialogTrigger,\r\n};\r\n", + "path": "registry/ui/dialog.tsx", + "target": "@ui/dialog.tsx", + "type": "registry:ui" + } + ], + "name": "dialog", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/dropdown-menu.json b/public/r/dropdown-menu.json new file mode 100644 index 0000000..bda6453 --- /dev/null +++ b/public/r/dropdown-menu.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-dropdown-menu"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';\r\nimport { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react';\r\nimport * as React from 'react';\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst DropdownMenu = ({ ...props }: React.ComponentProps) => (\r\n \r\n);\r\nDropdownMenu.displayName = 'DropdownMenu';\r\n\r\nconst DropdownMenuPortal = ({\r\n ...props\r\n}: React.ComponentProps) => (\r\n \r\n);\r\nDropdownMenuPortal.displayName = 'DropdownMenuPortal';\r\n\r\nconst DropdownMenuTrigger = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>((props, ref) => (\r\n \r\n));\r\nDropdownMenuTrigger.displayName = DropdownMenuPrimitive.Trigger.displayName;\r\n\r\nconst DropdownMenuContent = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, sideOffset = 4, ...props }, ref) => (\r\n \r\n \r\n \r\n));\r\nDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;\r\n\r\nconst DropdownMenuGroup = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>((props, ref) => (\r\n \r\n));\r\nDropdownMenuGroup.displayName = DropdownMenuPrimitive.Group.displayName;\r\n\r\nconst DropdownMenuItem = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef & {\r\n inset?: boolean;\r\n variant?: 'default' | 'destructive';\r\n }\r\n>(({ className, inset, variant = 'default', ...props }, ref) => (\r\n \r\n));\r\nDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;\r\n\r\nconst DropdownMenuCheckboxItem = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, children, checked, ...props }, ref) => (\r\n \r\n \r\n \r\n \r\n \r\n \r\n {children}\r\n \r\n));\r\nDropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;\r\n\r\nconst DropdownMenuRadioGroup = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>((props, ref) => (\r\n \r\n));\r\nDropdownMenuRadioGroup.displayName = DropdownMenuPrimitive.RadioGroup.displayName;\r\n\r\nconst DropdownMenuRadioItem = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, children, ...props }, ref) => (\r\n \r\n \r\n \r\n \r\n \r\n \r\n {children}\r\n \r\n));\r\nDropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;\r\n\r\nconst DropdownMenuLabel = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef & {\r\n inset?: boolean;\r\n }\r\n>(({ className, inset, ...props }, ref) => (\r\n \r\n));\r\nDropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;\r\n\r\nconst DropdownMenuSeparator = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nDropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;\r\n\r\nconst DropdownMenuShortcut = React.forwardRef<\r\n HTMLSpanElement,\r\n React.ComponentPropsWithoutRef<'span'>\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nDropdownMenuShortcut.displayName = 'DropdownMenuShortcut';\r\n\r\nconst DropdownMenuSub = ({ ...props }: React.ComponentProps) => (\r\n \r\n);\r\nDropdownMenuSub.displayName = 'DropdownMenuSub';\r\n\r\nconst DropdownMenuSubTrigger = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef & {\r\n inset?: boolean;\r\n }\r\n>(({ className, inset, children, ...props }, ref) => (\r\n \r\n {children}\r\n \r\n \r\n));\r\nDropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;\r\n\r\nconst DropdownMenuSubContent = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nDropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;\r\n\r\nexport {\r\n DropdownMenu,\r\n DropdownMenuCheckboxItem,\r\n DropdownMenuContent,\r\n DropdownMenuGroup,\r\n DropdownMenuItem,\r\n DropdownMenuLabel,\r\n DropdownMenuPortal,\r\n DropdownMenuRadioGroup,\r\n DropdownMenuRadioItem,\r\n DropdownMenuSeparator,\r\n DropdownMenuShortcut,\r\n DropdownMenuSub,\r\n DropdownMenuSubContent,\r\n DropdownMenuSubTrigger,\r\n DropdownMenuTrigger,\r\n};\r\n", + "path": "registry/ui/dropdown-menu.tsx", + "target": "@ui/dropdown-menu.tsx", + "type": "registry:ui" + } + ], + "name": "dropdown-menu", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/file-upload.json b/public/r/file-upload.json new file mode 100644 index 0000000..eb2ed20 --- /dev/null +++ b/public/r/file-upload.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-slot", "lucide-react"], + "files": [ + { + "content": "'use client';\r\n\r\nimport {\r\n FileArchiveIcon,\r\n FileAudioIcon,\r\n FileCodeIcon,\r\n FileCogIcon,\r\n FileIcon,\r\n FileTextIcon,\r\n FileVideoIcon,\r\n} from 'lucide-react';\r\nimport { Direction as DirectionPrimitive, Slot as SlotPrimitive } from 'radix-ui';\r\nimport * as React from 'react';\r\nimport { cn } from '@/lib/utils';\r\nimport { useAsRef } from '@/hooks/use-as-ref';\r\nimport { useLazyRef } from '@/hooks/use-lazy-ref';\r\n\r\nconst ROOT_NAME = 'FileUpload';\r\nconst DROPZONE_NAME = 'FileUploadDropzone';\r\nconst TRIGGER_NAME = 'FileUploadTrigger';\r\nconst LIST_NAME = 'FileUploadList';\r\nconst ITEM_NAME = 'FileUploadItem';\r\nconst ITEM_PREVIEW_NAME = 'FileUploadItemPreview';\r\nconst ITEM_METADATA_NAME = 'FileUploadItemMetadata';\r\nconst ITEM_PROGRESS_NAME = 'FileUploadItemProgress';\r\nconst ITEM_DELETE_NAME = 'FileUploadItemDelete';\r\nconst CLEAR_NAME = 'FileUploadClear';\r\n\r\nfunction formatBytes(bytes: number) {\r\n if (bytes === 0) return '0 B';\r\n const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];\r\n const i = Math.floor(Math.log(bytes) / Math.log(1024));\r\n return `${(bytes / 1024 ** i).toFixed(i ? 1 : 0)} ${sizes[i]}`;\r\n}\r\n\r\nfunction getFileIcon(file: File) {\r\n const type = file.type;\r\n const extension = file.name.split('.').pop()?.toLowerCase() ?? '';\r\n\r\n if (type.startsWith('video/')) {\r\n return ;\r\n }\r\n\r\n if (type.startsWith('audio/')) {\r\n return ;\r\n }\r\n\r\n if (type.startsWith('text/') || ['txt', 'md', 'rtf', 'pdf'].includes(extension)) {\r\n return ;\r\n }\r\n\r\n if (\r\n [\r\n 'html',\r\n 'css',\r\n 'js',\r\n 'jsx',\r\n 'ts',\r\n 'tsx',\r\n 'json',\r\n 'xml',\r\n 'php',\r\n 'py',\r\n 'rb',\r\n 'java',\r\n 'c',\r\n 'cpp',\r\n 'cs',\r\n ].includes(extension)\r\n ) {\r\n return ;\r\n }\r\n\r\n if (['zip', 'rar', '7z', 'tar', 'gz', 'bz2'].includes(extension)) {\r\n return ;\r\n }\r\n\r\n if (\r\n ['exe', 'msi', 'app', 'apk', 'deb', 'rpm'].includes(extension) ||\r\n type.startsWith('application/')\r\n ) {\r\n return ;\r\n }\r\n\r\n return ;\r\n}\r\n\r\ntype Direction = 'ltr' | 'rtl';\r\n\r\ninterface FileState {\r\n file: File;\r\n progress: number;\r\n error?: string;\r\n status: 'idle' | 'uploading' | 'error' | 'success';\r\n}\r\n\r\ninterface StoreState {\r\n files: Map;\r\n dragOver: boolean;\r\n invalid: boolean;\r\n}\r\n\r\ntype StoreAction =\r\n | { type: 'ADD_FILES'; files: File[] }\r\n | { type: 'SET_FILES'; files: File[] }\r\n | { type: 'SET_PROGRESS'; file: File; progress: number }\r\n | { type: 'SET_SUCCESS'; file: File }\r\n | { type: 'SET_ERROR'; file: File; error: string }\r\n | { type: 'REMOVE_FILE'; file: File }\r\n | { type: 'SET_DRAG_OVER'; dragOver: boolean }\r\n | { type: 'SET_INVALID'; invalid: boolean }\r\n | { type: 'CLEAR' };\r\n\r\ntype Store = {\r\n getState: () => StoreState;\r\n dispatch: (action: StoreAction) => void;\r\n subscribe: (listener: () => void) => () => void;\r\n};\r\n\r\nconst StoreContext = React.createContext(null);\r\n\r\nfunction useStoreContext(consumerName: string) {\r\n const context = React.useContext(StoreContext);\r\n if (!context) {\r\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\r\n }\r\n return context;\r\n}\r\n\r\nfunction useStore(selector: (state: StoreState) => T): T {\r\n const store = useStoreContext('useStore');\r\n\r\n const lastValueRef = useLazyRef<{ value: T; state: StoreState } | null>(() => null);\r\n\r\n const getSnapshot = React.useCallback(() => {\r\n const state = store.getState();\r\n const prevValue = lastValueRef.current;\r\n\r\n if (prevValue && prevValue.state === state) {\r\n return prevValue.value;\r\n }\r\n\r\n const nextValue = selector(state);\r\n lastValueRef.current = { state, value: nextValue };\r\n return nextValue;\r\n }, [store, selector, lastValueRef]);\r\n\r\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\r\n}\r\n\r\ninterface FileUploadContextValue {\r\n inputId: string;\r\n dropzoneId: string;\r\n listId: string;\r\n labelId: string;\r\n disabled: boolean;\r\n dir: Direction;\r\n inputRef: React.RefObject;\r\n urlCache: WeakMap;\r\n}\r\n\r\nconst FileUploadContext = React.createContext(null);\r\n\r\nfunction useFileUploadContext(consumerName: string) {\r\n const context = React.useContext(FileUploadContext);\r\n if (!context) {\r\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\r\n }\r\n return context;\r\n}\r\n\r\ninterface FileUploadProps extends Omit, 'defaultValue' | 'onChange'> {\r\n value?: File[];\r\n defaultValue?: File[];\r\n onValueChange?: (files: File[]) => void;\r\n onAccept?: (files: File[]) => void;\r\n onFileAccept?: (file: File) => void;\r\n onFileReject?: (file: File, message: string) => void;\r\n onFileValidate?: (file: File) => string | null | undefined;\r\n onUpload?: (\r\n files: File[],\r\n options: {\r\n onProgress: (file: File, progress: number) => void;\r\n onSuccess: (file: File) => void;\r\n onError: (file: File, error: Error) => void;\r\n },\r\n ) => Promise | void;\r\n accept?: string;\r\n maxFiles?: number;\r\n maxSize?: number;\r\n dir?: Direction;\r\n label?: string;\r\n name?: string;\r\n asChild?: boolean;\r\n disabled?: boolean;\r\n invalid?: boolean;\r\n multiple?: boolean;\r\n required?: boolean;\r\n}\r\n\r\nfunction FileUpload(props: FileUploadProps) {\r\n const {\r\n value,\r\n defaultValue,\r\n onValueChange,\r\n onAccept,\r\n onFileAccept,\r\n onFileReject,\r\n onFileValidate,\r\n onUpload,\r\n accept,\r\n maxFiles,\r\n maxSize,\r\n dir: dirProp,\r\n label,\r\n name,\r\n asChild,\r\n disabled = false,\r\n invalid = false,\r\n multiple = false,\r\n required = false,\r\n children,\r\n className,\r\n ...rootProps\r\n } = props;\r\n\r\n const inputId = React.useId();\r\n const dropzoneId = React.useId();\r\n const listId = React.useId();\r\n const labelId = React.useId();\r\n\r\n const dir = DirectionPrimitive.useDirection(dirProp);\r\n const listeners = useLazyRef(() => new Set<() => void>()).current;\r\n const files = useLazyRef>(() => new Map()).current;\r\n const urlCache = useLazyRef(() => new WeakMap()).current;\r\n const inputRef = React.useRef(null);\r\n const isControlled = value !== undefined;\r\n\r\n const propsRef = useAsRef({\r\n onAccept,\r\n onFileAccept,\r\n onFileReject,\r\n onFileValidate,\r\n onUpload,\r\n onValueChange,\r\n });\r\n\r\n const store = React.useMemo(() => {\r\n let state: StoreState = {\r\n dragOver: false,\r\n files,\r\n invalid: invalid,\r\n };\r\n\r\n function reducer(state: StoreState, action: StoreAction): StoreState {\r\n switch (action.type) {\r\n case 'ADD_FILES': {\r\n for (const file of action.files) {\r\n files.set(file, {\r\n file,\r\n progress: 0,\r\n status: 'idle',\r\n });\r\n }\r\n\r\n if (propsRef.current.onValueChange) {\r\n const fileList = Array.from(files.values()).map((fileState) => fileState.file);\r\n propsRef.current.onValueChange(fileList);\r\n }\r\n return { ...state, files };\r\n }\r\n\r\n case 'SET_FILES': {\r\n const newFileSet = new Set(action.files);\r\n for (const existingFile of files.keys()) {\r\n if (!newFileSet.has(existingFile)) {\r\n files.delete(existingFile);\r\n }\r\n }\r\n\r\n for (const file of action.files) {\r\n const existingState = files.get(file);\r\n if (!existingState) {\r\n files.set(file, {\r\n file,\r\n progress: 0,\r\n status: 'idle',\r\n });\r\n }\r\n }\r\n return { ...state, files };\r\n }\r\n\r\n case 'SET_PROGRESS': {\r\n const fileState = files.get(action.file);\r\n if (fileState) {\r\n files.set(action.file, {\r\n ...fileState,\r\n progress: action.progress,\r\n status: 'uploading',\r\n });\r\n }\r\n return { ...state, files };\r\n }\r\n\r\n case 'SET_SUCCESS': {\r\n const fileState = files.get(action.file);\r\n if (fileState) {\r\n files.set(action.file, {\r\n ...fileState,\r\n progress: 100,\r\n status: 'success',\r\n });\r\n }\r\n return { ...state, files };\r\n }\r\n\r\n case 'SET_ERROR': {\r\n const fileState = files.get(action.file);\r\n if (fileState) {\r\n files.set(action.file, {\r\n ...fileState,\r\n error: action.error,\r\n status: 'error',\r\n });\r\n }\r\n return { ...state, files };\r\n }\r\n\r\n case 'REMOVE_FILE': {\r\n const cachedUrl = urlCache.get(action.file);\r\n if (cachedUrl) {\r\n URL.revokeObjectURL(cachedUrl);\r\n urlCache.delete(action.file);\r\n }\r\n\r\n files.delete(action.file);\r\n\r\n if (propsRef.current.onValueChange) {\r\n const fileList = Array.from(files.values()).map((fileState) => fileState.file);\r\n propsRef.current.onValueChange(fileList);\r\n }\r\n return { ...state, files };\r\n }\r\n\r\n case 'SET_DRAG_OVER': {\r\n return { ...state, dragOver: action.dragOver };\r\n }\r\n\r\n case 'SET_INVALID': {\r\n return { ...state, invalid: action.invalid };\r\n }\r\n\r\n case 'CLEAR': {\r\n for (const file of files.keys()) {\r\n const cachedUrl = urlCache.get(file);\r\n if (cachedUrl) {\r\n URL.revokeObjectURL(cachedUrl);\r\n urlCache.delete(file);\r\n }\r\n }\r\n\r\n files.clear();\r\n if (propsRef.current.onValueChange) {\r\n propsRef.current.onValueChange([]);\r\n }\r\n return { ...state, files, invalid: false };\r\n }\r\n\r\n default:\r\n return state;\r\n }\r\n }\r\n\r\n return {\r\n dispatch: (action) => {\r\n state = reducer(state, action);\r\n for (const listener of listeners) {\r\n listener();\r\n }\r\n },\r\n getState: () => state,\r\n subscribe: (listener) => {\r\n listeners.add(listener);\r\n return () => listeners.delete(listener);\r\n },\r\n };\r\n }, [listeners, files, invalid, propsRef, urlCache]);\r\n\r\n const acceptTypes = React.useMemo(\r\n () => accept?.split(',').map((t) => t.trim()) ?? null,\r\n [accept],\r\n );\r\n\r\n const onProgress = useLazyRef(() => {\r\n let frame = 0;\r\n return (file: File, progress: number) => {\r\n if (frame) return;\r\n frame = requestAnimationFrame(() => {\r\n frame = 0;\r\n store.dispatch({\r\n file,\r\n progress: Math.min(Math.max(0, progress), 100),\r\n type: 'SET_PROGRESS',\r\n });\r\n });\r\n };\r\n }).current;\r\n\r\n React.useEffect(() => {\r\n if (isControlled) {\r\n store.dispatch({ files: value, type: 'SET_FILES' });\r\n } else if (defaultValue && defaultValue.length > 0 && !store.getState().files.size) {\r\n store.dispatch({ files: defaultValue, type: 'SET_FILES' });\r\n }\r\n }, [value, defaultValue, isControlled, store]);\r\n\r\n React.useEffect(() => {\r\n return () => {\r\n for (const file of files.keys()) {\r\n const cachedUrl = urlCache.get(file);\r\n if (cachedUrl) {\r\n URL.revokeObjectURL(cachedUrl);\r\n }\r\n }\r\n };\r\n }, [files, urlCache]);\r\n\r\n const onFilesUpload = React.useCallback(\r\n async (files: File[]) => {\r\n try {\r\n for (const file of files) {\r\n store.dispatch({ file, progress: 0, type: 'SET_PROGRESS' });\r\n }\r\n\r\n if (propsRef.current.onUpload) {\r\n await propsRef.current.onUpload(files, {\r\n onError: (file, error) => {\r\n store.dispatch({\r\n error: error.message ?? 'Upload failed',\r\n file,\r\n type: 'SET_ERROR',\r\n });\r\n },\r\n onProgress,\r\n onSuccess: (file) => {\r\n store.dispatch({ file, type: 'SET_SUCCESS' });\r\n },\r\n });\r\n } else {\r\n for (const file of files) {\r\n store.dispatch({ file, type: 'SET_SUCCESS' });\r\n }\r\n }\r\n } catch (error) {\r\n const errorMessage = error instanceof Error ? error.message : 'Upload failed';\r\n for (const file of files) {\r\n store.dispatch({\r\n error: errorMessage,\r\n file,\r\n type: 'SET_ERROR',\r\n });\r\n }\r\n }\r\n },\r\n [store, propsRef, onProgress],\r\n );\r\n\r\n const onFilesChange = React.useCallback(\r\n (originalFiles: File[]) => {\r\n if (disabled) return;\r\n\r\n let filesToProcess = [...originalFiles];\r\n let invalid = false;\r\n\r\n if (maxFiles) {\r\n const currentCount = store.getState().files.size;\r\n const remainingSlotCount = Math.max(0, maxFiles - currentCount);\r\n\r\n if (remainingSlotCount < filesToProcess.length) {\r\n const rejectedFiles = filesToProcess.slice(remainingSlotCount);\r\n invalid = true;\r\n\r\n filesToProcess = filesToProcess.slice(0, remainingSlotCount);\r\n\r\n for (const file of rejectedFiles) {\r\n let rejectionMessage = `Maximum ${maxFiles} files allowed`;\r\n\r\n if (propsRef.current.onFileValidate) {\r\n const validationMessage = propsRef.current.onFileValidate(file);\r\n if (validationMessage) {\r\n rejectionMessage = validationMessage;\r\n }\r\n }\r\n\r\n propsRef.current.onFileReject?.(file, rejectionMessage);\r\n }\r\n }\r\n }\r\n\r\n const acceptedFiles: File[] = [];\r\n const rejectedFiles: { file: File; message: string }[] = [];\r\n\r\n for (const file of filesToProcess) {\r\n let rejected = false;\r\n let rejectionMessage = '';\r\n\r\n if (propsRef.current.onFileValidate) {\r\n const validationMessage = propsRef.current.onFileValidate(file);\r\n if (validationMessage) {\r\n rejectionMessage = validationMessage;\r\n propsRef.current.onFileReject?.(file, rejectionMessage);\r\n rejected = true;\r\n invalid = true;\r\n continue;\r\n }\r\n }\r\n\r\n if (acceptTypes) {\r\n const fileType = file.type;\r\n const fileExtension = `.${file.name.split('.').pop()}`;\r\n\r\n if (\r\n !acceptTypes.some(\r\n (type) =>\r\n type === fileType ||\r\n type === fileExtension ||\r\n (type.includes('/*') && fileType.startsWith(type.replace('/*', '/'))),\r\n )\r\n ) {\r\n rejectionMessage = 'File type not accepted';\r\n propsRef.current.onFileReject?.(file, rejectionMessage);\r\n rejected = true;\r\n invalid = true;\r\n }\r\n }\r\n\r\n if (maxSize && file.size > maxSize) {\r\n rejectionMessage = 'File too large';\r\n propsRef.current.onFileReject?.(file, rejectionMessage);\r\n rejected = true;\r\n invalid = true;\r\n }\r\n\r\n if (!rejected) {\r\n acceptedFiles.push(file);\r\n } else {\r\n rejectedFiles.push({ file, message: rejectionMessage });\r\n }\r\n }\r\n\r\n if (invalid) {\r\n store.dispatch({ invalid, type: 'SET_INVALID' });\r\n setTimeout(() => {\r\n store.dispatch({ invalid: false, type: 'SET_INVALID' });\r\n }, 2000);\r\n }\r\n\r\n if (acceptedFiles.length > 0) {\r\n store.dispatch({ files: acceptedFiles, type: 'ADD_FILES' });\r\n\r\n if (isControlled && propsRef.current.onValueChange) {\r\n const currentFiles = Array.from(store.getState().files.values()).map((f) => f.file);\r\n propsRef.current.onValueChange([...currentFiles]);\r\n }\r\n\r\n if (propsRef.current.onAccept) {\r\n propsRef.current.onAccept(acceptedFiles);\r\n }\r\n\r\n for (const file of acceptedFiles) {\r\n propsRef.current.onFileAccept?.(file);\r\n }\r\n\r\n if (propsRef.current.onUpload) {\r\n requestAnimationFrame(() => {\r\n onFilesUpload(acceptedFiles);\r\n });\r\n }\r\n }\r\n },\r\n [store, isControlled, propsRef, onFilesUpload, maxFiles, acceptTypes, maxSize, disabled],\r\n );\r\n\r\n const onInputChange = React.useCallback(\r\n (event: React.ChangeEvent) => {\r\n const files = Array.from(event.target.files ?? []);\r\n onFilesChange(files);\r\n event.target.value = '';\r\n },\r\n [onFilesChange],\r\n );\r\n\r\n const contextValue = React.useMemo(\r\n () => ({\r\n dir,\r\n disabled,\r\n dropzoneId,\r\n inputId,\r\n inputRef,\r\n labelId,\r\n listId,\r\n urlCache,\r\n }),\r\n [dropzoneId, inputId, listId, labelId, dir, disabled, urlCache],\r\n );\r\n\r\n const RootPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n \r\n \r\n {children}\r\n \r\n
\r\n {label ?? 'File upload'}\r\n
\r\n \r\n
\r\n
\r\n );\r\n}\r\n\r\ninterface FileUploadDropzoneProps extends React.ComponentProps<'div'> {\r\n asChild?: boolean;\r\n}\r\n\r\nfunction FileUploadDropzone(props: FileUploadDropzoneProps) {\r\n const {\r\n asChild,\r\n className,\r\n onClick: onClickProp,\r\n onDragOver: onDragOverProp,\r\n onDragEnter: onDragEnterProp,\r\n onDragLeave: onDragLeaveProp,\r\n onDrop: onDropProp,\r\n onPaste: onPasteProp,\r\n onKeyDown: onKeyDownProp,\r\n ...dropzoneProps\r\n } = props;\r\n\r\n const context = useFileUploadContext(DROPZONE_NAME);\r\n const store = useStoreContext(DROPZONE_NAME);\r\n const dragOver = useStore((state) => state.dragOver);\r\n const invalid = useStore((state) => state.invalid);\r\n\r\n const propsRef = useAsRef({\r\n onClick: onClickProp,\r\n onDragEnter: onDragEnterProp,\r\n onDragLeave: onDragLeaveProp,\r\n onDragOver: onDragOverProp,\r\n onDrop: onDropProp,\r\n onKeyDown: onKeyDownProp,\r\n onPaste: onPasteProp,\r\n });\r\n\r\n const onClick = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n propsRef.current.onClick?.(event);\r\n\r\n if (event.defaultPrevented) return;\r\n\r\n const target = event.target;\r\n\r\n const isFromTrigger =\r\n target instanceof HTMLElement && target.closest('[data-slot=\"file-upload-trigger\"]');\r\n\r\n if (!isFromTrigger) {\r\n context.inputRef.current?.click();\r\n }\r\n },\r\n [context.inputRef, propsRef],\r\n );\r\n\r\n const onDragOver = React.useCallback(\r\n (event: React.DragEvent) => {\r\n propsRef.current.onDragOver?.(event);\r\n\r\n if (event.defaultPrevented) return;\r\n\r\n event.preventDefault();\r\n store.dispatch({ dragOver: true, type: 'SET_DRAG_OVER' });\r\n },\r\n [store, propsRef],\r\n );\r\n\r\n const onDragEnter = React.useCallback(\r\n (event: React.DragEvent) => {\r\n propsRef.current.onDragEnter?.(event);\r\n\r\n if (event.defaultPrevented) return;\r\n\r\n event.preventDefault();\r\n store.dispatch({ dragOver: true, type: 'SET_DRAG_OVER' });\r\n },\r\n [store, propsRef],\r\n );\r\n\r\n const onDragLeave = React.useCallback(\r\n (event: React.DragEvent) => {\r\n propsRef.current.onDragLeave?.(event);\r\n\r\n if (event.defaultPrevented) return;\r\n\r\n const relatedTarget = event.relatedTarget;\r\n if (\r\n relatedTarget &&\r\n relatedTarget instanceof Node &&\r\n event.currentTarget.contains(relatedTarget)\r\n ) {\r\n return;\r\n }\r\n\r\n event.preventDefault();\r\n store.dispatch({ dragOver: false, type: 'SET_DRAG_OVER' });\r\n },\r\n [store, propsRef],\r\n );\r\n\r\n const onDrop = React.useCallback(\r\n (event: React.DragEvent) => {\r\n propsRef.current.onDrop?.(event);\r\n\r\n if (event.defaultPrevented) return;\r\n\r\n event.preventDefault();\r\n store.dispatch({ dragOver: false, type: 'SET_DRAG_OVER' });\r\n\r\n const files = Array.from(event.dataTransfer.files);\r\n const inputElement = context.inputRef.current;\r\n if (!inputElement) return;\r\n\r\n const dataTransfer = new DataTransfer();\r\n for (const file of files) {\r\n dataTransfer.items.add(file);\r\n }\r\n\r\n inputElement.files = dataTransfer.files;\r\n inputElement.dispatchEvent(new Event('change', { bubbles: true }));\r\n },\r\n [store, context.inputRef, propsRef],\r\n );\r\n\r\n const onPaste = React.useCallback(\r\n (event: React.ClipboardEvent) => {\r\n propsRef.current.onPaste?.(event);\r\n\r\n if (event.defaultPrevented) return;\r\n\r\n event.preventDefault();\r\n store.dispatch({ dragOver: false, type: 'SET_DRAG_OVER' });\r\n\r\n const items = event.clipboardData?.items;\r\n if (!items) return;\r\n\r\n const files: File[] = [];\r\n for (let i = 0; i < items.length; i++) {\r\n const item = items[i];\r\n if (item?.kind === 'file') {\r\n const file = item.getAsFile();\r\n if (file) {\r\n files.push(file);\r\n }\r\n }\r\n }\r\n\r\n if (files.length === 0) return;\r\n\r\n const inputElement = context.inputRef.current;\r\n if (!inputElement) return;\r\n\r\n const dataTransfer = new DataTransfer();\r\n for (const file of files) {\r\n dataTransfer.items.add(file);\r\n }\r\n\r\n inputElement.files = dataTransfer.files;\r\n inputElement.dispatchEvent(new Event('change', { bubbles: true }));\r\n },\r\n [store, context.inputRef, propsRef],\r\n );\r\n\r\n const onKeyDown = React.useCallback(\r\n (event: React.KeyboardEvent) => {\r\n propsRef.current.onKeyDown?.(event);\r\n\r\n if (!event.defaultPrevented && (event.key === 'Enter' || event.key === ' ')) {\r\n event.preventDefault();\r\n context.inputRef.current?.click();\r\n }\r\n },\r\n [context.inputRef, propsRef],\r\n );\r\n\r\n const DropzonePrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\ninterface FileUploadTriggerProps extends React.ComponentProps<'button'> {\r\n asChild?: boolean;\r\n}\r\n\r\nfunction FileUploadTrigger(props: FileUploadTriggerProps) {\r\n const { asChild, onClick: onClickProp, ...triggerProps } = props;\r\n\r\n const context = useFileUploadContext(TRIGGER_NAME);\r\n\r\n const propsRef = useAsRef({\r\n onClick: onClickProp,\r\n });\r\n\r\n const onClick = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n propsRef.current.onClick?.(event);\r\n\r\n if (event.defaultPrevented) return;\r\n\r\n context.inputRef.current?.click();\r\n },\r\n [context.inputRef, propsRef],\r\n );\r\n\r\n const TriggerPrimitive = asChild ? SlotPrimitive.Slot : 'button';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\ninterface FileUploadListProps extends React.ComponentProps<'div'> {\r\n orientation?: 'horizontal' | 'vertical';\r\n asChild?: boolean;\r\n forceMount?: boolean;\r\n}\r\n\r\nfunction FileUploadList(props: FileUploadListProps) {\r\n const { className, orientation = 'vertical', asChild, forceMount, ...listProps } = props;\r\n\r\n const context = useFileUploadContext(LIST_NAME);\r\n const fileCount = useStore((state) => state.files.size);\r\n const shouldRender = forceMount || fileCount > 0;\r\n\r\n if (!shouldRender) return null;\r\n\r\n const ListPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\ninterface FileUploadItemContextValue {\r\n id: string;\r\n fileState: FileState | undefined;\r\n nameId: string;\r\n sizeId: string;\r\n statusId: string;\r\n messageId: string;\r\n}\r\n\r\nconst FileUploadItemContext = React.createContext(null);\r\n\r\nfunction useFileUploadItemContext(consumerName: string) {\r\n const context = React.useContext(FileUploadItemContext);\r\n if (!context) {\r\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ITEM_NAME}\\``);\r\n }\r\n return context;\r\n}\r\n\r\ninterface FileUploadItemProps extends React.ComponentProps<'div'> {\r\n value: File;\r\n asChild?: boolean;\r\n}\r\n\r\nfunction FileUploadItem(props: FileUploadItemProps) {\r\n const { value, asChild, className, ...itemProps } = props;\r\n\r\n const id = React.useId();\r\n const statusId = `${id}-status`;\r\n const nameId = `${id}-name`;\r\n const sizeId = `${id}-size`;\r\n const messageId = `${id}-message`;\r\n\r\n const context = useFileUploadContext(ITEM_NAME);\r\n const fileState = useStore((state) => state.files.get(value));\r\n const fileCount = useStore((state) => state.files.size);\r\n const fileIndex = useStore((state) => {\r\n const files = Array.from(state.files.keys());\r\n return files.indexOf(value) + 1;\r\n });\r\n\r\n const itemContext = React.useMemo(\r\n () => ({\r\n fileState,\r\n id,\r\n messageId,\r\n nameId,\r\n sizeId,\r\n statusId,\r\n }),\r\n [id, fileState, statusId, nameId, sizeId, messageId],\r\n );\r\n\r\n if (!fileState) return null;\r\n\r\n const statusText = fileState.error\r\n ? `Error: ${fileState.error}`\r\n : fileState.status === 'uploading'\r\n ? `Uploading: ${fileState.progress}% complete`\r\n : fileState.status === 'success'\r\n ? 'Upload complete'\r\n : 'Ready to upload';\r\n\r\n const ItemPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n \r\n {props.children}\r\n \r\n {statusText}\r\n \r\n \r\n \r\n );\r\n}\r\n\r\ninterface FileUploadItemPreviewProps extends React.ComponentProps<'div'> {\r\n render?: (file: File, fallback: () => React.ReactNode) => React.ReactNode;\r\n asChild?: boolean;\r\n}\r\n\r\nfunction FileUploadItemPreview(props: FileUploadItemPreviewProps) {\r\n const { render, asChild, children, className, ...previewProps } = props;\r\n\r\n const itemContext = useFileUploadItemContext(ITEM_PREVIEW_NAME);\r\n const context = useFileUploadContext(ITEM_PREVIEW_NAME);\r\n\r\n const getDefaultRender = React.useCallback(\r\n (file: File) => {\r\n if (itemContext.fileState?.file.type.startsWith('image/')) {\r\n let url = context.urlCache.get(file);\r\n if (!url) {\r\n url = URL.createObjectURL(file);\r\n context.urlCache.set(file, url);\r\n }\r\n\r\n return {file.name};\r\n }\r\n\r\n return getFileIcon(file);\r\n },\r\n [itemContext.fileState?.file.type, context.urlCache],\r\n );\r\n\r\n const onPreviewRender = React.useCallback(\r\n (file: File) => {\r\n if (render) {\r\n return render(file, () => getDefaultRender(file));\r\n }\r\n\r\n return getDefaultRender(file);\r\n },\r\n [render, getDefaultRender],\r\n );\r\n\r\n if (!itemContext.fileState) return null;\r\n\r\n const ItemPreviewPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n svg]:size-10',\r\n className,\r\n )}\r\n >\r\n {onPreviewRender(itemContext.fileState.file)}\r\n {children}\r\n \r\n );\r\n}\r\n\r\ninterface FileUploadItemMetadataProps extends React.ComponentProps<'div'> {\r\n asChild?: boolean;\r\n size?: 'default' | 'sm';\r\n}\r\n\r\nfunction FileUploadItemMetadata(props: FileUploadItemMetadataProps) {\r\n const { asChild, size = 'default', children, className, ...metadataProps } = props;\r\n\r\n const context = useFileUploadContext(ITEM_METADATA_NAME);\r\n const itemContext = useFileUploadItemContext(ITEM_METADATA_NAME);\r\n\r\n if (!itemContext.fileState) return null;\r\n\r\n const ItemMetadataPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n {children ?? (\r\n <>\r\n \r\n {itemContext.fileState.file.name}\r\n \r\n \r\n {formatBytes(itemContext.fileState.file.size)}\r\n \r\n {itemContext.fileState.error && (\r\n \r\n {itemContext.fileState.error}\r\n \r\n )}\r\n \r\n )}\r\n \r\n );\r\n}\r\ninterface FileUploadItemProgressProps extends React.ComponentProps<'div'> {\r\n variant?: 'linear' | 'circular' | 'fill';\r\n size?: number;\r\n asChild?: boolean;\r\n forceMount?: boolean;\r\n}\r\n\r\nfunction FileUploadItemProgress(props: FileUploadItemProgressProps) {\r\n const { variant = 'linear', size = 40, asChild, forceMount, className, ...progressProps } = props;\r\n\r\n const itemContext = useFileUploadItemContext(ITEM_PROGRESS_NAME);\r\n\r\n if (!itemContext.fileState) return null;\r\n\r\n const shouldRender = forceMount || itemContext.fileState.progress !== 100;\r\n\r\n if (!shouldRender) return null;\r\n\r\n const ItemProgressPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n switch (variant) {\r\n case 'circular': {\r\n const circumference = 2 * Math.PI * ((size - 4) / 2);\r\n const strokeDashoffset =\r\n circumference - (itemContext.fileState.progress / 100) * circumference;\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \r\n );\r\n }\r\n\r\n case 'fill': {\r\n const progressPercentage = itemContext.fileState.progress;\r\n const topInset = 100 - progressPercentage;\r\n\r\n return (\r\n \r\n );\r\n }\r\n\r\n default:\r\n return (\r\n \r\n \r\n \r\n );\r\n }\r\n}\r\n\r\ninterface FileUploadItemDeleteProps extends React.ComponentProps<'button'> {\r\n asChild?: boolean;\r\n}\r\n\r\nfunction FileUploadItemDelete(props: FileUploadItemDeleteProps) {\r\n const { asChild, onClick: onClickProp, ...deleteProps } = props;\r\n\r\n const store = useStoreContext(ITEM_DELETE_NAME);\r\n const itemContext = useFileUploadItemContext(ITEM_DELETE_NAME);\r\n\r\n const onClick = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n onClickProp?.(event);\r\n\r\n if (!itemContext.fileState || event.defaultPrevented) return;\r\n\r\n store.dispatch({\r\n file: itemContext.fileState.file,\r\n type: 'REMOVE_FILE',\r\n });\r\n },\r\n [store, itemContext.fileState, onClickProp],\r\n );\r\n\r\n if (!itemContext.fileState) return null;\r\n\r\n const ItemDeletePrimitive = asChild ? SlotPrimitive.Slot : 'button';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\ninterface FileUploadClearProps extends React.ComponentProps<'button'> {\r\n forceMount?: boolean;\r\n asChild?: boolean;\r\n}\r\n\r\nfunction FileUploadClear(props: FileUploadClearProps) {\r\n const { asChild, forceMount, disabled, onClick: onClickProp, ...clearProps } = props;\r\n\r\n const context = useFileUploadContext(CLEAR_NAME);\r\n const store = useStoreContext(CLEAR_NAME);\r\n const fileCount = useStore((state) => state.files.size);\r\n\r\n const isDisabled = disabled || context.disabled;\r\n\r\n const onClick = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n onClickProp?.(event);\r\n\r\n if (event.defaultPrevented) return;\r\n\r\n store.dispatch({ type: 'CLEAR' });\r\n },\r\n [store, onClickProp],\r\n );\r\n\r\n const shouldRender = forceMount || fileCount > 0;\r\n\r\n if (!shouldRender) return null;\r\n\r\n const ClearPrimitive = asChild ? SlotPrimitive.Slot : 'button';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\nexport {\r\n FileUpload,\r\n FileUploadClear,\r\n FileUploadDropzone,\r\n FileUploadItem,\r\n FileUploadItemDelete,\r\n FileUploadItemMetadata,\r\n FileUploadItemPreview,\r\n FileUploadItemProgress,\r\n FileUploadList,\r\n type FileUploadProps,\r\n FileUploadTrigger,\r\n useStore as useFileUpload,\r\n};\r\n", + "path": "registry/ui/file-upload.tsx", + "target": "@ui/file-upload.tsx", + "type": "registry:ui" + } + ], + "name": "file-upload", + "registryDependencies": ["@kombase/utils", "@kombase/use-as-ref", "@kombase/use-lazy-ref"], + "type": "registry:ui" +} diff --git a/public/r/filter-helper.json b/public/r/filter-helper.json new file mode 100644 index 0000000..8a5021b --- /dev/null +++ b/public/r/filter-helper.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["dayjs"], + "files": [ + { + "content": "import dayjs from 'dayjs';\r\nimport type {\r\n ExtendedColumnFilter,\r\n FilterOperator,\r\n JoinOperator,\r\n} from '@/components/data-table/types';\r\n\r\nconst DEFAULT_OPERATORS: Record = {\r\n eq: 'eq',\r\n gt: 'gt',\r\n gte: 'gte',\r\n iLike: 'contains',\r\n inArray: 'in',\r\n isBetween: 'between',\r\n isEmpty: 'isEmpty',\r\n isNotEmpty: 'isNotEmpty',\r\n isRelativeToToday: 'relative',\r\n lt: 'lt',\r\n lte: 'lte',\r\n ne: 'ne',\r\n notILike: 'notContains',\r\n notInArray: 'notIn',\r\n};\r\n\r\nfunction formatSingleDate(val: any, formatStr: string): string {\r\n if (!val) return '';\r\n const num = Number(val);\r\n const date = Number.isNaN(num) ? dayjs(val) : dayjs(num);\r\n return date.isValid() ? date.format(formatStr) : String(val);\r\n}\r\n\r\nfunction mergeParams(target: Record, source: Record) {\r\n Object.keys(source).forEach((key) => {\r\n if (key in target) {\r\n const existing = target[key];\r\n const nextVal = source[key];\r\n if (Array.isArray(existing)) {\r\n target[key] = [...existing, ...(Array.isArray(nextVal) ? nextVal : [nextVal])];\r\n } else {\r\n target[key] = [existing, ...(Array.isArray(nextVal) ? nextVal : [nextVal])];\r\n }\r\n } else {\r\n target[key] = source[key];\r\n }\r\n });\r\n}\r\n\r\nfunction formatFilterKeyValue(\r\n paramName: string,\r\n operator: FilterOperator,\r\n value: any,\r\n config: FilterResolutionConfig,\r\n): Record {\r\n const style = config.style ?? 'flat';\r\n const arrayFormat = config.arrayFormat ?? 'comma';\r\n\r\n // Format array values if applicable\r\n let keySuffix = '';\r\n let serializedValue = value;\r\n\r\n if (Array.isArray(value)) {\r\n if (arrayFormat === 'comma') {\r\n serializedValue = value.join(',');\r\n } else if (arrayFormat === 'brackets') {\r\n keySuffix = '[]';\r\n serializedValue = value;\r\n } else {\r\n serializedValue = value;\r\n }\r\n }\r\n\r\n // Handle PostgREST specifically\r\n if (style === 'postgrest') {\r\n if (operator === 'inArray' || operator === 'notInArray') {\r\n const arr = Array.isArray(value) ? value : [value];\r\n const opName = operator === 'inArray' ? 'in' : 'not.in';\r\n return { [paramName]: `${opName}.(${arr.join(',')})` };\r\n }\r\n if (operator === 'isEmpty') {\r\n return { [paramName]: 'is.null' };\r\n }\r\n if (operator === 'isNotEmpty') {\r\n return { [paramName]: 'not.is.null' };\r\n }\r\n const backendOp = config.operatorMap?.[operator] ?? DEFAULT_OPERATORS[operator] ?? operator;\r\n return { [paramName]: `${backendOp}.${serializedValue}` };\r\n }\r\n\r\n // Map operator to backend string\r\n const backendOp = config.operatorMap?.[operator] ?? DEFAULT_OPERATORS[operator] ?? operator;\r\n\r\n // Let's determine the key based on style\r\n let finalKey = paramName;\r\n if (backendOp) {\r\n if (style === 'suffix') {\r\n finalKey = `${paramName}_${backendOp}`;\r\n } else if (style === 'django') {\r\n finalKey = `${paramName}__${backendOp}`;\r\n } else if (style === 'nested') {\r\n finalKey = `${paramName}[${backendOp}]`;\r\n } else if (style === 'prefix') {\r\n finalKey = `${paramName}[$${backendOp}]`;\r\n }\r\n }\r\n\r\n // Append key suffix if brackets array format is used\r\n if (keySuffix) {\r\n finalKey = `${finalKey}${keySuffix}`;\r\n }\r\n\r\n // Handle empty operators for non-postgrest styles\r\n if (operator === 'isEmpty') {\r\n return { [finalKey]: true };\r\n }\r\n if (operator === 'isNotEmpty') {\r\n return { [finalKey]: false };\r\n }\r\n\r\n return { [finalKey]: serializedValue };\r\n}\r\n\r\nfunction formatFilterItem(\r\n filter: ExtendedColumnFilter,\r\n config: FilterResolutionConfig,\r\n): Record {\r\n const { id, operator, value, variant } = filter;\r\n const columnId = id as string;\r\n const nameMap = (config.paramNameMap || {}) as Record;\r\n const paramName = nameMap[columnId] || columnId;\r\n\r\n // 1. Custom mapper overrides everything\r\n const mappers = (config.customMappers || {}) as Record<\r\n string,\r\n (value: any, operator: FilterOperator) => Record\r\n >;\r\n if (mappers[columnId]) {\r\n return mappers[columnId](value, operator);\r\n }\r\n\r\n const dateFormat = config.dateFormat ?? 'YYYY-MM-DD';\r\n const style = config.style ?? 'flat';\r\n\r\n // 2. Custom formatFilter hook\r\n if (config.formatFilter) {\r\n const backendOp = config.operatorMap?.[operator] ?? DEFAULT_OPERATORS[operator] ?? operator;\r\n return config.formatFilter(paramName, operator, value, backendOp);\r\n }\r\n\r\n // 3. Resolve empty checks\r\n const isEmptyOperator = operator === 'isEmpty' || operator === 'isNotEmpty';\r\n const isValueEmpty =\r\n value === undefined ||\r\n value === null ||\r\n value === '' ||\r\n (Array.isArray(value) && value.length === 0);\r\n\r\n if (isValueEmpty && !isEmptyOperator) {\r\n return {};\r\n }\r\n\r\n // 4. Handle date formatting for single values/relative dates\r\n let resolvedValue = value;\r\n let resolvedOperator = operator;\r\n\r\n if (variant === 'date' || variant === 'dateRange') {\r\n if (operator === 'isRelativeToToday') {\r\n const days = Number(value);\r\n if (!Number.isNaN(days)) {\r\n resolvedValue = dayjs().add(days, 'day').format(dateFormat);\r\n resolvedOperator = 'eq';\r\n }\r\n } else if (operator !== 'isBetween' && !isEmptyOperator) {\r\n resolvedValue = formatSingleDate(value, dateFormat);\r\n }\r\n }\r\n\r\n // 5. Handle range (isBetween) decomposition\r\n if (resolvedOperator === 'isBetween') {\r\n if (Array.isArray(resolvedValue) && resolvedValue.length === 2) {\r\n let valStart = resolvedValue[0];\r\n let valEnd = resolvedValue[1];\r\n\r\n if (variant === 'date' || variant === 'dateRange') {\r\n valStart = formatSingleDate(valStart, dateFormat);\r\n valEnd = formatSingleDate(valEnd, dateFormat);\r\n }\r\n\r\n if (style === 'flat') {\r\n const suffix =\r\n config.flatRangeSuffix ??\r\n (variant === 'number' || variant === 'range' ? ['_min', '_max'] : ['_start', '_end']);\r\n return {\r\n [`${paramName}${suffix[0]}`]: valStart,\r\n [`${paramName}${suffix[1]}`]: valEnd,\r\n };\r\n } else {\r\n const startParams = formatFilterKeyValue(paramName, 'gte', valStart, config);\r\n const endParams = formatFilterKeyValue(paramName, 'lte', valEnd, config);\r\n const merged = { ...startParams };\r\n // Merge in case they map to the same key (like in postgrest)\r\n Object.keys(endParams).forEach((key) => {\r\n if (key in merged) {\r\n const existing = merged[key];\r\n const nextVal = endParams[key];\r\n merged[key] = Array.isArray(existing) ? [...existing, nextVal] : [existing, nextVal];\r\n } else {\r\n merged[key] = endParams[key];\r\n }\r\n });\r\n return merged;\r\n }\r\n }\r\n }\r\n\r\n // 6. Handle flat style legacy compatibility / defaults\r\n if (style === 'flat') {\r\n const optionsMap = (config.columnOptionsMap || {}) as Record;\r\n\r\n if (resolvedOperator === 'notInArray' && optionsMap[columnId]) {\r\n const excluded = Array.isArray(resolvedValue) ? resolvedValue : [resolvedValue];\r\n const allOptions = optionsMap[columnId];\r\n const complement = allOptions.filter((opt) => !excluded.includes(opt));\r\n return { [paramName]: complement.join(',') };\r\n }\r\n\r\n if (resolvedOperator === 'ne' && optionsMap[columnId]) {\r\n const excludedVal = String(resolvedValue);\r\n const allOptions = optionsMap[columnId];\r\n const complement = allOptions.filter((opt) => opt !== excludedVal);\r\n return { [paramName]: complement.join(',') };\r\n }\r\n\r\n if (resolvedOperator === 'inArray') {\r\n return {\r\n [paramName]: Array.isArray(resolvedValue) ? resolvedValue.join(',') : String(resolvedValue),\r\n };\r\n }\r\n\r\n if (resolvedOperator === 'eq') {\r\n return { [paramName]: String(resolvedValue) };\r\n }\r\n\r\n if (resolvedOperator === 'isEmpty') {\r\n return { [`${paramName}_is_empty`]: true };\r\n }\r\n if (resolvedOperator === 'isNotEmpty') {\r\n return { [`${paramName}_is_empty`]: false };\r\n }\r\n\r\n // Default fallback\r\n return { [paramName]: resolvedValue };\r\n }\r\n\r\n // 7. Handle non-flat styles (suffix, django, nested, prefix, postgrest)\r\n return formatFilterKeyValue(paramName, resolvedOperator, resolvedValue, config);\r\n}\r\n\r\n/**\r\n * Converts an array of column/advanced filters into a flat query parameters object.\r\n * Supports multiple formatting styles (flat, nested, suffix, prefix, django, postgrest)\r\n * and dynamically decomposes range operator (`isBetween`) and handles array formats.\r\n *\r\n * @template TData The data model of the table.\r\n * @template TParams The expected shape of the compiled API query parameters. Defaults to `Record`.\r\n *\r\n * @param filters Array of filters to resolve.\r\n * @param config Optional configuration object to customize mapping, styling, and naming conventions.\r\n *\r\n * @returns The resolved query parameters matching the TParams shape.\r\n *\r\n * @example\r\n * ```ts\r\n * const queryParams = resolveFiltersToFlatParams(filters, {\r\n * style: 'nested',\r\n * arrayFormat: 'brackets',\r\n * paramNameMap: { ticket_status: 'status' }\r\n * });\r\n * ```\r\n */\r\nexport function resolveFiltersToFlatParams, TData = any>(\r\n filters: ExtendedColumnFilter[],\r\n config: FilterResolutionConfig = {},\r\n): TParams {\r\n const params: Record = {};\r\n\r\n filters.forEach((filter) => {\r\n const formatted = formatFilterItem(filter, config);\r\n mergeParams(params, formatted);\r\n });\r\n\r\n // Apply join operator if joinParamName and joinOperator are present\r\n if (config.joinParamName && config.joinOperator && filters.length > 0) {\r\n params[config.joinParamName] = config.joinOperator;\r\n }\r\n\r\n return params as TParams;\r\n}\r\n\r\n/**\r\n * Maps advanced date filter values and operators into flat API parameters.\r\n * Handles single comparisons (eq, lt, lte, gt, gte) as well as ranges (isBetween).\r\n *\r\n * @param val The selected timestamp value (either string/number for single dates, or string[] for ranges).\r\n * @param operator The date comparison operator (e.g. 'isBetween', 'eq', 'lte', 'gte').\r\n * @param startParamName Custom parameter name for the start date (defaults to 'start_date').\r\n * @param endParamName Custom parameter name for the end date (defaults to 'end_date').\r\n * @param formatStr Format string pattern for dayjs formatting (defaults to 'YYYY-MM-DD').\r\n *\r\n * @example\r\n * ```ts\r\n * customMappers: {\r\n * date_updated: (val, operator) =>\r\n * mapDateFilterToParams(val, operator, 'update_start_date', 'update_end_date', 'YYYY-MM-DD')\r\n * }\r\n * ```\r\n */\r\nexport function mapDateFilterToParams<\r\n TParams = Record,\r\n TStart extends Extract = any,\r\n TEnd extends Extract = any,\r\n>(\r\n val: any,\r\n operator: FilterOperator,\r\n startParamName: TStart = 'start_date' as any,\r\n endParamName: TEnd = 'end_date' as any,\r\n formatStr = 'YYYY-MM-DD',\r\n): { [K in TStart]?: string } & { [K in TEnd]?: string } & Partial & {\r\n [key: `${string}_is_empty`]: boolean;\r\n } {\r\n if (operator === 'isBetween') {\r\n if (Array.isArray(val) && val[0] && val[1]) {\r\n return {\r\n [startParamName]: dayjs(Number(val[0])).format(formatStr),\r\n [endParamName]: dayjs(Number(val[1])).format(formatStr),\r\n } as any;\r\n }\r\n } else if (operator === 'eq') {\r\n if (val) {\r\n const dateStr = dayjs(Number(val)).format(formatStr);\r\n return {\r\n [startParamName]: dateStr,\r\n [endParamName]: dateStr,\r\n } as any;\r\n }\r\n } else if (operator === 'lt' || operator === 'lte') {\r\n if (val) {\r\n return {\r\n [endParamName]: dayjs(Number(val)).format(formatStr),\r\n } as any;\r\n }\r\n } else if (operator === 'gt' || operator === 'gte') {\r\n if (val) {\r\n return {\r\n [startParamName]: dayjs(Number(val)).format(formatStr),\r\n } as any;\r\n }\r\n } else if (operator === 'isRelativeToToday') {\r\n if (val) {\r\n const days = Number(val);\r\n if (!Number.isNaN(days)) {\r\n const dateStr = dayjs().add(days, 'day').format(formatStr);\r\n return {\r\n [startParamName]: dateStr,\r\n [endParamName]: dateStr,\r\n } as any;\r\n }\r\n }\r\n } else if (operator === 'isEmpty') {\r\n const baseParamName = startParamName.replace(/(_?start_?)/gi, '');\r\n return {\r\n [`${baseParamName}_is_empty`]: true,\r\n } as any;\r\n } else if (operator === 'isNotEmpty') {\r\n const baseParamName = startParamName.replace(/(_?start_?)/gi, '');\r\n return {\r\n [`${baseParamName}_is_empty`]: false,\r\n } as any;\r\n }\r\n return {} as any;\r\n}\r\n\r\nexport interface FilterResolutionConfig> {\r\n /**\r\n * Complete list of all possible values for select/multiSelect columns.\r\n * Key is the column ID, value is the array of all valid options.\r\n */\r\n columnOptionsMap?: Partial, string[]>>;\r\n\r\n /**\r\n * Simple mapping to rename parameter keys.\r\n * Example: { ticket_status: 'status' }\r\n */\r\n paramNameMap?: Partial<\r\n Record, Extract | string>\r\n >;\r\n\r\n /**\r\n * Custom mapper functions for columns requiring special serialization (e.g. date ranges, custom query logic).\r\n * Callback should return a flat key-value parameters object.\r\n */\r\n customMappers?: Partial<\r\n Record, (value: any, operator: FilterOperator) => Partial>\r\n >;\r\n\r\n /**\r\n * Style of parameter formatting to support different backend conventions.\r\n * - 'flat': Maps directly to parameter name (standard flat API structure, no operator suffixes).\r\n * - 'suffix': Appends operator with an underscore (e.g. `status_eq=active`, `price_gte=100`).\r\n * - 'django': Appends operator with a double underscore (e.g. `status__eq=active`, `price__gte=100`).\r\n * - 'nested': Uses brackets for operator nesting (e.g. `status[eq]=active`, `price[gte]=100`).\r\n * - 'prefix': Uses brackets with a dollar sign prefix (e.g. `status[$eq]=active`, `price[$gte]=100`).\r\n * - 'postgrest': Flat keys with value prefixed by operator (e.g. `status=eq.active`, `price=gte.100`).\r\n * - 'custom': Relies entirely on the `formatFilter` hook.\r\n * @default 'flat'\r\n */\r\n style?: 'flat' | 'suffix' | 'django' | 'nested' | 'prefix' | 'postgrest' | 'custom';\r\n\r\n /**\r\n * Custom mapping of operators to backend representation.\r\n * E.g. { eq: '', inArray: 'in', notInArray: 'nin' }\r\n */\r\n operatorMap?: Partial>;\r\n\r\n /**\r\n * How array/list values are serialized.\r\n * - 'comma': Joined into a comma-separated string (e.g., `status=active,inactive`).\r\n * - 'repeat': Returned as an array (e.g., `status=['active', 'inactive']`), which standard HTTP clients serialize as multiple parameters.\r\n * - 'brackets': Returned as an array with bracket keys (e.g., `status[]=['active', 'inactive']`).\r\n * @default 'comma'\r\n */\r\n arrayFormat?: 'comma' | 'repeat' | 'brackets';\r\n\r\n /**\r\n * Custom date format string for dayjs.\r\n * @default 'YYYY-MM-DD'\r\n */\r\n dateFormat?: string;\r\n\r\n /**\r\n * Custom suffix or key names for range fields (e.g., [minSuffix, maxSuffix] or [startSuffix, endSuffix])\r\n * used only when style is 'flat'.\r\n * @default ['_start', '_end']\r\n */\r\n flatRangeSuffix?: [string, string];\r\n\r\n /**\r\n * Optional custom hook to format any filter parameter.\r\n * Takes priority over default style formatting if provided.\r\n */\r\n formatFilter?: (\r\n paramName: string,\r\n operator: FilterOperator,\r\n value: any,\r\n backendOperator: string,\r\n ) => Record;\r\n\r\n /**\r\n * Optional parameter name to represent the global join operator ('and' | 'or').\r\n * If provided, the join operator will be added to the parameters.\r\n * E.g. `_join: 'and'`\r\n */\r\n joinParamName?: Extract | string;\r\n\r\n /**\r\n * The active logical join operator ('and' | 'or').\r\n * Used to format the join parameter if joinParamName is specified.\r\n */\r\n joinOperator?: JoinOperator;\r\n}\r\n", + "path": "registry/lib/filter-helper.ts", + "target": "@lib/filter-helper.ts", + "type": "registry:lib" + } + ], + "name": "filter-helper", + "registryDependencies": ["@kombase/data-table-types"], + "type": "registry:lib" +} diff --git a/public/r/form-date-picker.json b/public/r/form-date-picker.json new file mode 100644 index 0000000..21e9374 --- /dev/null +++ b/public/r/form-date-picker.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["dayjs"], + "files": [ + { + "content": "import dayjs from 'dayjs';\r\nimport { CalendarIcon } from 'lucide-react';\r\nimport React from 'react';\r\nimport type { DateRange } from 'react-day-picker';\r\nimport type { Control, FieldPath, FieldValues } from 'react-hook-form';\r\nimport { Calendar } from '@/components/ui/calendar';\r\nimport { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form';\r\nimport { Label } from '@/components/ui/label';\r\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype BaseProps = {\r\n control: Control;\r\n name: FieldPath;\r\n label?: string | React.ReactNode;\r\n layout?: 'vertical' | 'horizontal';\r\n formatLabel: (value?: Date | DateRange) => string;\r\n className?: string;\r\n labelClassName?: string;\r\n render: (params: { label: string; value?: Date | DateRange }) => React.ReactElement<{\r\n children?: React.ReactNode;\r\n className?: string;\r\n }>;\r\n};\r\n\r\ntype CalendarProps = React.ComponentProps;\r\n\r\ntype DatePickerProps = BaseProps & {\r\n datePickerProps: CalendarProps;\r\n};\r\n\r\ntype FormDatePickerProps = DatePickerProps;\r\n\r\nexport function FormDatePicker({\r\n control,\r\n name,\r\n label,\r\n datePickerProps,\r\n layout = 'vertical',\r\n className,\r\n labelClassName,\r\n formatLabel,\r\n render,\r\n}: FormDatePickerProps) {\r\n return (\r\n {\r\n const isRange = datePickerProps?.mode === 'range';\r\n\r\n const rangeValue = field.value as DateRange | undefined;\r\n\r\n const singleValue = field.value as Date | undefined;\r\n\r\n const currentValue = isRange ? rangeValue : singleValue;\r\n\r\n const defaultLabel = isRange\r\n ? rangeValue?.from\r\n ? rangeValue.to\r\n ? `${dayjs(rangeValue.from).format('PPP')} - ${dayjs(rangeValue.to).format('PPP')}`\r\n : dayjs(rangeValue.from).format('PPP')\r\n : 'Pick a date range'\r\n : singleValue\r\n ? dayjs(singleValue).format('PPP')\r\n : 'Pick a date';\r\n\r\n const text = formatLabel?.(field.value) ?? defaultLabel;\r\n\r\n const trigger = render({\r\n label: text,\r\n value: currentValue,\r\n });\r\n\r\n return (\r\n \r\n {label && (\r\n \r\n {label}\r\n \r\n )}\r\n
\r\n \r\n \r\n \r\n {React.cloneElement(trigger, {\r\n children: (\r\n
\r\n {trigger.props.children}\r\n\r\n \r\n
\r\n ),\r\n })}\r\n
\r\n \r\n \r\n \r\n
\r\n
\r\n \r\n
\r\n \r\n );\r\n }}\r\n />\r\n );\r\n}\r\n", + "path": "registry/form/form-date-picker.tsx", + "target": "@components/form/form-date-picker.tsx", + "type": "registry:component" + } + ], + "name": "form-date-picker", + "registryDependencies": ["calendar", "form", "label", "popover"], + "type": "registry:component" +} diff --git a/public/r/form-input-group.json b/public/r/form-input-group.json new file mode 100644 index 0000000..0e6bf09 --- /dev/null +++ b/public/r/form-input-group.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import type { Control, FieldPath, FieldValues } from 'react-hook-form';\r\nimport { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form';\r\nimport {\r\n InputGroup,\r\n InputGroupAddon,\r\n InputGroupInput,\r\n InputGroupText,\r\n} from '@/components/ui/input-group';\r\nimport { Label } from '@/components/ui/label';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype FormInputGroupProps = {\r\n control: Control;\r\n name: FieldPath;\r\n label?: string | React.ReactNode;\r\n addon?: React.ReactNode;\r\n inputGroupProps?: React.ComponentProps;\r\n layout?: 'vertical' | 'horizontal';\r\n className?: string;\r\n labelClassName?: string;\r\n};\r\n\r\nexport function FormInputGroup({\r\n control,\r\n name,\r\n label,\r\n addon,\r\n inputGroupProps,\r\n layout = 'vertical',\r\n className,\r\n labelClassName,\r\n}: FormInputGroupProps) {\r\n return (\r\n (\r\n \r\n {label && (\r\n \r\n {label}\r\n \r\n )}\r\n
\r\n \r\n \r\n \r\n {addon && (\r\n \r\n {addon}\r\n \r\n )}\r\n \r\n \r\n \r\n
\r\n \r\n )}\r\n />\r\n );\r\n}\r\n", + "path": "registry/form/form-input-group.tsx", + "target": "@components/form/form-input-group.tsx", + "type": "registry:component" + } + ], + "name": "form-input-group", + "registryDependencies": ["form", "input-group", "label"], + "type": "registry:component" +} diff --git a/public/r/form-input.json b/public/r/form-input.json new file mode 100644 index 0000000..575f36b --- /dev/null +++ b/public/r/form-input.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import type React from 'react';\r\nimport type { Control, FieldPath, FieldValues } from 'react-hook-form';\r\nimport { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form';\r\nimport { Input } from '@/components/ui/input';\r\nimport { Label } from '@/components/ui/label';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype FormInputProps = {\r\n control: Control;\r\n name: FieldPath;\r\n label?: string | React.ReactNode;\r\n inputProps?: React.ComponentProps;\r\n layout?: 'vertical' | 'horizontal';\r\n className?: string;\r\n labelClassName?: string;\r\n prefix?: React.ReactNode;\r\n suffix?: React.ReactNode;\r\n};\r\n\r\nexport function FormInput({\r\n control,\r\n name,\r\n label,\r\n inputProps,\r\n layout = 'vertical',\r\n className,\r\n labelClassName,\r\n prefix,\r\n suffix,\r\n}: FormInputProps) {\r\n return (\r\n (\r\n \r\n {label && (\r\n \r\n {label}\r\n \r\n )}\r\n
\r\n \r\n
\r\n {prefix && (\r\n
{prefix}
\r\n )}\r\n\r\n \r\n\r\n {suffix && (\r\n
{suffix}
\r\n )}\r\n
\r\n
\r\n \r\n
\r\n \r\n )}\r\n />\r\n );\r\n}\r\n", + "path": "registry/form/form-input.tsx", + "target": "@components/form/form-input.tsx", + "type": "registry:component" + } + ], + "name": "form-input", + "registryDependencies": ["form", "input", "label"], + "type": "registry:component" +} diff --git a/public/r/form-password.json b/public/r/form-password.json new file mode 100644 index 0000000..8b19f9b --- /dev/null +++ b/public/r/form-password.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import type React from 'react';\r\nimport type { Control, FieldPath, FieldValues } from 'react-hook-form';\r\nimport { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form';\r\nimport type { Input } from '@/components/ui/input';\r\nimport { Label } from '@/components/ui/label';\r\nimport { cn } from '@/lib/utils';\r\nimport { PasswordInput } from '@/components/password-input';\r\n\r\ntype FormInputPasswordProps = {\r\n control: Control;\r\n name: FieldPath;\r\n label?: string | React.ReactNode;\r\n inputProps?: React.ComponentProps;\r\n layout?: 'vertical' | 'horizontal';\r\n className?: string;\r\n labelClassName?: string;\r\n};\r\n\r\nexport function FormInputPassword({\r\n control,\r\n name,\r\n label,\r\n inputProps,\r\n layout = 'vertical',\r\n className,\r\n labelClassName,\r\n}: FormInputPasswordProps) {\r\n return (\r\n (\r\n \r\n {label && (\r\n \r\n {label}\r\n \r\n )}\r\n
\r\n \r\n \r\n \r\n \r\n
\r\n \r\n )}\r\n />\r\n );\r\n}\r\n", + "path": "registry/form/form-password.tsx", + "target": "@components/form/form-password.tsx", + "type": "registry:component" + } + ], + "name": "form-password", + "registryDependencies": ["form", "label", "@kombase/password-input"], + "type": "registry:component" +} diff --git a/public/r/form-phone-input.json b/public/r/form-phone-input.json new file mode 100644 index 0000000..fd6d0ad --- /dev/null +++ b/public/r/form-phone-input.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import type React from 'react';\r\nimport type { Control, FieldPath, FieldValues } from 'react-hook-form';\r\nimport {\r\n PhoneInput,\r\n PhoneInputCountrySelect,\r\n PhoneInputField,\r\n} from '@/components/dynamic/phone-input';\r\nimport { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form';\r\nimport { Label } from '@/components/ui/label';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype FormPhoneInputProps = {\r\n control: Control;\r\n name: FieldPath;\r\n label?: string | React.ReactNode;\r\n phoneInputProps?: Omit<\r\n React.ComponentProps,\r\n keyof React.ComponentProps<'div'>\r\n >;\r\n phoneInputCountrySelectProps?: React.ComponentProps;\r\n layout?: 'vertical' | 'horizontal';\r\n className?: string;\r\n labelClassName?: string;\r\n isVisibleCountrySelect?: boolean;\r\n};\r\n\r\nexport function FormPhoneInput({\r\n control,\r\n name,\r\n label,\r\n phoneInputProps,\r\n phoneInputCountrySelectProps,\r\n layout = 'vertical',\r\n className,\r\n labelClassName,\r\n isVisibleCountrySelect = true,\r\n}: FormPhoneInputProps) {\r\n return (\r\n (\r\n \r\n {label && (\r\n \r\n {label}\r\n \r\n )}\r\n
\r\n \r\n \r\n {isVisibleCountrySelect && (\r\n \r\n )}\r\n \r\n \r\n \r\n \r\n
\r\n \r\n )}\r\n />\r\n );\r\n}\r\n", + "path": "registry/form/form-phone-input.tsx", + "target": "@components/form/form-phone-input.tsx", + "type": "registry:component" + } + ], + "name": "form-phone-input", + "registryDependencies": ["form", "label", "@kombase/phone-input"], + "type": "registry:component" +} diff --git a/public/r/form-pick.json b/public/r/form-pick.json new file mode 100644 index 0000000..605cf0d --- /dev/null +++ b/public/r/form-pick.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import type * as React from 'react';\r\nimport type { ComponentProps } from 'react';\r\nimport type { Control, FieldPath, FieldPathValue, FieldValues } from 'react-hook-form';\r\nimport { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';\r\nimport { Label } from '@/components/ui/label';\r\nimport { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';\r\nimport { cn } from '@/lib/utils';\r\n\r\nexport type PickOption = {\r\n id: TValue;\r\n label: string | React.ReactNode;\r\n sub?: string;\r\n icon?: React.ElementType;\r\n};\r\n\r\nexport type FormPickProps = {\r\n control: Control;\r\n name: FieldPath;\r\n options?: readonly PickOption> & string>[];\r\n label?: string | React.ReactNode;\r\n className?: string;\r\n radioGroupProps?: Omit, 'value' | 'onValueChange'>;\r\n renderOption?: (\r\n option: Readonly> & string>>,\r\n isSelected: boolean,\r\n ) => React.ReactNode;\r\n onValueChange?: (value: FieldPathValue>) => void;\r\n layout?: 'vertical' | 'horizontal';\r\n labelClassName?: string;\r\n};\r\n\r\nexport function FormPick>({\r\n control,\r\n name,\r\n label,\r\n options,\r\n className,\r\n radioGroupProps,\r\n renderOption,\r\n onValueChange,\r\n layout = 'vertical',\r\n labelClassName,\r\n}: FormPickProps) {\r\n return (\r\n (\r\n \r\n {label && (\r\n \r\n {label}\r\n \r\n )}\r\n\r\n
\r\n \r\n {\r\n field.onChange(val);\r\n onValueChange?.(val as FieldPathValue);\r\n }}\r\n value={field.value as string}\r\n >\r\n {options?.map((opt) => {\r\n const isSelected = field.value === opt.id;\r\n const Icon = opt.icon;\r\n\r\n return (\r\n
\r\n \r\n\r\n {renderOption ? (\r\n // Custom render – consumer is responsible for the full card\r\n \r\n ) : (\r\n // Default card layout\r\n \r\n {Icon && (\r\n
\r\n \r\n
\r\n )}\r\n\r\n
\r\n \r\n {opt.label}\r\n \r\n {opt.sub && (\r\n \r\n {opt.sub}\r\n \r\n )}\r\n
\r\n \r\n )}\r\n
\r\n );\r\n })}\r\n \r\n
\r\n\r\n \r\n
\r\n \r\n )}\r\n />\r\n );\r\n}\r\n", + "path": "registry/form/form-pick.tsx", + "target": "@components/form/form-pick.tsx", + "type": "registry:component" + } + ], + "name": "form-pick", + "registryDependencies": ["form", "label", "radio-group"], + "type": "registry:component" +} diff --git a/public/r/form-radio.json b/public/r/form-radio.json new file mode 100644 index 0000000..a6d6689 --- /dev/null +++ b/public/r/form-radio.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import type * as React from 'react';\r\nimport type { ComponentProps } from 'react';\r\nimport type { Control, FieldPath, FieldPathValue, FieldValues } from 'react-hook-form';\r\nimport { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form';\r\nimport { Label } from '@/components/ui/label';\r\nimport { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';\r\nimport { cn } from '@/lib/utils';\r\n\r\nexport type RadioOption = {\r\n id: TValue;\r\n label: string | React.ReactNode;\r\n description?: string;\r\n};\r\n\r\nexport type FormRadioProps = {\r\n control: Control;\r\n name: FieldPath;\r\n options?: readonly RadioOption> & string>[];\r\n label?: string | React.ReactNode;\r\n className?: string;\r\n radioGroupProps?: Omit, 'value' | 'onValueChange'>;\r\n onValueChange?: (value: FieldPathValue>) => void;\r\n layout?: 'vertical' | 'horizontal';\r\n labelClassName?: string;\r\n orientation?: 'vertical' | 'horizontal';\r\n};\r\n\r\nexport function FormRadio>({\r\n control,\r\n name,\r\n label,\r\n options,\r\n className,\r\n radioGroupProps,\r\n onValueChange,\r\n layout = 'vertical',\r\n labelClassName,\r\n orientation = 'vertical',\r\n}: FormRadioProps) {\r\n return (\r\n (\r\n \r\n {label && (\r\n \r\n {label}\r\n \r\n )}\r\n\r\n
\r\n \r\n {\r\n field.onChange(val);\r\n onValueChange?.(val as FieldPathValue);\r\n }}\r\n value={field.value as string}\r\n >\r\n {options?.map((opt) => {\r\n const optionId = `${name}-${opt.id}`;\r\n const hasDescription = !!opt.description;\r\n return (\r\n \r\n \r\n \r\n {opt.label}\r\n {opt.description && (\r\n \r\n {opt.description}\r\n \r\n )}\r\n \r\n
\r\n );\r\n })}\r\n \r\n \r\n\r\n \r\n \r\n \r\n )}\r\n />\r\n );\r\n}\r\n", + "path": "registry/form/form-radio.tsx", + "target": "@components/form/form-radio.tsx", + "type": "registry:component" + } + ], + "name": "form-radio", + "registryDependencies": ["form", "label", "radio-group"], + "type": "registry:component" +} diff --git a/public/r/form-search-select.json b/public/r/form-search-select.json new file mode 100644 index 0000000..6eb670b --- /dev/null +++ b/public/r/form-search-select.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import type React from 'react';\r\nimport type { Control, FieldPath, FieldValues } from 'react-hook-form';\r\nimport { Combobox, useComboboxAnchor } from '@/components/ui/combobox';\r\nimport { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form';\r\nimport { Label } from '@/components/ui/label';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype FormSearchSelectProps = {\r\n control: Control;\r\n name: FieldPath;\r\n multiple?: boolean;\r\n label?: string | React.ReactNode;\r\n comboboxProps?: Omit<\r\n React.ComponentProps,\r\n 'value' | 'onValueChange' | 'multiple' | 'defaultValue'\r\n >;\r\n render: (props: { anchor: ReturnType }) => React.ReactNode;\r\n layout?: 'vertical' | 'horizontal';\r\n className?: string;\r\n labelClassName?: string;\r\n};\r\n\r\nexport function FormSearchSelect({\r\n control,\r\n name,\r\n render,\r\n label,\r\n comboboxProps,\r\n layout = 'vertical',\r\n className,\r\n labelClassName,\r\n multiple = false,\r\n}: FormSearchSelectProps) {\r\n const anchor = useComboboxAnchor();\r\n\r\n return (\r\n (\r\n \r\n {label && (\r\n \r\n {label}\r\n \r\n )}\r\n\r\n
\r\n \r\n \r\n {render({ anchor })}\r\n \r\n \r\n\r\n \r\n
\r\n \r\n )}\r\n />\r\n );\r\n}\r\n", + "path": "registry/form/form-search-select.tsx", + "target": "@components/form/form-search-select.tsx", + "type": "registry:component" + } + ], + "name": "form-search-select", + "registryDependencies": ["combobox", "form", "label"], + "type": "registry:component" +} diff --git a/public/r/form-textarea.json b/public/r/form-textarea.json new file mode 100644 index 0000000..f71d418 --- /dev/null +++ b/public/r/form-textarea.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import type React from 'react';\r\nimport type { Control, FieldPath, FieldValues } from 'react-hook-form';\r\nimport { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form';\r\nimport { Label } from '@/components/ui/label';\r\nimport { Textarea } from '@/components/ui/textarea';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype FormTextareaProps = {\r\n control: Control;\r\n name: FieldPath;\r\n label?: string | React.ReactNode;\r\n showCharacterCount?: boolean;\r\n maxLength?: number;\r\n textareaProps?: Omit, 'value' | 'onChange'>;\r\n layout?: 'vertical' | 'horizontal';\r\n className?: string;\r\n labelClassName?: string;\r\n};\r\n\r\nexport function FormTextarea({\r\n control,\r\n name,\r\n label,\r\n showCharacterCount = false,\r\n maxLength,\r\n textareaProps,\r\n layout = 'vertical',\r\n className,\r\n labelClassName,\r\n}: FormTextareaProps) {\r\n return (\r\n {\r\n const currentLength = field.value?.length || 0;\r\n const isExceeded = maxLength !== undefined && currentLength > maxLength;\r\n\r\n return (\r\n \r\n {label && (\r\n \r\n {label}\r\n \r\n )}\r\n
\r\n \r\n \r\n \r\n {showCharacterCount && (\r\n \r\n {maxLength !== undefined ? `${currentLength}/${maxLength}` : currentLength}\r\n
\r\n )}\r\n \r\n \r\n \r\n );\r\n }}\r\n />\r\n );\r\n}\r\n", + "path": "registry/form/form-textarea.tsx", + "target": "@components/form/form-textarea.tsx", + "type": "registry:component" + } + ], + "name": "form-textarea", + "registryDependencies": ["form", "label", "textarea"], + "type": "registry:component" +} diff --git a/public/r/form-upload.json b/public/r/form-upload.json new file mode 100644 index 0000000..319abff --- /dev/null +++ b/public/r/form-upload.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import type React from 'react';\r\nimport type { Control, FieldPath, FieldValues } from 'react-hook-form';\r\nimport { FileUpload, type FileUploadProps } from '@/components/ui/file-upload';\r\nimport { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form';\r\nimport { Label } from '@/components/ui/label';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype FormUploadProps = {\r\n control: Control;\r\n name: FieldPath;\r\n label?: string | React.ReactNode;\r\n uploadProps?: Omit;\r\n layout?: 'vertical' | 'horizontal';\r\n className?: string;\r\n labelClassName?: string;\r\n children?: React.ReactNode;\r\n};\r\n\r\nexport function FormUpload({\r\n control,\r\n name,\r\n label,\r\n uploadProps,\r\n layout = 'vertical',\r\n className,\r\n labelClassName,\r\n children,\r\n}: FormUploadProps) {\r\n return (\r\n (\r\n \r\n {label && (\r\n \r\n {label}\r\n \r\n )}\r\n
\r\n \r\n \r\n {children}\r\n \r\n \r\n \r\n
\r\n \r\n )}\r\n />\r\n );\r\n}\r\n", + "path": "registry/form/form-upload.tsx", + "target": "@components/form/form-upload.tsx", + "type": "registry:component" + } + ], + "name": "form-upload", + "registryDependencies": ["form", "label", "@kombase/file-upload"], + "type": "registry:component" +} diff --git a/public/r/form.json b/public/r/form.json new file mode 100644 index 0000000..7a13b73 --- /dev/null +++ b/public/r/form.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-form", "@radix-ui/react-label", "react-hook-form"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as Slot from '@radix-ui/react-slot';\r\nimport * as React from 'react';\r\nimport {\r\n Controller,\r\n type ControllerProps,\r\n type FieldPath,\r\n type FieldValues,\r\n FormProvider,\r\n useFormContext,\r\n useFormState,\r\n} from 'react-hook-form';\r\nimport { Label } from '@/components/ui/label';\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst Form = FormProvider;\r\n\r\ntype FormFieldContextValue<\r\n TFieldValues extends FieldValues = FieldValues,\r\n TName extends FieldPath = FieldPath,\r\n> = {\r\n name: TName;\r\n};\r\n\r\nconst FormFieldContext = React.createContext({} as FormFieldContextValue);\r\n\r\nconst FormField = <\r\n TFieldValues extends FieldValues = FieldValues,\r\n TName extends FieldPath = FieldPath,\r\n>({\r\n ...props\r\n}: ControllerProps) => {\r\n return (\r\n \r\n \r\n \r\n );\r\n};\r\n\r\nconst useFormField = () => {\r\n const fieldContext = React.useContext(FormFieldContext);\r\n const itemContext = React.useContext(FormItemContext);\r\n const { getFieldState } = useFormContext();\r\n const formState = useFormState({ name: fieldContext.name });\r\n const fieldState = getFieldState(fieldContext.name, formState);\r\n\r\n if (!fieldContext) {\r\n throw new Error('useFormField should be used within ');\r\n }\r\n\r\n const { id } = itemContext;\r\n\r\n return {\r\n formDescriptionId: `${id}-form-item-description`,\r\n formItemId: `${id}-form-item`,\r\n formMessageId: `${id}-form-item-message`,\r\n id,\r\n name: fieldContext.name,\r\n ...fieldState,\r\n };\r\n};\r\n\r\ntype FormItemContextValue = {\r\n id: string;\r\n};\r\n\r\nconst FormItemContext = React.createContext({} as FormItemContextValue);\r\n\r\nconst FormItem = React.forwardRef>(\r\n ({ className, ...props }, ref) => {\r\n const id = React.useId();\r\n\r\n return (\r\n \r\n
\r\n \r\n );\r\n },\r\n);\r\nFormItem.displayName = 'FormItem';\r\n\r\nconst FormLabel = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => {\r\n const { error, formItemId } = useFormField();\r\n\r\n return (\r\n \r\n );\r\n});\r\nFormLabel.displayName = 'FormLabel';\r\n\r\nconst FormControl = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>((props, ref) => {\r\n const { error, formItemId, formDescriptionId, formMessageId } = useFormField();\r\n\r\n return (\r\n \r\n );\r\n});\r\nFormControl.displayName = 'FormControl';\r\n\r\nconst FormDescription = React.forwardRef>(\r\n ({ className, ...props }, ref) => {\r\n const { formDescriptionId } = useFormField();\r\n\r\n return (\r\n \r\n );\r\n },\r\n);\r\nFormDescription.displayName = 'FormDescription';\r\n\r\nconst FormMessage = React.forwardRef>(\r\n ({ className, ...props }, ref) => {\r\n const { error, formMessageId } = useFormField();\r\n const body = error ? String(error?.message ?? '') : props.children;\r\n\r\n if (!body) {\r\n return null;\r\n }\r\n\r\n return (\r\n \r\n {body}\r\n

\r\n );\r\n },\r\n);\r\nFormMessage.displayName = 'FormMessage';\r\n\r\nexport {\r\n Form,\r\n FormControl,\r\n FormDescription,\r\n FormField,\r\n FormItem,\r\n FormLabel,\r\n FormMessage,\r\n useFormField,\r\n};\r\n", + "path": "registry/ui/form.tsx", + "target": "@ui/form.tsx", + "type": "registry:ui" + } + ], + "name": "form", + "registryDependencies": ["label", "@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/input-group.json b/public/r/input-group.json new file mode 100644 index 0000000..4292096 --- /dev/null +++ b/public/r/input-group.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "'use client';\r\n\r\nimport { cva, type VariantProps } from 'class-variance-authority';\r\nimport * as React from 'react';\r\nimport { cn } from '@/lib/utils';\r\nimport { Button } from '@/components/ui/button';\r\nimport { Input } from '@/components/ui/input';\r\nimport { Textarea } from '@/components/ui/textarea';\r\n\r\nconst InputGroup = React.forwardRef>(\r\n ({ className, ...props }, ref) => {\r\n return (\r\n textarea]:h-auto',\r\n\r\n // Variants based on alignment.\r\n 'has-[>[data-align=inline-start]]:[&>input]:pl-2',\r\n 'has-[>[data-align=inline-end]]:[&>input]:pr-2',\r\n 'has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3',\r\n 'has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3',\r\n\r\n // Focus state.\r\n 'has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50',\r\n\r\n // Error state.\r\n 'has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40',\r\n\r\n className,\r\n )}\r\n data-slot=\"input-group\"\r\n ref={ref}\r\n role=\"group\"\r\n {...props}\r\n />\r\n );\r\n },\r\n);\r\nInputGroup.displayName = 'InputGroup';\r\n\r\nconst inputGroupAddonVariants = cva(\r\n \"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4\",\r\n {\r\n defaultVariants: {\r\n align: 'inline-start',\r\n },\r\n variants: {\r\n align: {\r\n 'block-end':\r\n 'order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5 [.border-t]:pt-3',\r\n 'block-start':\r\n 'order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5 [.border-b]:pb-3',\r\n 'inline-end': 'order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]',\r\n 'inline-start': 'order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]',\r\n },\r\n },\r\n },\r\n);\r\n\r\nconst InputGroupAddon = React.forwardRef<\r\n HTMLDivElement,\r\n React.ComponentPropsWithoutRef<'div'> & VariantProps\r\n>(({ className, align = 'inline-start', ...props }, ref) => {\r\n return (\r\n {\r\n if ((e.target as HTMLElement).closest('button')) {\r\n return;\r\n }\r\n e.currentTarget.parentElement?.querySelector('input')?.focus();\r\n }}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Enter' || e.key === ' ') {\r\n if ((e.target as HTMLElement).closest('button')) {\r\n return;\r\n }\r\n if (e.key === ' ') e.preventDefault();\r\n e.currentTarget.parentElement?.querySelector('input')?.focus();\r\n }\r\n }}\r\n ref={ref}\r\n role=\"group\"\r\n {...props}\r\n />\r\n );\r\n});\r\nInputGroupAddon.displayName = 'InputGroupAddon';\r\n\r\nconst inputGroupButtonVariants = cva('flex items-center gap-2 text-sm shadow-none', {\r\n defaultVariants: {\r\n size: 'xs',\r\n },\r\n variants: {\r\n size: {\r\n 'icon-sm': 'size-8 p-0 has-[>svg]:p-0',\r\n 'icon-xs': 'size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0',\r\n sm: 'h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5',\r\n xs: \"h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5\",\r\n },\r\n },\r\n});\r\n\r\nconst InputGroupButton = React.forwardRef<\r\n React.ComponentRef,\r\n Omit, 'size'> &\r\n VariantProps\r\n>(({ className, type = 'button', variant = 'ghost', size = 'xs', ...props }, ref) => {\r\n return (\r\n \r\n );\r\n});\r\nInputGroupButton.displayName = 'InputGroupButton';\r\n\r\nconst InputGroupText = React.forwardRef>(\r\n ({ className, ...props }, ref) => {\r\n return (\r\n \r\n );\r\n },\r\n);\r\nInputGroupText.displayName = 'InputGroupText';\r\n\r\nconst InputGroupInput = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => {\r\n return (\r\n \r\n );\r\n});\r\nInputGroupInput.displayName = 'InputGroupInput';\r\n\r\nconst InputGroupTextarea = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => {\r\n return (\r\n \r\n );\r\n});\r\nInputGroupTextarea.displayName = 'InputGroupTextarea';\r\n\r\nexport {\r\n InputGroup,\r\n InputGroupAddon,\r\n InputGroupButton,\r\n InputGroupInput,\r\n InputGroupText,\r\n InputGroupTextarea,\r\n};\r\n", + "path": "registry/ui/input-group.tsx", + "target": "@ui/input-group.tsx", + "type": "registry:ui" + } + ], + "name": "input-group", + "registryDependencies": ["button", "input", "textarea", "@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/input.json b/public/r/input.json new file mode 100644 index 0000000..71f9c9f --- /dev/null +++ b/public/r/input.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "'use client';\r\n\r\nimport * as React from 'react';\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst Input = React.forwardRef>(\r\n ({ className, type, ...props }, ref) => {\r\n return (\r\n \r\n );\r\n },\r\n);\r\nInput.displayName = 'Input';\r\n\r\nexport { Input };\r\n", + "path": "registry/ui/input.tsx", + "target": "@ui/input.tsx", + "type": "registry:ui" + } + ], + "name": "input", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/label.json b/public/r/label.json new file mode 100644 index 0000000..adb2ff1 --- /dev/null +++ b/public/r/label.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-label"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as LabelPrimitive from '@radix-ui/react-label';\r\nimport * as React from 'react';\r\n\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst Label = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nLabel.displayName = LabelPrimitive.Root.displayName;\r\n\r\nexport { Label };\r\n", + "path": "registry/ui/label.tsx", + "target": "@ui/label.tsx", + "type": "registry:ui" + } + ], + "name": "label", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/lib-data-table.json b/public/r/lib-data-table.json new file mode 100644 index 0000000..7ffdf83 --- /dev/null +++ b/public/r/lib-data-table.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@tanstack/react-table"], + "files": [ + { + "content": "import type { Column } from '@tanstack/react-table';\r\nimport { dataTableConfig } from '@/components/data-table/data-table-config';\r\nimport type {\r\n ExtendedColumnFilter,\r\n FilterOperator,\r\n FilterVariant,\r\n} from '@/components/data-table/types';\r\n\r\nexport function getCommonPinningStyles({\r\n column,\r\n withBorder = false,\r\n isHeader = false,\r\n stickyHeader = false,\r\n}: {\r\n column: Column;\r\n withBorder?: boolean;\r\n isHeader?: boolean;\r\n stickyHeader?: boolean;\r\n}): React.CSSProperties {\r\n const isPinned = column.getIsPinned();\r\n const isLastLeftPinnedColumn = isPinned === 'left' && column.getIsLastColumn('left');\r\n const isFirstRightPinnedColumn = isPinned === 'right' && column.getIsFirstColumn('right');\r\n\r\n const isSticky = isPinned || (isHeader && stickyHeader);\r\n\r\n return {\r\n background: 'var(--background)',\r\n boxShadow: withBorder\r\n ? isLastLeftPinnedColumn\r\n ? '-4px 0 4px -4px var(--border) inset'\r\n : isFirstRightPinnedColumn\r\n ? '4px 0 4px -4px var(--border) inset'\r\n : undefined\r\n : undefined,\r\n left: isPinned === 'left' ? `${column.getStart('left')}px` : undefined,\r\n opacity: isPinned ? 0.97 : 1,\r\n position: isSticky ? 'sticky' : 'relative',\r\n right: isPinned === 'right' ? `${column.getAfter('right')}px` : undefined,\r\n top: isHeader && stickyHeader ? 0 : undefined,\r\n width: column.getSize(),\r\n zIndex: isPinned\r\n ? isHeader && stickyHeader\r\n ? 30\r\n : 10\r\n : isHeader && stickyHeader\r\n ? 20\r\n : undefined,\r\n };\r\n}\r\n\r\nexport function getFilterOperators(filterVariant: FilterVariant) {\r\n const operatorMap: Record = {\r\n boolean: dataTableConfig.booleanOperators,\r\n date: dataTableConfig.dateOperators,\r\n dateRange: dataTableConfig.dateOperators,\r\n multiSelect: dataTableConfig.multiSelectOperators,\r\n number: dataTableConfig.numericOperators,\r\n range: dataTableConfig.numericOperators,\r\n select: dataTableConfig.selectOperators,\r\n text: dataTableConfig.textOperators,\r\n };\r\n\r\n return operatorMap[filterVariant] ?? dataTableConfig.textOperators;\r\n}\r\n\r\nexport function getDefaultFilterOperator(filterVariant: FilterVariant) {\r\n if (filterVariant === 'dateRange' || filterVariant === 'range') {\r\n return 'isBetween';\r\n }\r\n const operators = getFilterOperators(filterVariant);\r\n\r\n return operators[0]?.value ?? (filterVariant === 'text' ? 'iLike' : 'eq');\r\n}\r\n\r\nexport function getValidFilters(\r\n filters: ExtendedColumnFilter[],\r\n): ExtendedColumnFilter[] {\r\n return filters.filter(\r\n (filter) =>\r\n filter.operator === 'isEmpty' ||\r\n filter.operator === 'isNotEmpty' ||\r\n (Array.isArray(filter.value)\r\n ? filter.value.length > 0\r\n : filter.value !== '' && filter.value !== null && filter.value !== undefined),\r\n );\r\n}\r\n", + "path": "registry/lib/data-table.ts", + "target": "@lib/data-table.ts", + "type": "registry:lib" + } + ], + "name": "lib-data-table", + "registryDependencies": ["@kombase/data-table-config", "@kombase/data-table-types"], + "type": "registry:lib" +} diff --git a/public/r/lib-date.json b/public/r/lib-date.json new file mode 100644 index 0000000..fcb9765 --- /dev/null +++ b/public/r/lib-date.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["dayjs", "react-day-picker"], + "files": [ + { + "content": "import dayjs from 'dayjs';\r\nimport type { DateRange } from 'react-day-picker';\r\n\r\nexport function getTimezone() {\r\n return Intl.DateTimeFormat().resolvedOptions().timeZone;\r\n}\r\n\r\ntype DateRangeParams = {\r\n start_date?: string;\r\n end_date?: string;\r\n};\r\n\r\n/**\r\n * Convert `DateRange` (react-day-picker) to a format string\r\n * that meets API requirements (default: `yyyy-MM-dd`).\r\n *\r\n * @param range - A DateRange object containing `from` and `to` (optional).\r\n * @param formatStr - The date format (default: `yyyy-MM-dd`).\r\n *\r\n * @returns An object containing:\r\n * - `start_date` → the format string resulting from `range.from`\r\n * - `end_date` → the format string resulting from `range.to`\r\n *\r\n * @remarks\r\n * - Will return `{}` if `range` is undefined\r\n * - Field will be `undefined` if `from` / `to` is not provided\r\n * - Safe to spread directly to object query parameters\r\n */\r\nexport const mapDateRangeToParams = (\r\n range?: DateRange,\r\n formatStr: string = 'yyyy-MM-dd',\r\n): DateRangeParams => {\r\n if (!range) return {};\r\n\r\n return {\r\n end_date: range.to ? dayjs(range.to).format(formatStr) : undefined,\r\n start_date: range.from ? dayjs(range.from).format(formatStr) : undefined,\r\n };\r\n};\r\n\r\nexport function formatDateFilterTable(\r\n date: Date | string | number | undefined,\r\n opts: Intl.DateTimeFormatOptions = {},\r\n) {\r\n if (!date) return '';\r\n\r\n try {\r\n return new Intl.DateTimeFormat('en-US', {\r\n day: opts.day ?? 'numeric',\r\n month: opts.month ?? 'long',\r\n year: opts.year ?? 'numeric',\r\n ...opts,\r\n }).format(new Date(date));\r\n } catch (_err) {\r\n return '';\r\n }\r\n}\r\n", + "path": "registry/lib/date.ts", + "target": "@lib/date.ts", + "type": "registry:lib" + } + ], + "name": "lib-date", + "type": "registry:lib" +} diff --git a/public/r/lib-pagination.json b/public/r/lib-pagination.json new file mode 100644 index 0000000..7884e14 --- /dev/null +++ b/public/r/lib-pagination.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "/**\r\n * Generates page numbers for pagination with ellipsis\r\n * @param currentPage - Current page number (1-based)\r\n * @param totalPages - Total number of pages\r\n * @returns Array of page numbers and ellipsis strings\r\n *\r\n * Examples:\r\n * - Small dataset (≤5 pages): [1, 2, 3, 4, 5]\r\n * - Near beginning: [1, 2, 3, 4, \"...\", 10]\r\n * - In middle: [1, \"...\", 4, 5, 6, \"...\", 10]\r\n * - Near end: [1, \"...\", 7, 8, 9, 10]\r\n */\r\nexport function getPageNumbers(currentPage: number, totalPages: number) {\r\n const maxVisiblePages = 5; // Maximum number of page buttons to show\r\n const rangeWithDots: (number | string)[] = [];\r\n\r\n if (totalPages <= maxVisiblePages) {\r\n // If total pages is 5 or less, show all pages\r\n for (let i = 1; i <= totalPages; i++) {\r\n rangeWithDots.push(i);\r\n }\r\n } else {\r\n // Always show first page\r\n rangeWithDots.push(1);\r\n\r\n if (currentPage <= 3) {\r\n // Near the beginning: [1] [2] [3] [4] ... [10]\r\n for (let i = 2; i <= 4; i++) {\r\n rangeWithDots.push(i);\r\n }\r\n rangeWithDots.push('...', totalPages);\r\n } else if (currentPage >= totalPages - 2) {\r\n // Near the end: [1] ... [7] [8] [9] [10]\r\n rangeWithDots.push('...');\r\n for (let i = totalPages - 3; i <= totalPages; i++) {\r\n rangeWithDots.push(i);\r\n }\r\n } else {\r\n // In the middle: [1] ... [4] [5] [6] ... [10]\r\n rangeWithDots.push('...');\r\n for (let i = currentPage - 1; i <= currentPage + 1; i++) {\r\n rangeWithDots.push(i);\r\n }\r\n rangeWithDots.push('...', totalPages);\r\n }\r\n }\r\n\r\n return rangeWithDots;\r\n}\r\n", + "path": "registry/lib/pagination.ts", + "target": "@lib/pagination.ts", + "type": "registry:lib" + } + ], + "name": "lib-pagination", + "type": "registry:lib" +} diff --git a/public/r/long-text.json b/public/r/long-text.json new file mode 100644 index 0000000..9432096 --- /dev/null +++ b/public/r/long-text.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "'use client';\r\n\r\nimport { useRef, useState } from 'react';\r\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';\r\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype LongTextProps = {\r\n children: React.ReactNode;\r\n className?: string;\r\n contentClassName?: string;\r\n};\r\n\r\nexport function LongText({ children, className = '', contentClassName = '' }: LongTextProps) {\r\n const ref = useRef(null);\r\n const [isOverflown, setIsOverflown] = useState(false);\r\n\r\n // Use ref callback to check overflow when element is mounted\r\n const refCallback = (node: HTMLDivElement | null) => {\r\n ref.current = node;\r\n if (node && checkOverflow(node)) {\r\n queueMicrotask(() => setIsOverflown(true));\r\n }\r\n };\r\n\r\n if (!isOverflown)\r\n return (\r\n
\r\n {children}\r\n
\r\n );\r\n\r\n return (\r\n <>\r\n
\r\n \r\n \r\n \r\n
\r\n {children}\r\n
\r\n
\r\n \r\n

{children}

\r\n
\r\n
\r\n
\r\n
\r\n
\r\n \r\n \r\n
\r\n {children}\r\n
\r\n
\r\n \r\n

{children}

\r\n
\r\n
\r\n
\r\n \r\n );\r\n}\r\n\r\nconst checkOverflow = (textContainer: HTMLDivElement | null) => {\r\n if (textContainer) {\r\n return (\r\n textContainer.offsetHeight < textContainer.scrollHeight ||\r\n textContainer.offsetWidth < textContainer.scrollWidth\r\n );\r\n }\r\n return false;\r\n};\r\n", + "path": "registry/components/long-text.tsx", + "target": "@components/long-text.tsx", + "type": "registry:component" + } + ], + "name": "long-text", + "registryDependencies": ["popover", "tooltip"], + "type": "registry:component" +} diff --git a/public/r/password-input.json b/public/r/password-input.json new file mode 100644 index 0000000..4c49cd2 --- /dev/null +++ b/public/r/password-input.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import { Eye, EyeOff } from 'lucide-react';\r\nimport * as React from 'react';\r\nimport { cn } from '@/lib/utils';\r\nimport { Button } from './ui/button';\r\n\r\ntype PasswordInputProps = Omit, 'type'>;\r\n\r\nexport const PasswordInput = React.forwardRef(\r\n ({ className, disabled, ...props }, ref) => {\r\n const [showPassword, setShowPassword] = React.useState(false);\r\n\r\n return (\r\n
\r\n \r\n setShowPassword((prev) => !prev)}\r\n size=\"icon\"\r\n type=\"button\"\r\n variant=\"ghost\"\r\n >\r\n {showPassword ? : }\r\n {showPassword ? 'Hide password' : 'Show password'}\r\n \r\n
\r\n );\r\n },\r\n);\r\nPasswordInput.displayName = 'PasswordInput';\r\n", + "path": "registry/components/password-input.tsx", + "target": "@components/password-input.tsx", + "type": "registry:component" + } + ], + "name": "password-input", + "type": "registry:component" +} diff --git a/public/r/phone-input.json b/public/r/phone-input.json new file mode 100644 index 0000000..dafface --- /dev/null +++ b/public/r/phone-input.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "'use client';\r\n\r\nimport * as SlotPrimitive from '@radix-ui/react-slot';\r\nimport { Check, ChevronDown } from 'lucide-react';\r\nimport * as React from 'react';\r\nimport { useAsRef } from '@/hooks/use-as-ref';\r\nimport { useIsomorphicLayoutEffect } from '@/hooks/use-isomorphic-layout-effect';\r\nimport { useLazyRef } from '@/hooks/use-lazy-ref';\r\nimport {\r\n Command,\r\n CommandEmpty,\r\n CommandGroup,\r\n CommandInput,\r\n CommandItem,\r\n CommandList,\r\n} from '@/components/ui/command';\r\nimport { Input } from '@/components/ui/input';\r\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';\r\nimport { useComposedRefs } from '@/lib/component-refs';\r\nimport { cn } from '@/lib/utils';\r\nimport { VisuallyHiddenInput } from '@/components/visually-hidden-input';\r\n\r\nconst ROOT_NAME = 'PhoneInput';\r\nconst COUNTRY_SELECT_NAME = 'PhoneInputCountrySelect';\r\nconst FIELD_NAME = 'PhoneInputField';\r\n\r\n/**\r\n * @see https://github.com/mukeshsoni/country-telephone-data/blob/master/country_telephone_data.js\r\n * @format [iso2, dialCode]\r\n */\r\nconst COUNTRY_DATA: [string, string][] = [\r\n ['af', '93'],\r\n ['ax', '358'],\r\n ['al', '355'],\r\n ['dz', '213'],\r\n ['as', '1684'],\r\n ['ad', '376'],\r\n ['ao', '244'],\r\n ['ai', '1264'],\r\n ['ag', '1268'],\r\n ['ar', '54'],\r\n ['am', '374'],\r\n ['aw', '297'],\r\n ['au', '61'],\r\n ['at', '43'],\r\n ['az', '994'],\r\n ['bs', '1242'],\r\n ['bh', '973'],\r\n ['bd', '880'],\r\n ['bb', '1246'],\r\n ['by', '375'],\r\n ['be', '32'],\r\n ['bz', '501'],\r\n ['bj', '229'],\r\n ['bm', '1441'],\r\n ['bt', '975'],\r\n ['bo', '591'],\r\n ['ba', '387'],\r\n ['bw', '267'],\r\n ['br', '55'],\r\n ['io', '246'],\r\n ['vg', '1284'],\r\n ['bn', '673'],\r\n ['bg', '359'],\r\n ['bf', '226'],\r\n ['bi', '257'],\r\n ['kh', '855'],\r\n ['cm', '237'],\r\n ['ca', '1'],\r\n ['cv', '238'],\r\n ['bq', '599'],\r\n ['ky', '1345'],\r\n ['cf', '236'],\r\n ['td', '235'],\r\n ['cl', '56'],\r\n ['cn', '86'],\r\n ['co', '57'],\r\n ['km', '269'],\r\n ['cd', '243'],\r\n ['cg', '242'],\r\n ['ck', '682'],\r\n ['cr', '506'],\r\n ['ci', '225'],\r\n ['hr', '385'],\r\n ['cu', '53'],\r\n ['cw', '599'],\r\n ['cy', '357'],\r\n ['cz', '420'],\r\n ['dk', '45'],\r\n ['dj', '253'],\r\n ['dm', '1767'],\r\n ['do', '1'],\r\n ['ec', '593'],\r\n ['eg', '20'],\r\n ['sv', '503'],\r\n ['gq', '240'],\r\n ['er', '291'],\r\n ['ee', '372'],\r\n ['et', '251'],\r\n ['fk', '500'],\r\n ['fo', '298'],\r\n ['fj', '679'],\r\n ['fi', '358'],\r\n ['fr', '33'],\r\n ['gf', '594'],\r\n ['pf', '689'],\r\n ['ga', '241'],\r\n ['gm', '220'],\r\n ['ge', '995'],\r\n ['de', '49'],\r\n ['gh', '233'],\r\n ['gi', '350'],\r\n ['gr', '30'],\r\n ['gl', '299'],\r\n ['gd', '1473'],\r\n ['gp', '590'],\r\n ['gu', '1671'],\r\n ['gt', '502'],\r\n ['gg', '44'],\r\n ['gn', '224'],\r\n ['gw', '245'],\r\n ['gy', '592'],\r\n ['ht', '509'],\r\n ['hn', '504'],\r\n ['hk', '852'],\r\n ['hu', '36'],\r\n ['is', '354'],\r\n ['in', '91'],\r\n ['id', '62'],\r\n ['ir', '98'],\r\n ['iq', '964'],\r\n ['ie', '353'],\r\n ['im', '44'],\r\n ['il', '972'],\r\n ['it', '39'],\r\n ['jm', '1876'],\r\n ['jp', '81'],\r\n ['je', '44'],\r\n ['jo', '962'],\r\n ['kz', '7'],\r\n ['ke', '254'],\r\n ['ki', '686'],\r\n ['xk', '383'],\r\n ['kw', '965'],\r\n ['kg', '996'],\r\n ['la', '856'],\r\n ['lv', '371'],\r\n ['lb', '961'],\r\n ['ls', '266'],\r\n ['lr', '231'],\r\n ['ly', '218'],\r\n ['li', '423'],\r\n ['lt', '370'],\r\n ['lu', '352'],\r\n ['mo', '853'],\r\n ['mk', '389'],\r\n ['mg', '261'],\r\n ['mw', '265'],\r\n ['my', '60'],\r\n ['mv', '960'],\r\n ['ml', '223'],\r\n ['mt', '356'],\r\n ['mh', '692'],\r\n ['mq', '596'],\r\n ['mr', '222'],\r\n ['mu', '230'],\r\n ['mx', '52'],\r\n ['fm', '691'],\r\n ['md', '373'],\r\n ['mc', '377'],\r\n ['mn', '976'],\r\n ['me', '382'],\r\n ['ms', '1664'],\r\n ['ma', '212'],\r\n ['mz', '258'],\r\n ['mm', '95'],\r\n ['na', '264'],\r\n ['nr', '674'],\r\n ['np', '977'],\r\n ['nl', '31'],\r\n ['nc', '687'],\r\n ['nz', '64'],\r\n ['ni', '505'],\r\n ['ne', '227'],\r\n ['ng', '234'],\r\n ['nu', '683'],\r\n ['nf', '672'],\r\n ['kp', '850'],\r\n ['mp', '1670'],\r\n ['no', '47'],\r\n ['om', '968'],\r\n ['pk', '92'],\r\n ['pw', '680'],\r\n ['ps', '970'],\r\n ['pa', '507'],\r\n ['pg', '675'],\r\n ['py', '595'],\r\n ['pe', '51'],\r\n ['ph', '63'],\r\n ['pl', '48'],\r\n ['pt', '351'],\r\n ['pr', '1'],\r\n ['qa', '974'],\r\n ['re', '262'],\r\n ['ro', '40'],\r\n ['ru', '7'],\r\n ['rw', '250'],\r\n ['bl', '590'],\r\n ['sh', '290'],\r\n ['kn', '1869'],\r\n ['lc', '1758'],\r\n ['mf', '590'],\r\n ['pm', '508'],\r\n ['vc', '1784'],\r\n ['ws', '685'],\r\n ['sm', '378'],\r\n ['st', '239'],\r\n ['sa', '966'],\r\n ['sn', '221'],\r\n ['rs', '381'],\r\n ['sc', '248'],\r\n ['sl', '232'],\r\n ['sg', '65'],\r\n ['sx', '1721'],\r\n ['sk', '421'],\r\n ['si', '386'],\r\n ['sb', '677'],\r\n ['so', '252'],\r\n ['za', '27'],\r\n ['kr', '82'],\r\n ['ss', '211'],\r\n ['es', '34'],\r\n ['lk', '94'],\r\n ['sd', '249'],\r\n ['sr', '597'],\r\n ['sz', '268'],\r\n ['se', '46'],\r\n ['ch', '41'],\r\n ['sy', '963'],\r\n ['tw', '886'],\r\n ['tj', '992'],\r\n ['tz', '255'],\r\n ['th', '66'],\r\n ['tl', '670'],\r\n ['tg', '228'],\r\n ['tk', '690'],\r\n ['to', '676'],\r\n ['tt', '1868'],\r\n ['tn', '216'],\r\n ['tr', '90'],\r\n ['tm', '993'],\r\n ['tc', '1649'],\r\n ['tv', '688'],\r\n ['vi', '1340'],\r\n ['ug', '256'],\r\n ['ua', '380'],\r\n ['ae', '971'],\r\n ['gb', '44'],\r\n ['us', '1'],\r\n ['uy', '598'],\r\n ['uz', '998'],\r\n ['vu', '678'],\r\n ['va', '39'],\r\n ['ve', '58'],\r\n ['vn', '84'],\r\n ['wf', '681'],\r\n ['eh', '212'],\r\n ['ye', '967'],\r\n ['zm', '260'],\r\n ['zw', '263'],\r\n];\r\n\r\ninterface Country {\r\n code: string;\r\n name: string;\r\n dialCode: string;\r\n flag?: string;\r\n}\r\n\r\nfunction getCountryName(countryCode: string, locale = 'en'): string {\r\n try {\r\n const regionNames = new Intl.DisplayNames([locale], { type: 'region' });\r\n return regionNames.of(countryCode) ?? countryCode;\r\n } catch {\r\n return countryCode;\r\n }\r\n}\r\n\r\nfunction getFlagEmoji(countryCode: string): string {\r\n const codePoints = countryCode\r\n .toUpperCase()\r\n .split('')\r\n .map((char) => 127397 + char.charCodeAt(0));\r\n return String.fromCodePoint(...codePoints);\r\n}\r\n\r\nfunction getCountries(): Country[] {\r\n return COUNTRY_DATA.map(([iso2, dialCode]): Country => {\r\n const code = iso2.toUpperCase();\r\n return {\r\n code,\r\n dialCode: `+${dialCode}`,\r\n flag: getFlagEmoji(code),\r\n name: getCountryName(code),\r\n };\r\n }).sort((a, b) => a.name.localeCompare(b.name));\r\n}\r\n\r\nfunction detectCountryFromNumber(value: string, countries: Country[]): Country | undefined {\r\n if (!value?.startsWith('+')) return undefined;\r\n\r\n const digits = value.slice(1).replace(/\\D/g, '');\r\n if (!digits) return undefined;\r\n\r\n const sorted = [...countries].sort((a, b) => b.dialCode.length - a.dialCode.length);\r\n\r\n const matches: Country[] = [];\r\n for (const country of sorted) {\r\n const dialCode = country.dialCode.slice(1);\r\n if (digits.startsWith(dialCode)) {\r\n matches.push(country);\r\n }\r\n }\r\n\r\n if (matches.length === 0) return undefined;\r\n\r\n if (matches.length > 1 && matches[0]?.dialCode === '+1') {\r\n const usCountry = matches.find((c) => c.code === 'US');\r\n if (usCountry) return usCountry;\r\n }\r\n\r\n return matches[0];\r\n}\r\n\r\nfunction formatPhoneNumber(value: string, countries: Country[]): string {\r\n if (!value) return '';\r\n\r\n const normalized = value.startsWith('+') ? value : `+${value}`;\r\n\r\n const digits = normalized.slice(1).replace(/\\D/g, '');\r\n if (!digits) return '+';\r\n\r\n const detected = detectCountryFromNumber(`+${digits}`, countries);\r\n const dialCodeLength = detected ? detected.dialCode.slice(1).length : Math.min(digits.length, 3);\r\n\r\n const countryCode = digits.slice(0, dialCodeLength);\r\n const rest = digits.slice(dialCodeLength);\r\n\r\n let formatted = `+${countryCode}`;\r\n\r\n if (rest) {\r\n formatted += ' ';\r\n for (let i = 0; i < rest.length; i++) {\r\n if (i > 0 && i % 3 === 0) {\r\n formatted += ' ';\r\n }\r\n formatted += rest[i];\r\n }\r\n }\r\n\r\n return formatted;\r\n}\r\n\r\ntype RootElement = React.ComponentRef;\r\n\r\ninterface StoreState {\r\n value: string;\r\n country: string;\r\n open: boolean;\r\n startsWithPlus: boolean;\r\n}\r\n\r\ninterface Store {\r\n subscribe: (callback: () => void) => () => void;\r\n getState: () => StoreState;\r\n setState: (key: K, value: StoreState[K]) => void;\r\n notify: () => void;\r\n}\r\n\r\nconst StoreContext = React.createContext(null);\r\n\r\nfunction useStoreContext(consumerName: string) {\r\n const context = React.useContext(StoreContext);\r\n if (!context) {\r\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\r\n }\r\n return context;\r\n}\r\n\r\nfunction useStore(selector: (state: StoreState) => T, ogStore?: Store | null): T {\r\n const contextStore = React.useContext(StoreContext);\r\n\r\n const store = ogStore ?? contextStore;\r\n\r\n if (!store) {\r\n throw new Error(`\\`useStore\\` must be used within \\`${ROOT_NAME}\\``);\r\n }\r\n\r\n const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);\r\n\r\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\r\n}\r\n\r\ninterface PhoneInputContextValue {\r\n rootId: string;\r\n countries: Country[];\r\n placeholder: string;\r\n disabled?: boolean;\r\n readOnly?: boolean;\r\n required?: boolean;\r\n invalid?: boolean;\r\n showFlag: boolean;\r\n inputRef: React.RefObject;\r\n}\r\n\r\nconst PhoneInputContext = React.createContext(null);\r\n\r\nfunction usePhoneInputContext(consumerName: string) {\r\n const context = React.useContext(PhoneInputContext);\r\n if (!context) {\r\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\r\n }\r\n return context;\r\n}\r\n\r\ninterface PhoneInputProps extends React.ComponentProps<'div'> {\r\n defaultValue?: string;\r\n value?: string;\r\n onValueChange?: (value: string) => void;\r\n defaultCountry?: string;\r\n country?: string;\r\n onCountryChange?: (country: string) => void;\r\n countries?: Country[];\r\n name?: string;\r\n placeholder?: string;\r\n asChild?: boolean;\r\n disabled?: boolean;\r\n readOnly?: boolean;\r\n required?: boolean;\r\n invalid?: boolean;\r\n showFlag?: boolean;\r\n}\r\n\r\nfunction PhoneInput(props: PhoneInputProps) {\r\n const {\r\n value: valueProp,\r\n defaultValue,\r\n defaultCountry,\r\n country: countryProp,\r\n onValueChange,\r\n onCountryChange,\r\n countries = getCountries(),\r\n name,\r\n placeholder = 'Enter phone number',\r\n asChild,\r\n disabled,\r\n required,\r\n readOnly,\r\n invalid,\r\n showFlag = true,\r\n className,\r\n id,\r\n ref,\r\n ...rootProps\r\n } = props;\r\n\r\n const instanceId = React.useId();\r\n const rootId = id ?? instanceId;\r\n\r\n const inputRef = React.useRef(null);\r\n\r\n const [formTrigger, setFormTrigger] = React.useState(null);\r\n const composedRef = useComposedRefs(ref, (node) => setFormTrigger(node));\r\n const isFormControl = formTrigger ? !!formTrigger.closest('form') : true;\r\n\r\n const listenersRef = useLazyRef(() => new Set<() => void>());\r\n const stateRef = useLazyRef(() => {\r\n const initialValue = valueProp ?? defaultValue ?? '';\r\n const initialCountry = countryProp ?? defaultCountry ?? '';\r\n\r\n return {\r\n country: initialCountry,\r\n open: false,\r\n startsWithPlus: initialValue.startsWith('+'),\r\n value: initialValue,\r\n };\r\n });\r\n\r\n const propsRef = useAsRef({\r\n onCountryChange,\r\n onValueChange,\r\n });\r\n\r\n const store = React.useMemo(() => {\r\n return {\r\n getState: () => stateRef.current,\r\n notify: () => {\r\n for (const cb of listenersRef.current) {\r\n cb();\r\n }\r\n },\r\n setState: (key, value) => {\r\n if (Object.is(stateRef.current[key], value)) return;\r\n\r\n if (key === 'value' && typeof value === 'string') {\r\n stateRef.current.value = value;\r\n propsRef.current.onValueChange?.(value);\r\n } else if (key === 'country' && typeof value === 'string') {\r\n stateRef.current.country = value;\r\n propsRef.current.onCountryChange?.(value);\r\n } else {\r\n stateRef.current[key] = value;\r\n }\r\n\r\n store.notify();\r\n },\r\n subscribe: (cb) => {\r\n listenersRef.current.add(cb);\r\n return () => listenersRef.current.delete(cb);\r\n },\r\n };\r\n }, [listenersRef, stateRef, propsRef]);\r\n\r\n const value = useStore((state) => state.value, store);\r\n const country = useStore((state) => state.country, store);\r\n\r\n useIsomorphicLayoutEffect(() => {\r\n if (valueProp !== undefined) {\r\n store.setState('value', valueProp);\r\n }\r\n }, [valueProp]);\r\n\r\n useIsomorphicLayoutEffect(() => {\r\n if (countryProp !== undefined) {\r\n store.setState('country', countryProp);\r\n }\r\n }, [countryProp]);\r\n\r\n const startsWithPlus = useStore((state) => state.startsWithPlus, store);\r\n\r\n React.useEffect(() => {\r\n if (!value) return;\r\n\r\n const digits = value.slice(1).replace(/\\D/g, '');\r\n const shouldDetect = startsWithPlus || digits.length >= 10;\r\n\r\n if (!shouldDetect) return;\r\n\r\n const detected = detectCountryFromNumber(value, countries);\r\n if (detected && detected.code !== country) {\r\n store.setState('country', detected.code);\r\n }\r\n }, [value, countries, country, store, startsWithPlus]);\r\n\r\n const contextValue = React.useMemo(\r\n () => ({\r\n countries,\r\n disabled,\r\n inputRef,\r\n invalid,\r\n placeholder,\r\n readOnly,\r\n required,\r\n rootId,\r\n showFlag,\r\n }),\r\n [rootId, countries, placeholder, disabled, required, readOnly, invalid, showFlag],\r\n );\r\n\r\n const RootPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n \r\n \r\n {isFormControl && (\r\n \r\n )}\r\n \r\n \r\n );\r\n}\r\n\r\ninterface PhoneInputCountrySelectProps\r\n extends React.ComponentProps,\r\n Pick, 'disabled' | 'className'> {}\r\n\r\nfunction PhoneInputCountrySelect(props: PhoneInputCountrySelectProps) {\r\n const {\r\n disabled: disabledProp,\r\n className,\r\n children,\r\n onOpenChange: onOpenChangeProp,\r\n ...popoverProps\r\n } = props;\r\n\r\n const { countries, inputRef, disabled, showFlag } = usePhoneInputContext(COUNTRY_SELECT_NAME);\r\n const store = useStoreContext(COUNTRY_SELECT_NAME);\r\n const country = useStore((state) => state.country);\r\n const open = useStore((state) => state.open);\r\n const onOpenChangeRef = useAsRef(onOpenChangeProp);\r\n\r\n const isDisabled = disabledProp || disabled;\r\n\r\n const countryContext = countries.find((c) => c.code === country);\r\n\r\n const onOpenChange = React.useCallback(\r\n (open: boolean) => {\r\n store.setState('open', open);\r\n onOpenChangeRef.current?.(open);\r\n },\r\n [store, onOpenChangeRef],\r\n );\r\n\r\n return (\r\n \r\n \r\n {!countryContext ? (\r\n
\r\n ) : (\r\n showFlag &&\r\n countryContext.flag && (\r\n
{countryContext.flag}
\r\n )\r\n )}\r\n \r\n \r\n \r\n \r\n \r\n \r\n No country found.\r\n \r\n {countries.map((c) => (\r\n {\r\n store.setState('country', c.code);\r\n store.setState('open', false);\r\n requestAnimationFrame(() => {\r\n inputRef.current?.focus();\r\n });\r\n }}\r\n value={`${c.name} ${c.dialCode} ${c.code}`}\r\n >\r\n {showFlag && c.flag && {c.flag}}\r\n {c.name}\r\n {c.dialCode}\r\n \r\n \r\n ))}\r\n \r\n \r\n \r\n \r\n \r\n );\r\n}\r\n\r\nfunction PhoneInputField(props: React.ComponentProps<'input'>) {\r\n const {\r\n onChange: onChangeProp,\r\n className,\r\n disabled: disabledProp,\r\n readOnly: readOnlyProp,\r\n required: requiredProp,\r\n ref,\r\n ...inputProps\r\n } = props;\r\n\r\n const { inputRef, disabled, invalid, readOnly, required, placeholder, countries } =\r\n usePhoneInputContext(FIELD_NAME);\r\n const store = useStoreContext(FIELD_NAME);\r\n const value = useStore((state) => state.value);\r\n\r\n const composedRef = useComposedRefs(ref, inputRef);\r\n\r\n const onChangeRef = useAsRef(onChangeProp);\r\n\r\n const isDisabled = disabledProp || disabled;\r\n const isReadOnly = readOnlyProp || readOnly;\r\n const isRequired = requiredProp || required;\r\n\r\n const onChange = React.useCallback(\r\n (event: React.ChangeEvent) => {\r\n if (isDisabled || isReadOnly) return;\r\n\r\n onChangeRef.current?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n const inputValue = event.target.value;\r\n\r\n const startsWithPlus = inputValue.startsWith('+');\r\n const digits = inputValue.replace(/\\D/g, '');\r\n const newValue = digits ? `+${digits}` : startsWithPlus ? '+' : '';\r\n store.setState('startsWithPlus', startsWithPlus);\r\n store.setState('value', newValue);\r\n },\r\n [store, onChangeRef, isDisabled, isReadOnly],\r\n );\r\n\r\n const displayValue = React.useMemo(() => {\r\n return formatPhoneNumber(value, countries);\r\n }, [value, countries]);\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\nexport {\r\n PhoneInput,\r\n PhoneInputCountrySelect,\r\n PhoneInputField,\r\n type PhoneInputProps,\r\n useStore as usePhoneInput,\r\n};\r\n", + "path": "registry/components/phone-input.tsx", + "target": "@components/phone-input.tsx", + "type": "registry:component" + } + ], + "name": "phone-input", + "registryDependencies": [ + "command", + "input", + "popover", + "@kombase/component-refs", + "@kombase/visually-hidden-input", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "type": "registry:component" +} diff --git a/public/r/popover.json b/public/r/popover.json new file mode 100644 index 0000000..9cc1a0c --- /dev/null +++ b/public/r/popover.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-popover"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as PopoverPrimitive from '@radix-ui/react-popover';\r\nimport * as React from 'react';\r\n\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst Popover = PopoverPrimitive.Root;\r\n\r\nconst PopoverTrigger = PopoverPrimitive.Trigger;\r\n\r\nconst PopoverAnchor = PopoverPrimitive.Anchor;\r\n\r\nconst PopoverContent = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (\r\n \r\n \r\n \r\n));\r\nPopoverContent.displayName = PopoverPrimitive.Content.displayName;\r\n\r\nexport { Popover, PopoverAnchor, PopoverContent, PopoverTrigger };\r\n", + "path": "registry/ui/popover.tsx", + "target": "@ui/popover.tsx", + "type": "registry:ui" + } + ], + "name": "popover", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/radio-group.json b/public/r/radio-group.json new file mode 100644 index 0000000..0039648 --- /dev/null +++ b/public/r/radio-group.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-radio-group"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as RadioGroupPrimitive from '@radix-ui/react-radio-group';\r\nimport { CircleIcon } from 'lucide-react';\r\nimport * as React from 'react';\r\n\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst RadioGroup = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => {\r\n return (\r\n \r\n );\r\n});\r\nRadioGroup.displayName = RadioGroupPrimitive.Root.displayName;\r\n\r\nconst RadioGroupItem = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => {\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n );\r\n});\r\nRadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;\r\n\r\nexport { RadioGroup, RadioGroupItem };\r\n", + "path": "registry/ui/radio-group.tsx", + "target": "@ui/radio-group.tsx", + "type": "registry:ui" + } + ], + "name": "radio-group", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/rating.json b/public/r/rating.json new file mode 100644 index 0000000..de759b6 --- /dev/null +++ b/public/r/rating.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-direction"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as DirectionPrimitive from '@radix-ui/react-direction';\r\nimport * as SlotPrimitive from '@radix-ui/react-slot';\r\nimport { Star } from 'lucide-react';\r\nimport * as React from 'react';\r\nimport { useAsRef } from '@/hooks/use-as-ref';\r\nimport { useIsomorphicLayoutEffect } from '@/hooks/use-isomorphic-layout-effect';\r\nimport { useLazyRef } from '@/hooks/use-lazy-ref';\r\nimport { useComposedRefs } from '@/lib/component-refs';\r\nimport { cn } from '@/lib/utils';\r\nimport { VisuallyHiddenInput } from '@/components/visually-hidden-input';\r\n\r\ntype Direction = 'ltr' | 'rtl';\r\ntype Orientation = 'horizontal' | 'vertical';\r\ntype ActivationMode = 'automatic' | 'manual';\r\ntype Size = 'default' | 'sm' | 'lg';\r\ntype Step = 0.5 | 1;\r\ntype DataState = 'full' | 'partial' | 'empty';\r\ntype FocusIntent = 'first' | 'last' | 'prev' | 'next';\r\n\r\ntype RootElement = React.ComponentRef;\r\ntype ItemElement = React.ComponentRef;\r\n\r\nconst ROOT_NAME = 'Rating';\r\nconst ITEM_NAME = 'RatingItem';\r\n\r\nconst ENTRY_FOCUS = 'ratingFocusGroup.onEntryFocus';\r\nconst EVENT_OPTIONS = { bubbles: false, cancelable: true };\r\n\r\nfunction getItemId(id: string, value: number) {\r\n return `${id}-item-${value}`;\r\n}\r\n\r\nfunction getPartialFillGradientId(id: string, step: Step) {\r\n return `partial-fill-gradient-${id}-${step}`;\r\n}\r\n\r\nconst MAP_KEY_TO_FOCUS_INTENT: Record = {\r\n ArrowDown: 'next',\r\n ArrowLeft: 'prev',\r\n ArrowRight: 'next',\r\n ArrowUp: 'prev',\r\n End: 'last',\r\n Home: 'first',\r\n};\r\n\r\nfunction getDirectionAwareKey(key: string, dir?: Direction) {\r\n if (dir !== 'rtl') return key;\r\n return key === 'ArrowLeft' ? 'ArrowRight' : key === 'ArrowRight' ? 'ArrowLeft' : key;\r\n}\r\n\r\nfunction getFocusIntent(\r\n event: React.KeyboardEvent,\r\n dir?: Direction,\r\n orientation?: Orientation,\r\n) {\r\n const key = getDirectionAwareKey(event.key, dir);\r\n if (orientation === 'horizontal' && ['ArrowUp', 'ArrowDown'].includes(key)) return undefined;\r\n if (orientation === 'vertical' && ['ArrowLeft', 'ArrowRight'].includes(key)) return undefined;\r\n return MAP_KEY_TO_FOCUS_INTENT[key];\r\n}\r\n\r\nfunction focusFirst(candidates: React.RefObject[], preventScroll = false) {\r\n const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;\r\n for (const candidateRef of candidates) {\r\n const candidate = candidateRef.current;\r\n if (!candidate) continue;\r\n if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;\r\n candidate.focus({ preventScroll });\r\n if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;\r\n }\r\n}\r\n\r\ninterface StoreState {\r\n value: number;\r\n hoveredValue: number | null;\r\n}\r\n\r\ninterface Store {\r\n subscribe: (callback: () => void) => () => void;\r\n getState: () => StoreState;\r\n setState: (key: K, value: StoreState[K]) => void;\r\n notify: () => void;\r\n}\r\n\r\nconst StoreContext = React.createContext(null);\r\n\r\nfunction useStoreContext(consumerName: string) {\r\n const context = React.useContext(StoreContext);\r\n if (!context) {\r\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\r\n }\r\n return context;\r\n}\r\n\r\nfunction useStore(selector: (state: StoreState) => T, ogStore?: Store | null): T {\r\n const contextStore = React.useContext(StoreContext);\r\n\r\n const store = ogStore ?? contextStore;\r\n\r\n if (!store) {\r\n throw new Error(`\\`useStore\\` must be used within \\`${ROOT_NAME}\\``);\r\n }\r\n\r\n const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);\r\n\r\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\r\n}\r\n\r\ninterface ItemData {\r\n id: string;\r\n ref: React.RefObject;\r\n value: number;\r\n disabled: boolean;\r\n}\r\n\r\ninterface RatingContextValue {\r\n rootId: string;\r\n dir: Direction;\r\n orientation: Orientation;\r\n activationMode: ActivationMode;\r\n size: Size;\r\n max: number;\r\n step: Step;\r\n clearable: boolean;\r\n disabled: boolean;\r\n readOnly: boolean;\r\n getAutoIndex: (instanceId: string) => number;\r\n}\r\n\r\nconst RatingContext = React.createContext(null);\r\n\r\nfunction useRatingContext(consumerName: string) {\r\n const context = React.useContext(RatingContext);\r\n if (!context) {\r\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\r\n }\r\n return context;\r\n}\r\n\r\ninterface FocusContextValue {\r\n tabStopId: string | null;\r\n onItemFocus: (tabStopId: string) => void;\r\n onItemShiftTab: () => void;\r\n onFocusableItemAdd: () => void;\r\n onFocusableItemRemove: () => void;\r\n onItemRegister: (item: ItemData) => void;\r\n onItemUnregister: (id: string) => void;\r\n getItems: () => ItemData[];\r\n}\r\n\r\nconst FocusContext = React.createContext(null);\r\n\r\nfunction useFocusContext(consumerName: string) {\r\n const context = React.useContext(FocusContext);\r\n if (!context) {\r\n throw new Error(`\\`${consumerName}\\` must be used within \\`FocusProvider\\``);\r\n }\r\n return context;\r\n}\r\n\r\ninterface RatingProps extends React.ComponentProps<'div'> {\r\n value?: number;\r\n defaultValue?: number;\r\n onValueChange?: (value: number) => void;\r\n onHover?: (value: number | null) => void;\r\n max?: number;\r\n activationMode?: ActivationMode;\r\n dir?: Direction;\r\n orientation?: Orientation;\r\n size?: Size;\r\n asChild?: boolean;\r\n step?: Step;\r\n clearable?: boolean;\r\n disabled?: boolean;\r\n readOnly?: boolean;\r\n required?: boolean;\r\n name?: string;\r\n}\r\n\r\nfunction Rating(props: RatingProps) {\r\n const {\r\n value: valueProp,\r\n defaultValue = 0,\r\n onValueChange,\r\n onHover,\r\n onFocus: onFocusProp,\r\n onMouseDown: onMouseDownProp,\r\n dir: dirProp,\r\n orientation = 'horizontal',\r\n activationMode = 'automatic',\r\n size = 'default',\r\n max = 5,\r\n step = 1,\r\n clearable = false,\r\n asChild,\r\n disabled = false,\r\n readOnly = false,\r\n required = false,\r\n className,\r\n id,\r\n name,\r\n ref,\r\n ...rootProps\r\n } = props;\r\n\r\n const dir = DirectionPrimitive.useDirection(dirProp);\r\n const instanceId = React.useId();\r\n const rootId = id ?? instanceId;\r\n\r\n const listenersRef = useLazyRef(() => new Set<() => void>());\r\n const stateRef = useLazyRef(() => ({\r\n hoveredValue: null,\r\n value: valueProp ?? defaultValue,\r\n }));\r\n\r\n const propsRef = useAsRef({\r\n onFocus: onFocusProp,\r\n onHover,\r\n onMouseDown: onMouseDownProp,\r\n onValueChange,\r\n step,\r\n });\r\n\r\n const store = React.useMemo(() => {\r\n return {\r\n getState: () => stateRef.current,\r\n notify: () => {\r\n for (const cb of listenersRef.current) {\r\n cb();\r\n }\r\n },\r\n setState: (key, value) => {\r\n if (Object.is(stateRef.current[key], value)) return;\r\n\r\n if (key === 'value' && typeof value === 'number') {\r\n stateRef.current.value = value;\r\n propsRef.current.onValueChange?.(value);\r\n } else if (key === 'hoveredValue') {\r\n stateRef.current.hoveredValue = value as number | null;\r\n propsRef.current.onHover?.(value as number | null);\r\n } else {\r\n stateRef.current[key] = value;\r\n }\r\n\r\n store.notify();\r\n },\r\n subscribe: (cb) => {\r\n listenersRef.current.add(cb);\r\n return () => listenersRef.current.delete(cb);\r\n },\r\n };\r\n }, [listenersRef, stateRef, propsRef]);\r\n\r\n useIsomorphicLayoutEffect(() => {\r\n if (valueProp !== undefined) {\r\n store.setState('value', valueProp);\r\n }\r\n }, [valueProp]);\r\n\r\n const value = useStore((state) => state.value, store);\r\n\r\n const [formTrigger, setFormTrigger] = React.useState(null);\r\n const composedRef = useComposedRefs(ref, (node) => setFormTrigger(node));\r\n const isFormControl = formTrigger ? !!formTrigger.closest('form') : true;\r\n\r\n const [tabStopId, setTabStopId] = React.useState(null);\r\n const [isTabbingBackOut, setIsTabbingBackOut] = React.useState(false);\r\n const [focusableItemCount, setFocusableItemCount] = React.useState(0);\r\n const isClickFocusRef = React.useRef(false);\r\n const itemsRef = React.useRef>(new Map());\r\n\r\n const autoIndexMapRef = React.useRef(new Map());\r\n const nextAutoIndexRef = React.useRef(0);\r\n\r\n const getAutoIndex = React.useCallback((instanceId: string) => {\r\n const existingIndex = autoIndexMapRef.current.get(instanceId);\r\n if (existingIndex !== undefined) {\r\n return existingIndex;\r\n }\r\n\r\n const newIndex = nextAutoIndexRef.current++;\r\n autoIndexMapRef.current.set(instanceId, newIndex);\r\n return newIndex;\r\n }, []);\r\n\r\n const onItemFocus = React.useCallback((tabStopId: string) => {\r\n setTabStopId(tabStopId);\r\n }, []);\r\n\r\n const onItemShiftTab = React.useCallback(() => {\r\n setIsTabbingBackOut(true);\r\n }, []);\r\n\r\n const onFocusableItemAdd = React.useCallback(() => {\r\n setFocusableItemCount((prevCount) => prevCount + 1);\r\n }, []);\r\n\r\n const onFocusableItemRemove = React.useCallback(() => {\r\n setFocusableItemCount((prevCount) => prevCount - 1);\r\n }, []);\r\n\r\n const onItemRegister = React.useCallback((item: ItemData) => {\r\n itemsRef.current.set(item.id, item);\r\n }, []);\r\n\r\n const onItemUnregister = React.useCallback((id: string) => {\r\n itemsRef.current.delete(id);\r\n }, []);\r\n\r\n const getItems = React.useCallback(() => {\r\n return Array.from(itemsRef.current.values())\r\n .filter((item) => item.ref.current)\r\n .sort((a, b) => {\r\n const elementA = a.ref.current;\r\n const elementB = b.ref.current;\r\n if (!elementA || !elementB) return 0;\r\n const position = elementA.compareDocumentPosition(elementB);\r\n if (position & Node.DOCUMENT_POSITION_FOLLOWING) {\r\n return -1;\r\n }\r\n if (position & Node.DOCUMENT_POSITION_PRECEDING) {\r\n return 1;\r\n }\r\n return 0;\r\n });\r\n }, []);\r\n\r\n const onBlur = React.useCallback(\r\n (event: React.FocusEvent) => {\r\n rootProps.onBlur?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n setIsTabbingBackOut(false);\r\n },\r\n [rootProps.onBlur],\r\n );\r\n\r\n const onFocus = React.useCallback(\r\n (event: React.FocusEvent) => {\r\n propsRef.current.onFocus?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n const isKeyboardFocus = !isClickFocusRef.current;\r\n if (event.target === event.currentTarget && isKeyboardFocus && !isTabbingBackOut) {\r\n const entryFocusEvent = new CustomEvent(ENTRY_FOCUS, EVENT_OPTIONS);\r\n event.currentTarget.dispatchEvent(entryFocusEvent);\r\n\r\n if (!entryFocusEvent.defaultPrevented) {\r\n const items = Array.from(itemsRef.current.values()).filter((item) => !item.disabled);\r\n // For half-step ratings, find the item that represents the selected value\r\n // by looking for the ceiling value (e.g., 3.5 → find item with value 4)\r\n const selectedItem =\r\n propsRef.current.step < 1\r\n ? items.find((item) => item.value === Math.ceil(value))\r\n : items.find((item) => item.value === value);\r\n const currentItem = items.find((item) => item.id === tabStopId);\r\n\r\n const candidateItems = [selectedItem, currentItem, ...items].filter(\r\n Boolean,\r\n ) as ItemData[];\r\n const candidateRefs = candidateItems.map((item) => item.ref);\r\n focusFirst(candidateRefs, false);\r\n }\r\n }\r\n isClickFocusRef.current = false;\r\n },\r\n [propsRef, isTabbingBackOut, value, tabStopId],\r\n );\r\n\r\n const onMouseDown = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n propsRef.current.onMouseDown?.(event);\r\n\r\n if (event.defaultPrevented) return;\r\n\r\n isClickFocusRef.current = true;\r\n },\r\n [propsRef],\r\n );\r\n\r\n const contextValue = React.useMemo(\r\n () => ({\r\n activationMode,\r\n clearable,\r\n dir,\r\n disabled,\r\n getAutoIndex,\r\n max,\r\n orientation,\r\n readOnly,\r\n rootId,\r\n size,\r\n step,\r\n }),\r\n [\r\n rootId,\r\n dir,\r\n orientation,\r\n activationMode,\r\n disabled,\r\n readOnly,\r\n size,\r\n max,\r\n step,\r\n clearable,\r\n getAutoIndex,\r\n ],\r\n );\r\n\r\n const focusContextValue = React.useMemo(\r\n () => ({\r\n getItems,\r\n onFocusableItemAdd,\r\n onFocusableItemRemove,\r\n onItemFocus,\r\n onItemRegister,\r\n onItemShiftTab,\r\n onItemUnregister,\r\n tabStopId,\r\n }),\r\n [\r\n tabStopId,\r\n onItemFocus,\r\n onItemShiftTab,\r\n onFocusableItemAdd,\r\n onFocusableItemRemove,\r\n onItemRegister,\r\n onItemUnregister,\r\n getItems,\r\n ],\r\n );\r\n\r\n const RootPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {dir === 'rtl' ? (\r\n <>\r\n \r\n \r\n \r\n ) : (\r\n <>\r\n \r\n \r\n \r\n )}\r\n \r\n \r\n \r\n {isFormControl && (\r\n \r\n )}\r\n \r\n \r\n \r\n );\r\n}\r\n\r\ninterface RatingItemProps extends Omit, 'children'> {\r\n index?: number;\r\n asChild?: boolean;\r\n children?: React.ReactNode | ((dataState: DataState) => React.ReactNode);\r\n}\r\n\r\nfunction RatingItem(props: RatingItemProps) {\r\n const {\r\n index,\r\n asChild,\r\n onClick: onClickProp,\r\n onFocus: onFocusProp,\r\n onKeyDown: onKeyDownProp,\r\n onMouseDown: onMouseDownProp,\r\n onMouseEnter: onMouseEnterProp,\r\n onMouseMove: onMouseMoveProp,\r\n onMouseLeave: onMouseLeaveProp,\r\n disabled,\r\n className,\r\n children,\r\n ref,\r\n ...itemProps\r\n } = props;\r\n\r\n const itemRef = React.useRef(null);\r\n const composedRef = useComposedRefs(ref, itemRef);\r\n\r\n const context = useRatingContext(ITEM_NAME);\r\n\r\n const instanceId = React.useId();\r\n\r\n const actualIndex = React.useMemo(() => {\r\n if (index !== undefined) {\r\n return index;\r\n }\r\n\r\n return context.getAutoIndex(instanceId);\r\n }, [index, context, instanceId]);\r\n\r\n const itemValue = actualIndex + 1;\r\n const store = useStoreContext(ITEM_NAME);\r\n const focusContext = useFocusContext(ITEM_NAME);\r\n const value = useStore((state) => state.value);\r\n const hoveredValue = useStore((state) => state.hoveredValue);\r\n const clearable = context.clearable;\r\n const step = context.step;\r\n const activationMode = context.activationMode;\r\n\r\n const itemId = getItemId(context.rootId, itemValue);\r\n const isDisabled = context.disabled || disabled;\r\n const isReadOnly = context.readOnly;\r\n const isTabStop = focusContext.tabStopId === itemId;\r\n\r\n const displayValue = hoveredValue ?? value;\r\n const isFilled = displayValue >= itemValue;\r\n const isPartiallyFilled =\r\n step < 1 && displayValue >= itemValue - step && displayValue < itemValue;\r\n const isHovered = hoveredValue !== null && hoveredValue < itemValue;\r\n\r\n const isMouseClickRef = React.useRef(false);\r\n\r\n const propsRef = useAsRef({\r\n onClick: onClickProp,\r\n onFocus: onFocusProp,\r\n onKeyDown: onKeyDownProp,\r\n onMouseDown: onMouseDownProp,\r\n onMouseEnter: onMouseEnterProp,\r\n onMouseLeave: onMouseLeaveProp,\r\n onMouseMove: onMouseMoveProp,\r\n });\r\n\r\n useIsomorphicLayoutEffect(() => {\r\n focusContext.onItemRegister({\r\n disabled: !!isDisabled,\r\n id: itemId,\r\n ref: itemRef,\r\n value: itemValue,\r\n });\r\n\r\n if (!isDisabled) {\r\n focusContext.onFocusableItemAdd();\r\n }\r\n\r\n return () => {\r\n focusContext.onItemUnregister(itemId);\r\n if (!isDisabled) {\r\n focusContext.onFocusableItemRemove();\r\n }\r\n };\r\n }, [focusContext, itemId, itemValue, isDisabled]);\r\n\r\n const onClick = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n propsRef.current.onClick?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n if (!isDisabled && !isReadOnly) {\r\n let newValue = itemValue;\r\n\r\n if (step < 1) {\r\n const rect = event.currentTarget.getBoundingClientRect();\r\n const clickX = event.clientX - rect.left;\r\n const isLeftHalf = clickX < rect.width / 2;\r\n\r\n if (context.dir === 'rtl') {\r\n if (!isLeftHalf) {\r\n newValue = itemValue - step;\r\n }\r\n } else {\r\n if (isLeftHalf) {\r\n newValue = itemValue - step;\r\n }\r\n }\r\n }\r\n\r\n if (clearable && value === newValue) {\r\n newValue = 0;\r\n }\r\n\r\n store.setState('value', newValue);\r\n }\r\n },\r\n [isDisabled, isReadOnly, clearable, step, value, itemValue, store, context.dir, propsRef],\r\n );\r\n\r\n const onFocus = React.useCallback(\r\n (event: React.FocusEvent) => {\r\n propsRef.current.onFocus?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n focusContext.onItemFocus(itemId);\r\n\r\n const isKeyboardFocus = !isMouseClickRef.current;\r\n\r\n if (!isDisabled && !isReadOnly && activationMode !== 'manual' && isKeyboardFocus) {\r\n // For half-step mode, check if the current value is a half-step that belongs to this item\r\n // e.g., if value is 3.5 and itemValue is 4, don't change it\r\n const isHalfStepValue = step < 1 && value === itemValue - step;\r\n\r\n if (!isHalfStepValue) {\r\n const newValue = clearable && value === itemValue ? 0 : itemValue;\r\n store.setState('value', newValue);\r\n }\r\n }\r\n\r\n isMouseClickRef.current = false;\r\n },\r\n [\r\n focusContext,\r\n itemId,\r\n activationMode,\r\n isDisabled,\r\n isReadOnly,\r\n clearable,\r\n value,\r\n itemValue,\r\n step,\r\n store,\r\n propsRef,\r\n ],\r\n );\r\n\r\n const onKeyDown = React.useCallback(\r\n (event: React.KeyboardEvent) => {\r\n propsRef.current.onKeyDown?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n if ((event.key === 'Enter' || event.key === ' ') && activationMode === 'manual') {\r\n event.preventDefault();\r\n if (!isDisabled && !isReadOnly && itemRef.current) {\r\n itemRef.current.click();\r\n }\r\n return;\r\n }\r\n\r\n if (event.key === 'Tab' && event.shiftKey) {\r\n focusContext.onItemShiftTab();\r\n return;\r\n }\r\n\r\n if (event.target !== event.currentTarget) return;\r\n\r\n const focusIntent = getFocusIntent(event, context.dir, context.orientation);\r\n\r\n if (focusIntent !== undefined) {\r\n if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;\r\n event.preventDefault();\r\n\r\n // For half-step mode, increment/decrement by step value instead of jumping to next item\r\n if (step < 1 && (focusIntent === 'prev' || focusIntent === 'next')) {\r\n if (!isDisabled && !isReadOnly) {\r\n let newValue = value;\r\n\r\n if (focusIntent === 'next') {\r\n newValue = Math.min(value + step, context.max);\r\n } else {\r\n newValue = Math.max(value - step, 0);\r\n }\r\n\r\n store.setState('value', newValue);\r\n\r\n // Find and focus the item that represents this value\r\n const items = focusContext.getItems().filter((item) => !item.disabled);\r\n const targetItem = items.find((item) => item.value === Math.ceil(newValue));\r\n if (targetItem?.ref.current) {\r\n queueMicrotask(() => targetItem.ref.current?.focus());\r\n }\r\n }\r\n return;\r\n }\r\n\r\n // For full-step mode or Home/End keys, use the original navigation\r\n const items = focusContext.getItems().filter((item) => !item.disabled);\r\n let candidateRefs = items.map((item) => item.ref);\r\n\r\n if (focusIntent === 'last') {\r\n candidateRefs.reverse();\r\n } else if (focusIntent === 'prev' || focusIntent === 'next') {\r\n if (focusIntent === 'prev') candidateRefs.reverse();\r\n const currentIndex = candidateRefs.findIndex(\r\n (ref) => ref.current === event.currentTarget,\r\n );\r\n candidateRefs = candidateRefs.slice(currentIndex + 1);\r\n }\r\n\r\n queueMicrotask(() => focusFirst(candidateRefs));\r\n }\r\n },\r\n [\r\n focusContext,\r\n context.dir,\r\n context.orientation,\r\n activationMode,\r\n isDisabled,\r\n isReadOnly,\r\n step,\r\n value,\r\n context.max,\r\n store,\r\n propsRef,\r\n ],\r\n );\r\n\r\n const onMouseDown = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n propsRef.current.onMouseDown?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n isMouseClickRef.current = true;\r\n\r\n if (isDisabled) {\r\n event.preventDefault();\r\n } else {\r\n focusContext.onItemFocus(itemId);\r\n }\r\n },\r\n [focusContext, itemId, isDisabled, propsRef],\r\n );\r\n\r\n const onMouseEnter = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n propsRef.current.onMouseEnter?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n if (!isDisabled && !isReadOnly) {\r\n let hoverValue = itemValue;\r\n\r\n if (step < 1) {\r\n const rect = event.currentTarget.getBoundingClientRect();\r\n const mouseX = event.clientX - rect.left;\r\n const isLeftHalf = mouseX < rect.width / 2;\r\n\r\n if (context.dir === 'rtl') {\r\n if (!isLeftHalf) {\r\n hoverValue = itemValue - step;\r\n }\r\n } else {\r\n if (isLeftHalf) {\r\n hoverValue = itemValue - step;\r\n }\r\n }\r\n }\r\n\r\n store.setState('hoveredValue', hoverValue);\r\n }\r\n },\r\n [isDisabled, isReadOnly, step, itemValue, store, context.dir, propsRef],\r\n );\r\n\r\n const onMouseLeave = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n propsRef.current.onMouseLeave?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n if (!isDisabled && !isReadOnly) {\r\n store.setState('hoveredValue', null);\r\n }\r\n },\r\n [isDisabled, isReadOnly, store, propsRef],\r\n );\r\n\r\n const onMouseMove = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n propsRef.current.onMouseMove?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n if (!isDisabled && !isReadOnly && step < 1) {\r\n const rect = event.currentTarget.getBoundingClientRect();\r\n const mouseX = event.clientX - rect.left;\r\n const isLeftHalf = mouseX < rect.width / 2;\r\n\r\n let hoverValue = itemValue;\r\n if (context.dir === 'rtl') {\r\n hoverValue = !isLeftHalf ? itemValue - step : itemValue;\r\n } else {\r\n hoverValue = isLeftHalf ? itemValue - step : itemValue;\r\n }\r\n\r\n store.setState('hoveredValue', hoverValue);\r\n }\r\n },\r\n [isDisabled, isReadOnly, step, itemValue, store, context.dir, propsRef],\r\n );\r\n\r\n const dataState: DataState = isFilled ? 'full' : isPartiallyFilled ? 'partial' : 'empty';\r\n\r\n const ItemPrimitive = asChild ? SlotPrimitive.Slot : 'button';\r\n\r\n return (\r\n \r\n {typeof children === 'function' ? children(dataState) : (children ?? )}\r\n \r\n );\r\n}\r\n\r\nexport { Rating, RatingItem, useStore as useRating };\r\n", + "path": "registry/components/rating.tsx", + "target": "@components/rating.tsx", + "type": "registry:component" + } + ], + "name": "rating", + "registryDependencies": [ + "@kombase/component-refs", + "@kombase/visually-hidden-input", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "type": "registry:component" +} diff --git a/public/r/registry.json b/public/r/registry.json new file mode 100644 index 0000000..0840ab8 --- /dev/null +++ b/public/r/registry.json @@ -0,0 +1,1023 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry.json", + "homepage": "https://kombase.komerce.id", + "items": [ + { + "dependencies": ["@radix-ui/react-alert-dialog"], + "files": [ + { + "path": "registry/ui/alert-dialog.tsx", + "target": "@ui/alert-dialog.tsx", + "type": "registry:ui" + } + ], + "name": "alert-dialog", + "registryDependencies": ["button"], + "type": "registry:ui" + }, + { + "files": [ + { + "path": "registry/ui/alert.tsx", + "target": "@ui/alert.tsx", + "type": "registry:ui" + } + ], + "name": "alert", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-avatar"], + "files": [ + { + "path": "registry/ui/avatar.tsx", + "target": "@ui/avatar.tsx", + "type": "registry:ui" + } + ], + "name": "avatar", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" + }, + { + "dependencies": ["class-variance-authority"], + "files": [ + { + "path": "registry/ui/badge.tsx", + "target": "@ui/badge.tsx", + "type": "registry:ui" + } + ], + "name": "badge", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-slot", "class-variance-authority"], + "files": [ + { + "path": "registry/ui/button.tsx", + "target": "@ui/button.tsx", + "type": "registry:ui" + } + ], + "name": "button", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" + }, + { + "dependencies": ["react-day-picker", "dayjs"], + "files": [ + { + "path": "registry/ui/calendar.tsx", + "target": "@ui/calendar.tsx", + "type": "registry:ui" + } + ], + "name": "calendar", + "registryDependencies": ["button", "@kombase/utils"], + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-checkbox"], + "files": [ + { + "path": "registry/ui/checkbox.tsx", + "target": "@ui/checkbox.tsx", + "type": "registry:ui" + } + ], + "name": "checkbox", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-collapsible"], + "files": [ + { + "path": "registry/ui/collapsible.tsx", + "target": "@ui/collapsible.tsx", + "type": "registry:ui" + } + ], + "name": "collapsible", + "type": "registry:ui" + }, + { + "dependencies": ["cmdk"], + "files": [ + { + "path": "registry/ui/combobox.tsx", + "target": "@ui/combobox.tsx", + "type": "registry:ui" + } + ], + "name": "combobox", + "registryDependencies": ["popover", "@kombase/utils"], + "type": "registry:ui" + }, + { + "dependencies": ["cmdk"], + "files": [ + { + "path": "registry/ui/command.tsx", + "target": "@ui/command.tsx", + "type": "registry:ui" + } + ], + "name": "command", + "registryDependencies": ["dialog", "@kombase/utils"], + "type": "registry:ui" + }, + { + "files": [ + { + "path": "registry/ui/debounced-input.tsx", + "target": "@ui/debounced-input.tsx", + "type": "registry:ui" + } + ], + "name": "debounced-input", + "registryDependencies": ["input"], + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-dialog"], + "files": [ + { + "path": "registry/ui/dialog.tsx", + "target": "@ui/dialog.tsx", + "type": "registry:ui" + } + ], + "name": "dialog", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-dropdown-menu"], + "files": [ + { + "path": "registry/ui/dropdown-menu.tsx", + "target": "@ui/dropdown-menu.tsx", + "type": "registry:ui" + } + ], + "name": "dropdown-menu", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-slot", "lucide-react"], + "files": [ + { + "path": "registry/ui/file-upload.tsx", + "target": "@ui/file-upload.tsx", + "type": "registry:ui" + } + ], + "name": "file-upload", + "registryDependencies": ["@kombase/utils", "@kombase/use-as-ref", "@kombase/use-lazy-ref"], + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-form", "@radix-ui/react-label", "react-hook-form"], + "files": [ + { + "path": "registry/ui/form.tsx", + "target": "@ui/form.tsx", + "type": "registry:ui" + } + ], + "name": "form", + "registryDependencies": ["label", "@kombase/utils"], + "type": "registry:ui" + }, + { + "files": [ + { + "path": "registry/ui/input-group.tsx", + "target": "@ui/input-group.tsx", + "type": "registry:ui" + } + ], + "name": "input-group", + "registryDependencies": ["button", "input", "textarea", "@kombase/utils"], + "type": "registry:ui" + }, + { + "files": [ + { + "path": "registry/ui/input.tsx", + "target": "@ui/input.tsx", + "type": "registry:ui" + } + ], + "name": "input", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-label"], + "files": [ + { + "path": "registry/ui/label.tsx", + "target": "@ui/label.tsx", + "type": "registry:ui" + } + ], + "name": "label", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-popover"], + "files": [ + { + "path": "registry/ui/popover.tsx", + "target": "@ui/popover.tsx", + "type": "registry:ui" + } + ], + "name": "popover", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-radio-group"], + "files": [ + { + "path": "registry/ui/radio-group.tsx", + "target": "@ui/radio-group.tsx", + "type": "registry:ui" + } + ], + "name": "radio-group", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-select"], + "files": [ + { + "path": "registry/ui/select.tsx", + "target": "@ui/select.tsx", + "type": "registry:ui" + } + ], + "name": "select", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-separator"], + "files": [ + { + "path": "registry/ui/separator.tsx", + "target": "@ui/separator.tsx", + "type": "registry:ui" + } + ], + "name": "separator", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" + }, + { + "files": [ + { + "path": "registry/ui/skeleton.tsx", + "target": "@ui/skeleton.tsx", + "type": "registry:ui" + } + ], + "name": "skeleton", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-slider"], + "files": [ + { + "path": "registry/ui/slider.tsx", + "target": "@ui/slider.tsx", + "type": "registry:ui" + } + ], + "name": "slider", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-switch"], + "files": [ + { + "path": "registry/ui/switch.tsx", + "target": "@ui/switch.tsx", + "type": "registry:ui" + } + ], + "name": "switch", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" + }, + { + "files": [ + { + "path": "registry/ui/table.tsx", + "target": "@ui/table.tsx", + "type": "registry:ui" + } + ], + "name": "table", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-tabs"], + "files": [ + { + "path": "registry/ui/tabs.tsx", + "target": "@ui/tabs.tsx", + "type": "registry:ui" + } + ], + "name": "tabs", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" + }, + { + "files": [ + { + "path": "registry/ui/textarea.tsx", + "target": "@ui/textarea.tsx", + "type": "registry:ui" + } + ], + "name": "textarea", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-tooltip"], + "files": [ + { + "path": "registry/ui/tooltip.tsx", + "target": "@ui/tooltip.tsx", + "type": "registry:ui" + } + ], + "name": "tooltip", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" + }, + { + "files": [ + { + "path": "registry/hooks/use-as-ref.ts", + "target": "@hooks/use-as-ref.ts", + "type": "registry:hook" + } + ], + "name": "use-as-ref", + "registryDependencies": ["@kombase/use-isomorphic-layout-effect"], + "type": "registry:hook" + }, + { + "files": [ + { + "path": "registry/hooks/use-callback-ref.ts", + "target": "@hooks/use-callback-ref.ts", + "type": "registry:hook" + } + ], + "name": "use-callback-ref", + "type": "registry:hook" + }, + { + "dependencies": ["@tanstack/react-table"], + "files": [ + { + "path": "registry/hooks/use-data-table.ts", + "target": "@hooks/use-data-table.ts", + "type": "registry:hook" + } + ], + "name": "use-data-table", + "registryDependencies": [ + "@kombase/data-table-config", + "@kombase/data-table-types", + "@kombase/lib-data-table" + ], + "type": "registry:hook" + }, + { + "files": [ + { + "path": "registry/hooks/use-debounced-callback.ts", + "target": "@hooks/use-debounced-callback.ts", + "type": "registry:hook" + } + ], + "name": "use-debounced-callback", + "registryDependencies": ["@kombase/use-callback-ref"], + "type": "registry:hook" + }, + { + "files": [ + { + "path": "registry/hooks/use-isomorphic-layout-effect.ts", + "target": "@hooks/use-isomorphic-layout-effect.ts", + "type": "registry:hook" + } + ], + "name": "use-isomorphic-layout-effect", + "type": "registry:hook" + }, + { + "files": [ + { + "path": "registry/hooks/use-lazy-ref.ts", + "target": "@hooks/use-lazy-ref.ts", + "type": "registry:hook" + } + ], + "name": "use-lazy-ref", + "type": "registry:hook" + }, + { + "dependencies": ["clsx", "tailwind-merge"], + "files": [ + { + "path": "registry/lib/utils.ts", + "target": "@lib/utils.ts", + "type": "registry:lib" + } + ], + "name": "utils", + "type": "registry:lib" + }, + { + "files": [ + { + "path": "registry/lib/component-refs.ts", + "target": "@lib/component-refs.ts", + "type": "registry:lib" + } + ], + "name": "component-refs", + "type": "registry:lib" + }, + { + "dependencies": ["@tanstack/react-table"], + "files": [ + { + "path": "registry/lib/data-table.ts", + "target": "@lib/data-table.ts", + "type": "registry:lib" + } + ], + "name": "lib-data-table", + "registryDependencies": ["@kombase/data-table-config", "@kombase/data-table-types"], + "type": "registry:lib" + }, + { + "dependencies": ["dayjs", "react-day-picker"], + "files": [ + { + "path": "registry/lib/date.ts", + "target": "@lib/date.ts", + "type": "registry:lib" + } + ], + "name": "lib-date", + "type": "registry:lib" + }, + { + "files": [ + { + "path": "registry/lib/pagination.ts", + "target": "@lib/pagination.ts", + "type": "registry:lib" + } + ], + "name": "lib-pagination", + "type": "registry:lib" + }, + { + "dependencies": ["dayjs"], + "files": [ + { + "path": "registry/lib/filter-helper.ts", + "target": "@lib/filter-helper.ts", + "type": "registry:lib" + } + ], + "name": "filter-helper", + "registryDependencies": ["@kombase/data-table-types"], + "type": "registry:lib" + }, + { + "files": [ + { + "path": "registry/components/data-table/data-table-config.ts", + "target": "@components/data-table/data-table-config.ts", + "type": "registry:component" + } + ], + "name": "data-table-config", + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/components/data-table/types.ts", + "target": "@components/data-table/types.ts", + "type": "registry:component" + } + ], + "name": "data-table-types", + "registryDependencies": ["@kombase/data-table-config"], + "type": "registry:component" + }, + { + "dependencies": ["@tanstack/react-table"], + "files": [ + { + "path": "registry/components/data-table/data-table-pagination.tsx", + "target": "@components/data-table/data-table-pagination.tsx", + "type": "registry:component" + } + ], + "name": "data-table-pagination", + "registryDependencies": ["button", "select", "@kombase/lib-pagination", "@kombase/utils"], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/components/data-table/data-table-range-filter.tsx", + "target": "@components/data-table/data-table-range-filter.tsx", + "type": "registry:component" + } + ], + "name": "data-table-range-filter", + "registryDependencies": ["debounced-input"], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/components/data-table/data-table-skeleton.tsx", + "target": "@components/data-table/data-table-skeleton.tsx", + "type": "registry:component" + } + ], + "name": "data-table-skeleton", + "registryDependencies": ["skeleton", "table"], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/components/data-table/data-table-date-filter.tsx", + "target": "@components/data-table/data-table-date-filter.tsx", + "type": "registry:component" + } + ], + "name": "data-table-date-filter", + "registryDependencies": ["button", "calendar", "popover", "separator", "@kombase/lib-date"], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/components/data-table/data-table-faceted-filter.tsx", + "target": "@components/data-table/data-table-faceted-filter.tsx", + "type": "registry:component" + } + ], + "name": "data-table-faceted-filter", + "registryDependencies": ["badge", "button", "command", "popover", "separator"], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/components/data-table/data-table-slider-filter.tsx", + "target": "@components/data-table/data-table-slider-filter.tsx", + "type": "registry:component" + } + ], + "name": "data-table-slider-filter", + "registryDependencies": ["button", "input", "label", "popover", "separator", "slider"], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/components/data-table/data-table-view-options.tsx", + "target": "@components/data-table/data-table-view-options.tsx", + "type": "registry:component" + } + ], + "name": "data-table-view-options", + "registryDependencies": ["button", "command", "popover"], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/components/data-table/data-table-toolbar.tsx", + "target": "@components/data-table/data-table-toolbar.tsx", + "type": "registry:component" + } + ], + "name": "data-table-toolbar", + "registryDependencies": [ + "button", + "debounced-input", + "@kombase/data-table-date-filter", + "@kombase/data-table-faceted-filter", + "@kombase/data-table-slider-filter", + "@kombase/data-table-view-options" + ], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/components/data-table/data-table-bulk-action.tsx", + "target": "@components/data-table/data-table-bulk-action.tsx", + "type": "registry:component" + } + ], + "name": "data-table-bulk-action", + "registryDependencies": ["badge", "button", "separator", "tooltip"], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/components/data-table/data-table-column-header.tsx", + "target": "@components/data-table/data-table-column-header.tsx", + "type": "registry:component" + } + ], + "name": "data-table-column-header", + "registryDependencies": ["dropdown-menu"], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/components/data-table/data-table-advance-filter.tsx", + "target": "@components/data-table/data-table-advance-filter.tsx", + "type": "registry:component" + } + ], + "name": "data-table-advance-filter", + "registryDependencies": [ + "@kombase/lib-data-table", + "badge", + "button", + "calendar", + "command", + "popover", + "select", + "@kombase/data-table-config", + "@kombase/data-table-date-filter", + "@kombase/data-table-range-filter" + ], + "type": "registry:component" + }, + { + "dependencies": ["@tanstack/react-table"], + "files": [ + { + "path": "registry/components/data-table/data-table.tsx", + "target": "@components/data-table/data-table.tsx", + "type": "registry:component" + } + ], + "name": "data-table", + "registryDependencies": [ + "table", + "@kombase/data-table-config", + "@kombase/data-table-types", + "@kombase/data-table-pagination", + "@kombase/data-table-toolbar", + "@kombase/data-table-advance-filter", + "@kombase/data-table-bulk-action", + "@kombase/data-table-column-header", + "@kombase/data-table-skeleton", + "@kombase/lib-data-table", + "@kombase/use-data-table" + ], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/components/action-bar.tsx", + "target": "@components/action-bar.tsx", + "type": "registry:component" + } + ], + "name": "action-bar", + "registryDependencies": [ + "button", + "@kombase/component-refs", + "@kombase/use-as-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "type": "registry:component" + }, + { + "dependencies": ["@radix-ui/react-avatar"], + "files": [ + { + "path": "registry/components/avatar-group.tsx", + "target": "@components/avatar-group.tsx", + "type": "registry:component" + } + ], + "name": "avatar-group", + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/components/confirm-dialog.tsx", + "target": "@components/confirm-dialog.tsx", + "type": "registry:component" + } + ], + "name": "confirm-dialog", + "registryDependencies": ["alert-dialog", "button"], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/components/long-text.tsx", + "target": "@components/long-text.tsx", + "type": "registry:component" + } + ], + "name": "long-text", + "registryDependencies": ["popover", "tooltip"], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/components/phone-input.tsx", + "target": "@components/phone-input.tsx", + "type": "registry:component" + } + ], + "name": "phone-input", + "registryDependencies": [ + "command", + "input", + "popover", + "@kombase/component-refs", + "@kombase/visually-hidden-input", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "type": "registry:component" + }, + { + "dependencies": ["@radix-ui/react-direction"], + "files": [ + { + "path": "registry/components/rating.tsx", + "target": "@components/rating.tsx", + "type": "registry:component" + } + ], + "name": "rating", + "registryDependencies": [ + "@kombase/component-refs", + "@kombase/visually-hidden-input", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "type": "registry:component" + }, + { + "dependencies": ["@radix-ui/react-direction"], + "files": [ + { + "path": "registry/components/stepper.tsx", + "target": "@components/stepper.tsx", + "type": "registry:component" + } + ], + "name": "stepper", + "registryDependencies": [ + "@kombase/component-refs", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "type": "registry:component" + }, + { + "dependencies": ["@radix-ui/react-direction", "class-variance-authority"], + "files": [ + { + "path": "registry/components/timeline.tsx", + "target": "@components/timeline.tsx", + "type": "registry:component" + } + ], + "name": "timeline", + "registryDependencies": [ + "@kombase/component-refs", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "type": "registry:component" + }, + { + "dependencies": ["@floating-ui/react-dom", "@radix-ui/react-direction"], + "files": [ + { + "path": "registry/components/tour.tsx", + "target": "@components/tour.tsx", + "type": "registry:component" + } + ], + "name": "tour", + "registryDependencies": [ + "button", + "@kombase/component-refs", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/components/visually-hidden-input.tsx", + "target": "@components/visually-hidden-input.tsx", + "type": "registry:component" + } + ], + "name": "visually-hidden-input", + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/components/password-input.tsx", + "target": "@components/password-input.tsx", + "type": "registry:component" + } + ], + "name": "password-input", + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/components/select-dropdown.tsx", + "target": "@components/select-dropdown.tsx", + "type": "registry:component" + } + ], + "name": "select-dropdown", + "type": "registry:component" + }, + { + "dependencies": ["dayjs"], + "files": [ + { + "path": "registry/form/form-date-picker.tsx", + "target": "@components/form/form-date-picker.tsx", + "type": "registry:component" + } + ], + "name": "form-date-picker", + "registryDependencies": ["calendar", "form", "label", "popover"], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/form/form-input.tsx", + "target": "@components/form/form-input.tsx", + "type": "registry:component" + } + ], + "name": "form-input", + "registryDependencies": ["form", "input", "label"], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/form/form-input-group.tsx", + "target": "@components/form/form-input-group.tsx", + "type": "registry:component" + } + ], + "name": "form-input-group", + "registryDependencies": ["form", "input-group", "label"], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/form/form-password.tsx", + "target": "@components/form/form-password.tsx", + "type": "registry:component" + } + ], + "name": "form-password", + "registryDependencies": ["form", "label", "@kombase/password-input"], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/form/form-phone-input.tsx", + "target": "@components/form/form-phone-input.tsx", + "type": "registry:component" + } + ], + "name": "form-phone-input", + "registryDependencies": ["form", "label", "@kombase/phone-input"], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/form/form-pick.tsx", + "target": "@components/form/form-pick.tsx", + "type": "registry:component" + } + ], + "name": "form-pick", + "registryDependencies": ["form", "label", "radio-group"], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/form/form-radio.tsx", + "target": "@components/form/form-radio.tsx", + "type": "registry:component" + } + ], + "name": "form-radio", + "registryDependencies": ["form", "label", "radio-group"], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/form/form-search-select.tsx", + "target": "@components/form/form-search-select.tsx", + "type": "registry:component" + } + ], + "name": "form-search-select", + "registryDependencies": ["combobox", "form", "label"], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/form/form-textarea.tsx", + "target": "@components/form/form-textarea.tsx", + "type": "registry:component" + } + ], + "name": "form-textarea", + "registryDependencies": ["form", "label", "textarea"], + "type": "registry:component" + }, + { + "files": [ + { + "path": "registry/form/form-upload.tsx", + "target": "@components/form/form-upload.tsx", + "type": "registry:component" + } + ], + "name": "form-upload", + "registryDependencies": ["form", "label", "@kombase/file-upload"], + "type": "registry:component" + } + ], + "name": "kombase" +} diff --git a/public/r/select-dropdown.json b/public/r/select-dropdown.json new file mode 100644 index 0000000..2756f9d --- /dev/null +++ b/public/r/select-dropdown.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import { Loader } from 'lucide-react';\r\nimport { FormControl } from '@/components/ui/form';\r\nimport {\r\n Select,\r\n SelectContent,\r\n SelectItem,\r\n SelectTrigger,\r\n SelectValue,\r\n} from '@/components/ui/select';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype SelectDropdownProps = {\r\n onValueChange?: (value: string) => void;\r\n defaultValue: string | undefined;\r\n placeholder?: string;\r\n isPending?: boolean;\r\n items: { label: string; value: string }[] | undefined;\r\n disabled?: boolean;\r\n className?: string;\r\n isControlled?: boolean;\r\n};\r\n\r\nexport function SelectDropdown({\r\n defaultValue,\r\n onValueChange,\r\n isPending,\r\n items,\r\n placeholder,\r\n disabled,\r\n className = '',\r\n isControlled = false,\r\n}: SelectDropdownProps) {\r\n const defaultState = isControlled\r\n ? { onValueChange, value: defaultValue }\r\n : { defaultValue, onValueChange };\r\n return (\r\n \r\n );\r\n}\r\n", + "path": "registry/components/select-dropdown.tsx", + "target": "@components/select-dropdown.tsx", + "type": "registry:component" + } + ], + "name": "select-dropdown", + "type": "registry:component" +} diff --git a/public/r/select.json b/public/r/select.json new file mode 100644 index 0000000..61b345a --- /dev/null +++ b/public/r/select.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-select"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as SelectPrimitive from '@radix-ui/react-select';\r\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';\r\nimport * as React from 'react';\r\n\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst Select = ({ ...props }: React.ComponentProps) => (\r\n \r\n);\r\nSelect.displayName = 'Select';\r\n\r\nconst SelectGroup = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>((props, ref) => );\r\nSelectGroup.displayName = SelectPrimitive.Group.displayName;\r\n\r\nconst SelectValue = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>((props, ref) => );\r\nSelectValue.displayName = SelectPrimitive.Value.displayName;\r\n\r\nconst SelectTrigger = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef & {\r\n size?: 'sm' | 'default';\r\n }\r\n>(({ className, size = 'default', children, ...props }, ref) => (\r\n \r\n {children}\r\n \r\n \r\n \r\n \r\n));\r\nSelectTrigger.displayName = SelectPrimitive.Trigger.displayName;\r\n\r\nconst SelectContent = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, children, position = 'item-aligned', align = 'center', ...props }, ref) => (\r\n \r\n \r\n \r\n \r\n {children}\r\n \r\n \r\n \r\n \r\n));\r\nSelectContent.displayName = SelectPrimitive.Content.displayName;\r\n\r\nconst SelectLabel = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nSelectLabel.displayName = SelectPrimitive.Label.displayName;\r\n\r\nconst SelectItem = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, children, ...props }, ref) => (\r\n \r\n \r\n \r\n \r\n \r\n \r\n {children}\r\n \r\n));\r\nSelectItem.displayName = SelectPrimitive.Item.displayName;\r\n\r\nconst SelectSeparator = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nSelectSeparator.displayName = SelectPrimitive.Separator.displayName;\r\n\r\nconst SelectScrollUpButton = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n \r\n \r\n));\r\nSelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;\r\n\r\nconst SelectScrollDownButton = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n \r\n \r\n));\r\nSelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;\r\n\r\nexport {\r\n Select,\r\n SelectContent,\r\n SelectGroup,\r\n SelectItem,\r\n SelectLabel,\r\n SelectScrollDownButton,\r\n SelectScrollUpButton,\r\n SelectSeparator,\r\n SelectTrigger,\r\n SelectValue,\r\n};\r\n", + "path": "registry/ui/select.tsx", + "target": "@ui/select.tsx", + "type": "registry:ui" + } + ], + "name": "select", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/separator.json b/public/r/separator.json new file mode 100644 index 0000000..3d0d2bf --- /dev/null +++ b/public/r/separator.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-separator"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as SeparatorPrimitive from '@radix-ui/react-separator';\r\nimport * as React from 'react';\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst Separator = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => (\r\n \r\n));\r\nSeparator.displayName = SeparatorPrimitive.Root.displayName;\r\n\r\nexport { Separator };\r\n", + "path": "registry/ui/separator.tsx", + "target": "@ui/separator.tsx", + "type": "registry:ui" + } + ], + "name": "separator", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/skeleton.json b/public/r/skeleton.json new file mode 100644 index 0000000..0151dd5 --- /dev/null +++ b/public/r/skeleton.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "'use client';\r\n\r\nimport * as React from 'react';\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst Skeleton = React.forwardRef>(\r\n ({ className, ...props }, ref) => {\r\n return (\r\n \r\n );\r\n },\r\n);\r\nSkeleton.displayName = 'Skeleton';\r\n\r\nexport { Skeleton };\r\n", + "path": "registry/ui/skeleton.tsx", + "target": "@ui/skeleton.tsx", + "type": "registry:ui" + } + ], + "name": "skeleton", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/slider.json b/public/r/slider.json new file mode 100644 index 0000000..2befdc3 --- /dev/null +++ b/public/r/slider.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-slider"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as SliderPrimitive from '@radix-ui/react-slider';\r\nimport * as React from 'react';\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst Slider = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, defaultValue, value, min = 0, max = 100, ...props }, ref) => {\r\n const _values = React.useMemo(\r\n () => (Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max]),\r\n [value, defaultValue, min, max],\r\n );\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n {Array.from({ length: _values.length }, (_, index) => (\r\n \r\n ))}\r\n \r\n );\r\n});\r\nSlider.displayName = SliderPrimitive.Root.displayName;\r\n\r\nexport { Slider };\r\n", + "path": "registry/ui/slider.tsx", + "target": "@ui/slider.tsx", + "type": "registry:ui" + } + ], + "name": "slider", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/stepper.json b/public/r/stepper.json new file mode 100644 index 0000000..83e8da9 --- /dev/null +++ b/public/r/stepper.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-direction"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as DirectionPrimitive from '@radix-ui/react-direction';\r\nimport * as SlotPrimitive from '@radix-ui/react-slot';\r\nimport { Check } from 'lucide-react';\r\nimport * as React from 'react';\r\nimport { useAsRef } from '@/components/hooks/use-as-ref';\r\nimport { useIsomorphicLayoutEffect } from '@/components/hooks/use-isomorphic-layout-effect';\r\nimport { useLazyRef } from '@/components/hooks/use-lazy-ref';\r\nimport { useComposedRefs } from '@/lib/component-refs';\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst ROOT_NAME = 'Stepper';\r\nconst LIST_NAME = 'StepperList';\r\nconst ITEM_NAME = 'StepperItem';\r\nconst TRIGGER_NAME = 'StepperTrigger';\r\nconst INDICATOR_NAME = 'StepperIndicator';\r\nconst SEPARATOR_NAME = 'StepperSeparator';\r\nconst TITLE_NAME = 'StepperTitle';\r\nconst DESCRIPTION_NAME = 'StepperDescription';\r\nconst CONTENT_NAME = 'StepperContent';\r\nconst PREV_NAME = 'StepperPrev';\r\nconst NEXT_NAME = 'StepperNext';\r\n\r\nconst ENTRY_FOCUS = 'stepperFocusGroup.onEntryFocus';\r\nconst EVENT_OPTIONS = { bubbles: false, cancelable: true };\r\nconst ARROW_KEYS = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'];\r\n\r\ntype Direction = 'ltr' | 'rtl';\r\ntype Orientation = 'horizontal' | 'vertical';\r\ntype NavigationDirection = 'next' | 'prev';\r\ntype ActivationMode = 'automatic' | 'manual';\r\ntype DataState = 'inactive' | 'active' | 'completed';\r\n\r\ninterface DivProps extends React.ComponentProps<'div'> {\r\n asChild?: boolean;\r\n}\r\ninterface ButtonProps extends React.ComponentProps<'button'> {\r\n asChild?: boolean;\r\n}\r\n\r\ntype ListElement = React.ComponentRef;\r\ntype TriggerElement = React.ComponentRef;\r\n\r\nfunction getId(\r\n id: string,\r\n variant: 'trigger' | 'content' | 'title' | 'description',\r\n value: string,\r\n) {\r\n return `${id}-${variant}-${value}`;\r\n}\r\n\r\ntype FocusIntent = 'first' | 'last' | 'prev' | 'next';\r\n\r\nconst MAP_KEY_TO_FOCUS_INTENT: Record = {\r\n ArrowDown: 'next',\r\n ArrowLeft: 'prev',\r\n ArrowRight: 'next',\r\n ArrowUp: 'prev',\r\n End: 'last',\r\n Home: 'first',\r\n PageDown: 'last',\r\n PageUp: 'first',\r\n};\r\n\r\nfunction getDirectionAwareKey(key: string, dir?: Direction) {\r\n if (dir !== 'rtl') return key;\r\n return key === 'ArrowLeft' ? 'ArrowRight' : key === 'ArrowRight' ? 'ArrowLeft' : key;\r\n}\r\n\r\nfunction getFocusIntent(\r\n event: React.KeyboardEvent,\r\n dir?: Direction,\r\n orientation?: Orientation,\r\n) {\r\n const key = getDirectionAwareKey(event.key, dir);\r\n if (orientation === 'horizontal' && ['ArrowUp', 'ArrowDown'].includes(key)) return undefined;\r\n if (orientation === 'vertical' && ['ArrowLeft', 'ArrowRight'].includes(key)) return undefined;\r\n return MAP_KEY_TO_FOCUS_INTENT[key];\r\n}\r\n\r\nfunction focusFirst(candidates: React.RefObject[], preventScroll = false) {\r\n const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;\r\n for (const candidateRef of candidates) {\r\n const candidate = candidateRef.current;\r\n if (!candidate) continue;\r\n if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;\r\n candidate.focus({ preventScroll });\r\n if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;\r\n }\r\n}\r\n\r\nfunction wrapArray(array: T[], startIndex: number) {\r\n return array.map((_, index) => array[(startIndex + index) % array.length] as T);\r\n}\r\n\r\nfunction getDataState(\r\n value: string | undefined,\r\n itemValue: string,\r\n stepState: StepState | undefined,\r\n steps: Map,\r\n variant: 'item' | 'separator' = 'item',\r\n): DataState {\r\n const stepKeys = Array.from(steps.keys());\r\n const currentIndex = stepKeys.indexOf(itemValue);\r\n\r\n if (stepState?.completed) return 'completed';\r\n\r\n if (value === itemValue) {\r\n return variant === 'separator' ? 'inactive' : 'active';\r\n }\r\n\r\n if (value) {\r\n const activeIndex = stepKeys.indexOf(value);\r\n\r\n if (activeIndex > currentIndex) return 'completed';\r\n }\r\n\r\n return 'inactive';\r\n}\r\n\r\ninterface StepState {\r\n value: string;\r\n completed: boolean;\r\n disabled: boolean;\r\n}\r\n\r\ninterface StoreState {\r\n steps: Map;\r\n value: string;\r\n}\r\n\r\ninterface Store {\r\n subscribe: (callback: () => void) => () => void;\r\n getState: () => StoreState;\r\n setState: (key: K, value: StoreState[K]) => void;\r\n setStateWithValidation: (value: string, direction: NavigationDirection) => Promise;\r\n hasValidation: () => boolean;\r\n notify: () => void;\r\n addStep: (value: string, completed: boolean, disabled: boolean) => void;\r\n removeStep: (value: string) => void;\r\n setStep: (value: string, completed: boolean, disabled: boolean) => void;\r\n}\r\n\r\nconst StoreContext = React.createContext(null);\r\n\r\nfunction useStoreContext(consumerName: string) {\r\n const context = React.useContext(StoreContext);\r\n if (!context) {\r\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\r\n }\r\n return context;\r\n}\r\n\r\nfunction useStore(selector: (state: StoreState) => T): T {\r\n const store = useStoreContext('useStore');\r\n\r\n const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);\r\n\r\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\r\n}\r\n\r\ninterface ItemData {\r\n id: string;\r\n ref: React.RefObject;\r\n value: string;\r\n active: boolean;\r\n disabled: boolean;\r\n}\r\n\r\ninterface StepperContextValue {\r\n rootId: string;\r\n dir: Direction;\r\n orientation: Orientation;\r\n activationMode: ActivationMode;\r\n disabled: boolean;\r\n nonInteractive: boolean;\r\n loop: boolean;\r\n}\r\n\r\nconst StepperContext = React.createContext(null);\r\n\r\nfunction useStepperContext(consumerName: string) {\r\n const context = React.useContext(StepperContext);\r\n if (!context) {\r\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\r\n }\r\n return context;\r\n}\r\n\r\ninterface StepperProps extends DivProps {\r\n value?: string;\r\n defaultValue?: string;\r\n onValueChange?: (value: string) => void;\r\n onValueComplete?: (value: string, completed: boolean) => void;\r\n onValueAdd?: (value: string) => void;\r\n onValueRemove?: (value: string) => void;\r\n onValidate?: (value: string, direction: NavigationDirection) => boolean | Promise;\r\n activationMode?: ActivationMode;\r\n dir?: Direction;\r\n orientation?: Orientation;\r\n disabled?: boolean;\r\n loop?: boolean;\r\n nonInteractive?: boolean;\r\n}\r\n\r\nfunction Stepper(props: StepperProps) {\r\n const {\r\n value,\r\n defaultValue,\r\n onValueChange,\r\n onValueComplete,\r\n onValueAdd,\r\n onValueRemove,\r\n onValidate,\r\n dir: dirProp,\r\n orientation = 'horizontal',\r\n activationMode = 'automatic',\r\n asChild,\r\n disabled = false,\r\n nonInteractive = false,\r\n loop = false,\r\n className,\r\n id,\r\n ...rootProps\r\n } = props;\r\n\r\n const listenersRef = useLazyRef(() => new Set<() => void>());\r\n const stateRef = useLazyRef(() => ({\r\n steps: new Map(),\r\n value: value ?? defaultValue ?? '',\r\n }));\r\n\r\n const propsRef = useAsRef({\r\n onValidate,\r\n onValueAdd,\r\n onValueChange,\r\n onValueComplete,\r\n onValueRemove,\r\n });\r\n\r\n const store = React.useMemo(() => {\r\n return {\r\n addStep: (value, completed, disabled) => {\r\n const newStep: StepState = { completed, disabled, value };\r\n stateRef.current.steps.set(value, newStep);\r\n propsRef.current.onValueAdd?.(value);\r\n store.notify();\r\n },\r\n getState: () => stateRef.current,\r\n hasValidation: () => !!propsRef.current.onValidate,\r\n notify: () => {\r\n for (const cb of listenersRef.current) {\r\n cb();\r\n }\r\n },\r\n removeStep: (value) => {\r\n stateRef.current.steps.delete(value);\r\n propsRef.current.onValueRemove?.(value);\r\n store.notify();\r\n },\r\n setState: (key, value) => {\r\n if (Object.is(stateRef.current[key], value)) return;\r\n\r\n if (key === 'value' && typeof value === 'string') {\r\n stateRef.current.value = value;\r\n propsRef.current.onValueChange?.(value);\r\n } else {\r\n stateRef.current[key] = value;\r\n }\r\n\r\n store.notify();\r\n },\r\n setStateWithValidation: async (value, direction) => {\r\n if (!propsRef.current.onValidate) {\r\n store.setState('value', value);\r\n return true;\r\n }\r\n\r\n try {\r\n const isValid = await propsRef.current.onValidate(value, direction);\r\n if (isValid) {\r\n store.setState('value', value);\r\n }\r\n return isValid;\r\n } catch {\r\n return false;\r\n }\r\n },\r\n setStep: (value, completed, disabled) => {\r\n const step = stateRef.current.steps.get(value);\r\n if (step) {\r\n const updatedStep: StepState = { ...step, completed, disabled };\r\n stateRef.current.steps.set(value, updatedStep);\r\n\r\n if (completed !== step.completed) {\r\n propsRef.current.onValueComplete?.(value, completed);\r\n }\r\n\r\n store.notify();\r\n }\r\n },\r\n subscribe: (cb) => {\r\n listenersRef.current.add(cb);\r\n return () => listenersRef.current.delete(cb);\r\n },\r\n };\r\n }, [listenersRef, stateRef, propsRef]);\r\n\r\n useIsomorphicLayoutEffect(() => {\r\n if (value !== undefined) {\r\n store.setState('value', value);\r\n }\r\n }, [value]);\r\n\r\n const dir = DirectionPrimitive.useDirection(dirProp);\r\n\r\n const instanceId = React.useId();\r\n const rootId = id ?? instanceId;\r\n\r\n const contextValue = React.useMemo(\r\n () => ({\r\n activationMode,\r\n dir,\r\n disabled,\r\n loop,\r\n nonInteractive,\r\n orientation,\r\n rootId,\r\n }),\r\n [rootId, dir, orientation, activationMode, disabled, nonInteractive, loop],\r\n );\r\n\r\n const RootPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n );\r\n}\r\n\r\ninterface FocusContextValue {\r\n tabStopId: string | null;\r\n onItemFocus: (tabStopId: string) => void;\r\n onItemShiftTab: () => void;\r\n onFocusableItemAdd: () => void;\r\n onFocusableItemRemove: () => void;\r\n onItemRegister: (item: ItemData) => void;\r\n onItemUnregister: (id: string) => void;\r\n getItems: () => ItemData[];\r\n}\r\n\r\nconst FocusContext = React.createContext(null);\r\n\r\nfunction useFocusContext(consumerName: string) {\r\n const context = React.useContext(FocusContext);\r\n if (!context) {\r\n throw new Error(`\\`${consumerName}\\` must be used within \\`FocusProvider\\``);\r\n }\r\n return context;\r\n}\r\n\r\nfunction StepperList(props: DivProps) {\r\n const {\r\n asChild,\r\n onBlur: onBlurProp,\r\n onFocus: onFocusProp,\r\n onMouseDown: onMouseDownProp,\r\n className,\r\n children,\r\n ref,\r\n ...listProps\r\n } = props;\r\n\r\n const context = useStepperContext(LIST_NAME);\r\n const orientation = context.orientation;\r\n const currentValue = useStore((state) => state.value);\r\n\r\n const propsRef = useAsRef({\r\n onBlur: onBlurProp,\r\n onFocus: onFocusProp,\r\n onMouseDown: onMouseDownProp,\r\n });\r\n\r\n const [tabStopId, setTabStopId] = React.useState(null);\r\n const [isTabbingBackOut, setIsTabbingBackOut] = React.useState(false);\r\n const [focusableItemCount, setFocusableItemCount] = React.useState(0);\r\n const isClickFocusRef = React.useRef(false);\r\n const itemsRef = React.useRef>(new Map());\r\n const listRef = React.useRef(null);\r\n const composedRef = useComposedRefs(ref, listRef);\r\n\r\n const onItemFocus = React.useCallback((tabStopId: string) => {\r\n setTabStopId(tabStopId);\r\n }, []);\r\n\r\n const onItemShiftTab = React.useCallback(() => {\r\n setIsTabbingBackOut(true);\r\n }, []);\r\n\r\n const onFocusableItemAdd = React.useCallback(() => {\r\n setFocusableItemCount((prevCount) => prevCount + 1);\r\n }, []);\r\n\r\n const onFocusableItemRemove = React.useCallback(() => {\r\n setFocusableItemCount((prevCount) => prevCount - 1);\r\n }, []);\r\n\r\n const onItemRegister = React.useCallback((item: ItemData) => {\r\n itemsRef.current.set(item.id, item);\r\n }, []);\r\n\r\n const onItemUnregister = React.useCallback((id: string) => {\r\n itemsRef.current.delete(id);\r\n }, []);\r\n\r\n const getItems = React.useCallback(() => {\r\n return Array.from(itemsRef.current.values())\r\n .filter((item) => item.ref.current)\r\n .sort((a, b) => {\r\n const elementA = a.ref.current;\r\n const elementB = b.ref.current;\r\n if (!elementA || !elementB) return 0;\r\n const position = elementA.compareDocumentPosition(elementB);\r\n if (position & Node.DOCUMENT_POSITION_FOLLOWING) {\r\n return -1;\r\n }\r\n if (position & Node.DOCUMENT_POSITION_PRECEDING) {\r\n return 1;\r\n }\r\n return 0;\r\n });\r\n }, []);\r\n\r\n const onBlur = React.useCallback(\r\n (event: React.FocusEvent) => {\r\n propsRef.current.onBlur?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n setIsTabbingBackOut(false);\r\n },\r\n [propsRef],\r\n );\r\n\r\n const onFocus = React.useCallback(\r\n (event: React.FocusEvent) => {\r\n propsRef.current.onFocus?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n const isKeyboardFocus = !isClickFocusRef.current;\r\n if (event.target === event.currentTarget && isKeyboardFocus && !isTabbingBackOut) {\r\n const entryFocusEvent = new CustomEvent(ENTRY_FOCUS, EVENT_OPTIONS);\r\n event.currentTarget.dispatchEvent(entryFocusEvent);\r\n\r\n if (!entryFocusEvent.defaultPrevented) {\r\n const items = Array.from(itemsRef.current.values()).filter((item) => !item.disabled);\r\n const selectedItem = currentValue\r\n ? items.find((item) => item.value === currentValue)\r\n : undefined;\r\n const activeItem = items.find((item) => item.active);\r\n const currentItem = items.find((item) => item.id === tabStopId);\r\n\r\n const candidateItems = [selectedItem, activeItem, currentItem, ...items].filter(\r\n Boolean,\r\n ) as ItemData[];\r\n const candidateRefs = candidateItems.map((item) => item.ref);\r\n focusFirst(candidateRefs, false);\r\n }\r\n }\r\n isClickFocusRef.current = false;\r\n },\r\n [propsRef, isTabbingBackOut, currentValue, tabStopId],\r\n );\r\n\r\n const onMouseDown = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n propsRef.current.onMouseDown?.(event);\r\n\r\n if (event.defaultPrevented) return;\r\n\r\n isClickFocusRef.current = true;\r\n },\r\n [propsRef],\r\n );\r\n\r\n const focusContextValue = React.useMemo(\r\n () => ({\r\n getItems,\r\n onFocusableItemAdd,\r\n onFocusableItemRemove,\r\n onItemFocus,\r\n onItemRegister,\r\n onItemShiftTab,\r\n onItemUnregister,\r\n tabStopId,\r\n }),\r\n [\r\n tabStopId,\r\n onItemFocus,\r\n onItemShiftTab,\r\n onFocusableItemAdd,\r\n onFocusableItemRemove,\r\n onItemRegister,\r\n onItemUnregister,\r\n getItems,\r\n ],\r\n );\r\n\r\n const ListPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n \r\n {children}\r\n \r\n \r\n );\r\n}\r\n\r\ninterface StepperItemContextValue {\r\n value: string;\r\n stepState: StepState | undefined;\r\n}\r\n\r\nconst StepperItemContext = React.createContext(null);\r\n\r\nfunction useStepperItemContext(consumerName: string) {\r\n const context = React.useContext(StepperItemContext);\r\n if (!context) {\r\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ITEM_NAME}\\``);\r\n }\r\n return context;\r\n}\r\n\r\ninterface StepperItemProps extends DivProps {\r\n value: string;\r\n completed?: boolean;\r\n disabled?: boolean;\r\n}\r\n\r\nfunction StepperItem(props: StepperItemProps) {\r\n const {\r\n value: itemValue,\r\n completed = false,\r\n disabled = false,\r\n asChild,\r\n className,\r\n children,\r\n ref,\r\n ...itemProps\r\n } = props;\r\n\r\n const context = useStepperContext(ITEM_NAME);\r\n const store = useStoreContext(ITEM_NAME);\r\n const orientation = context.orientation;\r\n const value = useStore((state) => state.value);\r\n\r\n useIsomorphicLayoutEffect(() => {\r\n store.addStep(itemValue, completed, disabled);\r\n\r\n return () => {\r\n store.removeStep(itemValue);\r\n };\r\n }, [itemValue, completed, disabled]);\r\n\r\n useIsomorphicLayoutEffect(() => {\r\n store.setStep(itemValue, completed, disabled);\r\n }, [itemValue, completed, disabled]);\r\n\r\n const stepState = useStore((state) => state.steps.get(itemValue));\r\n const steps = useStore((state) => state.steps);\r\n const dataState = getDataState(value, itemValue, stepState, steps);\r\n\r\n const itemContextValue = React.useMemo(\r\n () => ({\r\n stepState,\r\n value: itemValue,\r\n }),\r\n [itemValue, stepState],\r\n );\r\n\r\n const ItemPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n \r\n {children}\r\n \r\n \r\n );\r\n}\r\n\r\nfunction StepperTrigger(props: ButtonProps) {\r\n const {\r\n asChild,\r\n onClick: onClickProp,\r\n onFocus: onFocusProp,\r\n onKeyDown: onKeyDownProp,\r\n onMouseDown: onMouseDownProp,\r\n disabled,\r\n className,\r\n ref,\r\n ...triggerProps\r\n } = props;\r\n\r\n const context = useStepperContext(TRIGGER_NAME);\r\n const itemContext = useStepperItemContext(TRIGGER_NAME);\r\n const itemValue = itemContext.value;\r\n\r\n const store = useStoreContext(TRIGGER_NAME);\r\n const focusContext = useFocusContext(TRIGGER_NAME);\r\n const value = useStore((state) => state.value);\r\n const steps = useStore((state) => state.steps);\r\n const stepState = useStore((state) => state.steps.get(itemValue));\r\n\r\n const propsRef = useAsRef({\r\n onClick: onClickProp,\r\n onFocus: onFocusProp,\r\n onKeyDown: onKeyDownProp,\r\n onMouseDown: onMouseDownProp,\r\n });\r\n\r\n const activationMode = context.activationMode;\r\n const orientation = context.orientation;\r\n const loop = context.loop;\r\n\r\n const stepIndex = Array.from(steps.keys()).indexOf(itemValue);\r\n\r\n const stepPosition = stepIndex + 1;\r\n const stepCount = steps.size;\r\n\r\n const triggerId = getId(context.rootId, 'trigger', itemValue);\r\n const contentId = getId(context.rootId, 'content', itemValue);\r\n const titleId = getId(context.rootId, 'title', itemValue);\r\n const descriptionId = getId(context.rootId, 'description', itemValue);\r\n\r\n const isDisabled = disabled || stepState?.disabled || context.disabled;\r\n const isActive = value === itemValue;\r\n const isTabStop = focusContext.tabStopId === triggerId;\r\n const dataState = getDataState(value, itemValue, stepState, steps);\r\n\r\n const triggerRef = React.useRef(null);\r\n const composedRef = useComposedRefs(ref, triggerRef);\r\n const isArrowKeyPressedRef = React.useRef(false);\r\n const isMouseClickRef = React.useRef(false);\r\n\r\n React.useEffect(() => {\r\n function onKeyDown(event: KeyboardEvent) {\r\n if (ARROW_KEYS.includes(event.key)) {\r\n isArrowKeyPressedRef.current = true;\r\n }\r\n }\r\n function onKeyUp() {\r\n isArrowKeyPressedRef.current = false;\r\n }\r\n document.addEventListener('keydown', onKeyDown);\r\n document.addEventListener('keyup', onKeyUp);\r\n return () => {\r\n document.removeEventListener('keydown', onKeyDown);\r\n document.removeEventListener('keyup', onKeyUp);\r\n };\r\n }, []);\r\n\r\n useIsomorphicLayoutEffect(() => {\r\n focusContext.onItemRegister({\r\n active: isTabStop,\r\n disabled: !!isDisabled,\r\n id: triggerId,\r\n ref: triggerRef,\r\n value: itemValue,\r\n });\r\n\r\n if (!isDisabled) {\r\n focusContext.onFocusableItemAdd();\r\n }\r\n\r\n return () => {\r\n focusContext.onItemUnregister(triggerId);\r\n if (!isDisabled) {\r\n focusContext.onFocusableItemRemove();\r\n }\r\n };\r\n }, [focusContext, triggerId, itemValue, isTabStop, isDisabled]);\r\n\r\n const onClick = React.useCallback(\r\n async (event: React.MouseEvent) => {\r\n propsRef.current.onClick?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n if (!isDisabled && !context.nonInteractive) {\r\n const currentStepIndex = Array.from(steps.keys()).indexOf(value ?? '');\r\n const targetStepIndex = Array.from(steps.keys()).indexOf(itemValue);\r\n const direction = targetStepIndex > currentStepIndex ? 'next' : 'prev';\r\n\r\n await store.setStateWithValidation(itemValue, direction);\r\n }\r\n },\r\n [isDisabled, context.nonInteractive, store, itemValue, value, steps, propsRef],\r\n );\r\n\r\n const onFocus = React.useCallback(\r\n async (event: React.FocusEvent) => {\r\n propsRef.current.onFocus?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n focusContext.onItemFocus(triggerId);\r\n\r\n const isKeyboardFocus = !isMouseClickRef.current;\r\n\r\n if (\r\n !isActive &&\r\n !isDisabled &&\r\n activationMode !== 'manual' &&\r\n !context.nonInteractive &&\r\n isKeyboardFocus\r\n ) {\r\n const currentStepIndex = Array.from(steps.keys()).indexOf(value || '');\r\n const targetStepIndex = Array.from(steps.keys()).indexOf(itemValue);\r\n const direction = targetStepIndex > currentStepIndex ? 'next' : 'prev';\r\n\r\n await store.setStateWithValidation(itemValue, direction);\r\n }\r\n\r\n isMouseClickRef.current = false;\r\n },\r\n [\r\n focusContext,\r\n triggerId,\r\n activationMode,\r\n isActive,\r\n isDisabled,\r\n context.nonInteractive,\r\n store,\r\n itemValue,\r\n value,\r\n steps,\r\n propsRef,\r\n ],\r\n );\r\n\r\n const onKeyDown = React.useCallback(\r\n async (event: React.KeyboardEvent) => {\r\n propsRef.current.onKeyDown?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n if (event.key === 'Enter' && context.nonInteractive) {\r\n event.preventDefault();\r\n return;\r\n }\r\n\r\n if (\r\n (event.key === 'Enter' || event.key === ' ') &&\r\n activationMode === 'manual' &&\r\n !context.nonInteractive\r\n ) {\r\n event.preventDefault();\r\n if (!isDisabled && triggerRef.current) {\r\n triggerRef.current.click();\r\n }\r\n return;\r\n }\r\n\r\n if (event.key === 'Tab' && event.shiftKey) {\r\n focusContext.onItemShiftTab();\r\n return;\r\n }\r\n\r\n if (event.target !== event.currentTarget) return;\r\n\r\n const focusIntent = getFocusIntent(event, context.dir, orientation);\r\n\r\n if (focusIntent !== undefined) {\r\n if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;\r\n event.preventDefault();\r\n\r\n const items = focusContext.getItems().filter((item) => !item.disabled);\r\n let candidateRefs = items.map((item) => item.ref);\r\n\r\n if (focusIntent === 'last') {\r\n candidateRefs.reverse();\r\n } else if (focusIntent === 'prev' || focusIntent === 'next') {\r\n if (focusIntent === 'prev') candidateRefs.reverse();\r\n const currentIndex = candidateRefs.findIndex(\r\n (ref) => ref.current === event.currentTarget,\r\n );\r\n candidateRefs = loop\r\n ? wrapArray(candidateRefs, currentIndex + 1)\r\n : candidateRefs.slice(currentIndex + 1);\r\n }\r\n\r\n if (store.hasValidation() && candidateRefs.length > 0) {\r\n const nextRef = candidateRefs[0];\r\n const nextElement = nextRef?.current;\r\n const nextItem = items.find((item) => item.ref.current === nextElement);\r\n\r\n if (nextItem && nextItem.value !== itemValue) {\r\n const currentStepIndex = Array.from(steps.keys()).indexOf(value || '');\r\n const targetStepIndex = Array.from(steps.keys()).indexOf(nextItem.value);\r\n const direction: NavigationDirection =\r\n targetStepIndex > currentStepIndex ? 'next' : 'prev';\r\n\r\n if (direction === 'next') {\r\n const isValid = await store.setStateWithValidation(nextItem.value, direction);\r\n if (!isValid) return;\r\n } else {\r\n store.setState('value', nextItem.value);\r\n }\r\n\r\n queueMicrotask(() => nextElement?.focus());\r\n return;\r\n }\r\n }\r\n\r\n queueMicrotask(() => focusFirst(candidateRefs));\r\n }\r\n },\r\n [\r\n focusContext,\r\n context.nonInteractive,\r\n context.dir,\r\n activationMode,\r\n orientation,\r\n loop,\r\n isDisabled,\r\n store,\r\n propsRef,\r\n itemValue,\r\n value,\r\n steps,\r\n ],\r\n );\r\n\r\n const onMouseDown = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n propsRef.current.onMouseDown?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n isMouseClickRef.current = true;\r\n\r\n if (isDisabled) {\r\n event.preventDefault();\r\n } else {\r\n focusContext.onItemFocus(triggerId);\r\n }\r\n },\r\n [focusContext, triggerId, isDisabled, propsRef],\r\n );\r\n\r\n const TriggerPrimitive = asChild ? SlotPrimitive.Slot : 'button';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\ninterface StepperIndicatorProps extends Omit {\r\n children?: React.ReactNode | ((dataState: DataState) => React.ReactNode);\r\n}\r\n\r\nfunction StepperIndicator(props: StepperIndicatorProps) {\r\n const { className, children, asChild, ref, ...indicatorProps } = props;\r\n\r\n const context = useStepperContext(INDICATOR_NAME);\r\n const itemContext = useStepperItemContext(INDICATOR_NAME);\r\n\r\n const value = useStore((state) => state.value);\r\n const itemValue = itemContext.value;\r\n const stepState = useStore((state) => state.steps.get(itemValue));\r\n const steps = useStore((state) => state.steps);\r\n\r\n const stepPosition = Array.from(steps.keys()).indexOf(itemValue) + 1;\r\n\r\n const dataState = getDataState(value, itemValue, stepState, steps);\r\n\r\n const IndicatorPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n {typeof children === 'function' ? (\r\n children(dataState)\r\n ) : children ? (\r\n children\r\n ) : dataState === 'completed' ? (\r\n \r\n ) : (\r\n stepPosition\r\n )}\r\n \r\n );\r\n}\r\n\r\ninterface StepperSeparatorProps extends DivProps {\r\n forceMount?: boolean;\r\n}\r\n\r\nfunction StepperSeparator(props: StepperSeparatorProps) {\r\n const { className, asChild, forceMount = false, ref, ...separatorProps } = props;\r\n\r\n const context = useStepperContext(SEPARATOR_NAME);\r\n const itemContext = useStepperItemContext(SEPARATOR_NAME);\r\n const value = useStore((state) => state.value);\r\n const steps = useStore((state) => state.steps);\r\n\r\n const orientation = context.orientation;\r\n\r\n const stepIndex = Array.from(steps.keys()).indexOf(itemContext.value);\r\n\r\n const isLastStep = stepIndex === steps.size - 1;\r\n\r\n if (isLastStep && !forceMount) return null;\r\n\r\n const dataState = getDataState(\r\n value,\r\n itemContext.value,\r\n itemContext.stepState,\r\n steps,\r\n 'separator',\r\n );\r\n\r\n const SeparatorPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\ninterface StepperTitleProps extends React.ComponentProps<'span'> {\r\n asChild?: boolean;\r\n}\r\n\r\nfunction StepperTitle(props: StepperTitleProps) {\r\n const { className, asChild, ref, ...titleProps } = props;\r\n\r\n const context = useStepperContext(TITLE_NAME);\r\n const itemContext = useStepperItemContext(TITLE_NAME);\r\n\r\n const titleId = getId(context.rootId, 'title', itemContext.value);\r\n\r\n const TitlePrimitive = asChild ? SlotPrimitive.Slot : 'span';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\ninterface StepperDescriptionProps extends React.ComponentProps<'span'> {\r\n asChild?: boolean;\r\n}\r\n\r\nfunction StepperDescription(props: StepperDescriptionProps) {\r\n const { className, asChild, ref, ...descriptionProps } = props;\r\n\r\n const context = useStepperContext(DESCRIPTION_NAME);\r\n const itemContext = useStepperItemContext(DESCRIPTION_NAME);\r\n\r\n const descriptionId = getId(context.rootId, 'description', itemContext.value);\r\n\r\n const DescriptionPrimitive = asChild ? SlotPrimitive.Slot : 'span';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\ninterface StepperContentProps extends DivProps {\r\n value: string;\r\n forceMount?: boolean;\r\n}\r\n\r\nfunction StepperContent(props: StepperContentProps) {\r\n const { value: valueProp, asChild, forceMount = false, ref, className, ...contentProps } = props;\r\n\r\n const context = useStepperContext(CONTENT_NAME);\r\n const value = useStore((state) => state.value);\r\n\r\n const contentId = getId(context.rootId, 'content', valueProp);\r\n const triggerId = getId(context.rootId, 'trigger', valueProp);\r\n\r\n if (valueProp !== value && !forceMount) return null;\r\n\r\n const ContentPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\nfunction StepperPrev(props: ButtonProps) {\r\n const { asChild, onClick: onClickProp, disabled, ...prevProps } = props;\r\n\r\n const store = useStoreContext(PREV_NAME);\r\n const value = useStore((state) => state.value);\r\n const steps = useStore((state) => state.steps);\r\n\r\n const propsRef = useAsRef({\r\n onClick: onClickProp,\r\n });\r\n\r\n const stepKeys = Array.from(steps.keys());\r\n const currentIndex = value ? stepKeys.indexOf(value) : -1;\r\n const isDisabled = disabled || currentIndex <= 0;\r\n\r\n const onClick = React.useCallback(\r\n async (event: React.MouseEvent) => {\r\n propsRef.current.onClick?.(event);\r\n if (event.defaultPrevented || isDisabled) return;\r\n\r\n const prevIndex = Math.max(currentIndex - 1, 0);\r\n const prevStepValue = stepKeys[prevIndex];\r\n\r\n if (prevStepValue) {\r\n store.setState('value', prevStepValue);\r\n }\r\n },\r\n [propsRef, isDisabled, currentIndex, stepKeys, store],\r\n );\r\n\r\n const PrevPrimitive = asChild ? SlotPrimitive.Slot : 'button';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\nfunction StepperNext(props: ButtonProps) {\r\n const { asChild, onClick: onClickProp, disabled, ...nextProps } = props;\r\n\r\n const store = useStoreContext(NEXT_NAME);\r\n const value = useStore((state) => state.value);\r\n const steps = useStore((state) => state.steps);\r\n\r\n const propsRef = useAsRef({\r\n onClick: onClickProp,\r\n });\r\n\r\n const stepKeys = Array.from(steps.keys());\r\n const currentIndex = value ? stepKeys.indexOf(value) : -1;\r\n const isDisabled = disabled || currentIndex >= stepKeys.length - 1;\r\n\r\n const onClick = React.useCallback(\r\n async (event: React.MouseEvent) => {\r\n propsRef.current.onClick?.(event);\r\n if (event.defaultPrevented || isDisabled) return;\r\n\r\n const nextIndex = Math.min(currentIndex + 1, stepKeys.length - 1);\r\n const nextStepValue = stepKeys[nextIndex];\r\n\r\n if (nextStepValue) {\r\n await store.setStateWithValidation(nextStepValue, 'next');\r\n }\r\n },\r\n [propsRef, isDisabled, currentIndex, stepKeys, store],\r\n );\r\n\r\n const NextPrimitive = asChild ? SlotPrimitive.Slot : 'button';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\nexport {\r\n Stepper,\r\n StepperContent,\r\n StepperDescription,\r\n StepperIndicator,\r\n StepperItem,\r\n StepperList,\r\n StepperNext,\r\n StepperPrev,\r\n type StepperProps,\r\n StepperSeparator,\r\n StepperTitle,\r\n StepperTrigger,\r\n useStore as useStepper,\r\n};\r\n", + "path": "registry/components/stepper.tsx", + "target": "@components/stepper.tsx", + "type": "registry:component" + } + ], + "name": "stepper", + "registryDependencies": [ + "@kombase/component-refs", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "type": "registry:component" +} diff --git a/public/r/switch.json b/public/r/switch.json new file mode 100644 index 0000000..6c7f6ff --- /dev/null +++ b/public/r/switch.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-switch"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as SwitchPrimitive from '@radix-ui/react-switch';\r\nimport * as React from 'react';\r\n\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst Switch = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef & {\r\n size?: 'sm' | 'default';\r\n }\r\n>(({ className, size = 'default', ...props }, ref) => {\r\n return (\r\n \r\n \r\n \r\n );\r\n});\r\nSwitch.displayName = SwitchPrimitive.Root.displayName;\r\n\r\nexport { Switch };\r\n", + "path": "registry/ui/switch.tsx", + "target": "@ui/switch.tsx", + "type": "registry:ui" + } + ], + "name": "switch", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/table.json b/public/r/table.json new file mode 100644 index 0000000..b469dba --- /dev/null +++ b/public/r/table.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "'use client';\r\n\r\nimport * as React from 'react';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype TableProps = React.HTMLAttributes & {\r\n wrapperClassname?: string;\r\n};\r\n\r\nconst Table = React.forwardRef(\r\n ({ wrapperClassname, className, ...props }, ref) => (\r\n
\r\n \r\n \r\n ),\r\n);\r\nTable.displayName = 'Table';\r\n\r\nconst TableHeader = React.forwardRef<\r\n HTMLTableSectionElement,\r\n React.HTMLAttributes\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nTableHeader.displayName = 'TableHeader';\r\n\r\nconst TableBody = React.forwardRef<\r\n HTMLTableSectionElement,\r\n React.HTMLAttributes\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nTableBody.displayName = 'TableBody';\r\n\r\nconst TableFooter = React.forwardRef<\r\n HTMLTableSectionElement,\r\n React.HTMLAttributes\r\n>(({ className, ...props }, ref) => (\r\n tr]:last:border-b-0', className)}\r\n ref={ref}\r\n {...props}\r\n />\r\n));\r\nTableFooter.displayName = 'TableFooter';\r\n\r\nconst TableRow = React.forwardRef>(\r\n ({ className, ...props }, ref) => (\r\n \r\n ),\r\n);\r\nTableRow.displayName = 'TableRow';\r\n\r\nconst TableHead = React.forwardRef<\r\n HTMLTableCellElement,\r\n React.ThHTMLAttributes\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nTableHead.displayName = 'TableHead';\r\n\r\nconst TableCell = React.forwardRef<\r\n HTMLTableCellElement,\r\n React.TdHTMLAttributes\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nTableCell.displayName = 'TableCell';\r\n\r\nconst TableCaption = React.forwardRef<\r\n HTMLTableCaptionElement,\r\n React.HTMLAttributes\r\n>(({ className, ...props }, ref) => (\r\n
\r\n));\r\nTableCaption.displayName = 'TableCaption';\r\n\r\nexport { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow };\r\n", + "path": "registry/ui/table.tsx", + "target": "@ui/table.tsx", + "type": "registry:ui" + } + ], + "name": "table", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/tabs.json b/public/r/tabs.json new file mode 100644 index 0000000..4cab218 --- /dev/null +++ b/public/r/tabs.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-tabs"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as TabsPrimitive from '@radix-ui/react-tabs';\r\nimport { cva, type VariantProps } from 'class-variance-authority';\r\nimport * as React from 'react';\r\n\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst Tabs = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, orientation = 'horizontal', ...props }, ref) => (\r\n \r\n));\r\nTabs.displayName = TabsPrimitive.Root.displayName;\r\n\r\nconst tabsListVariants = cva(\r\n 'group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground data-[variant=line]:rounded-none group-data-[orientation=horizontal]/tabs:h-9 group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col',\r\n {\r\n defaultVariants: {\r\n variant: 'default',\r\n },\r\n variants: {\r\n variant: {\r\n default: 'bg-muted',\r\n line: 'gap-1 bg-transparent',\r\n },\r\n },\r\n },\r\n);\r\n\r\nconst TabsList = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef & VariantProps\r\n>(({ className, variant = 'default', ...props }, ref) => (\r\n \r\n));\r\nTabsList.displayName = TabsPrimitive.List.displayName;\r\n\r\nconst TabsTrigger = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nTabsTrigger.displayName = TabsPrimitive.Trigger.displayName;\r\n\r\nconst TabsContent = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, ...props }, ref) => (\r\n \r\n));\r\nTabsContent.displayName = TabsPrimitive.Content.displayName;\r\n\r\nexport { Tabs, TabsContent, TabsList, TabsTrigger, tabsListVariants };\r\n", + "path": "registry/ui/tabs.tsx", + "target": "@ui/tabs.tsx", + "type": "registry:ui" + } + ], + "name": "tabs", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/textarea.json b/public/r/textarea.json new file mode 100644 index 0000000..6161799 --- /dev/null +++ b/public/r/textarea.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "'use client';\r\n\r\nimport * as React from 'react';\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst Textarea = React.forwardRef>(\r\n ({ className, ...props }, ref) => {\r\n return (\r\n \r\n );\r\n },\r\n);\r\nTextarea.displayName = 'Textarea';\r\n\r\nexport { Textarea };\r\n", + "path": "registry/ui/textarea.tsx", + "target": "@ui/textarea.tsx", + "type": "registry:ui" + } + ], + "name": "textarea", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/timeline.json b/public/r/timeline.json new file mode 100644 index 0000000..326689f --- /dev/null +++ b/public/r/timeline.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-direction", "class-variance-authority"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as DirectionPrimitive from '@radix-ui/react-direction';\r\nimport * as SlotPrimitive from '@radix-ui/react-slot';\r\nimport { cva } from 'class-variance-authority';\r\nimport * as React from 'react';\r\nimport { useIsomorphicLayoutEffect } from '@/components/hooks/use-isomorphic-layout-effect';\r\nimport { useLazyRef } from '@/components/hooks/use-lazy-ref';\r\nimport { useComposedRefs } from '@/lib/component-refs';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype Direction = 'ltr' | 'rtl';\r\ntype Orientation = 'vertical' | 'horizontal';\r\ntype Variant = 'default' | 'alternate';\r\ntype Status = 'completed' | 'active' | 'pending';\r\n\r\ninterface DivProps extends React.ComponentProps<'div'> {\r\n asChild?: boolean;\r\n}\r\n\r\ntype ItemElement = React.ComponentRef;\r\n\r\nconst ROOT_NAME = 'Timeline';\r\nconst ITEM_NAME = 'TimelineItem';\r\nconst DOT_NAME = 'TimelineDot';\r\nconst CONNECTOR_NAME = 'TimelineConnector';\r\nconst CONTENT_NAME = 'TimelineContent';\r\n\r\nfunction getItemStatus(itemIndex: number, activeIndex?: number): Status {\r\n if (activeIndex === undefined) return 'pending';\r\n if (itemIndex < activeIndex) return 'completed';\r\n if (itemIndex === activeIndex) return 'active';\r\n return 'pending';\r\n}\r\n\r\nfunction getSortedEntries(entries: [string, React.RefObject][]) {\r\n return entries.sort((a, b) => {\r\n const elementA = a[1].current;\r\n const elementB = b[1].current;\r\n if (!elementA || !elementB) return 0;\r\n const position = elementA.compareDocumentPosition(elementB);\r\n if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;\r\n if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;\r\n return 0;\r\n });\r\n}\r\n\r\nfunction useStore(selector: (store: Store) => T): T {\r\n const store = React.useContext(StoreContext);\r\n if (!store) {\r\n throw new Error(`\\`useStore\\` must be used within \\`${ROOT_NAME}\\``);\r\n }\r\n\r\n const getSnapshot = React.useCallback(() => selector(store), [store, selector]);\r\n\r\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\r\n}\r\n\r\ninterface StoreState {\r\n items: Map>;\r\n}\r\n\r\ninterface Store {\r\n subscribe: (callback: () => void) => () => void;\r\n getState: () => StoreState;\r\n notify: () => void;\r\n onItemRegister: (id: string, ref: React.RefObject) => void;\r\n onItemUnregister: (id: string) => void;\r\n getNextItemStatus: (id: string, activeIndex?: number) => Status | undefined;\r\n getItemIndex: (id: string) => number;\r\n}\r\n\r\nconst StoreContext = React.createContext(null);\r\n\r\nfunction useStoreContext(consumerName: string) {\r\n const context = React.useContext(StoreContext);\r\n if (!context) {\r\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\r\n }\r\n return context;\r\n}\r\n\r\ninterface TimelineContextValue {\r\n dir: Direction;\r\n orientation: Orientation;\r\n variant: Variant;\r\n activeIndex?: number;\r\n}\r\n\r\nconst TimelineContext = React.createContext(null);\r\n\r\nfunction useTimelineContext(consumerName: string) {\r\n const context = React.useContext(TimelineContext);\r\n if (!context) {\r\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\r\n }\r\n return context;\r\n}\r\n\r\nconst timelineVariants = cva(\r\n 'relative flex [--timeline-connector-thickness:0.125rem] [--timeline-dot-size:0.875rem]',\r\n {\r\n compoundVariants: [\r\n {\r\n class: 'gap-6',\r\n orientation: 'vertical',\r\n variant: 'default',\r\n },\r\n {\r\n class: 'gap-8',\r\n orientation: 'horizontal',\r\n variant: 'default',\r\n },\r\n {\r\n class: 'relative w-full gap-3',\r\n orientation: 'vertical',\r\n variant: 'alternate',\r\n },\r\n {\r\n class: 'items-center gap-4',\r\n orientation: 'horizontal',\r\n variant: 'alternate',\r\n },\r\n ],\r\n defaultVariants: {\r\n orientation: 'vertical',\r\n variant: 'default',\r\n },\r\n variants: {\r\n orientation: {\r\n horizontal: 'flex-row items-start',\r\n vertical: 'flex-col',\r\n },\r\n variant: {\r\n alternate: '',\r\n default: '',\r\n },\r\n },\r\n },\r\n);\r\n\r\ninterface TimelineProps extends DivProps {\r\n dir?: Direction;\r\n orientation?: Orientation;\r\n variant?: Variant;\r\n activeIndex?: number;\r\n}\r\n\r\nfunction Timeline(props: TimelineProps) {\r\n const {\r\n orientation = 'vertical',\r\n variant = 'default',\r\n dir: dirProp,\r\n activeIndex,\r\n asChild,\r\n className,\r\n ...rootProps\r\n } = props;\r\n\r\n const dir = DirectionPrimitive.useDirection(dirProp);\r\n\r\n const listenersRef = useLazyRef(() => new Set<() => void>());\r\n const stateRef = useLazyRef(() => ({\r\n items: new Map(),\r\n }));\r\n\r\n const store = React.useMemo(() => {\r\n return {\r\n getItemIndex: (id: string) => {\r\n const entries = Array.from(stateRef.current.items.entries());\r\n const sortedEntries = getSortedEntries(entries);\r\n return sortedEntries.findIndex(([key]) => key === id);\r\n },\r\n getNextItemStatus: (id: string, activeIndex?: number) => {\r\n const entries = Array.from(stateRef.current.items.entries());\r\n const sortedEntries = getSortedEntries(entries);\r\n\r\n const currentIndex = sortedEntries.findIndex(([key]) => key === id);\r\n if (currentIndex === -1 || currentIndex === sortedEntries.length - 1) {\r\n return undefined;\r\n }\r\n\r\n const nextItemIndex = currentIndex + 1;\r\n return getItemStatus(nextItemIndex, activeIndex);\r\n },\r\n getState: () => stateRef.current,\r\n notify: () => {\r\n for (const cb of listenersRef.current) {\r\n cb();\r\n }\r\n },\r\n onItemRegister: (id: string, ref: React.RefObject) => {\r\n stateRef.current.items.set(id, ref);\r\n store.notify();\r\n },\r\n onItemUnregister: (id: string) => {\r\n stateRef.current.items.delete(id);\r\n store.notify();\r\n },\r\n subscribe: (cb) => {\r\n listenersRef.current.add(cb);\r\n return () => listenersRef.current.delete(cb);\r\n },\r\n };\r\n }, [listenersRef, stateRef]);\r\n\r\n const contextValue = React.useMemo(\r\n () => ({\r\n activeIndex,\r\n dir,\r\n orientation,\r\n variant,\r\n }),\r\n [dir, orientation, variant, activeIndex],\r\n );\r\n\r\n const RootPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n );\r\n}\r\n\r\ninterface TimelineItemContextValue {\r\n id: string;\r\n status: Status;\r\n isAlternateRight: boolean;\r\n}\r\n\r\nconst TimelineItemContext = React.createContext(null);\r\n\r\nfunction useTimelineItemContext(consumerName: string) {\r\n const context = React.useContext(TimelineItemContext);\r\n if (!context) {\r\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ITEM_NAME}\\``);\r\n }\r\n return context;\r\n}\r\n\r\nconst timelineItemVariants = cva('relative flex', {\r\n compoundVariants: [\r\n {\r\n class: 'gap-3 pb-8 last:pb-0',\r\n orientation: 'vertical',\r\n variant: 'default',\r\n },\r\n {\r\n class: 'flex-col gap-3',\r\n orientation: 'horizontal',\r\n variant: 'default',\r\n },\r\n {\r\n class: 'w-1/2 gap-3 pr-6 pb-12 last:pb-0',\r\n isAlternateRight: false,\r\n orientation: 'vertical',\r\n variant: 'alternate',\r\n },\r\n {\r\n class: 'ml-auto w-1/2 flex-row-reverse gap-3 pb-12 pl-6 last:pb-0',\r\n isAlternateRight: true,\r\n orientation: 'vertical',\r\n variant: 'alternate',\r\n },\r\n {\r\n class: 'grid min-w-0 grid-rows-[1fr_auto_1fr] gap-3',\r\n orientation: 'horizontal',\r\n variant: 'alternate',\r\n },\r\n ],\r\n defaultVariants: {\r\n isAlternateRight: false,\r\n orientation: 'vertical',\r\n variant: 'default',\r\n },\r\n variants: {\r\n isAlternateRight: {\r\n false: '',\r\n true: '',\r\n },\r\n orientation: {\r\n horizontal: '',\r\n vertical: '',\r\n },\r\n variant: {\r\n alternate: '',\r\n default: '',\r\n },\r\n },\r\n});\r\n\r\nfunction TimelineItem(props: DivProps) {\r\n const { asChild, className, id, ref, ...itemProps } = props;\r\n\r\n const { dir, orientation, variant, activeIndex } = useTimelineContext(ITEM_NAME);\r\n const store = useStoreContext(ITEM_NAME);\r\n\r\n const instanceId = React.useId();\r\n const itemId = id ?? instanceId;\r\n const itemRef = React.useRef(null);\r\n const composedRef = useComposedRefs(ref, itemRef);\r\n\r\n const itemIndex = useStore((state) => state.getItemIndex(itemId));\r\n\r\n const status = React.useMemo(() => {\r\n return getItemStatus(itemIndex, activeIndex);\r\n }, [activeIndex, itemIndex]);\r\n\r\n useIsomorphicLayoutEffect(() => {\r\n store.onItemRegister(itemId, itemRef);\r\n return () => {\r\n store.onItemUnregister(itemId);\r\n };\r\n }, [id, store]);\r\n\r\n const isAlternateRight = variant === 'alternate' && itemIndex % 2 === 1;\r\n\r\n const itemContextValue = React.useMemo(\r\n () => ({ id: itemId, isAlternateRight, status }),\r\n [itemId, status, isAlternateRight],\r\n );\r\n\r\n const ItemPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n \r\n \r\n );\r\n}\r\n\r\nconst timelineContentVariants = cva('flex-1', {\r\n compoundVariants: [\r\n {\r\n class: 'text-right',\r\n isAlternateRight: false,\r\n orientation: 'vertical',\r\n variant: 'alternate',\r\n },\r\n {\r\n class: 'row-start-3 pt-2',\r\n isAlternateRight: false,\r\n orientation: 'horizontal',\r\n variant: 'alternate',\r\n },\r\n {\r\n class: 'row-start-1 pb-2',\r\n isAlternateRight: true,\r\n orientation: 'horizontal',\r\n variant: 'alternate',\r\n },\r\n ],\r\n defaultVariants: {\r\n isAlternateRight: false,\r\n orientation: 'vertical',\r\n variant: 'default',\r\n },\r\n variants: {\r\n isAlternateRight: {\r\n false: '',\r\n true: '',\r\n },\r\n orientation: {\r\n horizontal: '',\r\n vertical: '',\r\n },\r\n variant: {\r\n alternate: '',\r\n default: '',\r\n },\r\n },\r\n});\r\n\r\nfunction TimelineContent(props: DivProps) {\r\n const { asChild, className, ...contentProps } = props;\r\n\r\n const { variant, orientation } = useTimelineContext(CONTENT_NAME);\r\n const { status, isAlternateRight } = useTimelineItemContext(CONTENT_NAME);\r\n\r\n const ContentPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\nconst timelineDotVariants = cva(\r\n 'relative z-10 flex size-[var(--timeline-dot-size)] shrink-0 items-center justify-center rounded-full border-2 bg-background',\r\n {\r\n compoundVariants: [\r\n {\r\n class:\r\n 'absolute -right-[calc(var(--timeline-dot-size)/2-var(--timeline-connector-thickness)/2)] bg-background',\r\n isAlternateRight: false,\r\n orientation: 'vertical',\r\n variant: 'alternate',\r\n },\r\n {\r\n class:\r\n 'absolute -left-[calc(var(--timeline-dot-size)/2-var(--timeline-connector-thickness)/2)] bg-background',\r\n isAlternateRight: true,\r\n orientation: 'vertical',\r\n variant: 'alternate',\r\n },\r\n {\r\n class: 'row-start-2 bg-background',\r\n orientation: 'horizontal',\r\n variant: 'alternate',\r\n },\r\n {\r\n class: 'bg-background',\r\n status: 'completed',\r\n variant: 'alternate',\r\n },\r\n {\r\n class: 'bg-background',\r\n status: 'active',\r\n variant: 'alternate',\r\n },\r\n ],\r\n defaultVariants: {\r\n isAlternateRight: false,\r\n orientation: 'vertical',\r\n status: 'pending',\r\n variant: 'default',\r\n },\r\n variants: {\r\n isAlternateRight: {\r\n false: '',\r\n true: '',\r\n },\r\n orientation: {\r\n horizontal: '',\r\n vertical: '',\r\n },\r\n status: {\r\n active: 'border-primary',\r\n completed: 'border-primary',\r\n pending: 'border-border',\r\n },\r\n variant: {\r\n alternate: '',\r\n default: '',\r\n },\r\n },\r\n },\r\n);\r\n\r\nfunction TimelineDot(props: DivProps) {\r\n const { asChild, className, ...dotProps } = props;\r\n\r\n const { orientation, variant } = useTimelineContext(DOT_NAME);\r\n const { status, isAlternateRight } = useTimelineItemContext(DOT_NAME);\r\n\r\n const DotPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\nconst timelineConnectorVariants = cva('absolute z-0', {\r\n compoundVariants: [\r\n {\r\n class:\r\n 'start-[calc(var(--timeline-dot-size)/2-var(--timeline-connector-thickness)/2)] top-3 h-[calc(100%+0.5rem)] w-[var(--timeline-connector-thickness)]',\r\n orientation: 'vertical',\r\n variant: 'default',\r\n },\r\n {\r\n class:\r\n 'start-3 top-[calc(var(--timeline-dot-size)/2-var(--timeline-connector-thickness)/2)] h-[var(--timeline-connector-thickness)] w-[calc(100%+0.5rem)]',\r\n orientation: 'horizontal',\r\n variant: 'default',\r\n },\r\n {\r\n class:\r\n 'top-2 -right-[calc(var(--timeline-connector-thickness)/2)] h-full w-[var(--timeline-connector-thickness)]',\r\n isAlternateRight: false,\r\n orientation: 'vertical',\r\n variant: 'alternate',\r\n },\r\n {\r\n class:\r\n 'top-2 -left-[calc(var(--timeline-connector-thickness)/2)] h-full w-[var(--timeline-connector-thickness)]',\r\n isAlternateRight: true,\r\n orientation: 'vertical',\r\n variant: 'alternate',\r\n },\r\n {\r\n class:\r\n 'top-[calc(var(--timeline-dot-size)/2-var(--timeline-connector-thickness)/2)] left-3 row-start-2 h-[var(--timeline-connector-thickness)] w-[calc(100%+0.5rem)]',\r\n orientation: 'horizontal',\r\n variant: 'alternate',\r\n },\r\n ],\r\n defaultVariants: {\r\n isAlternateRight: false,\r\n isCompleted: false,\r\n orientation: 'vertical',\r\n variant: 'default',\r\n },\r\n variants: {\r\n isAlternateRight: {\r\n false: '',\r\n true: '',\r\n },\r\n isCompleted: {\r\n false: 'bg-border',\r\n true: 'bg-primary',\r\n },\r\n orientation: {\r\n horizontal: '',\r\n vertical: '',\r\n },\r\n variant: {\r\n alternate: '',\r\n default: '',\r\n },\r\n },\r\n});\r\n\r\ninterface TimelineConnectorProps extends DivProps {\r\n forceMount?: boolean;\r\n}\r\n\r\nfunction TimelineConnector(props: TimelineConnectorProps) {\r\n const { asChild, forceMount, className, ...connectorProps } = props;\r\n\r\n const { orientation, variant, activeIndex } = useTimelineContext(CONNECTOR_NAME);\r\n const { id, status, isAlternateRight } = useTimelineItemContext(CONNECTOR_NAME);\r\n\r\n const nextItemStatus = useStore((state) => state.getNextItemStatus(id, activeIndex));\r\n\r\n const isLastItem = nextItemStatus === undefined;\r\n\r\n if (!forceMount && isLastItem) return null;\r\n\r\n const isConnectorCompleted = nextItemStatus === 'completed' || nextItemStatus === 'active';\r\n\r\n const ConnectorPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\nfunction TimelineHeader(props: DivProps) {\r\n const { asChild, className, ...headerProps } = props;\r\n\r\n const HeaderPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\nfunction TimelineTitle(props: DivProps) {\r\n const { asChild, className, ...titleProps } = props;\r\n\r\n const TitlePrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\nfunction TimelineDescription(props: DivProps) {\r\n const { asChild, className, ...descriptionProps } = props;\r\n\r\n const DescriptionPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\ninterface TimelineTimeProps extends React.ComponentProps<'time'> {\r\n asChild?: boolean;\r\n}\r\n\r\nfunction TimelineTime(props: TimelineTimeProps) {\r\n const { asChild, className, ...timeProps } = props;\r\n\r\n const TimePrimitive = asChild ? SlotPrimitive.Slot : 'time';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\nexport {\r\n Timeline,\r\n TimelineConnector,\r\n TimelineContent,\r\n TimelineDescription,\r\n TimelineDot,\r\n TimelineHeader,\r\n TimelineItem,\r\n type TimelineProps,\r\n TimelineTime,\r\n TimelineTitle,\r\n};\r\n", + "path": "registry/components/timeline.tsx", + "target": "@components/timeline.tsx", + "type": "registry:component" + } + ], + "name": "timeline", + "registryDependencies": [ + "@kombase/component-refs", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "type": "registry:component" +} diff --git a/public/r/tooltip.json b/public/r/tooltip.json new file mode 100644 index 0000000..33ef352 --- /dev/null +++ b/public/r/tooltip.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@radix-ui/react-tooltip"], + "files": [ + { + "content": "'use client';\r\n\r\nimport * as TooltipPrimitive from '@radix-ui/react-tooltip';\r\nimport * as React from 'react';\r\n\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst TooltipProvider = ({\r\n delayDuration = 0,\r\n ...props\r\n}: React.ComponentProps) => (\r\n \r\n);\r\nTooltipProvider.displayName = 'TooltipProvider';\r\n\r\nconst Tooltip = ({ ...props }: React.ComponentProps) => (\r\n \r\n);\r\nTooltip.displayName = 'Tooltip';\r\n\r\nconst TooltipTrigger = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>((props, ref) => );\r\nTooltipTrigger.displayName = TooltipPrimitive.Trigger.displayName;\r\n\r\nconst TooltipContent = React.forwardRef<\r\n React.ComponentRef,\r\n React.ComponentPropsWithoutRef\r\n>(({ className, sideOffset = 0, children, ...props }, ref) => (\r\n \r\n \r\n {children}\r\n \r\n \r\n \r\n));\r\nTooltipContent.displayName = TooltipPrimitive.Content.displayName;\r\n\r\nexport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };\r\n", + "path": "registry/ui/tooltip.tsx", + "target": "@ui/tooltip.tsx", + "type": "registry:ui" + } + ], + "name": "tooltip", + "registryDependencies": ["@kombase/utils"], + "type": "registry:ui" +} diff --git a/public/r/tour.json b/public/r/tour.json new file mode 100644 index 0000000..d24dc17 --- /dev/null +++ b/public/r/tour.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@floating-ui/react-dom", "@radix-ui/react-direction"], + "files": [ + { + "content": "import {\r\n autoUpdate,\r\n flip,\r\n hide,\r\n limitShift,\r\n type Middleware,\r\n offset,\r\n arrow as onArrow,\r\n type Placement,\r\n shift,\r\n useFloating,\r\n} from '@floating-ui/react-dom';\r\nimport * as DirectionPrimitive from '@radix-ui/react-direction';\r\nimport * as SlotPrimitive from '@radix-ui/react-slot';\r\nimport { ChevronLeft, ChevronRight, X } from 'lucide-react';\r\nimport * as React from 'react';\r\nimport * as ReactDOM from 'react-dom';\r\nimport { useAsRef } from '@/components/hooks/use-as-ref';\r\nimport { useIsomorphicLayoutEffect } from '@/components/hooks/use-isomorphic-layout-effect';\r\nimport { useLazyRef } from '@/components/hooks/use-lazy-ref';\r\nimport { Button } from '@/components/ui/button';\r\nimport { useComposedRefs } from '@/lib/component-refs';\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst ROOT_NAME = 'Tour';\r\nconst PORTAL_NAME = 'TourPortal';\r\nconst STEP_NAME = 'TourStep';\r\nconst ARROW_NAME = 'TourArrow';\r\nconst HEADER_NAME = 'TourHeader';\r\nconst TITLE_NAME = 'TourTitle';\r\nconst DESCRIPTION_NAME = 'TourDescription';\r\nconst CLOSE_NAME = 'TourClose';\r\nconst PREV_NAME = 'TourPrev';\r\nconst NEXT_NAME = 'TourNext';\r\nconst SKIP_NAME = 'TourSkip';\r\nconst FOOTER_NAME = 'TourFooter';\r\n\r\nconst POINTER_DOWN_OUTSIDE = 'tour.pointerDownOutside';\r\nconst INTERACT_OUTSIDE = 'tour.interactOutside';\r\nconst OPEN_AUTO_FOCUS = 'tour.openAutoFocus';\r\nconst CLOSE_AUTO_FOCUS = 'tour.closeAutoFocus';\r\nconst EVENT_OPTIONS = { bubbles: false, cancelable: true };\r\n\r\nconst SIDE_OPTIONS = ['top', 'right', 'bottom', 'left'] as const;\r\nconst ALIGN_OPTIONS = ['start', 'center', 'end'] as const;\r\n\r\nconst DEFAULT_ALIGN_OFFSET = 0;\r\nconst DEFAULT_SIDE_OFFSET = 16;\r\nconst DEFAULT_SPOTLIGHT_PADDING = 4;\r\n\r\ntype Side = (typeof SIDE_OPTIONS)[number];\r\ntype Align = (typeof ALIGN_OPTIONS)[number];\r\ntype Direction = 'ltr' | 'rtl';\r\n\r\ninterface ScrollOffset {\r\n top?: number;\r\n bottom?: number;\r\n left?: number;\r\n right?: number;\r\n}\r\n\r\ntype Boundary = Element | null;\r\n\r\ninterface DivProps extends React.ComponentProps<'div'> {\r\n asChild?: boolean;\r\n}\r\n\r\ntype StepElement = React.ComponentRef;\r\ntype CloseElement = React.ComponentRef;\r\ntype PrevElement = React.ComponentRef;\r\ntype NextElement = React.ComponentRef;\r\ntype SkipElement = React.ComponentRef;\r\ntype FooterElement = React.ComponentRef;\r\n\r\nconst OPPOSITE_SIDE: Record = {\r\n bottom: 'top',\r\n left: 'right',\r\n right: 'left',\r\n top: 'bottom',\r\n};\r\n\r\n/**\r\n * @see https://github.com/radix-ui/primitives/blob/main/packages/react/focus-guards/src/focus-guards.tsx\r\n */\r\nlet focusGuardCount = 0;\r\n\r\nfunction createFocusGuard() {\r\n const element = document.createElement('span');\r\n element.setAttribute('data-tour-focus-guard', '');\r\n element.tabIndex = 0;\r\n element.style.outline = 'none';\r\n element.style.opacity = '0';\r\n element.style.position = 'fixed';\r\n element.style.pointerEvents = 'none';\r\n return element;\r\n}\r\n\r\nfunction useFocusGuards() {\r\n React.useEffect(() => {\r\n const edgeGuards = document.querySelectorAll('[data-tour-focus-guard]');\r\n document.body.insertAdjacentElement('afterbegin', edgeGuards[0] ?? createFocusGuard());\r\n document.body.insertAdjacentElement('beforeend', edgeGuards[1] ?? createFocusGuard());\r\n focusGuardCount++;\r\n\r\n return () => {\r\n if (focusGuardCount === 1) {\r\n const guards = document.querySelectorAll('[data-tour-focus-guard]');\r\n for (const node of guards) {\r\n node.remove();\r\n }\r\n }\r\n focusGuardCount--;\r\n };\r\n }, []);\r\n}\r\n\r\nfunction useFocusTrap(\r\n containerRef: React.RefObject,\r\n enabled: boolean,\r\n tourOpen: boolean,\r\n onOpenAutoFocus?: (event: OpenAutoFocusEvent) => void,\r\n onCloseAutoFocus?: (event: CloseAutoFocusEvent) => void,\r\n) {\r\n const lastFocusedElementRef = React.useRef(null);\r\n const onOpenAutoFocusRef = useAsRef(onOpenAutoFocus);\r\n const onCloseAutoFocusRef = useAsRef(onCloseAutoFocus);\r\n const tourOpenRef = useAsRef(tourOpen);\r\n\r\n React.useEffect(() => {\r\n if (!enabled) return;\r\n\r\n const container = containerRef.current;\r\n if (!container) return;\r\n\r\n const previouslyFocusedElement = document.activeElement as HTMLElement | null;\r\n\r\n function getTabbableCandidates() {\r\n if (!container) return [];\r\n\r\n const nodes: HTMLElement[] = [];\r\n const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {\r\n acceptNode: (node: Element) => {\r\n const element = node as HTMLElement;\r\n const isHiddenInput =\r\n element.tagName === 'INPUT' && (element as HTMLInputElement).type === 'hidden';\r\n if (element.hidden || isHiddenInput) return NodeFilter.FILTER_SKIP;\r\n return element.tabIndex >= 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;\r\n },\r\n });\r\n while (walker.nextNode()) {\r\n nodes.push(walker.currentNode as HTMLElement);\r\n }\r\n return nodes;\r\n }\r\n\r\n function getTabbableEdges() {\r\n const candidates = getTabbableCandidates();\r\n const first = candidates[0];\r\n const last = candidates[candidates.length - 1];\r\n return [first, last] as const;\r\n }\r\n\r\n function onFocusIn(event: FocusEvent) {\r\n if (!container) return;\r\n\r\n const target = event.target as HTMLElement | null;\r\n if (container.contains(target)) {\r\n lastFocusedElementRef.current = target;\r\n } else {\r\n const elementToFocus = lastFocusedElementRef.current ?? getTabbableCandidates()[0];\r\n elementToFocus?.focus({ preventScroll: true });\r\n }\r\n }\r\n\r\n function onKeyDown(event: KeyboardEvent) {\r\n if (event.key !== 'Tab' || event.altKey || event.ctrlKey || event.metaKey) return;\r\n\r\n const [first, last] = getTabbableEdges();\r\n const hasTabbableElements = first && last;\r\n\r\n if (!hasTabbableElements) {\r\n if (document.activeElement === container) event.preventDefault();\r\n return;\r\n }\r\n\r\n if (!event.shiftKey && document.activeElement === last) {\r\n event.preventDefault();\r\n first?.focus({ preventScroll: true });\r\n } else if (event.shiftKey && document.activeElement === first) {\r\n event.preventDefault();\r\n last?.focus({ preventScroll: true });\r\n }\r\n }\r\n\r\n const openAutoFocusEvent = new CustomEvent(OPEN_AUTO_FOCUS, EVENT_OPTIONS);\r\n if (onOpenAutoFocusRef.current) {\r\n container.addEventListener(OPEN_AUTO_FOCUS, onOpenAutoFocusRef.current as EventListener, {\r\n once: true,\r\n });\r\n }\r\n container.dispatchEvent(openAutoFocusEvent);\r\n\r\n if (!openAutoFocusEvent.defaultPrevented) {\r\n const tabbableCandidates = getTabbableCandidates();\r\n if (tabbableCandidates.length > 0) {\r\n tabbableCandidates[0]?.focus({ preventScroll: true });\r\n } else {\r\n container.focus({ preventScroll: true });\r\n }\r\n }\r\n\r\n document.addEventListener('focusin', onFocusIn);\r\n container.addEventListener('keydown', onKeyDown);\r\n\r\n return () => {\r\n document.removeEventListener('focusin', onFocusIn);\r\n container.removeEventListener('keydown', onKeyDown);\r\n\r\n if (!tourOpenRef.current) {\r\n setTimeout(() => {\r\n const closeAutoFocusEvent = new CustomEvent(CLOSE_AUTO_FOCUS, EVENT_OPTIONS);\r\n if (onCloseAutoFocusRef.current) {\r\n container.addEventListener(\r\n CLOSE_AUTO_FOCUS,\r\n onCloseAutoFocusRef.current as EventListener,\r\n { once: true },\r\n );\r\n }\r\n container.dispatchEvent(closeAutoFocusEvent);\r\n\r\n if (!closeAutoFocusEvent.defaultPrevented) {\r\n if (previouslyFocusedElement && document.body.contains(previouslyFocusedElement)) {\r\n previouslyFocusedElement.focus({ preventScroll: true });\r\n }\r\n }\r\n\r\n if (onCloseAutoFocusRef.current) {\r\n container.removeEventListener(\r\n CLOSE_AUTO_FOCUS,\r\n onCloseAutoFocusRef.current as EventListener,\r\n );\r\n }\r\n }, 0);\r\n }\r\n };\r\n }, [containerRef, enabled, onOpenAutoFocusRef, onCloseAutoFocusRef, tourOpenRef]);\r\n}\r\n\r\nfunction getDataState(open: boolean) {\r\n return open ? 'open' : 'closed';\r\n}\r\n\r\ninterface StepData {\r\n target: string | React.RefObject | HTMLElement;\r\n align?: Align;\r\n alignOffset?: number;\r\n side?: Side;\r\n sideOffset?: number;\r\n collisionBoundary?: Boundary | Boundary[];\r\n collisionPadding?: number | Partial>;\r\n arrowPadding?: number;\r\n sticky?: 'partial' | 'always';\r\n hideWhenDetached?: boolean;\r\n avoidCollisions?: boolean;\r\n onStepEnter?: () => void;\r\n onStepLeave?: () => void;\r\n required?: boolean;\r\n}\r\n\r\ninterface StoreState {\r\n open: boolean;\r\n value: number;\r\n steps: StepData[];\r\n maskPath: string;\r\n spotlightRect: { x: number; y: number; width: number; height: number } | null;\r\n}\r\n\r\ninterface Store {\r\n subscribe: (callback: () => void) => () => void;\r\n getState: () => StoreState;\r\n setState: (key: K, value: StoreState[K], opts?: unknown) => void;\r\n notify: () => void;\r\n addStep: (stepData: StepData) => { id: string; index: number };\r\n removeStep: (id: string) => void;\r\n}\r\n\r\nfunction useStore(selector: (state: StoreState) => T, ogStore?: Store | null): T {\r\n const contextStore = React.useContext(StoreContext);\r\n\r\n const store = ogStore ?? contextStore;\r\n\r\n if (!store) {\r\n throw new Error(`\\`useStore\\` must be used within \\`${ROOT_NAME}\\``);\r\n }\r\n\r\n const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);\r\n\r\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\r\n}\r\n\r\nfunction getTargetElement(\r\n target: string | React.RefObject | HTMLElement,\r\n): HTMLElement | null {\r\n if (typeof target === 'string') {\r\n return document.querySelector(target);\r\n }\r\n if (target && 'current' in target) {\r\n return target.current;\r\n }\r\n if (target instanceof HTMLElement) {\r\n return target;\r\n }\r\n return null;\r\n}\r\n\r\nfunction getDefaultScrollBehavior(): ScrollBehavior {\r\n if (typeof window === 'undefined') return 'smooth';\r\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth';\r\n}\r\n\r\nfunction onScrollToElement(\r\n element: HTMLElement,\r\n scrollBehavior: ScrollBehavior = getDefaultScrollBehavior(),\r\n scrollOffset?: ScrollOffset,\r\n) {\r\n const offset: Required = {\r\n bottom: 100,\r\n left: 0,\r\n right: 0,\r\n top: 100,\r\n ...scrollOffset,\r\n };\r\n const rect = element.getBoundingClientRect();\r\n const viewportHeight = window.innerHeight;\r\n const viewportWidth = window.innerWidth;\r\n\r\n const isInViewport =\r\n rect.top >= offset.top &&\r\n rect.bottom <= viewportHeight - offset.bottom &&\r\n rect.left >= offset.left &&\r\n rect.right <= viewportWidth - offset.right;\r\n\r\n if (!isInViewport) {\r\n const elementTop = rect.top + window.scrollY;\r\n const scrollTop = elementTop - offset.top;\r\n\r\n window.scrollTo({\r\n behavior: scrollBehavior,\r\n top: Math.max(0, scrollTop),\r\n });\r\n }\r\n}\r\n\r\nfunction getSideAndAlignFromPlacement(placement: Placement): [Side, Align] {\r\n const [side, align = 'center'] = placement.split('-') as [Side, Align?];\r\n return [side, align];\r\n}\r\n\r\nfunction getPlacement(side: Side, align: Align): Placement {\r\n if (align === 'center') {\r\n return side as Placement;\r\n }\r\n return `${side}-${align}` as Placement;\r\n}\r\n\r\nfunction updateMask(\r\n store: Store,\r\n targetElement: HTMLElement,\r\n padding: number = DEFAULT_SPOTLIGHT_PADDING,\r\n) {\r\n const clientRect = targetElement.getBoundingClientRect();\r\n const viewportWidth = window.innerWidth;\r\n const viewportHeight = window.innerHeight;\r\n\r\n const x = Math.max(0, clientRect.left - padding);\r\n const y = Math.max(0, clientRect.top - padding);\r\n const width = Math.min(viewportWidth - x, clientRect.width + padding * 2);\r\n const height = Math.min(viewportHeight - y, clientRect.height + padding * 2);\r\n\r\n const path = `polygon(0% 0%, 0% 100%, ${x}px 100%, ${x}px ${y}px, ${x + width}px ${y}px, ${x + width}px ${y + height}px, ${x}px ${y + height}px, ${x}px 100%, 100% 100%, 100% 0%)`;\r\n store.setState('maskPath', path);\r\n store.setState('spotlightRect', { height, width, x, y });\r\n}\r\n\r\nconst StoreContext = React.createContext(null);\r\n\r\nfunction useStoreContext(consumerName: string) {\r\n const context = React.useContext(StoreContext);\r\n if (!context) {\r\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\r\n }\r\n return context;\r\n}\r\n\r\ninterface TourContextValue {\r\n dir: Direction;\r\n alignOffset: number;\r\n sideOffset: number;\r\n spotlightPadding: number;\r\n dismissible: boolean;\r\n modal: boolean;\r\n stepFooter?: React.ReactElement;\r\n onPointerDownOutside?: (event: PointerDownOutsideEvent) => void;\r\n onInteractOutside?: (event: InteractOutsideEvent) => void;\r\n onOpenAutoFocus?: (event: OpenAutoFocusEvent) => void;\r\n onCloseAutoFocus?: (event: CloseAutoFocusEvent) => void;\r\n}\r\n\r\nconst TourContext = React.createContext(null);\r\n\r\nfunction useTourContext(consumerName: string) {\r\n const context = React.useContext(TourContext);\r\n if (!context) {\r\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\r\n }\r\n return context;\r\n}\r\n\r\ninterface StepContextValue {\r\n arrowX?: number;\r\n arrowY?: number;\r\n placedAlign: Align;\r\n placedSide: Side;\r\n shouldHideArrow: boolean;\r\n onArrowChange: (arrow: HTMLSpanElement | null) => void;\r\n onFooterChange: (footer: FooterElement | null) => void;\r\n}\r\n\r\nconst StepContext = React.createContext(null);\r\n\r\nfunction useStepContext(consumerName: string) {\r\n const context = React.useContext(StepContext);\r\n if (!context) {\r\n throw new Error(`\\`${consumerName}\\` must be used within \\`${STEP_NAME}\\``);\r\n }\r\n return context;\r\n}\r\n\r\nconst DefaultFooterContext = React.createContext(false);\r\n\r\ninterface PortalContextValue {\r\n portal: HTMLElement | null;\r\n onPortalChange: (node: HTMLElement | null) => void;\r\n}\r\n\r\nconst PortalContext = React.createContext(null);\r\n\r\nfunction usePortalContext(consumerName: string) {\r\n const context = React.useContext(PortalContext);\r\n if (!context) {\r\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\r\n }\r\n return context;\r\n}\r\n\r\nfunction useScrollLock(enabled: boolean) {\r\n React.useEffect(() => {\r\n if (!enabled) return;\r\n\r\n const originalStyle = window.getComputedStyle(document.body).overflow;\r\n const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;\r\n\r\n document.body.style.overflow = 'hidden';\r\n if (scrollbarWidth > 0) {\r\n document.body.style.paddingRight = `${scrollbarWidth}px`;\r\n }\r\n\r\n return () => {\r\n document.body.style.overflow = originalStyle;\r\n document.body.style.paddingRight = '';\r\n };\r\n }, [enabled]);\r\n}\r\n\r\ntype PointerDownOutsideEvent = CustomEvent<{ originalEvent: PointerEvent }>;\r\ntype InteractOutsideEvent = CustomEvent<{\r\n originalEvent: PointerEvent | FocusEvent;\r\n}>;\r\ntype OpenAutoFocusEvent = CustomEvent>;\r\ntype CloseAutoFocusEvent = CustomEvent>;\r\n\r\ninterface TourProps extends DivProps {\r\n open?: boolean;\r\n defaultOpen?: boolean;\r\n onOpenChange?: (open: boolean) => void;\r\n value?: number;\r\n defaultValue?: number;\r\n onValueChange?: (step: number) => void;\r\n onComplete?: () => void;\r\n onSkip?: () => void;\r\n onEscapeKeyDown?: (event: KeyboardEvent) => void;\r\n onPointerDownOutside?: (event: PointerDownOutsideEvent) => void;\r\n onInteractOutside?: (event: InteractOutsideEvent) => void;\r\n onOpenAutoFocus?: (event: OpenAutoFocusEvent) => void;\r\n onCloseAutoFocus?: (event: CloseAutoFocusEvent) => void;\r\n dir?: Direction;\r\n alignOffset?: number;\r\n sideOffset?: number;\r\n spotlightPadding?: number;\r\n autoScroll?: boolean;\r\n scrollBehavior?: ScrollBehavior;\r\n scrollOffset?: ScrollOffset;\r\n dismissible?: boolean;\r\n modal?: boolean;\r\n stepFooter?: React.ReactElement;\r\n}\r\n\r\nfunction Tour(props: TourProps) {\r\n const {\r\n open: openProp,\r\n defaultOpen = false,\r\n onOpenChange,\r\n value: valueProp,\r\n defaultValue = 0,\r\n onValueChange,\r\n onComplete,\r\n onSkip,\r\n autoScroll = true,\r\n scrollBehavior = getDefaultScrollBehavior(),\r\n scrollOffset,\r\n onEscapeKeyDown,\r\n onPointerDownOutside,\r\n onInteractOutside,\r\n onOpenAutoFocus,\r\n onCloseAutoFocus,\r\n dir: dirProp,\r\n alignOffset = DEFAULT_ALIGN_OFFSET,\r\n sideOffset = DEFAULT_SIDE_OFFSET,\r\n spotlightPadding = DEFAULT_SPOTLIGHT_PADDING,\r\n dismissible = true,\r\n modal = true,\r\n stepFooter,\r\n asChild,\r\n ...rootProps\r\n } = props;\r\n\r\n const dir = DirectionPrimitive.useDirection(dirProp);\r\n\r\n const [portal, setPortal] = React.useState(null);\r\n const prevOpenRef = React.useRef(undefined);\r\n const previouslyFocusedElementRef = React.useRef(null);\r\n\r\n const stateRef = useLazyRef(() => ({\r\n maskPath: '',\r\n open: openProp ?? defaultOpen,\r\n spotlightRect: null,\r\n steps: [],\r\n value: valueProp ?? defaultValue,\r\n }));\r\n const listenersRef = useLazyRef void>>(() => new Set());\r\n const stepIdsMapRef = useLazyRef>(() => new Map());\r\n const stepIdCounterRef = useLazyRef(() => ({ current: 0 }));\r\n const propsRef = useAsRef({\r\n autoScroll,\r\n onCloseAutoFocus,\r\n onComplete,\r\n onEscapeKeyDown,\r\n onOpenChange,\r\n onSkip,\r\n onValueChange,\r\n scrollBehavior,\r\n scrollOffset,\r\n valueProp,\r\n });\r\n\r\n const store: Store = React.useMemo(\r\n () => ({\r\n addStep: (stepData) => {\r\n const id = `step-${stepIdCounterRef.current.current++}`;\r\n const index = stateRef.current.steps.length;\r\n stepIdsMapRef.current.set(id, index);\r\n stateRef.current.steps = [...stateRef.current.steps, stepData];\r\n store.notify();\r\n return { id, index };\r\n },\r\n getState: () => {\r\n return stateRef.current;\r\n },\r\n notify: () => {\r\n listenersRef.current.forEach((l) => {\r\n l();\r\n });\r\n },\r\n removeStep: (id) => {\r\n const index = stepIdsMapRef.current.get(id);\r\n if (index === undefined) return;\r\n\r\n stateRef.current.steps = stateRef.current.steps.filter((_, i) => i !== index);\r\n\r\n stepIdsMapRef.current.delete(id);\r\n\r\n for (const [stepId, stepIndex] of stepIdsMapRef.current.entries()) {\r\n if (stepIndex > index) {\r\n stepIdsMapRef.current.set(stepId, stepIndex - 1);\r\n }\r\n }\r\n\r\n store.notify();\r\n },\r\n setState: (key, value) => {\r\n if (Object.is(stateRef.current[key], value)) return;\r\n stateRef.current[key] = value;\r\n\r\n if (key === 'open' && typeof value === 'boolean') {\r\n propsRef.current.onOpenChange?.(value);\r\n\r\n if (value) {\r\n if (stateRef.current.steps.length > 0) {\r\n if (stateRef.current.value >= stateRef.current.steps.length) {\r\n store.setState('value', 0);\r\n }\r\n }\r\n } else {\r\n if (stateRef.current.value < (stateRef.current.steps.length || 0) - 1) {\r\n propsRef.current.onSkip?.();\r\n }\r\n }\r\n } else if (key === 'value' && typeof value === 'number') {\r\n const prevStep = stateRef.current.steps[stateRef.current.value];\r\n const nextStep = stateRef.current.steps[value];\r\n\r\n prevStep?.onStepLeave?.();\r\n nextStep?.onStepEnter?.();\r\n\r\n if (value >= stateRef.current.steps.length) {\r\n propsRef.current.onComplete?.();\r\n\r\n if (propsRef.current.valueProp !== undefined) {\r\n propsRef.current.onValueChange?.(value);\r\n }\r\n\r\n store.setState('open', false);\r\n return;\r\n }\r\n\r\n if (propsRef.current.valueProp !== undefined) {\r\n propsRef.current.onValueChange?.(value);\r\n return;\r\n }\r\n\r\n propsRef.current.onValueChange?.(value);\r\n\r\n if (nextStep && propsRef.current.autoScroll) {\r\n const targetElement = getTargetElement(nextStep.target);\r\n if (targetElement) {\r\n onScrollToElement(\r\n targetElement,\r\n propsRef.current.scrollBehavior,\r\n propsRef.current.scrollOffset,\r\n );\r\n }\r\n }\r\n }\r\n\r\n store.notify();\r\n },\r\n subscribe: (cb) => {\r\n listenersRef.current.add(cb);\r\n return () => listenersRef.current.delete(cb);\r\n },\r\n }),\r\n [stateRef, listenersRef, stepIdsMapRef, stepIdCounterRef, propsRef],\r\n );\r\n\r\n const open = useStore((state) => state.open, store);\r\n\r\n React.useEffect(() => {\r\n function onKeyDown(event: KeyboardEvent) {\r\n if (open && event.key === 'Escape') {\r\n if (propsRef.current.onEscapeKeyDown) {\r\n propsRef.current.onEscapeKeyDown(event);\r\n if (event.defaultPrevented) return;\r\n }\r\n store.setState('open', false);\r\n }\r\n }\r\n\r\n document.addEventListener('keydown', onKeyDown);\r\n return () => document.removeEventListener('keydown', onKeyDown);\r\n }, [store, open, propsRef]);\r\n\r\n useIsomorphicLayoutEffect(() => {\r\n const wasOpen = prevOpenRef.current;\r\n\r\n if (open && !wasOpen) {\r\n previouslyFocusedElementRef.current = document.activeElement as HTMLElement | null;\r\n } else if (!open && wasOpen) {\r\n setTimeout(() => {\r\n const container = portal ?? document.body;\r\n const closeAutoFocusEvent = new CustomEvent(CLOSE_AUTO_FOCUS, EVENT_OPTIONS);\r\n\r\n if (propsRef.current.onCloseAutoFocus) {\r\n container.addEventListener(\r\n CLOSE_AUTO_FOCUS,\r\n propsRef.current.onCloseAutoFocus as EventListener,\r\n { once: true },\r\n );\r\n }\r\n container.dispatchEvent(closeAutoFocusEvent);\r\n\r\n if (!closeAutoFocusEvent.defaultPrevented) {\r\n const elementToFocus = previouslyFocusedElementRef.current;\r\n if (elementToFocus && document.body.contains(elementToFocus)) {\r\n elementToFocus.focus({ preventScroll: true });\r\n }\r\n }\r\n\r\n previouslyFocusedElementRef.current = null;\r\n }, 0);\r\n }\r\n\r\n prevOpenRef.current = open;\r\n }, [open, portal, propsRef]);\r\n\r\n useIsomorphicLayoutEffect(() => {\r\n if (openProp !== undefined) {\r\n store.setState('open', openProp);\r\n }\r\n }, [openProp, store]);\r\n\r\n useIsomorphicLayoutEffect(() => {\r\n if (valueProp !== undefined) {\r\n store.setState('value', valueProp);\r\n }\r\n }, [valueProp, store]);\r\n\r\n const contextValue = React.useMemo(\r\n () => ({\r\n alignOffset,\r\n dir,\r\n dismissible,\r\n modal,\r\n onCloseAutoFocus,\r\n onInteractOutside,\r\n onOpenAutoFocus,\r\n onPointerDownOutside,\r\n sideOffset,\r\n spotlightPadding,\r\n stepFooter,\r\n }),\r\n [\r\n dir,\r\n alignOffset,\r\n sideOffset,\r\n spotlightPadding,\r\n dismissible,\r\n modal,\r\n stepFooter,\r\n onPointerDownOutside,\r\n onInteractOutside,\r\n onOpenAutoFocus,\r\n onCloseAutoFocus,\r\n ],\r\n );\r\n\r\n const portalContextValue = React.useMemo(\r\n () => ({\r\n onPortalChange: setPortal,\r\n portal,\r\n }),\r\n [portal],\r\n );\r\n\r\n useScrollLock(open && modal);\r\n\r\n const RootPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n );\r\n}\r\n\r\ninterface TourStepProps extends DivProps {\r\n target: string | React.RefObject | HTMLElement;\r\n side?: Side;\r\n sideOffset?: number;\r\n align?: Align;\r\n alignOffset?: number;\r\n collisionBoundary?: Boundary | Boundary[];\r\n collisionPadding?: number | Partial>;\r\n arrowPadding?: number;\r\n sticky?: 'partial' | 'always';\r\n hideWhenDetached?: boolean;\r\n avoidCollisions?: boolean;\r\n required?: boolean;\r\n forceMount?: boolean;\r\n onStepEnter?: () => void;\r\n onStepLeave?: () => void;\r\n}\r\n\r\nfunction TourStep(props: TourStepProps) {\r\n const {\r\n target,\r\n side = 'bottom',\r\n sideOffset,\r\n align = 'center',\r\n alignOffset,\r\n collisionBoundary = [],\r\n collisionPadding = 0,\r\n arrowPadding = 0,\r\n sticky = 'partial',\r\n hideWhenDetached = false,\r\n avoidCollisions = true,\r\n required = false,\r\n forceMount = false,\r\n onStepEnter,\r\n onStepLeave,\r\n onPointerDownCapture: onPointerDownCaptureProp,\r\n onFocusCapture: onFocusCaptureProp,\r\n onBlurCapture: onBlurCaptureProp,\r\n children,\r\n className,\r\n style,\r\n asChild,\r\n ...stepProps\r\n } = props;\r\n\r\n const store = useStoreContext(STEP_NAME);\r\n\r\n const [arrow, setArrow] = React.useState(null);\r\n const [footer, setFooter] = React.useState(null);\r\n\r\n const stepRef = React.useRef(null);\r\n const stepIdRef = React.useRef('');\r\n const stepOrderRef = React.useRef(-1);\r\n const isPointerInsideReactTreeRef = React.useRef(false);\r\n const isFocusInsideReactTreeRef = React.useRef(false);\r\n\r\n const open = useStore((state) => state.open);\r\n const value = useStore((state) => state.value);\r\n const steps = useStore((state) => state.steps);\r\n const context = useTourContext(STEP_NAME);\r\n\r\n const resolvedSideOffset = sideOffset ?? context.sideOffset;\r\n const resolvedAlignOffset = alignOffset ?? context.alignOffset;\r\n\r\n useIsomorphicLayoutEffect(() => {\r\n const { id, index } = store.addStep({\r\n align,\r\n alignOffset: resolvedAlignOffset,\r\n arrowPadding,\r\n avoidCollisions,\r\n collisionBoundary,\r\n collisionPadding,\r\n hideWhenDetached,\r\n onStepEnter,\r\n onStepLeave,\r\n required,\r\n side,\r\n sideOffset: resolvedSideOffset,\r\n sticky,\r\n target,\r\n });\r\n stepIdRef.current = id;\r\n stepOrderRef.current = index;\r\n\r\n return () => {\r\n store.removeStep(stepIdRef.current);\r\n };\r\n }, [\r\n target,\r\n side,\r\n resolvedSideOffset,\r\n align,\r\n resolvedAlignOffset,\r\n collisionPadding,\r\n arrowPadding,\r\n sticky,\r\n hideWhenDetached,\r\n avoidCollisions,\r\n required,\r\n onStepEnter,\r\n onStepLeave,\r\n store,\r\n ]);\r\n\r\n const stepData = steps[value];\r\n const targetElement = stepData ? getTargetElement(stepData.target) : null;\r\n\r\n const isCurrentStep = stepOrderRef.current === value;\r\n\r\n const middleware = React.useMemo(() => {\r\n if (!stepData) return [];\r\n\r\n const mainAxisOffset = stepData.sideOffset ?? resolvedSideOffset;\r\n const crossAxisOffset = stepData.alignOffset ?? resolvedAlignOffset;\r\n\r\n const padding =\r\n typeof stepData.collisionPadding === 'number'\r\n ? stepData.collisionPadding\r\n : {\r\n bottom: stepData.collisionPadding?.bottom ?? 0,\r\n left: stepData.collisionPadding?.left ?? 0,\r\n right: stepData.collisionPadding?.right ?? 0,\r\n top: stepData.collisionPadding?.top ?? 0,\r\n };\r\n\r\n const boundary = Array.isArray(stepData.collisionBoundary)\r\n ? stepData.collisionBoundary\r\n : stepData.collisionBoundary\r\n ? [stepData.collisionBoundary]\r\n : [];\r\n const hasExplicitBoundaries = boundary.length > 0;\r\n\r\n const detectOverflowOptions = {\r\n altBoundary: hasExplicitBoundaries,\r\n boundary: boundary.filter((b): b is Element => b !== null),\r\n padding,\r\n };\r\n\r\n return [\r\n offset({\r\n alignmentAxis: crossAxisOffset,\r\n mainAxis: mainAxisOffset,\r\n }),\r\n stepData.avoidCollisions &&\r\n shift({\r\n crossAxis: false,\r\n limiter: stepData.sticky === 'partial' ? limitShift() : undefined,\r\n mainAxis: true,\r\n ...detectOverflowOptions,\r\n }),\r\n stepData.avoidCollisions && flip({ ...detectOverflowOptions }),\r\n arrow && onArrow({ element: arrow, padding: stepData.arrowPadding }),\r\n stepData.hideWhenDetached &&\r\n hide({\r\n strategy: 'referenceHidden',\r\n ...detectOverflowOptions,\r\n }),\r\n ].filter(Boolean) as Middleware[];\r\n }, [stepData, resolvedSideOffset, resolvedAlignOffset, arrow]);\r\n\r\n const placement = getPlacement(stepData?.side ?? side, stepData?.align ?? align);\r\n\r\n const {\r\n refs,\r\n floatingStyles,\r\n placement: finalPlacement,\r\n middlewareData,\r\n } = useFloating({\r\n elements: {\r\n reference: targetElement,\r\n },\r\n middleware,\r\n placement,\r\n strategy: 'fixed',\r\n whileElementsMounted: autoUpdate,\r\n });\r\n\r\n const composedRef = useComposedRefs(refs.setFloating, stepRef);\r\n\r\n const [placedSide, placedAlign] = getSideAndAlignFromPlacement(finalPlacement);\r\n\r\n const arrowX = middlewareData.arrow?.x;\r\n const arrowY = middlewareData.arrow?.y;\r\n const cannotCenterArrow = middlewareData.arrow?.centerOffset !== 0;\r\n const isHidden = hideWhenDetached && middlewareData.hide?.referenceHidden;\r\n\r\n const stepContextValue = React.useMemo(\r\n () => ({\r\n arrowX,\r\n arrowY,\r\n onArrowChange: setArrow,\r\n onFooterChange: setFooter,\r\n placedAlign,\r\n placedSide,\r\n shouldHideArrow: cannotCenterArrow,\r\n }),\r\n [arrowX, arrowY, placedSide, placedAlign, cannotCenterArrow],\r\n );\r\n\r\n React.useEffect(() => {\r\n if (open && targetElement && isCurrentStep) {\r\n updateMask(store, targetElement, context.spotlightPadding);\r\n\r\n let rafId: number | null = null;\r\n\r\n function onResize() {\r\n if (targetElement) {\r\n updateMask(store, targetElement, context.spotlightPadding);\r\n }\r\n }\r\n\r\n function onScroll() {\r\n if (rafId !== null) return;\r\n rafId = requestAnimationFrame(() => {\r\n if (targetElement) {\r\n updateMask(store, targetElement, context.spotlightPadding);\r\n }\r\n rafId = null;\r\n });\r\n }\r\n\r\n window.addEventListener('resize', onResize);\r\n window.addEventListener('scroll', onScroll, { passive: true });\r\n return () => {\r\n window.removeEventListener('resize', onResize);\r\n window.removeEventListener('scroll', onScroll);\r\n if (rafId !== null) {\r\n cancelAnimationFrame(rafId);\r\n }\r\n };\r\n }\r\n }, [open, targetElement, isCurrentStep, store, context.spotlightPadding]);\r\n\r\n React.useEffect(() => {\r\n if (!open || !isCurrentStep) return;\r\n\r\n const stepElement = stepRef.current;\r\n if (!stepElement) return;\r\n\r\n const ownerDocument = stepElement.ownerDocument;\r\n\r\n function onPointerDown(event: PointerEvent) {\r\n if (event.target && !isPointerInsideReactTreeRef.current) {\r\n const pointerDownOutsideEvent = new CustomEvent(POINTER_DOWN_OUTSIDE, {\r\n ...EVENT_OPTIONS,\r\n detail: { originalEvent: event },\r\n });\r\n\r\n context.onPointerDownOutside?.(pointerDownOutsideEvent);\r\n\r\n const interactOutsideEvent = new CustomEvent(INTERACT_OUTSIDE, {\r\n ...EVENT_OPTIONS,\r\n detail: { originalEvent: event },\r\n });\r\n context.onInteractOutside?.(interactOutsideEvent);\r\n\r\n if (\r\n !pointerDownOutsideEvent.defaultPrevented &&\r\n !interactOutsideEvent.defaultPrevented &&\r\n context.dismissible\r\n ) {\r\n store.setState('open', false);\r\n }\r\n }\r\n\r\n isPointerInsideReactTreeRef.current = false;\r\n }\r\n\r\n const timerId = window.setTimeout(() => {\r\n ownerDocument.addEventListener('pointerdown', onPointerDown);\r\n }, 0);\r\n\r\n return () => {\r\n window.clearTimeout(timerId);\r\n ownerDocument.removeEventListener('pointerdown', onPointerDown);\r\n };\r\n }, [open, isCurrentStep, store, context]);\r\n\r\n React.useEffect(() => {\r\n if (!open || !isCurrentStep) return;\r\n\r\n const stepElement = stepRef.current;\r\n if (!stepElement) return;\r\n\r\n const ownerDocument = stepElement.ownerDocument;\r\n\r\n function onFocusIn(event: FocusEvent) {\r\n const target = event.target as HTMLElement;\r\n\r\n const isFocusInStep = stepElement?.contains(target);\r\n const isFocusInTarget = targetElement?.contains(target);\r\n\r\n if (\r\n event.target &&\r\n !isFocusInsideReactTreeRef.current &&\r\n !isFocusInStep &&\r\n !isFocusInTarget\r\n ) {\r\n const interactOutsideEvent = new CustomEvent(INTERACT_OUTSIDE, {\r\n ...EVENT_OPTIONS,\r\n detail: { originalEvent: event },\r\n });\r\n\r\n context.onInteractOutside?.(interactOutsideEvent);\r\n\r\n if (!interactOutsideEvent.defaultPrevented && context.dismissible) {\r\n store.setState('open', false);\r\n }\r\n }\r\n }\r\n\r\n ownerDocument.addEventListener('focusin', onFocusIn);\r\n\r\n return () => {\r\n ownerDocument.removeEventListener('focusin', onFocusIn);\r\n };\r\n }, [open, isCurrentStep, store, context, targetElement]);\r\n\r\n const onPointerDownCapture = React.useCallback(\r\n (event: React.PointerEvent) => {\r\n onPointerDownCaptureProp?.(event);\r\n isPointerInsideReactTreeRef.current = true;\r\n },\r\n [onPointerDownCaptureProp],\r\n );\r\n\r\n const onFocusCapture = React.useCallback(\r\n (event: React.FocusEvent) => {\r\n onFocusCaptureProp?.(event);\r\n isFocusInsideReactTreeRef.current = true;\r\n },\r\n [onFocusCaptureProp],\r\n );\r\n\r\n const onBlurCapture = React.useCallback(\r\n (event: React.FocusEvent) => {\r\n onBlurCaptureProp?.(event);\r\n isFocusInsideReactTreeRef.current = false;\r\n },\r\n [onBlurCaptureProp],\r\n );\r\n\r\n React.useEffect(() => {\r\n if (!open || !isCurrentStep || !targetElement) return;\r\n\r\n function onTargetPointerDownCapture() {\r\n isPointerInsideReactTreeRef.current = true;\r\n }\r\n\r\n function onTargetFocusCapture() {\r\n isFocusInsideReactTreeRef.current = true;\r\n }\r\n\r\n function onTargetBlurCapture() {\r\n isFocusInsideReactTreeRef.current = false;\r\n }\r\n\r\n targetElement.addEventListener('pointerdown', onTargetPointerDownCapture, true);\r\n targetElement.addEventListener('focus', onTargetFocusCapture, true);\r\n targetElement.addEventListener('blur', onTargetBlurCapture, true);\r\n\r\n return () => {\r\n targetElement.removeEventListener('pointerdown', onTargetPointerDownCapture, true);\r\n targetElement.removeEventListener('focus', onTargetFocusCapture, true);\r\n targetElement.removeEventListener('blur', onTargetBlurCapture, true);\r\n };\r\n }, [open, isCurrentStep, targetElement]);\r\n\r\n useFocusGuards();\r\n useFocusTrap(\r\n stepRef,\r\n open && isCurrentStep,\r\n open,\r\n context.onOpenAutoFocus,\r\n context.onCloseAutoFocus,\r\n );\r\n\r\n if (!open || !stepData || (!targetElement && !forceMount) || !isCurrentStep) {\r\n return null;\r\n }\r\n\r\n const StepPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n \r\n {children}\r\n {!footer && (\r\n \r\n {context.stepFooter}\r\n \r\n )}\r\n \r\n \r\n );\r\n}\r\n\r\ninterface TourSpotlightProps extends DivProps {\r\n forceMount?: boolean;\r\n}\r\n\r\nfunction TourSpotlight(props: TourSpotlightProps) {\r\n const { asChild, className, style, forceMount = false, ...backdropProps } = props;\r\n\r\n const open = useStore((state) => state.open);\r\n const maskPath = useStore((state) => state.maskPath);\r\n\r\n if (!open && !forceMount) return null;\r\n\r\n const SpotlightPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\ninterface TourSpotlightRingProps extends DivProps {\r\n forceMount?: boolean;\r\n}\r\n\r\nfunction TourSpotlightRing(props: TourSpotlightRingProps) {\r\n const { asChild, className, style, forceMount = false, ...ringProps } = props;\r\n\r\n const open = useStore((state) => state.open);\r\n const spotlightRect = useStore((state) => state.spotlightRect);\r\n\r\n if (!open && !forceMount) return null;\r\n if (!spotlightRect) return null;\r\n\r\n const RingPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\ninterface TourPortalProps {\r\n children?: React.ReactNode;\r\n container?: HTMLElement | null;\r\n}\r\n\r\nfunction TourPortal(props: TourPortalProps) {\r\n const { children, container } = props;\r\n\r\n const portalContext = usePortalContext(PORTAL_NAME);\r\n\r\n const [mounted, setMounted] = React.useState(false);\r\n\r\n useIsomorphicLayoutEffect(() => {\r\n setMounted(true);\r\n\r\n const node = container ?? document.body;\r\n\r\n portalContext?.onPortalChange(node);\r\n return () => {\r\n portalContext?.onPortalChange(null);\r\n };\r\n }, [container, portalContext]);\r\n\r\n if (!mounted) return null;\r\n\r\n const portalContainer = container ?? portalContext?.portal ?? document.body;\r\n\r\n return ReactDOM.createPortal(children, portalContainer);\r\n}\r\n\r\ninterface TourArrowProps extends React.ComponentProps<'svg'> {\r\n width?: number;\r\n height?: number;\r\n asChild?: boolean;\r\n}\r\n\r\nfunction TourArrow(props: TourArrowProps) {\r\n const { width = 10, height = 5, className, children, asChild, ...arrowProps } = props;\r\n\r\n const stepContext = useStepContext(ARROW_NAME);\r\n const baseSide = OPPOSITE_SIDE[stepContext.placedSide];\r\n\r\n return (\r\n \r\n \r\n {asChild ? children : }\r\n \r\n \r\n );\r\n}\r\n\r\nfunction TourHeader(props: DivProps) {\r\n const { asChild, className, ...headerProps } = props;\r\n\r\n const context = useTourContext(HEADER_NAME);\r\n\r\n const HeaderPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\nfunction TourTitle(props: DivProps) {\r\n const { asChild, className, ...titleProps } = props;\r\n\r\n const context = useTourContext(TITLE_NAME);\r\n\r\n const TitlePrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\nfunction TourDescription(props: DivProps) {\r\n const { asChild, className, ...descriptionProps } = props;\r\n\r\n const context = useTourContext(DESCRIPTION_NAME);\r\n\r\n const DescriptionPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\ninterface TourCloseProps extends React.ComponentProps<'button'> {\r\n asChild?: boolean;\r\n}\r\n\r\nfunction TourClose(props: TourCloseProps) {\r\n const { asChild, className, onClick: onClickProp, ...closeButtonProps } = props;\r\n\r\n const store = useStoreContext(CLOSE_NAME);\r\n\r\n const onClick = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n onClickProp?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n store.setState('open', false);\r\n },\r\n [store, onClickProp],\r\n );\r\n\r\n const ClosePrimitive = asChild ? SlotPrimitive.Slot : 'button';\r\n\r\n return (\r\n \r\n \r\n \r\n );\r\n}\r\n\r\nfunction TourPrev(props: React.ComponentProps) {\r\n const { children, onClick: onClickProp, ...prevButtonProps } = props;\r\n\r\n const store = useStoreContext(PREV_NAME);\r\n const value = useStore((state) => state.value);\r\n\r\n const onClick = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n onClickProp?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n if (value > 0) {\r\n store.setState('value', value - 1);\r\n }\r\n },\r\n [value, store, onClickProp],\r\n );\r\n\r\n return (\r\n \r\n {children ?? (\r\n <>\r\n \r\n Previous\r\n \r\n )}\r\n \r\n );\r\n}\r\n\r\nfunction TourNext(props: React.ComponentProps) {\r\n const { children, onClick: onClickProp, ...nextButtonProps } = props;\r\n const store = useStoreContext(NEXT_NAME);\r\n const value = useStore((state) => state.value);\r\n const steps = useStore((state) => state.steps);\r\n\r\n const isLastStep = value === steps.length - 1;\r\n\r\n const onClick = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n onClickProp?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n store.setState('value', value + 1);\r\n },\r\n [value, store, onClickProp],\r\n );\r\n\r\n return (\r\n \r\n {children ?? (\r\n <>\r\n {isLastStep ? 'Finish' : 'Next'}\r\n {!isLastStep && }\r\n \r\n )}\r\n \r\n );\r\n}\r\n\r\nfunction TourSkip(props: React.ComponentProps) {\r\n const { children, onClick: onClickProp, ...skipButtonProps } = props;\r\n\r\n const store = useStoreContext(SKIP_NAME);\r\n\r\n const onClick = React.useCallback(\r\n (event: React.MouseEvent) => {\r\n onClickProp?.(event);\r\n if (event.defaultPrevented) return;\r\n\r\n store.setState('open', false);\r\n },\r\n [store, onClickProp],\r\n );\r\n\r\n return (\r\n \r\n {children ?? 'Skip'}\r\n \r\n );\r\n}\r\n\r\ninterface TourStepCounterProps extends DivProps {\r\n format?: (current: number, total: number) => string;\r\n}\r\n\r\nfunction TourStepCounter(props: TourStepCounterProps) {\r\n const {\r\n format = (current, total) => `${current} / ${total}`,\r\n asChild,\r\n className,\r\n children,\r\n ...stepCounterProps\r\n } = props;\r\n\r\n const value = useStore((state) => state.value);\r\n const steps = useStore((state) => state.steps);\r\n\r\n const StepCounterPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n {children ?? format(value + 1, steps.length)}\r\n \r\n );\r\n}\r\n\r\nfunction TourFooter(props: DivProps) {\r\n const { asChild, className, ref, ...footerProps } = props;\r\n\r\n const stepContext = useStepContext(FOOTER_NAME);\r\n const hasDefaultFooter = React.useContext(DefaultFooterContext);\r\n const context = useTourContext(FOOTER_NAME);\r\n\r\n const composedRef = useComposedRefs(\r\n ref,\r\n hasDefaultFooter ? undefined : stepContext.onFooterChange,\r\n );\r\n\r\n const FooterPrimitive = asChild ? SlotPrimitive.Slot : 'div';\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\nexport {\r\n Tour,\r\n TourArrow,\r\n TourClose,\r\n TourDescription,\r\n TourFooter,\r\n TourHeader,\r\n TourNext,\r\n TourPortal,\r\n TourPrev,\r\n type TourProps,\r\n TourSkip,\r\n TourSpotlight,\r\n TourSpotlightRing,\r\n TourStep,\r\n TourStepCounter,\r\n TourTitle,\r\n};\r\n", + "path": "registry/components/tour.tsx", + "target": "@components/tour.tsx", + "type": "registry:component" + } + ], + "name": "tour", + "registryDependencies": [ + "button", + "@kombase/component-refs", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "type": "registry:component" +} diff --git a/public/r/use-as-ref.json b/public/r/use-as-ref.json new file mode 100644 index 0000000..d42b79e --- /dev/null +++ b/public/r/use-as-ref.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import * as React from 'react';\r\n\r\nimport { useIsomorphicLayoutEffect } from '@/hooks/use-isomorphic-layout-effect';\r\n\r\nfunction useAsRef(props: T) {\r\n const ref = React.useRef(props);\r\n\r\n useIsomorphicLayoutEffect(() => {\r\n ref.current = props;\r\n });\r\n\r\n return ref;\r\n}\r\n\r\nexport { useAsRef };\r\n", + "path": "registry/hooks/use-as-ref.ts", + "target": "@hooks/use-as-ref.ts", + "type": "registry:hook" + } + ], + "name": "use-as-ref", + "registryDependencies": ["@kombase/use-isomorphic-layout-effect"], + "type": "registry:hook" +} diff --git a/public/r/use-callback-ref.json b/public/r/use-callback-ref.json new file mode 100644 index 0000000..a2496b0 --- /dev/null +++ b/public/r/use-callback-ref.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import * as React from 'react';\r\n\r\n/**\r\n * @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-callback-ref/src/useCallbackRef.tsx\r\n */\r\n\r\n/**\r\n * A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a\r\n * prop or avoid re-executing effects when passed as a dependency\r\n */\r\nfunction useCallbackRef unknown>(callback: T | undefined): T {\r\n const callbackRef = React.useRef(callback);\r\n\r\n React.useEffect(() => {\r\n callbackRef.current = callback;\r\n });\r\n\r\n // https://github.com/facebook/react/issues/19240\r\n return React.useMemo(() => ((...args) => callbackRef.current?.(...args)) as T, []);\r\n}\r\n\r\nexport { useCallbackRef };\r\n", + "path": "registry/hooks/use-callback-ref.ts", + "target": "@hooks/use-callback-ref.ts", + "type": "registry:hook" + } + ], + "name": "use-callback-ref", + "type": "registry:hook" +} diff --git a/public/r/use-data-table.json b/public/r/use-data-table.json new file mode 100644 index 0000000..af05360 --- /dev/null +++ b/public/r/use-data-table.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["@tanstack/react-table"], + "files": [ + { + "content": "import {\r\n type ColumnFiltersState,\r\n getCoreRowModel,\r\n getFacetedMinMaxValues,\r\n getFacetedRowModel,\r\n getFacetedUniqueValues,\r\n getFilteredRowModel,\r\n getPaginationRowModel,\r\n getSortedRowModel,\r\n type PaginationState,\r\n type RowSelectionState,\r\n type SortingState,\r\n type TableOptions,\r\n type TableState,\r\n type Updater,\r\n useReactTable,\r\n type VisibilityState,\r\n} from '@tanstack/react-table';\r\nimport dayjs from 'dayjs';\r\nimport * as React from 'react';\r\nimport type {\r\n ExtendedColumnFilter,\r\n ExtendedColumnSort,\r\n JoinOperator,\r\n} from '@/components/data-table/types';\r\nimport { getValidFilters } from '@/lib/data-table';\r\n\r\nconst DEBOUNCE_MS = 300;\r\n\r\ninterface UseDataTableProps\r\n extends Omit<\r\n TableOptions,\r\n | 'state'\r\n | 'pageCount'\r\n | 'getCoreRowModel'\r\n | 'manualFiltering'\r\n | 'manualPagination'\r\n | 'manualSorting'\r\n >,\r\n Required, 'pageCount'>> {\r\n initialState?: Omit, 'sorting'> & {\r\n sorting?: ExtendedColumnSort[];\r\n };\r\n debounceMs?: number;\r\n enableAdvancedFilter?: boolean;\r\n isAdvanceFilter?: boolean;\r\n page?: number;\r\n perPage?: number;\r\n onPageChange?: (page: number) => void;\r\n onPerPageChange?: (perPage: number) => void;\r\n manualFiltering?: boolean;\r\n filters?: ExtendedColumnFilter[];\r\n setFilters?: React.Dispatch[]>>;\r\n joinOperator?: JoinOperator;\r\n setJoinOperator?: React.Dispatch>;\r\n filterValues?: Record;\r\n onFilterValuesChange?: (updates: Record) => void;\r\n}\r\n\r\nexport function useDataTable(props: UseDataTableProps) {\r\n const {\r\n columns,\r\n pageCount = -1,\r\n initialState,\r\n debounceMs = DEBOUNCE_MS,\r\n enableAdvancedFilter = false,\r\n isAdvanceFilter = false,\r\n page: controlledPage,\r\n perPage: controlledPerPage,\r\n onPageChange,\r\n onPerPageChange,\r\n manualFiltering: controlledManualFiltering,\r\n filters: controlledFilters,\r\n setFilters: controlledSetFilters,\r\n joinOperator: controlledJoinOperator,\r\n setJoinOperator: controlledSetJoinOperator,\r\n ...tableProps\r\n } = props;\r\n\r\n const isAdvanced = enableAdvancedFilter || isAdvanceFilter;\r\n\r\n const [localFilters, setLocalFilters] = React.useState[]>(\r\n initialState?.columnFilters\r\n ? initialState.columnFilters.map((cf) => ({\r\n filterId: Math.random().toString(36).substring(7),\r\n id: cf.id as Extract,\r\n operator: 'eq',\r\n value: cf.value as string | string[],\r\n variant: 'text',\r\n }))\r\n : [],\r\n );\r\n const [localJoinOperator, setLocalJoinOperator] = React.useState('and');\r\n\r\n const filters = controlledFilters !== undefined ? controlledFilters : localFilters;\r\n const setFilters = controlledSetFilters !== undefined ? controlledSetFilters : setLocalFilters;\r\n\r\n const joinOperator =\r\n controlledJoinOperator !== undefined ? controlledJoinOperator : localJoinOperator;\r\n const setJoinOperator =\r\n controlledSetJoinOperator !== undefined ? controlledSetJoinOperator : setLocalJoinOperator;\r\n\r\n const [rowSelection, setRowSelection] = React.useState(\r\n initialState?.rowSelection ?? {},\r\n );\r\n const [columnVisibility, setColumnVisibility] = React.useState(\r\n initialState?.columnVisibility ?? {},\r\n );\r\n\r\n const [internalPage, setInternalPage] = React.useState(\r\n initialState?.pagination?.pageIndex !== undefined ? initialState.pagination.pageIndex + 1 : 1,\r\n );\r\n const [internalPerPage, setInternalPerPage] = React.useState(\r\n initialState?.pagination?.pageSize ?? 10,\r\n );\r\n\r\n const page = controlledPage ?? internalPage;\r\n const perPage = controlledPerPage ?? internalPerPage;\r\n\r\n const pagination: PaginationState = React.useMemo(() => {\r\n return {\r\n pageIndex: page - 1, // zero-based index -> one-based index\r\n pageSize: perPage,\r\n };\r\n }, [page, perPage]);\r\n\r\n const onPaginationChange = React.useCallback(\r\n (updaterOrValue: Updater) => {\r\n if (typeof updaterOrValue === 'function') {\r\n const newPagination = updaterOrValue(pagination);\r\n const newPage = newPagination.pageIndex + 1;\r\n\r\n if (controlledPage === undefined) setInternalPage(newPage);\r\n if (controlledPerPage === undefined) setInternalPerPage(newPagination.pageSize);\r\n\r\n onPageChange?.(newPage);\r\n onPerPageChange?.(newPagination.pageSize);\r\n } else {\r\n const newPage = updaterOrValue.pageIndex + 1;\r\n if (controlledPage === undefined) setInternalPage(newPage);\r\n if (controlledPerPage === undefined) setInternalPerPage(updaterOrValue.pageSize);\r\n\r\n onPageChange?.(newPage);\r\n onPerPageChange?.(updaterOrValue.pageSize);\r\n }\r\n },\r\n [pagination, controlledPage, controlledPerPage, onPageChange, onPerPageChange],\r\n );\r\n\r\n const [sorting, setSorting] = React.useState[]>(\r\n initialState?.sorting ?? [],\r\n );\r\n\r\n const onSortingChange = React.useCallback(\r\n (updaterOrValue: Updater) => {\r\n if (typeof updaterOrValue === 'function') {\r\n const newSorting = updaterOrValue(sorting);\r\n setSorting(newSorting as ExtendedColumnSort[]);\r\n } else {\r\n setSorting(updaterOrValue as ExtendedColumnSort[]);\r\n }\r\n },\r\n [sorting],\r\n );\r\n\r\n const [localColumnFilters, setLocalColumnFilters] = React.useState(\r\n initialState?.columnFilters ?? [],\r\n );\r\n\r\n const isControlled = props.filterValues !== undefined;\r\n\r\n const columnFilters = React.useMemo(() => {\r\n if (isControlled) {\r\n return Object.entries(props.filterValues || {})\r\n .map(([id, value]) => ({ id, value }))\r\n .filter(\r\n (f) =>\r\n f.value !== null &&\r\n f.value !== undefined &&\r\n f.value !== '' &&\r\n (Array.isArray(f.value) ? f.value.length > 0 : true),\r\n ) as ColumnFiltersState;\r\n }\r\n return localColumnFilters;\r\n }, [isControlled, props.filterValues, localColumnFilters]);\r\n\r\n const onColumnFiltersChange = React.useCallback(\r\n (updaterOrValue: Updater) => {\r\n if (isAdvanced) return;\r\n\r\n const next =\r\n typeof updaterOrValue === 'function' ? updaterOrValue(columnFilters) : updaterOrValue;\r\n\r\n if (isControlled && props.onFilterValuesChange) {\r\n const updates: Record = {};\r\n next.forEach((f) => {\r\n updates[f.id] = f.value;\r\n });\r\n\r\n columnFilters.forEach((prevF) => {\r\n if (!next.some((f) => f.id === prevF.id)) {\r\n updates[prevF.id] = null;\r\n }\r\n });\r\n\r\n props.onFilterValuesChange(updates);\r\n } else {\r\n setLocalColumnFilters(next);\r\n }\r\n },\r\n [columnFilters, isControlled, props.onFilterValuesChange, isAdvanced],\r\n );\r\n\r\n const table = useReactTable({\r\n ...tableProps,\r\n columns,\r\n defaultColumn: {\r\n ...tableProps.defaultColumn,\r\n enableColumnFilter: false,\r\n },\r\n enableRowSelection: true,\r\n getCoreRowModel: getCoreRowModel(),\r\n getFacetedMinMaxValues: getFacetedMinMaxValues(),\r\n getFacetedRowModel: getFacetedRowModel(),\r\n getFacetedUniqueValues: getFacetedUniqueValues(),\r\n getFilteredRowModel: getFilteredRowModel(),\r\n getPaginationRowModel: getPaginationRowModel(),\r\n getSortedRowModel: getSortedRowModel(),\r\n globalFilterFn: (row, _columnId, filterValue) => {\r\n if (filterValue && typeof filterValue === 'object' && 'filters' in filterValue) {\r\n return matchRow(row, filterValue.filters, filterValue.joinOperator || 'and');\r\n }\r\n return true;\r\n },\r\n initialState,\r\n manualFiltering:\r\n controlledManualFiltering !== undefined ? controlledManualFiltering : !isAdvanced,\r\n manualPagination: true,\r\n manualSorting: true,\r\n meta: {\r\n ...tableProps.meta,\r\n debounceMs,\r\n filters,\r\n isAdvanceFilter: isAdvanced,\r\n joinOperator,\r\n setFilters,\r\n setJoinOperator,\r\n },\r\n onColumnFiltersChange,\r\n onColumnVisibilityChange: setColumnVisibility,\r\n onPaginationChange,\r\n onRowSelectionChange: setRowSelection,\r\n onSortingChange,\r\n pageCount,\r\n state: {\r\n columnFilters,\r\n columnVisibility,\r\n globalFilter: isAdvanced ? { filters, joinOperator } : undefined,\r\n pagination,\r\n rowSelection,\r\n sorting,\r\n },\r\n });\r\n\r\n return {\r\n table,\r\n };\r\n}\r\n\r\nfunction matchRow(\r\n row: any,\r\n filters: ExtendedColumnFilter[],\r\n joinOperator: JoinOperator,\r\n): boolean {\r\n const validFilters = getValidFilters(filters);\r\n if (validFilters.length === 0) return true;\r\n\r\n const results = validFilters.map((filter) => {\r\n const cellValue = row.getValue(filter.id);\r\n const filterValue = filter.value;\r\n const operator = filter.operator;\r\n\r\n if (operator === 'isEmpty') {\r\n return cellValue === null || cellValue === undefined || cellValue === '';\r\n }\r\n if (operator === 'isNotEmpty') {\r\n return cellValue !== null && cellValue !== undefined && cellValue !== '';\r\n }\r\n\r\n if (filter.variant === 'boolean') {\r\n return String(cellValue) === String(filterValue);\r\n }\r\n\r\n if (filter.variant === 'date' || filter.variant === 'dateRange') {\r\n if (!cellValue) return false;\r\n const cellDate = dayjs(cellValue);\r\n if (!cellDate.isValid()) return false;\r\n\r\n if (operator === 'isBetween') {\r\n if (!Array.isArray(filterValue) || filterValue.length < 2) return false;\r\n const start = dayjs(Number(filterValue[0]));\r\n const end = dayjs(Number(filterValue[1]));\r\n return cellDate.isAfter(start.startOf('day')) && cellDate.isBefore(end.endOf('day'));\r\n }\r\n\r\n const cmpDate = dayjs(Number(filterValue));\r\n if (!cmpDate.isValid()) return false;\r\n\r\n switch (operator) {\r\n case 'eq':\r\n return cellDate.isSame(cmpDate, 'day');\r\n case 'ne':\r\n return !cellDate.isSame(cmpDate, 'day');\r\n case 'lt':\r\n return cellDate.isBefore(cmpDate, 'day');\r\n case 'lte':\r\n return cellDate.isBefore(cmpDate, 'day') || cellDate.isSame(cmpDate, 'day');\r\n case 'gt':\r\n return cellDate.isAfter(cmpDate, 'day');\r\n case 'gte':\r\n return cellDate.isAfter(cmpDate, 'day') || cellDate.isSame(cmpDate, 'day');\r\n default:\r\n return false;\r\n }\r\n }\r\n\r\n if (filter.variant === 'select' || filter.variant === 'multiSelect') {\r\n const selected = Array.isArray(filterValue) ? filterValue : [filterValue].filter(Boolean);\r\n if (selected.length === 0) return true;\r\n const valStr = String(cellValue).toLowerCase();\r\n\r\n if (operator === 'notInArray') {\r\n return !selected.some((v) => String(v).toLowerCase() === valStr);\r\n }\r\n return selected.some((v) => String(v).toLowerCase() === valStr);\r\n }\r\n\r\n if (filter.variant === 'number' || filter.variant === 'range') {\r\n if (operator === 'isBetween') {\r\n if (!Array.isArray(filterValue) || filterValue.length < 2) return false;\r\n const val = Number(cellValue);\r\n return val >= Number(filterValue[0]) && val <= Number(filterValue[1]);\r\n }\r\n const val = Number(cellValue);\r\n const filterNum = Number(filterValue);\r\n switch (operator) {\r\n case 'eq':\r\n return val === filterNum;\r\n case 'ne':\r\n return val !== filterNum;\r\n case 'lt':\r\n return val < filterNum;\r\n case 'lte':\r\n return val <= filterNum;\r\n case 'gt':\r\n return val > filterNum;\r\n case 'gte':\r\n return val >= filterNum;\r\n default:\r\n return false;\r\n }\r\n }\r\n\r\n const strCellValue = String(cellValue).toLowerCase();\r\n const strFilterValue = String(filterValue).toLowerCase();\r\n\r\n switch (operator) {\r\n case 'iLike':\r\n return strCellValue.includes(strFilterValue);\r\n case 'notILike':\r\n return !strCellValue.includes(strFilterValue);\r\n case 'eq':\r\n return strCellValue === strFilterValue;\r\n case 'ne':\r\n return strCellValue !== strFilterValue;\r\n default:\r\n return false;\r\n }\r\n });\r\n\r\n if (joinOperator === 'or') {\r\n return results.some(Boolean);\r\n }\r\n return results.every(Boolean);\r\n}\r\n", + "path": "registry/hooks/use-data-table.ts", + "target": "@hooks/use-data-table.ts", + "type": "registry:hook" + } + ], + "name": "use-data-table", + "registryDependencies": [ + "@kombase/data-table-config", + "@kombase/data-table-types", + "@kombase/lib-data-table" + ], + "type": "registry:hook" +} diff --git a/public/r/use-debounced-callback.json b/public/r/use-debounced-callback.json new file mode 100644 index 0000000..78d2f6b --- /dev/null +++ b/public/r/use-debounced-callback.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import * as React from 'react';\r\n\r\nimport { useCallbackRef } from '@/hooks/use-callback-ref';\r\n\r\nexport function useDebouncedCallback unknown>(\r\n callback: T,\r\n delay: number,\r\n) {\r\n const handleCallback = useCallbackRef(callback);\r\n const debounceTimerRef = React.useRef(0);\r\n React.useEffect(() => () => window.clearTimeout(debounceTimerRef.current), []);\r\n\r\n const setValue = React.useCallback(\r\n (...args: Parameters) => {\r\n window.clearTimeout(debounceTimerRef.current);\r\n debounceTimerRef.current = window.setTimeout(() => handleCallback(...args), delay);\r\n },\r\n [handleCallback, delay],\r\n );\r\n\r\n return setValue;\r\n}\r\n", + "path": "registry/hooks/use-debounced-callback.ts", + "target": "@hooks/use-debounced-callback.ts", + "type": "registry:hook" + } + ], + "name": "use-debounced-callback", + "registryDependencies": ["@kombase/use-callback-ref"], + "type": "registry:hook" +} diff --git a/public/r/use-isomorphic-layout-effect.json b/public/r/use-isomorphic-layout-effect.json new file mode 100644 index 0000000..297d03f --- /dev/null +++ b/public/r/use-isomorphic-layout-effect.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import * as React from 'react';\r\n\r\nconst useIsomorphicLayoutEffect =\r\n typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;\r\n\r\nexport { useIsomorphicLayoutEffect };\r\n", + "path": "registry/hooks/use-isomorphic-layout-effect.ts", + "target": "@hooks/use-isomorphic-layout-effect.ts", + "type": "registry:hook" + } + ], + "name": "use-isomorphic-layout-effect", + "type": "registry:hook" +} diff --git a/public/r/use-lazy-ref.json b/public/r/use-lazy-ref.json new file mode 100644 index 0000000..7ab16c9 --- /dev/null +++ b/public/r/use-lazy-ref.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import * as React from 'react';\r\n\r\nfunction useLazyRef(fn: () => T) {\r\n const ref = React.useRef(null);\r\n\r\n if (ref.current === null) {\r\n ref.current = fn();\r\n }\r\n\r\n return ref as React.RefObject;\r\n}\r\n\r\nexport { useLazyRef };\r\n", + "path": "registry/hooks/use-lazy-ref.ts", + "target": "@hooks/use-lazy-ref.ts", + "type": "registry:hook" + } + ], + "name": "use-lazy-ref", + "type": "registry:hook" +} diff --git a/public/r/utils.json b/public/r/utils.json new file mode 100644 index 0000000..3a27e8b --- /dev/null +++ b/public/r/utils.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "dependencies": ["clsx", "tailwind-merge"], + "files": [ + { + "content": "import { type ClassValue, clsx } from 'clsx';\r\nimport { twMerge } from 'tailwind-merge';\r\n\r\nexport function cn(...inputs: ClassValue[]) {\r\n return twMerge(clsx(inputs));\r\n}\r\n", + "path": "registry/lib/utils.ts", + "target": "@lib/utils.ts", + "type": "registry:lib" + } + ], + "name": "utils", + "type": "registry:lib" +} diff --git a/public/r/visually-hidden-input.json b/public/r/visually-hidden-input.json new file mode 100644 index 0000000..628fb1b --- /dev/null +++ b/public/r/visually-hidden-input.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "files": [ + { + "content": "import * as React from 'react';\r\n\r\ntype InputValue = string[] | string;\r\n\r\ninterface VisuallyHiddenInputProps\r\n extends Omit, 'value' | 'checked' | 'onReset'> {\r\n value?: T;\r\n checked?: boolean;\r\n control: HTMLElement | null;\r\n bubbles?: boolean;\r\n}\r\n\r\nfunction VisuallyHiddenInput(props: VisuallyHiddenInputProps) {\r\n const { control, value, checked, bubbles = true, type = 'hidden', style, ...inputProps } = props;\r\n\r\n const isCheckInput = React.useMemo(\r\n () => type === 'checkbox' || type === 'radio' || type === 'switch',\r\n [type],\r\n );\r\n const inputRef = React.useRef(null);\r\n\r\n const prevValueRef = React.useRef<{\r\n value: T | boolean | undefined;\r\n previous: T | boolean | undefined;\r\n }>({\r\n previous: isCheckInput ? checked : value,\r\n value: isCheckInput ? checked : value,\r\n });\r\n\r\n const prevValue = React.useMemo(() => {\r\n const currentValue = isCheckInput ? checked : value;\r\n if (prevValueRef.current.value !== currentValue) {\r\n prevValueRef.current.previous = prevValueRef.current.value;\r\n prevValueRef.current.value = currentValue;\r\n }\r\n return prevValueRef.current.previous;\r\n }, [isCheckInput, value, checked]);\r\n\r\n const [controlSize, setControlSize] = React.useState<{\r\n width?: number;\r\n height?: number;\r\n }>({});\r\n\r\n React.useLayoutEffect(() => {\r\n if (!control) {\r\n setControlSize({});\r\n return;\r\n }\r\n\r\n setControlSize({\r\n height: control.offsetHeight,\r\n width: control.offsetWidth,\r\n });\r\n\r\n if (typeof window === 'undefined') return;\r\n\r\n const resizeObserver = new ResizeObserver((entries) => {\r\n if (!Array.isArray(entries) || !entries.length) return;\r\n\r\n const entry = entries[0];\r\n if (!entry) return;\r\n\r\n let width: number;\r\n let height: number;\r\n\r\n if ('borderBoxSize' in entry) {\r\n const borderSizeEntry = entry.borderBoxSize;\r\n const borderSize = Array.isArray(borderSizeEntry) ? borderSizeEntry[0] : borderSizeEntry;\r\n width = borderSize.inlineSize;\r\n height = borderSize.blockSize;\r\n } else {\r\n width = control.offsetWidth;\r\n height = control.offsetHeight;\r\n }\r\n\r\n setControlSize({ height, width });\r\n });\r\n\r\n resizeObserver.observe(control, { box: 'border-box' });\r\n return () => {\r\n resizeObserver.disconnect();\r\n };\r\n }, [control]);\r\n\r\n React.useEffect(() => {\r\n const input = inputRef.current;\r\n if (!input) return;\r\n\r\n const inputProto = window.HTMLInputElement.prototype;\r\n const propertyKey = isCheckInput ? 'checked' : 'value';\r\n const eventType = isCheckInput ? 'click' : 'input';\r\n const currentValue = isCheckInput ? checked : value;\r\n\r\n const serializedCurrentValue = isCheckInput\r\n ? checked\r\n : typeof value === 'object' && value !== null\r\n ? JSON.stringify(value)\r\n : value;\r\n\r\n const descriptor = Object.getOwnPropertyDescriptor(inputProto, propertyKey);\r\n\r\n const setter = descriptor?.set;\r\n\r\n if (prevValue !== currentValue && setter) {\r\n const event = new Event(eventType, { bubbles });\r\n setter.call(input, serializedCurrentValue);\r\n input.dispatchEvent(event);\r\n }\r\n }, [prevValue, value, checked, bubbles, isCheckInput]);\r\n\r\n const composedStyle = React.useMemo(() => {\r\n return {\r\n ...style,\r\n ...(controlSize.width !== undefined && controlSize.height !== undefined ? controlSize : {}),\r\n border: 0,\r\n clip: 'rect(0 0 0 0)',\r\n clipPath: 'inset(50%)',\r\n height: '1px',\r\n margin: '-1px',\r\n overflow: 'hidden',\r\n padding: 0,\r\n position: 'absolute',\r\n whiteSpace: 'nowrap',\r\n width: '1px',\r\n };\r\n }, [style, controlSize]);\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\nexport { VisuallyHiddenInput };\r\n", + "path": "registry/components/visually-hidden-input.tsx", + "target": "@components/visually-hidden-input.tsx", + "type": "registry:component" + } + ], + "name": "visually-hidden-input", + "type": "registry:component" +} diff --git a/registry.json b/registry.json new file mode 100644 index 0000000..81c50ce --- /dev/null +++ b/registry.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry.json", + "homepage": "https://kombase.komerce.id", + "include": [ + "registry/ui/registry.json", + "registry/hooks/registry.json", + "registry/lib/registry.json", + "registry/components/registry.json", + "registry/form/registry.json" + ], + "name": "kombase" +} diff --git a/packages/src/components/dynamic/action-bar.tsx b/registry/components/action-bar.tsx similarity index 99% rename from packages/src/components/dynamic/action-bar.tsx rename to registry/components/action-bar.tsx index 2476305..6cb079a 100644 --- a/packages/src/components/dynamic/action-bar.tsx +++ b/registry/components/action-bar.tsx @@ -5,10 +5,10 @@ import * as SlotPrimitive from '@radix-ui/react-slot'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { Button } from '@/components/ui/button'; +import { useAsRef } from '@/hooks/use-as-ref'; +import { useIsomorphicLayoutEffect } from '@/hooks/use-isomorphic-layout-effect'; import { useComposedRefs } from '@/lib/component-refs'; import { cn } from '@/lib/utils'; -import { useAsRef } from '../hooks/use-as-ref'; -import { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect'; const ROOT_NAME = 'ActionBar'; const GROUP_NAME = 'ActionBarGroup'; diff --git a/packages/src/components/dynamic/avatar-group.tsx b/registry/components/avatar-group.tsx similarity index 100% rename from packages/src/components/dynamic/avatar-group.tsx rename to registry/components/avatar-group.tsx diff --git a/packages/src/components/dynamic/confirm-dialog.tsx b/registry/components/confirm-dialog.tsx similarity index 100% rename from packages/src/components/dynamic/confirm-dialog.tsx rename to registry/components/confirm-dialog.tsx diff --git a/packages/src/components/data-table/data-table-advance-filter.tsx b/registry/components/data-table/data-table-advance-filter.tsx similarity index 98% rename from packages/src/components/data-table/data-table-advance-filter.tsx rename to registry/components/data-table/data-table-advance-filter.tsx index bb11837..73f8b69 100644 --- a/packages/src/components/data-table/data-table-advance-filter.tsx +++ b/registry/components/data-table/data-table-advance-filter.tsx @@ -3,15 +3,9 @@ import dayjs from 'dayjs'; import { Calendar as CalendarIcon, Check, ChevronsUpDown, ListFilter, Trash2 } from 'lucide-react'; import * as React from 'react'; import type { DateRange } from 'react-day-picker'; -import { - getDefaultFilterOperator, - getFilterOperators, - getValidFilters, -} from '../../lib/data-table'; -import { cn } from '../../lib/utils'; -import { Badge } from '../ui/badge'; -import { Button } from '../ui/button'; -import { Calendar } from '../ui/calendar'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Calendar } from '@/components/ui/calendar'; import { Command, CommandEmpty, @@ -19,9 +13,9 @@ import { CommandInput, CommandItem, CommandList, -} from '../ui/command'; -import { DebouncedInput } from '../ui/debounced-input'; -import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'; +} from '@/components/ui/command'; +import { DebouncedInput } from '@/components/ui/debounced-input'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Select, SelectContent, @@ -29,7 +23,9 @@ import { SelectItem, SelectTrigger, SelectValue, -} from '../ui/select'; +} from '@/components/ui/select'; +import { getDefaultFilterOperator, getFilterOperators, getValidFilters } from '@/lib/data-table'; +import { cn } from '@/lib/utils'; import { dataTableConfig } from './data-table-config'; import { getIsDateRange, parseAsDate, parseColumnFilterValue } from './data-table-date-filter'; import { DataTableRangeFilter } from './data-table-range-filter'; diff --git a/packages/src/components/data-table/data-table-bulk-action.tsx b/registry/components/data-table/data-table-bulk-action.tsx similarity index 100% rename from packages/src/components/data-table/data-table-bulk-action.tsx rename to registry/components/data-table/data-table-bulk-action.tsx diff --git a/packages/src/components/data-table/data-table-column-header.tsx b/registry/components/data-table/data-table-column-header.tsx similarity index 100% rename from packages/src/components/data-table/data-table-column-header.tsx rename to registry/components/data-table/data-table-column-header.tsx diff --git a/packages/src/components/data-table/data-table-config.ts b/registry/components/data-table/data-table-config.ts similarity index 100% rename from packages/src/components/data-table/data-table-config.ts rename to registry/components/data-table/data-table-config.ts diff --git a/packages/src/components/data-table/data-table-date-filter.tsx b/registry/components/data-table/data-table-date-filter.tsx similarity index 100% rename from packages/src/components/data-table/data-table-date-filter.tsx rename to registry/components/data-table/data-table-date-filter.tsx diff --git a/packages/src/components/data-table/data-table-faceted-filter.tsx b/registry/components/data-table/data-table-faceted-filter.tsx similarity index 100% rename from packages/src/components/data-table/data-table-faceted-filter.tsx rename to registry/components/data-table/data-table-faceted-filter.tsx diff --git a/packages/src/components/data-table/data-table-pagination.tsx b/registry/components/data-table/data-table-pagination.tsx similarity index 100% rename from packages/src/components/data-table/data-table-pagination.tsx rename to registry/components/data-table/data-table-pagination.tsx diff --git a/packages/src/components/data-table/data-table-range-filter.tsx b/registry/components/data-table/data-table-range-filter.tsx similarity index 97% rename from packages/src/components/data-table/data-table-range-filter.tsx rename to registry/components/data-table/data-table-range-filter.tsx index 0652570..d07e82a 100644 --- a/packages/src/components/data-table/data-table-range-filter.tsx +++ b/registry/components/data-table/data-table-range-filter.tsx @@ -1,7 +1,7 @@ import type { Column } from '@tanstack/react-table'; import * as React from 'react'; -import { cn } from '../../lib/utils'; -import { DebouncedInput } from '../ui/debounced-input'; +import { DebouncedInput } from '@/components/ui/debounced-input'; +import { cn } from '@/lib/utils'; import type { ExtendedColumnFilter } from './types'; interface DataTableRangeFilterProps extends React.ComponentProps<'div'> { diff --git a/packages/src/components/data-table/data-table-skeleton.tsx b/registry/components/data-table/data-table-skeleton.tsx similarity index 100% rename from packages/src/components/data-table/data-table-skeleton.tsx rename to registry/components/data-table/data-table-skeleton.tsx diff --git a/packages/src/components/data-table/data-table-slider-filter.tsx b/registry/components/data-table/data-table-slider-filter.tsx similarity index 100% rename from packages/src/components/data-table/data-table-slider-filter.tsx rename to registry/components/data-table/data-table-slider-filter.tsx diff --git a/packages/src/components/data-table/data-table-toolbar.tsx b/registry/components/data-table/data-table-toolbar.tsx similarity index 100% rename from packages/src/components/data-table/data-table-toolbar.tsx rename to registry/components/data-table/data-table-toolbar.tsx diff --git a/packages/src/components/data-table/data-table-view-options.tsx b/registry/components/data-table/data-table-view-options.tsx similarity index 100% rename from packages/src/components/data-table/data-table-view-options.tsx rename to registry/components/data-table/data-table-view-options.tsx diff --git a/packages/src/components/data-table/data-table.tsx b/registry/components/data-table/data-table.tsx similarity index 100% rename from packages/src/components/data-table/data-table.tsx rename to registry/components/data-table/data-table.tsx diff --git a/packages/src/components/data-table/types.ts b/registry/components/data-table/types.ts similarity index 100% rename from packages/src/components/data-table/types.ts rename to registry/components/data-table/types.ts diff --git a/packages/src/components/dynamic/long-text.tsx b/registry/components/long-text.tsx similarity index 100% rename from packages/src/components/dynamic/long-text.tsx rename to registry/components/long-text.tsx diff --git a/packages/src/components/password-input.tsx b/registry/components/password-input.tsx similarity index 74% rename from packages/src/components/password-input.tsx rename to registry/components/password-input.tsx index ed4eb3c..3bd1a00 100644 --- a/packages/src/components/password-input.tsx +++ b/registry/components/password-input.tsx @@ -1,7 +1,7 @@ import { Eye, EyeOff } from 'lucide-react'; import * as React from 'react'; +import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils'; -import { Button } from './ui/button'; type PasswordInputProps = Omit, 'type'>; @@ -12,7 +12,7 @@ export const PasswordInput = React.forwardRef - {showPassword ? : } + {showPassword ? : } {showPassword ? 'Hide password' : 'Show password'} diff --git a/packages/src/components/dynamic/phone-input.tsx b/registry/components/phone-input.tsx similarity index 98% rename from packages/src/components/dynamic/phone-input.tsx rename to registry/components/phone-input.tsx index ba11734..926f8ab 100644 --- a/packages/src/components/dynamic/phone-input.tsx +++ b/registry/components/phone-input.tsx @@ -3,9 +3,6 @@ import * as SlotPrimitive from '@radix-ui/react-slot'; import { Check, ChevronDown } from 'lucide-react'; import * as React from 'react'; -import { useAsRef } from '@/components/hooks/use-as-ref'; -import { useIsomorphicLayoutEffect } from '@/components/hooks/use-isomorphic-layout-effect'; -import { useLazyRef } from '@/components/hooks/use-lazy-ref'; import { Command, CommandEmpty, @@ -16,9 +13,12 @@ import { } from '@/components/ui/command'; import { Input } from '@/components/ui/input'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; +import { VisuallyHiddenInput } from '@/components/visually-hidden-input'; +import { useAsRef } from '@/hooks/use-as-ref'; +import { useIsomorphicLayoutEffect } from '@/hooks/use-isomorphic-layout-effect'; +import { useLazyRef } from '@/hooks/use-lazy-ref'; import { useComposedRefs } from '@/lib/component-refs'; import { cn } from '@/lib/utils'; -import { VisuallyHiddenInput } from '../visually-hidden-input'; const ROOT_NAME = 'PhoneInput'; const COUNTRY_SELECT_NAME = 'PhoneInputCountrySelect'; diff --git a/packages/src/components/dynamic/rating.tsx b/registry/components/rating.tsx similarity index 98% rename from packages/src/components/dynamic/rating.tsx rename to registry/components/rating.tsx index 6c8b995..d2e1f06 100644 --- a/packages/src/components/dynamic/rating.tsx +++ b/registry/components/rating.tsx @@ -4,12 +4,12 @@ import * as DirectionPrimitive from '@radix-ui/react-direction'; import * as SlotPrimitive from '@radix-ui/react-slot'; import { Star } from 'lucide-react'; import * as React from 'react'; -import { useAsRef } from '@/components/hooks/use-as-ref'; -import { useIsomorphicLayoutEffect } from '@/components/hooks/use-isomorphic-layout-effect'; -import { useLazyRef } from '@/components/hooks/use-lazy-ref'; +import { VisuallyHiddenInput } from '@/components/visually-hidden-input'; +import { useAsRef } from '@/hooks/use-as-ref'; +import { useIsomorphicLayoutEffect } from '@/hooks/use-isomorphic-layout-effect'; +import { useLazyRef } from '@/hooks/use-lazy-ref'; import { useComposedRefs } from '@/lib/component-refs'; import { cn } from '@/lib/utils'; -import { VisuallyHiddenInput } from '../visually-hidden-input'; type Direction = 'ltr' | 'rtl'; type Orientation = 'horizontal' | 'vertical'; diff --git a/registry/components/registry.json b/registry/components/registry.json new file mode 100644 index 0000000..cccc8d0 --- /dev/null +++ b/registry/components/registry.json @@ -0,0 +1,443 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry.json", + "items": [ + { + "description": "Configuration constants and translations for data table components.", + "files": [ + { + "path": "data-table/data-table-config.ts", + "target": "@components/data-table/data-table-config.ts", + "type": "registry:component" + } + ], + "name": "data-table-config", + "title": "Data Table Config", + "type": "registry:component" + }, + { + "description": "TypeScript type definitions for data table column filters and options.", + "files": [ + { + "path": "data-table/types.ts", + "target": "@components/data-table/types.ts", + "type": "registry:component" + } + ], + "name": "data-table-types", + "registryDependencies": ["@kombase/data-table-config"], + "title": "Data Table Types", + "type": "registry:component" + }, + { + "dependencies": ["@tanstack/react-table"], + "description": "Pagination controls with page size selector for data tables.", + "files": [ + { + "path": "data-table/data-table-pagination.tsx", + "target": "@components/data-table/data-table-pagination.tsx", + "type": "registry:component" + } + ], + "name": "data-table-pagination", + "registryDependencies": ["button", "select", "@kombase/lib-pagination", "@kombase/utils"], + "title": "Data Table Pagination", + "type": "registry:component" + }, + { + "description": "Min/max range filter with debounced inputs for numeric columns.", + "files": [ + { + "path": "data-table/data-table-range-filter.tsx", + "target": "@components/data-table/data-table-range-filter.tsx", + "type": "registry:component" + } + ], + "name": "data-table-range-filter", + "registryDependencies": ["@kombase/debounced-input"], + "title": "Data Table Range Filter", + "type": "registry:component" + }, + { + "description": "Loading skeleton placeholder for data tables.", + "files": [ + { + "path": "data-table/data-table-skeleton.tsx", + "target": "@components/data-table/data-table-skeleton.tsx", + "type": "registry:component" + } + ], + "name": "data-table-skeleton", + "registryDependencies": ["skeleton", "table"], + "title": "Data Table Skeleton", + "type": "registry:component" + }, + { + "description": "Date range filter with calendar presets for data table columns.", + "files": [ + { + "path": "data-table/data-table-date-filter.tsx", + "target": "@components/data-table/data-table-date-filter.tsx", + "type": "registry:component" + } + ], + "name": "data-table-date-filter", + "registryDependencies": ["button", "calendar", "popover", "separator", "@kombase/lib-date"], + "title": "Data Table Date Filter", + "type": "registry:component" + }, + { + "description": "Multi-select faceted filter with search for categorical columns.", + "files": [ + { + "path": "data-table/data-table-faceted-filter.tsx", + "target": "@components/data-table/data-table-faceted-filter.tsx", + "type": "registry:component" + } + ], + "name": "data-table-faceted-filter", + "registryDependencies": ["badge", "button", "command", "popover", "separator"], + "title": "Data Table Faceted Filter", + "type": "registry:component" + }, + { + "description": "Numeric range filter with slider input for data table columns.", + "files": [ + { + "path": "data-table/data-table-slider-filter.tsx", + "target": "@components/data-table/data-table-slider-filter.tsx", + "type": "registry:component" + } + ], + "name": "data-table-slider-filter", + "registryDependencies": ["button", "input", "label", "popover", "separator", "slider"], + "title": "Data Table Slider Filter", + "type": "registry:component" + }, + { + "description": "Column visibility toggle menu for data tables.", + "files": [ + { + "path": "data-table/data-table-view-options.tsx", + "target": "@components/data-table/data-table-view-options.tsx", + "type": "registry:component" + } + ], + "name": "data-table-view-options", + "registryDependencies": ["button", "command", "popover"], + "title": "Data Table View Options", + "type": "registry:component" + }, + { + "description": "Toolbar with search, filters, and view options for data tables.", + "files": [ + { + "path": "data-table/data-table-toolbar.tsx", + "target": "@components/data-table/data-table-toolbar.tsx", + "type": "registry:component" + } + ], + "name": "data-table-toolbar", + "registryDependencies": [ + "button", + "@kombase/debounced-input", + "@kombase/data-table-date-filter", + "@kombase/data-table-faceted-filter", + "@kombase/data-table-slider-filter", + "@kombase/data-table-view-options" + ], + "title": "Data Table Toolbar", + "type": "registry:component" + }, + { + "description": "Action bar for batch operations on selected table rows.", + "files": [ + { + "path": "data-table/data-table-bulk-action.tsx", + "target": "@components/data-table/data-table-bulk-action.tsx", + "type": "registry:component" + } + ], + "name": "data-table-bulk-action", + "registryDependencies": ["badge", "button", "separator", "tooltip"], + "title": "Data Table Bulk Action", + "type": "registry:component" + }, + { + "description": "Sortable column header with dropdown menu for data tables.", + "files": [ + { + "path": "data-table/data-table-column-header.tsx", + "target": "@components/data-table/data-table-column-header.tsx", + "type": "registry:component" + } + ], + "name": "data-table-column-header", + "registryDependencies": ["dropdown-menu"], + "title": "Data Table Column Header", + "type": "registry:component" + }, + { + "description": "Advanced multi-column filter panel with custom operators and debounced input.", + "files": [ + { + "path": "data-table/data-table-advance-filter.tsx", + "target": "@components/data-table/data-table-advance-filter.tsx", + "type": "registry:component" + } + ], + "name": "data-table-advance-filter", + "registryDependencies": [ + "@kombase/lib-data-table", + "badge", + "button", + "calendar", + "command", + "popover", + "select", + "@kombase/data-table-config", + "@kombase/data-table-date-filter", + "@kombase/data-table-range-filter" + ], + "title": "Data Table Advanced Filter", + "type": "registry:component" + }, + { + "dependencies": ["@tanstack/react-table"], + "description": "Full-featured data table with sorting, filtering, pagination, and row selection powered by TanStack Table.", + "files": [ + { + "path": "data-table/data-table.tsx", + "target": "@components/data-table/data-table.tsx", + "type": "registry:component" + } + ], + "name": "data-table", + "registryDependencies": [ + "table", + "@kombase/data-table-config", + "@kombase/data-table-types", + "@kombase/data-table-pagination", + "@kombase/data-table-toolbar", + "@kombase/data-table-advance-filter", + "@kombase/data-table-bulk-action", + "@kombase/data-table-column-header", + "@kombase/data-table-skeleton", + "@kombase/lib-data-table", + "@kombase/use-data-table" + ], + "title": "Data Table", + "type": "registry:component" + }, + { + "dependencies": ["@radix-ui/react-direction"], + "description": "A fixed bottom bar for contextual actions with animated entrance.", + "files": [ + { + "path": "action-bar.tsx", + "target": "@components/action-bar.tsx", + "type": "registry:component" + } + ], + "name": "action-bar", + "registryDependencies": [ + "button", + "@kombase/component-refs", + "@kombase/use-as-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "title": "Action Bar", + "type": "registry:component" + }, + { + "dependencies": ["@radix-ui/react-avatar"], + "description": "Grouped avatar display with overflow counter and RTL support.", + "files": [ + { + "path": "avatar-group.tsx", + "target": "@components/avatar-group.tsx", + "type": "registry:component" + } + ], + "name": "avatar-group", + "title": "Avatar Group", + "type": "registry:component" + }, + { + "description": "A confirmation modal with customizable actions and descriptions.", + "files": [ + { + "path": "confirm-dialog.tsx", + "target": "@components/confirm-dialog.tsx", + "type": "registry:component" + } + ], + "name": "confirm-dialog", + "registryDependencies": ["alert-dialog", "button"], + "title": "Confirm Dialog", + "type": "registry:component" + }, + { + "description": "Truncated text with tooltip or popover for viewing full content.", + "files": [ + { + "path": "long-text.tsx", + "target": "@components/long-text.tsx", + "type": "registry:component" + } + ], + "name": "long-text", + "registryDependencies": ["popover", "tooltip"], + "title": "Long Text", + "type": "registry:component" + }, + { + "description": "International phone number input with country code selector and flag icons.", + "files": [ + { + "path": "phone-input.tsx", + "target": "@components/phone-input.tsx", + "type": "registry:component" + } + ], + "name": "phone-input", + "registryDependencies": [ + "command", + "input", + "popover", + "@kombase/component-refs", + "@kombase/visually-hidden-input", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "title": "Phone Input", + "type": "registry:component" + }, + { + "dependencies": ["@radix-ui/react-direction"], + "description": "Customizable star rating component with keyboard navigation and form integration.", + "files": [ + { + "path": "rating.tsx", + "target": "@components/rating.tsx", + "type": "registry:component" + } + ], + "name": "rating", + "registryDependencies": [ + "@kombase/component-refs", + "@kombase/visually-hidden-input", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "title": "Rating", + "type": "registry:component" + }, + { + "dependencies": ["@radix-ui/react-direction"], + "description": "Multi-step progress indicator with vertical layout and form validation support.", + "files": [ + { + "path": "stepper.tsx", + "target": "@components/stepper.tsx", + "type": "registry:component" + } + ], + "name": "stepper", + "registryDependencies": [ + "@kombase/component-refs", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "title": "Stepper", + "type": "registry:component" + }, + { + "dependencies": ["@radix-ui/react-direction", "class-variance-authority"], + "description": "Chronological event timeline with alternate positioning and horizontal layout.", + "files": [ + { + "path": "timeline.tsx", + "target": "@components/timeline.tsx", + "type": "registry:component" + } + ], + "name": "timeline", + "registryDependencies": [ + "@kombase/component-refs", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "title": "Timeline", + "type": "registry:component" + }, + { + "dependencies": ["@floating-ui/react-dom", "@radix-ui/react-direction"], + "description": "Step-by-step onboarding tour with spotlight highlighting and floating UI.", + "files": [ + { + "path": "tour.tsx", + "target": "@components/tour.tsx", + "type": "registry:component" + } + ], + "name": "tour", + "registryDependencies": [ + "button", + "@kombase/component-refs", + "@kombase/use-as-ref", + "@kombase/use-callback-ref", + "@kombase/use-isomorphic-layout-effect" + ], + "title": "Tour", + "type": "registry:component" + }, + { + "description": "An invisible input for form integration with custom controls.", + "files": [ + { + "path": "visually-hidden-input.tsx", + "target": "@components/visually-hidden-input.tsx", + "type": "registry:component" + } + ], + "name": "visually-hidden-input", + "title": "Visually Hidden Input", + "type": "registry:component" + }, + { + "dependencies": ["lucide-react"], + "description": "A password field with show/hide toggle button.", + "files": [ + { + "path": "password-input.tsx", + "target": "@components/password-input.tsx", + "type": "registry:component" + } + ], + "name": "password-input", + "registryDependencies": ["button", "@kombase/utils"], + "title": "Password Input", + "type": "registry:component" + }, + { + "dependencies": ["lucide-react"], + "description": "A form-integrated select dropdown with loading state support.", + "files": [ + { + "path": "select-dropdown.tsx", + "target": "@components/select-dropdown.tsx", + "type": "registry:component" + } + ], + "name": "select-dropdown", + "registryDependencies": ["form", "select", "@kombase/utils"], + "title": "Select Dropdown", + "type": "registry:component" + } + ], + "name": "kombase-components" +} diff --git a/packages/src/components/select-dropdown.tsx b/registry/components/select-dropdown.tsx similarity index 100% rename from packages/src/components/select-dropdown.tsx rename to registry/components/select-dropdown.tsx diff --git a/packages/src/components/dynamic/stepper.tsx b/registry/components/stepper.tsx similarity index 99% rename from packages/src/components/dynamic/stepper.tsx rename to registry/components/stepper.tsx index 85d2214..19d4b1b 100644 --- a/packages/src/components/dynamic/stepper.tsx +++ b/registry/components/stepper.tsx @@ -4,9 +4,9 @@ import * as DirectionPrimitive from '@radix-ui/react-direction'; import * as SlotPrimitive from '@radix-ui/react-slot'; import { Check } from 'lucide-react'; import * as React from 'react'; -import { useAsRef } from '@/components/hooks/use-as-ref'; -import { useIsomorphicLayoutEffect } from '@/components/hooks/use-isomorphic-layout-effect'; -import { useLazyRef } from '@/components/hooks/use-lazy-ref'; +import { useAsRef } from '@/hooks/use-as-ref'; +import { useIsomorphicLayoutEffect } from '@/hooks/use-isomorphic-layout-effect'; +import { useLazyRef } from '@/hooks/use-lazy-ref'; import { useComposedRefs } from '@/lib/component-refs'; import { cn } from '@/lib/utils'; diff --git a/packages/src/components/dynamic/timeline.tsx b/registry/components/timeline.tsx similarity index 99% rename from packages/src/components/dynamic/timeline.tsx rename to registry/components/timeline.tsx index 751ba3d..07a7c91 100644 --- a/packages/src/components/dynamic/timeline.tsx +++ b/registry/components/timeline.tsx @@ -4,8 +4,8 @@ import * as DirectionPrimitive from '@radix-ui/react-direction'; import * as SlotPrimitive from '@radix-ui/react-slot'; import { cva } from 'class-variance-authority'; import * as React from 'react'; -import { useIsomorphicLayoutEffect } from '@/components/hooks/use-isomorphic-layout-effect'; -import { useLazyRef } from '@/components/hooks/use-lazy-ref'; +import { useIsomorphicLayoutEffect } from '@/hooks/use-isomorphic-layout-effect'; +import { useLazyRef } from '@/hooks/use-lazy-ref'; import { useComposedRefs } from '@/lib/component-refs'; import { cn } from '@/lib/utils'; diff --git a/packages/src/components/dynamic/tour.tsx b/registry/components/tour.tsx similarity index 99% rename from packages/src/components/dynamic/tour.tsx rename to registry/components/tour.tsx index 61d37a8..bafa1be 100644 --- a/packages/src/components/dynamic/tour.tsx +++ b/registry/components/tour.tsx @@ -15,10 +15,10 @@ import * as SlotPrimitive from '@radix-ui/react-slot'; import { ChevronLeft, ChevronRight, X } from 'lucide-react'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; -import { useAsRef } from '@/components/hooks/use-as-ref'; -import { useIsomorphicLayoutEffect } from '@/components/hooks/use-isomorphic-layout-effect'; -import { useLazyRef } from '@/components/hooks/use-lazy-ref'; import { Button } from '@/components/ui/button'; +import { useAsRef } from '@/hooks/use-as-ref'; +import { useIsomorphicLayoutEffect } from '@/hooks/use-isomorphic-layout-effect'; +import { useLazyRef } from '@/hooks/use-lazy-ref'; import { useComposedRefs } from '@/lib/component-refs'; import { cn } from '@/lib/utils'; diff --git a/packages/src/components/visually-hidden-input.tsx b/registry/components/visually-hidden-input.tsx similarity index 100% rename from packages/src/components/visually-hidden-input.tsx rename to registry/components/visually-hidden-input.tsx diff --git a/packages/src/components/form/form-date-picker.tsx b/registry/form/form-date-picker.tsx similarity index 100% rename from packages/src/components/form/form-date-picker.tsx rename to registry/form/form-date-picker.tsx diff --git a/packages/src/components/form/form-input-group.tsx b/registry/form/form-input-group.tsx similarity index 100% rename from packages/src/components/form/form-input-group.tsx rename to registry/form/form-input-group.tsx diff --git a/packages/src/components/form/form-input.tsx b/registry/form/form-input.tsx similarity index 100% rename from packages/src/components/form/form-input.tsx rename to registry/form/form-input.tsx diff --git a/packages/src/components/form/form-password.tsx b/registry/form/form-password.tsx similarity index 90% rename from packages/src/components/form/form-password.tsx rename to registry/form/form-password.tsx index 66662c6..7600f3b 100644 --- a/packages/src/components/form/form-password.tsx +++ b/registry/form/form-password.tsx @@ -1,16 +1,15 @@ import type React from 'react'; import type { Control, FieldPath, FieldValues } from 'react-hook-form'; +import { PasswordInput } from '@/components/password-input'; import { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form'; -import type { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { cn } from '@/lib/utils'; -import { PasswordInput } from '../password-input'; type FormInputPasswordProps = { control: Control; name: FieldPath; label?: string | React.ReactNode; - inputProps?: React.ComponentProps; + inputProps?: React.ComponentProps; layout?: 'vertical' | 'horizontal'; className?: string; labelClassName?: string; diff --git a/packages/src/components/form/form-phone-input.tsx b/registry/form/form-phone-input.tsx similarity index 94% rename from packages/src/components/form/form-phone-input.tsx rename to registry/form/form-phone-input.tsx index bcae01e..b6054d3 100644 --- a/packages/src/components/form/form-phone-input.tsx +++ b/registry/form/form-phone-input.tsx @@ -1,10 +1,6 @@ import type React from 'react'; import type { Control, FieldPath, FieldValues } from 'react-hook-form'; -import { - PhoneInput, - PhoneInputCountrySelect, - PhoneInputField, -} from '@/components/dynamic/phone-input'; +import { PhoneInput, PhoneInputCountrySelect, PhoneInputField } from '@/components/phone-input'; import { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form'; import { Label } from '@/components/ui/label'; import { cn } from '@/lib/utils'; diff --git a/packages/src/components/form/form-pick.tsx b/registry/form/form-pick.tsx similarity index 100% rename from packages/src/components/form/form-pick.tsx rename to registry/form/form-pick.tsx diff --git a/packages/src/components/form/form-radio.tsx b/registry/form/form-radio.tsx similarity index 100% rename from packages/src/components/form/form-radio.tsx rename to registry/form/form-radio.tsx diff --git a/packages/src/components/form/form-seach-select.tsx b/registry/form/form-search-select.tsx similarity index 100% rename from packages/src/components/form/form-seach-select.tsx rename to registry/form/form-search-select.tsx diff --git a/packages/src/components/form/form-textarea.tsx b/registry/form/form-textarea.tsx similarity index 100% rename from packages/src/components/form/form-textarea.tsx rename to registry/form/form-textarea.tsx diff --git a/packages/src/components/form/form-upload.tsx b/registry/form/form-upload.tsx similarity index 100% rename from packages/src/components/form/form-upload.tsx rename to registry/form/form-upload.tsx diff --git a/registry/form/registry.json b/registry/form/registry.json new file mode 100644 index 0000000..02a41c9 --- /dev/null +++ b/registry/form/registry.json @@ -0,0 +1,147 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry.json", + "items": [ + { + "dependencies": ["dayjs"], + "description": "Date picker with calendar popup, integrated with react-hook-form.", + "files": [ + { + "path": "form-date-picker.tsx", + "target": "@components/form/form-date-picker.tsx", + "type": "registry:component" + } + ], + "name": "form-date-picker", + "registryDependencies": ["calendar", "form", "label", "popover"], + "title": "Form Date Picker", + "type": "registry:component" + }, + { + "description": "Text input with label and validation, integrated with react-hook-form.", + "files": [ + { + "path": "form-input.tsx", + "target": "@components/form/form-input.tsx", + "type": "registry:component" + } + ], + "name": "form-input", + "registryDependencies": ["form", "input", "label"], + "title": "Form Input", + "type": "registry:component" + }, + { + "description": "Input group with prefix/suffix addons, integrated with react-hook-form.", + "files": [ + { + "path": "form-input-group.tsx", + "target": "@components/form/form-input-group.tsx", + "type": "registry:component" + } + ], + "name": "form-input-group", + "registryDependencies": ["form", "@kombase/input-group", "label"], + "title": "Form Input Group", + "type": "registry:component" + }, + { + "description": "Password input with show/hide toggle, integrated with react-hook-form.", + "files": [ + { + "path": "form-password.tsx", + "target": "@components/form/form-password.tsx", + "type": "registry:component" + } + ], + "name": "form-password", + "registryDependencies": ["form", "label", "@kombase/password-input"], + "title": "Form Password", + "type": "registry:component" + }, + { + "description": "International phone number input with country select, integrated with react-hook-form.", + "files": [ + { + "path": "form-phone-input.tsx", + "target": "@components/form/form-phone-input.tsx", + "type": "registry:component" + } + ], + "name": "form-phone-input", + "registryDependencies": ["form", "label", "@kombase/phone-input"], + "title": "Form Phone Input", + "type": "registry:component" + }, + { + "description": "Card-style single selection input, integrated with react-hook-form.", + "files": [ + { + "path": "form-pick.tsx", + "target": "@components/form/form-pick.tsx", + "type": "registry:component" + } + ], + "name": "form-pick", + "registryDependencies": ["form", "label", "radio-group"], + "title": "Form Pick", + "type": "registry:component" + }, + { + "description": "Radio button group with label and layout options, integrated with react-hook-form.", + "files": [ + { + "path": "form-radio.tsx", + "target": "@components/form/form-radio.tsx", + "type": "registry:component" + } + ], + "name": "form-radio", + "registryDependencies": ["form", "label", "radio-group"], + "title": "Form Radio", + "type": "registry:component" + }, + { + "description": "Searchable combobox select with async data support, integrated with react-hook-form.", + "files": [ + { + "path": "form-search-select.tsx", + "target": "@components/form/form-search-select.tsx", + "type": "registry:component" + } + ], + "name": "form-search-select", + "registryDependencies": ["combobox", "form", "label"], + "title": "Form Search Select", + "type": "registry:component" + }, + { + "description": "Multi-line text area with character count and validation, integrated with react-hook-form.", + "files": [ + { + "path": "form-textarea.tsx", + "target": "@components/form/form-textarea.tsx", + "type": "registry:component" + } + ], + "name": "form-textarea", + "registryDependencies": ["form", "label", "textarea"], + "title": "Form Textarea", + "type": "registry:component" + }, + { + "description": "File upload with drag-and-drop and progress tracking, integrated with react-hook-form.", + "files": [ + { + "path": "form-upload.tsx", + "target": "@components/form/form-upload.tsx", + "type": "registry:component" + } + ], + "name": "form-upload", + "registryDependencies": ["form", "label", "@kombase/file-upload"], + "title": "Form Upload", + "type": "registry:component" + } + ], + "name": "kombase-form" +} diff --git a/registry/hooks/registry.json b/registry/hooks/registry.json new file mode 100644 index 0000000..37b9f7a --- /dev/null +++ b/registry/hooks/registry.json @@ -0,0 +1,92 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry.json", + "items": [ + { + "description": "Keeps a mutable ref synchronized with the latest value using layout effect.", + "files": [ + { + "path": "use-as-ref.ts", + "target": "@hooks/use-as-ref.ts", + "type": "registry:hook" + } + ], + "name": "use-as-ref", + "registryDependencies": ["@kombase/use-isomorphic-layout-effect"], + "title": "useAsRef", + "type": "registry:hook" + }, + { + "description": "Creates a stable callback ref that always calls the latest function.", + "files": [ + { + "path": "use-callback-ref.ts", + "target": "@hooks/use-callback-ref.ts", + "type": "registry:hook" + } + ], + "name": "use-callback-ref", + "title": "useCallbackRef", + "type": "registry:hook" + }, + { + "dependencies": ["@tanstack/react-table"], + "description": "Hook for managing data table state, filtering, sorting, and pagination with TanStack Table.", + "files": [ + { + "path": "use-data-table.ts", + "target": "@hooks/use-data-table.ts", + "type": "registry:hook" + } + ], + "name": "use-data-table", + "registryDependencies": [ + "@kombase/data-table-config", + "@kombase/data-table-types", + "@kombase/lib-data-table" + ], + "title": "useDataTable", + "type": "registry:hook" + }, + { + "description": "Returns a debounced version of a callback function with configurable delay.", + "files": [ + { + "path": "use-debounced-callback.ts", + "target": "@hooks/use-debounced-callback.ts", + "type": "registry:hook" + } + ], + "name": "use-debounced-callback", + "registryDependencies": ["@kombase/use-callback-ref"], + "title": "useDebouncedCallback", + "type": "registry:hook" + }, + { + "description": "SSR-safe wrapper around useLayoutEffect that falls back to useEffect on the server.", + "files": [ + { + "path": "use-isomorphic-layout-effect.ts", + "target": "@hooks/use-isomorphic-layout-effect.ts", + "type": "registry:hook" + } + ], + "name": "use-isomorphic-layout-effect", + "title": "useIsomorphicLayoutEffect", + "type": "registry:hook" + }, + { + "description": "Creates a ref that is lazily initialized on first access.", + "files": [ + { + "path": "use-lazy-ref.ts", + "target": "@hooks/use-lazy-ref.ts", + "type": "registry:hook" + } + ], + "name": "use-lazy-ref", + "title": "useLazyRef", + "type": "registry:hook" + } + ], + "name": "kombase-hooks" +} diff --git a/packages/src/components/hooks/use-as-ref.ts b/registry/hooks/use-as-ref.ts similarity index 72% rename from packages/src/components/hooks/use-as-ref.ts rename to registry/hooks/use-as-ref.ts index 6a8c52d..9c8d508 100644 --- a/packages/src/components/hooks/use-as-ref.ts +++ b/registry/hooks/use-as-ref.ts @@ -1,6 +1,6 @@ import * as React from 'react'; -import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect'; +import { useIsomorphicLayoutEffect } from '@/hooks/use-isomorphic-layout-effect'; function useAsRef(props: T) { const ref = React.useRef(props); diff --git a/packages/src/components/hooks/use-callback-ref.ts b/registry/hooks/use-callback-ref.ts similarity index 100% rename from packages/src/components/hooks/use-callback-ref.ts rename to registry/hooks/use-callback-ref.ts diff --git a/packages/src/components/hooks/use-data-table.ts b/registry/hooks/use-data-table.ts similarity index 100% rename from packages/src/components/hooks/use-data-table.ts rename to registry/hooks/use-data-table.ts diff --git a/packages/src/components/hooks/use-debounced-callback.ts b/registry/hooks/use-debounced-callback.ts similarity index 91% rename from packages/src/components/hooks/use-debounced-callback.ts rename to registry/hooks/use-debounced-callback.ts index ef79b78..4a6f0bd 100644 --- a/packages/src/components/hooks/use-debounced-callback.ts +++ b/registry/hooks/use-debounced-callback.ts @@ -1,6 +1,6 @@ import * as React from 'react'; -import { useCallbackRef } from './use-callback-ref'; +import { useCallbackRef } from '@/hooks/use-callback-ref'; export function useDebouncedCallback unknown>( callback: T, diff --git a/packages/src/components/hooks/use-isomorphic-layout-effect.ts b/registry/hooks/use-isomorphic-layout-effect.ts similarity index 100% rename from packages/src/components/hooks/use-isomorphic-layout-effect.ts rename to registry/hooks/use-isomorphic-layout-effect.ts diff --git a/packages/src/components/hooks/use-lazy-ref.ts b/registry/hooks/use-lazy-ref.ts similarity index 100% rename from packages/src/components/hooks/use-lazy-ref.ts rename to registry/hooks/use-lazy-ref.ts diff --git a/packages/src/lib/component-refs.ts b/registry/lib/component-refs.ts similarity index 100% rename from packages/src/lib/component-refs.ts rename to registry/lib/component-refs.ts diff --git a/packages/src/lib/data-table.ts b/registry/lib/data-table.ts similarity index 95% rename from packages/src/lib/data-table.ts rename to registry/lib/data-table.ts index 9d14f48..48567f9 100644 --- a/packages/src/lib/data-table.ts +++ b/registry/lib/data-table.ts @@ -1,10 +1,10 @@ import type { Column } from '@tanstack/react-table'; -import { dataTableConfig } from '../components/data-table/data-table-config'; +import { dataTableConfig } from '@/components/data-table/data-table-config'; import type { ExtendedColumnFilter, FilterOperator, FilterVariant, -} from '../components/data-table/types'; +} from '@/components/data-table/types'; export function getCommonPinningStyles({ column, diff --git a/packages/src/lib/date.ts b/registry/lib/date.ts similarity index 100% rename from packages/src/lib/date.ts rename to registry/lib/date.ts diff --git a/packages/src/lib/filter-helper.ts b/registry/lib/filter-helper.ts similarity index 99% rename from packages/src/lib/filter-helper.ts rename to registry/lib/filter-helper.ts index 501e444..c1e1e49 100644 --- a/packages/src/lib/filter-helper.ts +++ b/registry/lib/filter-helper.ts @@ -3,7 +3,7 @@ import type { ExtendedColumnFilter, FilterOperator, JoinOperator, -} from '../components/data-table/types'; +} from '@/components/data-table/types'; const DEFAULT_OPERATORS: Record = { eq: 'eq', diff --git a/packages/src/lib/pagination.ts b/registry/lib/pagination.ts similarity index 100% rename from packages/src/lib/pagination.ts rename to registry/lib/pagination.ts diff --git a/registry/lib/registry.json b/registry/lib/registry.json new file mode 100644 index 0000000..d1dacc3 --- /dev/null +++ b/registry/lib/registry.json @@ -0,0 +1,90 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry.json", + "items": [ + { + "dependencies": ["clsx", "tailwind-merge"], + "description": "Core utility function (cn) for merging Tailwind CSS classes.", + "files": [ + { + "path": "utils.ts", + "target": "@lib/utils.ts", + "type": "registry:lib" + } + ], + "name": "utils", + "title": "Utilities", + "type": "registry:lib" + }, + { + "description": "Ref composition utilities for combining multiple React refs.", + "files": [ + { + "path": "component-refs.ts", + "target": "@lib/component-refs.ts", + "type": "registry:lib" + } + ], + "name": "component-refs", + "title": "Component Refs", + "type": "registry:lib" + }, + { + "dependencies": ["@tanstack/react-table"], + "description": "Helper functions for data table filter validation and parsing.", + "files": [ + { + "path": "data-table.ts", + "target": "@lib/data-table.ts", + "type": "registry:lib" + } + ], + "name": "lib-data-table", + "registryDependencies": ["@kombase/data-table-config", "@kombase/data-table-types"], + "title": "Data Table Helpers", + "type": "registry:lib" + }, + { + "dependencies": ["dayjs", "react-day-picker"], + "description": "Date formatting and range utilities using dayjs and react-day-picker.", + "files": [ + { + "path": "date.ts", + "target": "@lib/date.ts", + "type": "registry:lib" + } + ], + "name": "lib-date", + "title": "Date Helpers", + "type": "registry:lib" + }, + { + "description": "Pagination range calculation utilities for data table pagination.", + "files": [ + { + "path": "pagination.ts", + "target": "@lib/pagination.ts", + "type": "registry:lib" + } + ], + "name": "lib-pagination", + "title": "Pagination Helpers", + "type": "registry:lib" + }, + { + "dependencies": ["dayjs"], + "description": "Serializes data table filters to backend query parameters with multiple format styles.", + "files": [ + { + "path": "filter-helper.ts", + "target": "@lib/filter-helper.ts", + "type": "registry:lib" + } + ], + "name": "filter-helper", + "registryDependencies": ["@kombase/data-table-types"], + "title": "Filter Helper", + "type": "registry:lib" + } + ], + "name": "kombase-lib" +} diff --git a/packages/src/lib/utils.ts b/registry/lib/utils.ts similarity index 100% rename from packages/src/lib/utils.ts rename to registry/lib/utils.ts diff --git a/packages/tsconfig.json b/registry/tsconfig.json similarity index 59% rename from packages/tsconfig.json rename to registry/tsconfig.json index cca91cb..f636500 100644 --- a/packages/tsconfig.json +++ b/registry/tsconfig.json @@ -1,31 +1,29 @@ { "compilerOptions": { "allowJs": true, - /* AND if you're building for a library: */ + "baseUrl": "..", "declaration": true, - /* Base Options: */ "esModuleInterop": true, - "ignoreDeprecations": "6.0", - "isolatedModules": true, "jsx": "react-jsx", - /* If your code runs in the DOM: */ "lib": ["es2022", "dom", "dom.iterable"], - /* If transpiling with TypeScript: */ "module": "esnext", "moduleDetection": "force", "moduleResolution": "bundler", "noImplicitOverride": true, "noUncheckedIndexedAccess": true, "paths": { - "@/*": ["./src/*"] + "@/components/*": ["registry/components/*"], + "@/components/form/*": ["registry/form/*"], + "@/components/ui/*": ["registry/ui/*"], + "@/hooks/*": ["registry/hooks/*"], + "@/lib/*": ["registry/lib/*"] }, "resolveJsonModule": true, "skipLibCheck": true, "sourceMap": true, - /* Strictness */ "strict": true, "target": "es2022", - "types": ["react"], - "verbatimModuleSyntax": true - } + "types": ["react"] + }, + "include": ["**/*"] } diff --git a/packages/src/components/ui/alert-dialog.tsx b/registry/ui/alert-dialog.tsx similarity index 98% rename from packages/src/components/ui/alert-dialog.tsx rename to registry/ui/alert-dialog.tsx index 57d2a4d..d55d322 100644 --- a/packages/src/components/ui/alert-dialog.tsx +++ b/registry/ui/alert-dialog.tsx @@ -2,8 +2,8 @@ import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'; import React from 'react'; +import { buttonVariants } from '@/components/ui/button'; import { cn } from '@/lib/utils'; -import { buttonVariants } from './button'; const AlertDialog = ({ ...props }: React.ComponentProps) => ( diff --git a/packages/src/components/ui/alert.tsx b/registry/ui/alert.tsx similarity index 100% rename from packages/src/components/ui/alert.tsx rename to registry/ui/alert.tsx diff --git a/packages/src/components/ui/avatar.tsx b/registry/ui/avatar.tsx similarity index 100% rename from packages/src/components/ui/avatar.tsx rename to registry/ui/avatar.tsx diff --git a/packages/src/components/ui/badge.tsx b/registry/ui/badge.tsx similarity index 100% rename from packages/src/components/ui/badge.tsx rename to registry/ui/badge.tsx diff --git a/packages/src/components/ui/button.tsx b/registry/ui/button.tsx similarity index 100% rename from packages/src/components/ui/button.tsx rename to registry/ui/button.tsx diff --git a/packages/src/components/ui/calendar.tsx b/registry/ui/calendar.tsx similarity index 100% rename from packages/src/components/ui/calendar.tsx rename to registry/ui/calendar.tsx diff --git a/packages/src/components/ui/checkbox.tsx b/registry/ui/checkbox.tsx similarity index 100% rename from packages/src/components/ui/checkbox.tsx rename to registry/ui/checkbox.tsx diff --git a/packages/src/components/ui/collapsible.tsx b/registry/ui/collapsible.tsx similarity index 100% rename from packages/src/components/ui/collapsible.tsx rename to registry/ui/collapsible.tsx diff --git a/packages/src/components/ui/combobox.tsx b/registry/ui/combobox.tsx similarity index 100% rename from packages/src/components/ui/combobox.tsx rename to registry/ui/combobox.tsx diff --git a/packages/src/components/ui/command.tsx b/registry/ui/command.tsx similarity index 97% rename from packages/src/components/ui/command.tsx rename to registry/ui/command.tsx index 91c7277..05f7c5d 100644 --- a/packages/src/components/ui/command.tsx +++ b/registry/ui/command.tsx @@ -3,8 +3,14 @@ import { Command as CommandPrimitive } from 'cmdk'; import { SearchIcon } from 'lucide-react'; import * as React from 'react'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; import { cn } from '@/lib/utils'; -import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from './dialog'; const Command = React.forwardRef< React.ComponentRef, diff --git a/packages/src/components/ui/debounced-input.tsx b/registry/ui/debounced-input.tsx similarity index 95% rename from packages/src/components/ui/debounced-input.tsx rename to registry/ui/debounced-input.tsx index 56b45d0..3d93630 100644 --- a/packages/src/components/ui/debounced-input.tsx +++ b/registry/ui/debounced-input.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Input } from './input'; +import { Input } from '@/components/ui/input'; export interface DebouncedInputProps extends Omit, 'onChange' | 'value'> { diff --git a/packages/src/components/ui/dialog.tsx b/registry/ui/dialog.tsx similarity index 100% rename from packages/src/components/ui/dialog.tsx rename to registry/ui/dialog.tsx diff --git a/packages/src/components/ui/dropdown-menu.tsx b/registry/ui/dropdown-menu.tsx similarity index 100% rename from packages/src/components/ui/dropdown-menu.tsx rename to registry/ui/dropdown-menu.tsx diff --git a/packages/src/components/ui/file-upload.tsx b/registry/ui/file-upload.tsx similarity index 99% rename from packages/src/components/ui/file-upload.tsx rename to registry/ui/file-upload.tsx index b7354ca..f168395 100644 --- a/packages/src/components/ui/file-upload.tsx +++ b/registry/ui/file-upload.tsx @@ -11,9 +11,9 @@ import { } from 'lucide-react'; import { Direction as DirectionPrimitive, Slot as SlotPrimitive } from 'radix-ui'; import * as React from 'react'; +import { useAsRef } from '@/hooks/use-as-ref'; +import { useLazyRef } from '@/hooks/use-lazy-ref'; import { cn } from '@/lib/utils'; -import { useAsRef } from '../hooks/use-as-ref'; -import { useLazyRef } from '../hooks/use-lazy-ref'; const ROOT_NAME = 'FileUpload'; const DROPZONE_NAME = 'FileUploadDropzone'; diff --git a/packages/src/components/ui/form.tsx b/registry/ui/form.tsx similarity index 100% rename from packages/src/components/ui/form.tsx rename to registry/ui/form.tsx diff --git a/packages/src/components/ui/input-group.tsx b/registry/ui/input-group.tsx similarity index 97% rename from packages/src/components/ui/input-group.tsx rename to registry/ui/input-group.tsx index 0661498..442dbee 100644 --- a/packages/src/components/ui/input-group.tsx +++ b/registry/ui/input-group.tsx @@ -2,10 +2,10 @@ import { cva, type VariantProps } from 'class-variance-authority'; import * as React from 'react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Textarea } from '@/components/ui/textarea'; import { cn } from '@/lib/utils'; -import { Button } from './button'; -import { Input } from './input'; -import { Textarea } from './textarea'; const InputGroup = React.forwardRef>( ({ className, ...props }, ref) => { diff --git a/packages/src/components/ui/input.tsx b/registry/ui/input.tsx similarity index 100% rename from packages/src/components/ui/input.tsx rename to registry/ui/input.tsx diff --git a/packages/src/components/ui/label.tsx b/registry/ui/label.tsx similarity index 100% rename from packages/src/components/ui/label.tsx rename to registry/ui/label.tsx diff --git a/packages/src/components/ui/popover.tsx b/registry/ui/popover.tsx similarity index 100% rename from packages/src/components/ui/popover.tsx rename to registry/ui/popover.tsx diff --git a/packages/src/components/ui/radio-group.tsx b/registry/ui/radio-group.tsx similarity index 100% rename from packages/src/components/ui/radio-group.tsx rename to registry/ui/radio-group.tsx diff --git a/registry/ui/registry.json b/registry/ui/registry.json new file mode 100644 index 0000000..bc30be4 --- /dev/null +++ b/registry/ui/registry.json @@ -0,0 +1,433 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry.json", + "items": [ + { + "dependencies": ["@radix-ui/react-alert-dialog"], + "description": "A modal dialog for critical actions that requires user acknowledgment.", + "files": [ + { + "path": "alert-dialog.tsx", + "target": "@ui/alert-dialog.tsx", + "type": "registry:ui" + } + ], + "name": "alert-dialog", + "registryDependencies": ["button"], + "title": "Alert Dialog", + "type": "registry:ui" + }, + { + "description": "Displays a callout for important information with variant styles.", + "files": [ + { + "path": "alert.tsx", + "target": "@ui/alert.tsx", + "type": "registry:ui" + } + ], + "name": "alert", + "registryDependencies": ["@kombase/utils"], + "title": "Alert", + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-avatar"], + "description": "An image element with a fallback for user profile pictures.", + "files": [ + { + "path": "avatar.tsx", + "target": "@ui/avatar.tsx", + "type": "registry:ui" + } + ], + "name": "avatar", + "registryDependencies": ["@kombase/utils"], + "title": "Avatar", + "type": "registry:ui" + }, + { + "dependencies": ["class-variance-authority"], + "description": "A small status descriptor with multiple visual variants.", + "files": [ + { + "path": "badge.tsx", + "target": "@ui/badge.tsx", + "type": "registry:ui" + } + ], + "name": "badge", + "registryDependencies": ["@kombase/utils"], + "title": "Badge", + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-slot", "class-variance-authority"], + "description": "An interactive button with multiple size and variant options.", + "files": [ + { + "path": "button.tsx", + "target": "@ui/button.tsx", + "type": "registry:ui" + } + ], + "name": "button", + "registryDependencies": ["@kombase/utils"], + "title": "Button", + "type": "registry:ui" + }, + { + "dependencies": ["react-day-picker", "dayjs"], + "description": "A date picker calendar with range selection, presets, and RTL support.", + "files": [ + { + "path": "calendar.tsx", + "target": "@ui/calendar.tsx", + "type": "registry:ui" + } + ], + "name": "calendar", + "registryDependencies": ["button", "@kombase/utils"], + "title": "Calendar", + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-checkbox"], + "description": "A control for boolean input with indeterminate state support.", + "files": [ + { + "path": "checkbox.tsx", + "target": "@ui/checkbox.tsx", + "type": "registry:ui" + } + ], + "name": "checkbox", + "registryDependencies": ["@kombase/utils"], + "title": "Checkbox", + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-collapsible"], + "description": "An interactive component that expands and collapses content.", + "files": [ + { + "path": "collapsible.tsx", + "target": "@ui/collapsible.tsx", + "type": "registry:ui" + } + ], + "name": "collapsible", + "title": "Collapsible", + "type": "registry:ui" + }, + { + "dependencies": ["cmdk"], + "description": "A searchable select input with command palette integration.", + "files": [ + { + "path": "combobox.tsx", + "target": "@ui/combobox.tsx", + "type": "registry:ui" + } + ], + "name": "combobox", + "registryDependencies": ["popover", "@kombase/utils"], + "title": "Combobox", + "type": "registry:ui" + }, + { + "dependencies": ["cmdk"], + "description": "A command palette for searchable actions and navigation.", + "files": [ + { + "path": "command.tsx", + "target": "@ui/command.tsx", + "type": "registry:ui" + } + ], + "name": "command", + "registryDependencies": ["dialog", "@kombase/utils"], + "title": "Command", + "type": "registry:ui" + }, + { + "description": "An input that delays onChange calls to reduce unnecessary re-renders.", + "files": [ + { + "path": "debounced-input.tsx", + "target": "@ui/debounced-input.tsx", + "type": "registry:ui" + } + ], + "name": "debounced-input", + "registryDependencies": ["input"], + "title": "Debounced Input", + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-dialog"], + "description": "A modal overlay for focused content or forms.", + "files": [ + { + "path": "dialog.tsx", + "target": "@ui/dialog.tsx", + "type": "registry:ui" + } + ], + "name": "dialog", + "registryDependencies": ["@kombase/utils"], + "title": "Dialog", + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-dropdown-menu"], + "description": "A contextual menu triggered by a button click.", + "files": [ + { + "path": "dropdown-menu.tsx", + "target": "@ui/dropdown-menu.tsx", + "type": "registry:ui" + } + ], + "name": "dropdown-menu", + "registryDependencies": ["@kombase/utils"], + "title": "Dropdown Menu", + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-slot", "lucide-react"], + "description": "A drag-and-drop file upload component with validation, progress tracking, and paste support.", + "files": [ + { + "path": "file-upload.tsx", + "target": "@ui/file-upload.tsx", + "type": "registry:ui" + } + ], + "name": "file-upload", + "registryDependencies": ["@kombase/utils", "@kombase/use-as-ref", "@kombase/use-lazy-ref"], + "title": "File Upload", + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-form", "@radix-ui/react-label", "react-hook-form"], + "description": "Form primitives with react-hook-form integration and validation messages.", + "files": [ + { + "path": "form.tsx", + "target": "@ui/form.tsx", + "type": "registry:ui" + } + ], + "name": "form", + "registryDependencies": ["label", "@kombase/utils"], + "title": "Form", + "type": "registry:ui" + }, + { + "description": "An input with prefix/suffix addons and action buttons.", + "files": [ + { + "path": "input-group.tsx", + "target": "@ui/input-group.tsx", + "type": "registry:ui" + } + ], + "name": "input-group", + "registryDependencies": ["button", "input", "textarea", "@kombase/utils"], + "title": "Input Group", + "type": "registry:ui" + }, + { + "description": "A styled text input field.", + "files": [ + { + "path": "input.tsx", + "target": "@ui/input.tsx", + "type": "registry:ui" + } + ], + "name": "input", + "registryDependencies": ["@kombase/utils"], + "title": "Input", + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-label"], + "description": "An accessible label for form controls.", + "files": [ + { + "path": "label.tsx", + "target": "@ui/label.tsx", + "type": "registry:ui" + } + ], + "name": "label", + "registryDependencies": ["@kombase/utils"], + "title": "Label", + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-popover"], + "description": "A floating panel anchored to a trigger element.", + "files": [ + { + "path": "popover.tsx", + "target": "@ui/popover.tsx", + "type": "registry:ui" + } + ], + "name": "popover", + "registryDependencies": ["@kombase/utils"], + "title": "Popover", + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-radio-group"], + "description": "A set of radio buttons for single-option selection.", + "files": [ + { + "path": "radio-group.tsx", + "target": "@ui/radio-group.tsx", + "type": "registry:ui" + } + ], + "name": "radio-group", + "registryDependencies": ["@kombase/utils"], + "title": "Radio Group", + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-select"], + "description": "A dropdown select input with search and grouping.", + "files": [ + { + "path": "select.tsx", + "target": "@ui/select.tsx", + "type": "registry:ui" + } + ], + "name": "select", + "registryDependencies": ["@kombase/utils"], + "title": "Select", + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-separator"], + "description": "A visual divider between content sections.", + "files": [ + { + "path": "separator.tsx", + "target": "@ui/separator.tsx", + "type": "registry:ui" + } + ], + "name": "separator", + "registryDependencies": ["@kombase/utils"], + "title": "Separator", + "type": "registry:ui" + }, + { + "description": "A placeholder loading indicator.", + "files": [ + { + "path": "skeleton.tsx", + "target": "@ui/skeleton.tsx", + "type": "registry:ui" + } + ], + "name": "skeleton", + "registryDependencies": ["@kombase/utils"], + "title": "Skeleton", + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-slider"], + "description": "A range input for selecting numeric values.", + "files": [ + { + "path": "slider.tsx", + "target": "@ui/slider.tsx", + "type": "registry:ui" + } + ], + "name": "slider", + "registryDependencies": ["@kombase/utils"], + "title": "Slider", + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-switch"], + "description": "A toggle for binary on/off states.", + "files": [ + { + "path": "switch.tsx", + "target": "@ui/switch.tsx", + "type": "registry:ui" + } + ], + "name": "switch", + "registryDependencies": ["@kombase/utils"], + "title": "Switch", + "type": "registry:ui" + }, + { + "description": "A styled HTML table with header, body, and footer.", + "files": [ + { + "path": "table.tsx", + "target": "@ui/table.tsx", + "type": "registry:ui" + } + ], + "name": "table", + "registryDependencies": ["@kombase/utils"], + "title": "Table", + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-tabs"], + "description": "A tabbed navigation component for switching between views.", + "files": [ + { + "path": "tabs.tsx", + "target": "@ui/tabs.tsx", + "type": "registry:ui" + } + ], + "name": "tabs", + "registryDependencies": ["@kombase/utils"], + "title": "Tabs", + "type": "registry:ui" + }, + { + "description": "A multi-line text input field.", + "files": [ + { + "path": "textarea.tsx", + "target": "@ui/textarea.tsx", + "type": "registry:ui" + } + ], + "name": "textarea", + "registryDependencies": ["@kombase/utils"], + "title": "Textarea", + "type": "registry:ui" + }, + { + "dependencies": ["@radix-ui/react-tooltip"], + "description": "A popup that displays information on hover or focus.", + "files": [ + { + "path": "tooltip.tsx", + "target": "@ui/tooltip.tsx", + "type": "registry:ui" + } + ], + "name": "tooltip", + "registryDependencies": ["@kombase/utils"], + "title": "Tooltip", + "type": "registry:ui" + } + ], + "name": "kombase-ui" +} diff --git a/packages/src/components/ui/select.tsx b/registry/ui/select.tsx similarity index 100% rename from packages/src/components/ui/select.tsx rename to registry/ui/select.tsx diff --git a/packages/src/components/ui/separator.tsx b/registry/ui/separator.tsx similarity index 100% rename from packages/src/components/ui/separator.tsx rename to registry/ui/separator.tsx diff --git a/packages/src/components/ui/skeleton.tsx b/registry/ui/skeleton.tsx similarity index 100% rename from packages/src/components/ui/skeleton.tsx rename to registry/ui/skeleton.tsx diff --git a/packages/src/components/ui/slider.tsx b/registry/ui/slider.tsx similarity index 100% rename from packages/src/components/ui/slider.tsx rename to registry/ui/slider.tsx diff --git a/packages/src/components/ui/switch.tsx b/registry/ui/switch.tsx similarity index 100% rename from packages/src/components/ui/switch.tsx rename to registry/ui/switch.tsx diff --git a/packages/src/components/ui/table.tsx b/registry/ui/table.tsx similarity index 100% rename from packages/src/components/ui/table.tsx rename to registry/ui/table.tsx diff --git a/packages/src/components/ui/tabs.tsx b/registry/ui/tabs.tsx similarity index 100% rename from packages/src/components/ui/tabs.tsx rename to registry/ui/tabs.tsx diff --git a/packages/src/components/ui/textarea.tsx b/registry/ui/textarea.tsx similarity index 100% rename from packages/src/components/ui/textarea.tsx rename to registry/ui/textarea.tsx diff --git a/packages/src/components/ui/tooltip.tsx b/registry/ui/tooltip.tsx similarity index 100% rename from packages/src/components/ui/tooltip.tsx rename to registry/ui/tooltip.tsx