diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..d259a428 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @Adel2411 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8acc8f5d..92dccc81 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -212,43 +212,42 @@ When adding a new item, you will create/edit these files: | Component | Design State | Dev State | | -------------------- | ------------ | --------- | | `mc-button` | ✅ | ✅ | -| `mc-input` | ✅ | | -| `mc-textarea` | ✅ | | +| `mc-input` | ✅ | ✅ | +| `mc-textarea` | ✅ | ✅ | | `mc-input-otp` | ✅ | ✅ | | `mc-checkbox` | ✅ | ✅ | -| `mc-radio-group` | ✅ | | -| `mc-card` | ✅ | | -| `mc-select` | ✅ | | -| `mc-combobox` | ✅ | | -| `mc-switch` | ✅ | | -| `mc-navigation-menu` | ✅ | | -| `mc-sidebar` | ✅ | | -| `mc-tabs` | ✅ | | -| `mc-breadcrumb` | ✅ | | -| `mc-pagination` | ✅ | | -| `mc-dialog` | | | -| `mc-alert-dialog` | | | -| `mc-alert` | | | -| `mc-sonner` | | | -| `mc-tooltip` | | | -| `mc-popover` | | | -| `mc-dropdown-menu` | | | -| `mc-context-menu` | | | -| `mc-data-table` | | | -| `mc-accordion` | | | -| `mc-collapsible` | | | -| `mc-separator` | | | -| `mc-progress` | | | -| `mc-calendar` | | | -| `mc-scrollarea` | | | -| `mc-skeleton` | | | -| `mc-badge` | | | -| `mc-tag` | | | -| `mc-avatar` | | | -| `mc-drawer` | | | -| `mc-hover-card` | | | -| `mc-slider` | | | -| `mc-carousel` | | | +| `mc-radio-group` | ✅ | ✅ | +| `mc-card` | ✅ | ✅ | +| `mc-select` | ✅ | ✅ | +| `mc-combobox` | ✅ | ✅ | +| `mc-switch` | ✅ | ✅ | +| `mc-navigation-menu` | ✅ | ✅ | +| `mc-sidebar` | ✅ | ✅ | +| `mc-tabs` | ✅ | ✅ | +| `mc-breadcrumb` | ✅ | ✅ | +| `mc-pagination` | ✅ | ✅ | +| `mc-dialog` | ✅ | ✅ | +| `mc-alert-dialog` | ✅ | ✅ | +| `mc-alert` | ✅ | ✅ | +| `mc-sonner` | ✅ | ✅ | +| `mc-tooltip` | ✅ | ✅ | +| `mc-popover` | ✅ | ✅ | +| `mc-dropdown-menu` | ✅ | ✅ | +| `mc-context-menu` | ✅ | ✅ | +| `mc-data-table` | ✅ | | +| `mc-accordion` | ✅ | ✅ | +| `mc-collapsible` | ✅ | | +| `mc-separator` | ✅ | ✅ | +| `mc-progress` | ✅ | | +| `mc-calendar` | ✅ | ✅ | +| `mc-scrollarea` | ✅ | ✅ | +| `mc-skeleton` | ✅ | ✅ | +| `mc-badge` | ✅ | ✅ | +| `mc-avatar` | ✅ | ✅ | +| `mc-drawer` | ✅ | ✅ | +| `mc-hover-card` | ✅ | ✅ | +| `mc-slider` | ✅ | | +| `mc-carousel` | ✅ | ✅ | # Important Notes diff --git a/package.json b/package.json index 4fee11df..cc5adeb2 100644 --- a/package.json +++ b/package.json @@ -5,14 +5,15 @@ "private": true, "scripts": { "dev": "pnpm --filter @mcoli/web dev", - "build": "pnpm --filter @mcoli/web build", + "build": "pnpm --filter @mcoli/web build && prettier --write .", + "build:registry": "pnpm --filter @mcoli/web build:registry", "start": "pnpm --filter @mcoli/web start", "lint": "pnpm --filter @mcoli/web lint", "typecheck": "pnpm --filter @mcoli/web typecheck", "storybook": "pnpm --filter @mcoli/web storybook", "build-storybook": "pnpm --filter @mcoli/web build-storybook", - "cli": "pnpm --filter mcoli-ui", - "cli:dev": "MCOLI_UI_REGISTRY_URL=http://localhost:3000 pnpm --filter mcoli-ui", + "cli": "pnpm --filter mcoli-ui exec mcoli-ui", + "cli:dev": "MCOLI_UI_REGISTRY_URL=http://localhost:3000 pnpm --filter mcoli-ui exec mcoli-ui", "format": "prettier --write .", "format:check": "prettier --check .", "prepare": "husky" diff --git a/packages/web/.storybook/withTheme.tsx b/packages/web/.storybook/withTheme.tsx index 9fd01bd2..3a5c6e8f 100644 --- a/packages/web/.storybook/withTheme.tsx +++ b/packages/web/.storybook/withTheme.tsx @@ -1,5 +1,5 @@ import { useEffect } from 'react'; -import type { Decorator } from '@storybook/react'; +import type { Decorator } from '@storybook/nextjs-vite'; import { ColorThemeProvider } from '../components/ColorThemeProvider'; import { RootProvider } from 'fumadocs-ui/provider/next'; diff --git a/packages/web/__registry__/index.tsx b/packages/web/__registry__/index.tsx index 704d4baa..b979dcd5 100644 --- a/packages/web/__registry__/index.tsx +++ b/packages/web/__registry__/index.tsx @@ -2072,6 +2072,32 @@ export const Index: Record = { }, ], }, + 'mc-input': { + name: 'mc-input', + description: 'An input component with field integration and addon support for MicroClub UI', + type: 'registry:component', + files: [ + { + path: 'registry/ui/mc-input.tsx', + content: + "'use client';\n\nimport * as React from 'react';\nimport { Input as InputPrimitive } from '@base-ui/react/input';\n\nimport { cn } from '@/lib/utils';\n\nexport interface McInputProps extends React.ComponentProps<'input'> {\n /** Label rendered above the input. */\n label?: React.ReactNode;\n /** Help text below the input. Replaced by error when present. */\n description?: React.ReactNode;\n /** Truthy = error state. ReactNode = renders the message. `true` = red border only. */\n error?: React.ReactNode;\n /** Inline-start addon (icons, text, dropdowns). */\n addonStart?: React.ReactNode;\n /** Inline-end addon (buttons, badges, spinners). */\n addonEnd?: React.ReactNode;\n /** Block-start addon (toolbar, controls above the input row). */\n addonTop?: React.ReactNode;\n /** Block-end addon (character count, status bar below the input row). */\n addonBottom?: React.ReactNode;\n}\n\nfunction McInput({\n className,\n label,\n description,\n error,\n addonStart,\n addonEnd,\n addonTop,\n addonBottom,\n type,\n id,\n disabled,\n required,\n ...props\n}: McInputProps) {\n const generatedId = React.useId();\n const inputId = id ?? generatedId;\n\n const hasError = !!error;\n const errorMessage = typeof error === 'boolean' ? null : error;\n const showError = hasError && !!errorMessage;\n const showDescription = !hasError && !!description;\n const messageId = showError\n ? `${inputId}-error`\n : showDescription\n ? `${inputId}-description`\n : undefined;\n\n const handleGroupClick = (e: React.MouseEvent) => {\n const target = e.target as HTMLElement;\n if (!target.closest('button, a, input, select, textarea, [tabindex]')) {\n e.currentTarget.querySelector('[data-slot=\"input\"]')?.focus();\n }\n };\n\n return (\n
\n {label && (\n \n {label}\n \n )}\n\n \n {addonTop && (\n \n {addonTop}\n
\n )}\n\n {/*\n has-data-attached:items-stretch — When an attached McInputButton is\n anywhere inside this row, switch from center to stretch so the addon\n container fills the full row height and the button can match it.\n */}\n
\n {addonStart && (\n svg:first-child):pl-3.5 — widen padding only when the edge element is an icon\n // has-data-attached:pl-0 — flush for attached buttons (attribute selector beats type selector)\n // has-data-attached:items-stretch — let attached button fill full height\n 'group/addon flex shrink-0 items-center gap-2 pl-2 text-sm text-muted-foreground select-none [&:has(>svg:first-child)]:pl-3.5 has-data-attached:items-stretch has-data-attached:pl-0',\n \"[&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0\"\n )}\n >\n {addonStart}\n
\n )}\n\n \n\n {addonEnd && (\n svg:last-child)]:pr-3.5 has-data-attached:items-stretch has-data-attached:pr-0',\n \"[&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0\"\n )}\n >\n {addonEnd}\n \n )}\n \n\n {addonBottom && (\n \n {addonBottom}\n \n )}\n \n\n {showError && (\n \n {errorMessage}\n

\n )}\n\n {showDescription && (\n \n {description}\n

\n )}\n \n );\n}\n\nexport interface McInputButtonProps extends React.ComponentProps<'button'> {\n /** Visual style of the button. `attached` extends to the input edge and shares its border. */\n variant?: 'ghost' | 'outline' | 'filled' | 'attached';\n}\n\nfunction McInputButton({\n className,\n variant = 'ghost',\n type = 'button',\n ...props\n}: McInputButtonProps) {\n const isAttached = variant === 'attached';\n\n return (\n \n );\n}\n\nexport { McInput, McInputButton };\n", + type: 'registry:component', + }, + ], + }, + 'mc-textarea': { + name: 'mc-textarea', + description: 'A multi-line text input with field integration and block addons for MicroClub UI', + type: 'registry:component', + files: [ + { + path: 'registry/ui/mc-textarea.tsx', + content: + '\'use client\';\n\nimport * as React from \'react\';\n\nimport { cn } from \'@/lib/utils\';\n\nexport interface McTextareaProps extends React.ComponentProps<\'textarea\'> {\n /** Label rendered above the textarea. */\n label?: React.ReactNode;\n /** Help text below the textarea. Replaced by error when present. */\n description?: React.ReactNode;\n /** Truthy = error state. ReactNode = renders the message. `true` = red border only. */\n error?: React.ReactNode;\n /** Block-start addon (toolbar, controls above the textarea). */\n addonTop?: React.ReactNode;\n /** Block-end addon (character count, action buttons below the textarea). */\n addonBottom?: React.ReactNode;\n}\n\nfunction McTextarea({\n className,\n label,\n description,\n error,\n addonTop,\n addonBottom,\n id,\n disabled,\n required,\n rows = 3,\n ...props\n}: McTextareaProps) {\n const generatedId = React.useId();\n const textareaId = id ?? generatedId;\n\n const hasError = !!error;\n const errorMessage = typeof error === \'boolean\' ? null : error;\n const showError = hasError && !!errorMessage;\n const showDescription = !hasError && !!description;\n const messageId = showError\n ? `${textareaId}-error`\n : showDescription\n ? `${textareaId}-description`\n : undefined;\n\n const handleGroupClick = (e: React.MouseEvent) => {\n const target = e.target as HTMLElement;\n if (!target.closest(\'button, a, input, select, textarea, [tabindex]\')) {\n e.currentTarget.querySelector(\'[data-slot="textarea"]\')?.focus();\n }\n };\n\n return (\n
\n {label && (\n \n {label}\n \n )}\n\n \n {addonTop && (\n \n {addonTop}\n
\n )}\n\n \n\n {addonBottom && (\n \n {addonBottom}\n \n )}\n \n\n {showError && (\n \n {errorMessage}\n

\n )}\n\n {showDescription && (\n \n {description}\n

\n )}\n \n );\n}\n\nexport { McTextarea };\n', + type: 'registry:component', + }, + ], + }, 'mc-input-otp': { name: 'mc-input-otp', description: 'An OTP input component for MicroClub UI', @@ -2093,7 +2119,371 @@ export const Index: Record = { { path: 'registry/ui/mc-checkbox.tsx', content: - "'use client';\n\nimport * as React from 'react';\nimport { Checkbox as CheckboxPrimitive } from '@base-ui/react/checkbox';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { CheckIcon } from 'lucide-react';\n\nimport { cn } from '@/lib/utils';\n\nconst checkboxVariants = cva(\n 'peer relative flex shrink-0 items-center justify-center rounded-[4px] border transition-colors outline-none disabled:cursor-not-allowed disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n size: {\n sm: 'size-4 [&_[data-slot=checkbox-indicator]>svg]:size-3',\n md: 'size-5 [&_[data-slot=checkbox-indicator]>svg]:size-3.5',\n },\n },\n defaultVariants: {\n size: 'sm',\n },\n }\n);\n\nconst textVariants = cva('font-medium transition-colors', {\n variants: {\n size: {\n sm: 'text-sm',\n md: 'text-base',\n },\n disabled: {\n true: 'text-muted-foreground',\n false: 'text-foreground',\n },\n },\n defaultVariants: {\n size: 'sm',\n disabled: false,\n },\n});\n\nconst supportTextVariants = cva('font-regular', {\n variants: {\n size: {\n sm: 'text-sm',\n md: 'text-base',\n },\n },\n defaultVariants: {\n size: 'sm',\n },\n});\n\nexport interface McCheckboxProps\n extends CheckboxPrimitive.Root.Props, VariantProps {\n text?: string;\n supportText?: string;\n}\n\nfunction McCheckbox({\n className,\n size = 'sm',\n text,\n supportText,\n id,\n disabled,\n checked,\n onCheckedChange,\n ...props\n}: McCheckboxProps) {\n const checkboxId = React.useId();\n const finalId = id ?? checkboxId;\n const labelId = text ? `${finalId}-label` : undefined;\n const descriptionId = supportText ? `${finalId}-description` : undefined;\n\n return (\n \n \n \n \n \n \n\n {(text || supportText) && (\n \n {text && (\n \n {text}\n \n )}\n {supportText && (\n \n {supportText}\n \n )}\n \n )}\n \n );\n}\n\nexport { McCheckbox };\n", + "'use client';\n\nimport * as React from 'react';\nimport { Checkbox as CheckboxPrimitive } from '@base-ui/react/checkbox';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { CheckIcon } from 'lucide-react';\n\nimport { cn } from '@/lib/utils';\n\nconst checkboxVariants = cva(\n 'peer relative flex shrink-0 items-center justify-center rounded-[4px] border transition-colors outline-none disabled:cursor-not-allowed disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n size: {\n sm: 'size-4 [&_[data-slot=checkbox-indicator]>svg]:size-3',\n md: 'size-5 [&_[data-slot=checkbox-indicator]>svg]:size-3.5',\n },\n },\n defaultVariants: {\n size: 'sm',\n },\n }\n);\n\nconst textVariants = cva('font-medium transition-colors', {\n variants: {\n size: {\n sm: 'text-sm',\n md: 'text-base',\n },\n disabled: {\n true: 'text-muted-foreground',\n false: 'text-foreground',\n },\n },\n defaultVariants: {\n size: 'sm',\n disabled: false,\n },\n});\n\nconst supportTextVariants = cva('font-regular', {\n variants: {\n size: {\n sm: 'text-sm',\n md: 'text-base',\n },\n },\n defaultVariants: {\n size: 'sm',\n },\n});\n\nexport interface McCheckboxProps\n extends CheckboxPrimitive.Root.Props, VariantProps {\n text?: string;\n supportText?: string;\n}\n\nfunction McCheckbox({\n className,\n size = 'sm',\n text,\n supportText,\n id,\n disabled,\n checked,\n onCheckedChange,\n ...props\n}: McCheckboxProps) {\n const checkboxId = React.useId();\n const finalId = id ?? checkboxId;\n const labelId = text ? `${finalId}-label` : undefined;\n const descriptionId = supportText ? `${finalId}-description` : undefined;\n\n return (\n \n \n \n \n \n \n\n {(text || supportText) && (\n \n {text && (\n \n {text}\n \n )}\n {supportText && (\n \n {supportText}\n \n )}\n \n )}\n \n );\n}\n\nexport { McCheckbox };\n", + type: 'registry:component', + }, + ], + }, + 'mc-radio-group': { + name: 'mc-radio-group', + description: 'A radio group component for MicroClub UI', + type: 'registry:component', + files: [ + { + path: 'registry/ui/mc-radio-group.tsx', + content: + "'use client';\n\nimport * as React from 'react';\nimport { Radio as RadioPrimitive } from '@base-ui/react/radio';\nimport { RadioGroup as RadioGroupPrimitive } from '@base-ui/react/radio-group';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@/lib/utils';\n\nconst radioGroupVariants = cva(\n 'peer relative flex shrink-0 items-center justify-center rounded-full border transition-colors outline-none disabled:cursor-not-allowed disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n size: {\n sm: 'size-4 [&_[data-slot=radio-group-indicator]>span]:size-2',\n md: 'size-5 [&_[data-slot=radio-group-indicator]>span]:size-2.5',\n },\n },\n defaultVariants: {\n size: 'sm',\n },\n }\n);\n\nconst radioGroupItemVariants = cva('inline-flex', {\n variants: {\n size: {\n sm: 'gap-2',\n md: 'gap-3',\n },\n },\n defaultVariants: {\n size: 'sm',\n },\n});\n\nconst textVariants = cva('font-medium transition-colors', {\n variants: {\n size: {\n sm: 'text-sm',\n md: 'text-base',\n },\n disabled: {\n true: 'text-muted-foreground',\n false: 'text-foreground',\n },\n },\n defaultVariants: {\n size: 'sm',\n disabled: false,\n },\n});\n\nconst supportTextVariants = cva('font-regular', {\n variants: {\n size: {\n sm: 'text-sm',\n md: 'text-base',\n },\n disabled: {\n true: 'text-muted-foreground',\n false: 'text-foreground',\n },\n },\n defaultVariants: {\n size: 'sm',\n disabled: false,\n },\n});\n\nfunction McRadioGroup({ className, ...props }: RadioGroupPrimitive.Props) {\n return (\n \n );\n}\n\nexport interface McRadioGroupItemProps\n extends RadioPrimitive.Root.Props, VariantProps {\n text?: string;\n supportText?: string;\n}\n\nfunction McRadioGroupItem({\n className,\n size = 'sm',\n text,\n supportText,\n id,\n disabled,\n value,\n ...props\n}: McRadioGroupItemProps) {\n const radioId = React.useId();\n const finalId = id ?? radioId;\n const labelId = text ? `${finalId}-label` : undefined;\n const descriptionId = supportText ? `${finalId}-description` : undefined;\n\n return (\n \n \n \n \n \n \n\n {(text || supportText) && (\n \n {text && (\n \n {text}\n \n )}\n {supportText && (\n \n {supportText}\n \n )}\n \n )}\n \n );\n}\n\nexport { McRadioGroup, McRadioGroupItem };\n", + type: 'registry:component', + }, + ], + }, + 'mc-card': { + name: 'mc-card', + description: 'A card layout with header, body, and footer regions for MicroClub UI', + type: 'registry:component', + files: [ + { + path: 'registry/ui/mc-card.tsx', + content: + "'use client';\n\nimport * as React from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@/lib/utils';\n\nconst McCardDirectionContext = React.createContext<{ direction: 'row' | 'column' } | null>(null);\n\nconst cardVariants = cva(\n 'flex gap-4 rounded-lg border border-border bg-card p-4 text-card-foreground shadow-sm w-full',\n {\n variants: {\n direction: {\n row: 'flex-row',\n column: 'flex-col',\n },\n },\n defaultVariants: {\n direction: 'column',\n },\n }\n);\n\nfunction McCard({\n className,\n direction = 'column',\n children,\n ...props\n}: React.ComponentProps<'div'> & { direction?: 'row' | 'column' }) {\n return (\n \n
\n {children}\n
\n
\n );\n}\n\nexport interface McCardHeaderProps extends React.ComponentProps<'div'> {\n title: string;\n description?: string;\n}\n\nfunction McCardHeader({ className, title, description, ...props }: McCardHeaderProps) {\n return (\n
\n

{title}

\n {description &&

{description}

}\n
\n );\n}\n\nconst footerVariants = cva('flex gap-2', {\n variants: {\n cardDirection: {\n row: 'self-stretch ms-auto',\n column: '',\n },\n direction: {\n row: 'flex-row',\n column: 'flex-col',\n },\n align: {\n start: '',\n end: '',\n center: '',\n stretch: '',\n },\n },\n compoundVariants: [\n // cardDirection: column — matches previous footer-only (direction × align) behavior\n {\n cardDirection: 'column',\n direction: 'row',\n align: 'start',\n className: 'items-center justify-start [&>*]:shrink-0 [&>*]:grow-0',\n },\n {\n cardDirection: 'column',\n direction: 'row',\n align: 'end',\n className: 'items-center justify-end [&>*]:shrink-0 [&>*]:grow-0',\n },\n {\n cardDirection: 'column',\n direction: 'row',\n align: 'center',\n className: 'items-center justify-center [&>*]:shrink-0 [&>*]:grow-0',\n },\n {\n cardDirection: 'column',\n direction: 'row',\n align: 'stretch',\n className: 'items-center justify-stretch *:min-w-0 *:flex-1',\n },\n {\n cardDirection: 'column',\n direction: 'column',\n align: 'start',\n className: 'items-start justify-start [&>*]:shrink-0 [&>*]:grow-0',\n },\n {\n cardDirection: 'column',\n direction: 'column',\n align: 'end',\n className: 'items-end justify-start [&>*]:shrink-0 [&>*]:grow-0',\n },\n {\n cardDirection: 'column',\n direction: 'column',\n align: 'center',\n className: 'items-center justify-start [&>*]:shrink-0 [&>*]:grow-0',\n },\n {\n cardDirection: 'column',\n direction: 'column',\n align: 'stretch',\n className: 'items-stretch justify-start *:min-h-0 *:flex-1',\n },\n // cardDirection: row — horizontal card; footer sits beside header/body, align controls vertical position\n // footer direction: row → children horizontal, align maps to cross-axis (items-*)\n {\n cardDirection: 'row',\n direction: 'row',\n align: 'start',\n className: 'items-start [&>*]:shrink-0 [&>*]:grow-0',\n },\n {\n cardDirection: 'row',\n direction: 'row',\n align: 'end',\n className: 'items-end [&>*]:shrink-0 [&>*]:grow-0',\n },\n {\n cardDirection: 'row',\n direction: 'row',\n align: 'center',\n className: 'items-center [&>*]:shrink-0 [&>*]:grow-0',\n },\n {\n cardDirection: 'row',\n direction: 'row',\n align: 'stretch',\n className: 'items-stretch *:min-w-0 *:flex-1',\n },\n // footer direction: column → children vertical, align maps to main-axis (justify-*)\n {\n cardDirection: 'row',\n direction: 'column',\n align: 'start',\n className: 'justify-start [&>*]:shrink-0 [&>*]:grow-0',\n },\n {\n cardDirection: 'row',\n direction: 'column',\n align: 'end',\n className: 'justify-end [&>*]:shrink-0 [&>*]:grow-0',\n },\n {\n cardDirection: 'row',\n direction: 'column',\n align: 'center',\n className: 'justify-center [&>*]:shrink-0 [&>*]:grow-0',\n },\n {\n cardDirection: 'row',\n direction: 'column',\n align: 'stretch',\n className: 'items-stretch justify-start *:min-h-0 *:flex-1',\n },\n ],\n defaultVariants: {\n cardDirection: 'column',\n direction: 'row',\n align: 'stretch',\n },\n});\n\nexport interface McCardFooterProps\n extends React.ComponentProps<'div'>, VariantProps {}\n\nfunction McCardFooter({\n className,\n direction,\n align,\n cardDirection: cardDirectionProp,\n ...props\n}: McCardFooterProps) {\n const ctx = React.useContext(McCardDirectionContext);\n const cardDirection = cardDirectionProp ?? ctx?.direction ?? 'column';\n\n return (\n \n );\n}\n\nexport type McCardContentProps = React.ComponentProps<'div'>;\n\nfunction McCardContent({ className, ...props }: McCardContentProps) {\n return
;\n}\n\nexport { McCard, McCardHeader, McCardFooter, McCardContent };\n", + type: 'registry:component', + }, + ], + }, + 'mc-select': { + name: 'mc-select', + description: 'A select component for MicroClub UI', + type: 'registry:component', + files: [ + { + path: 'registry/ui/mc-select.tsx', + content: + "'use client';\n\nimport * as React from 'react';\nimport { Select as SelectPrimitive } from '@base-ui/react/select';\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon, SearchIcon } from 'lucide-react';\n\nimport { cn } from '@/lib/utils';\n\nexport type McSelectVariant =\n | 'default'\n | 'icon-leading'\n | 'avatar-leading'\n | 'dot-leading'\n | 'search';\n\nconst McSelect = SelectPrimitive.Root;\n\nfunction McSelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {\n return (\n \n );\n}\n\nfunction McSelectValue({ className, ...props }: SelectPrimitive.Value.Props) {\n return (\n \n );\n}\n\nfunction McSelectLabel({ className, children }: { className?: string; children: React.ReactNode }) {\n return (\n \n {children}\n \n );\n}\n\ninterface McSelectTriggerProps extends SelectPrimitive.Trigger.Props {\n size?: 'sm' | 'default';\n variant?: McSelectVariant;\n\n leadingIcon?: React.ReactNode;\n\n leadingAvatar?: React.ReactNode;\n\n dotColor?: string;\n}\n\nfunction McSelectTrigger({\n className,\n size = 'default',\n variant = 'default',\n leadingIcon,\n leadingAvatar,\n dotColor,\n children,\n ...props\n}: McSelectTriggerProps) {\n const isSearch = variant === 'search';\n\n return (\n \n {variant === 'search' && }\n\n {variant === 'icon-leading' && leadingIcon && {leadingIcon}}\n\n {variant === 'avatar-leading' && leadingAvatar && (\n \n {leadingAvatar}\n \n )}\n\n {variant === 'dot-leading' && (\n \n )}\n\n {children}\n\n {!isSearch && (\n \n }\n />\n )}\n \n );\n}\n\nfunction McSelectIcons({ className, ...props }: React.HTMLAttributes) {\n return (\n \n );\n}\n\nfunction McSelectContent({\n className,\n children,\n side = 'bottom',\n sideOffset = 8,\n scrollbar = false,\n align = 'center',\n alignOffset = 0,\n alignItemWithTrigger = true,\n ...props\n}: SelectPrimitive.Popup.Props &\n Pick<\n SelectPrimitive.Positioner.Props,\n 'align' | 'alignOffset' | 'side' | 'sideOffset' | 'alignItemWithTrigger'\n > & {\n scrollbar?: boolean;\n }) {\n return (\n \n \n \n \n {children}\n \n \n \n \n );\n}\n\nfunction McSelectGroupLabel({ className, ...props }: SelectPrimitive.GroupLabel.Props) {\n return (\n \n );\n}\n\nfunction McSelectItem({\n className,\n children,\n supportingText,\n leadingIcon,\n leadingAvatar,\n dotColor,\n ...props\n}: SelectPrimitive.Item.Props & {\n supportingText?: string;\n leadingIcon?: React.ReactNode;\n leadingAvatar?: React.ReactNode;\n dotColor?: string;\n}) {\n return (\n \n {leadingIcon && (\n \n {leadingIcon}\n \n )}\n\n {leadingAvatar && (\n \n {leadingAvatar}\n \n )}\n\n {dotColor && }\n\n \n \n {children}\n \n {supportingText && (\n \n {supportingText}\n \n )}\n \n\n \n \n \n }\n />\n \n );\n}\n\nfunction McSelectSeparator({ className, ...props }: SelectPrimitive.Separator.Props) {\n return (\n \n );\n}\n\nfunction McSelectScrollUpButton({\n className,\n ...props\n}: React.ComponentProps) {\n return (\n \n \n \n );\n}\n\nfunction McSelectScrollDownButton({\n className,\n ...props\n}: React.ComponentProps) {\n return (\n \n \n \n );\n}\n\nexport {\n McSelect,\n McSelectContent,\n McSelectGroup,\n McSelectGroupLabel,\n McSelectIcons,\n McSelectItem,\n McSelectLabel,\n McSelectScrollDownButton,\n McSelectScrollUpButton,\n McSelectSeparator,\n McSelectTrigger,\n McSelectValue,\n};\n", + type: 'registry:component', + }, + ], + }, + 'mc-combobox': { + name: 'mc-combobox', + description: 'A searchable dropdown with autocomplete functionality for MicroClub UI', + type: 'registry:component', + files: [ + { + path: 'registry/ui/mc-combobox.tsx', + content: + "'use client';\n\nimport { Combobox as ComboboxPrimitive } from '@base-ui/react';\nimport { CheckIcon, ChevronsUpDown, SearchIcon } from 'lucide-react';\nimport { cn } from '@/lib/utils';\n\nconst McCombobox = ComboboxPrimitive.Root;\n\nfunction McComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) {\n return ;\n}\n\nfunction McComboboxTrigger({\n className,\n placeholder,\n ...props\n}: ComboboxPrimitive.Trigger.Props & { placeholder?: string }) {\n return (\n \n \n \n \n );\n}\n\nfunction McComboboxSearch({\n className,\n placeholder,\n ...props\n}: ComboboxPrimitive.Input.Props & { placeholder?: string }) {\n return (\n
\n \n \n
\n );\n}\n\nfunction McComboboxContent({\n className,\n side = 'bottom',\n sideOffset = 8,\n align = 'start',\n ...props\n}: ComboboxPrimitive.Popup.Props &\n Pick) {\n return (\n \n \n \n \n \n );\n}\n\nfunction McComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {\n return (\n \n );\n}\n\nfunction McComboboxItem({ className, children, ...props }: ComboboxPrimitive.Item.Props) {\n return (\n \n {children}\n \n \n \n \n );\n}\n\nexport {\n McCombobox,\n McComboboxTrigger,\n McComboboxSearch,\n McComboboxContent,\n McComboboxList,\n McComboboxItem,\n McComboboxValue,\n};\n", + type: 'registry:component', + }, + ], + }, + 'mc-switch': { + name: 'mc-switch', + description: 'A switch component for MicroClub UI', + type: 'registry:component', + files: [ + { + path: 'registry/ui/mc-switch.tsx', + content: + "'use client';\n\nimport { Switch as SwitchPrimitive } from '@base-ui/react/switch';\nimport { cn } from '@/lib/utils';\n\nfunction McSwitch({\n className,\n size = 'default',\n ...props\n}: SwitchPrimitive.Root.Props & {\n size?: 'sm' | 'default';\n}) {\n return (\n \n \n \n );\n}\n\nexport { McSwitch };\n", + type: 'registry:component', + }, + ], + }, + 'mc-navigation-menu': { + name: 'mc-navigation-menu', + description: 'A navigation menu component for MicroClub UI', + type: 'registry:component', + files: [ + { + path: 'registry/ui/mc-navigation-menu.tsx', + content: + "import { NavigationMenu as NavigationMenuPrimitive } from '@base-ui/react/navigation-menu';\nimport { cva } from 'class-variance-authority';\nimport { ChevronDownIcon } from 'lucide-react';\n\nimport { cn } from '@/lib/utils';\n\nfunction McNavigationMenu({\n align = 'start',\n className,\n children,\n ...props\n}: NavigationMenuPrimitive.Root.Props & Pick) {\n return (\n \n {children}\n \n \n );\n}\n\nfunction McNavigationMenuList({\n className,\n ...props\n}: React.ComponentPropsWithRef) {\n return (\n \n );\n}\n\nfunction McNavigationMenuItem({\n className,\n ...props\n}: React.ComponentPropsWithRef) {\n return (\n \n );\n}\n\nconst navigationMenuTriggerStyle = cva(\n 'group/navigation-menu-trigger inline-flex h-9 w-max items-center justify-center paragraph-sm font-medium transition-all outline-none focus-visible:bg-accent focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-popup-open:bg-accent data-popup-open:hover:bg-accent data-open:bg-accent/50 data-open:hover:bg-accent data-open:focus:bg-accent data-popup-open:shadow-[inset_0_0_0_2px_var(--border),0_0_0_2px_var(--border)] data-open:shadow-[inset_0_0_0_2px_var(--border),0_0_0_2px_var(--border)] data-popup-open:hover:shadow-[inset_0_0_0_2px_var(--border),0_0_0_2px_var(--border)] data-popup-open:hover:bg-accent px-4 py-2 rounded-md'\n);\n\nfunction McNavigationMenuTrigger({\n className,\n children,\n ...props\n}: NavigationMenuPrimitive.Trigger.Props) {\n return (\n \n {children}{' '}\n \n \n );\n}\n\nfunction McNavigationMenuContent({ className, ...props }: NavigationMenuPrimitive.Content.Props) {\n return (\n \n );\n}\n\nfunction McNavigationMenuPositioner({\n className,\n side = 'bottom',\n sideOffset = 8,\n align = 'start',\n alignOffset = 0,\n ...props\n}: NavigationMenuPrimitive.Positioner.Props) {\n return (\n \n \n \n \n \n \n \n );\n}\n\nfunction McNavigationMenuLink({ className, ...props }: NavigationMenuPrimitive.Link.Props) {\n return (\n \n );\n}\n\nfunction McNavigationMenuIndicator({\n className,\n ...props\n}: React.ComponentPropsWithRef) {\n return (\n \n
\n \n );\n}\n\nexport {\n McNavigationMenu,\n McNavigationMenuContent,\n McNavigationMenuIndicator,\n McNavigationMenuItem,\n McNavigationMenuLink,\n McNavigationMenuList,\n McNavigationMenuTrigger,\n navigationMenuTriggerStyle,\n McNavigationMenuPositioner,\n};\n", + type: 'registry:component', + }, + ], + }, + 'mc-sidebar': { + name: 'mc-sidebar', + description: 'A sidebar component for MicroClub UI', + type: 'registry:component', + files: [ + { + path: 'registry/ui/mc-sidebar.tsx', + content: + "'use client';\n\nimport * as React from 'react';\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { ChevronDownIcon, PanelLeftIcon } from 'lucide-react';\n\nimport { cn } from '@/lib/utils';\nimport { McButton } from '@/registry/ui/mc-button';\n\nconst SIDEBAR_COOKIE_NAME = 'sidebar_state';\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_WIDTH = '255px';\nconst SIDEBAR_WIDTH_ICON = '51px';\nconst SIDEBAR_KEYBOARD_SHORTCUT = 'b';\n\ntype SidebarContextProps = {\n state: 'expanded' | 'collapsed';\n open: boolean;\n setOpen: (open: boolean) => void;\n toggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext(null);\n\nfunction useSidebar() {\n const context = React.useContext(SidebarContext);\n if (!context) {\n throw new Error('useSidebar must be used within a SidebarProvider.');\n }\n return context;\n}\n\nfunction SidebarProvider({\n defaultOpen = true,\n open: openProp,\n onOpenChange: setOpenProp,\n className,\n style,\n children,\n ...props\n}: React.ComponentProps<'div'> & {\n defaultOpen?: boolean;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n}) {\n const [_open, _setOpen] = React.useState(() => {\n if (typeof document === 'undefined') {\n return defaultOpen;\n }\n\n const cookie = document.cookie\n .split('; ')\n .find((entry) => entry.startsWith(`${SIDEBAR_COOKIE_NAME}=`));\n\n if (!cookie) {\n return defaultOpen;\n }\n\n return cookie.split('=')[1] === 'true';\n });\n const open = openProp ?? _open;\n const setOpen = React.useCallback(\n (value: boolean | ((value: boolean) => boolean)) => {\n const openState = typeof value === 'function' ? value(open) : value;\n if (setOpenProp) {\n setOpenProp(openState);\n } else {\n _setOpen(openState);\n }\n },\n [setOpenProp, open]\n );\n\n React.useEffect(() => {\n document.cookie = `${SIDEBAR_COOKIE_NAME}=${open}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n }, [open]);\n\n const toggleSidebar = React.useCallback(() => {\n return setOpen((open) => !open);\n }, [setOpen]);\n\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {\n event.preventDefault();\n toggleSidebar();\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [toggleSidebar]);\n\n const state = open ? 'expanded' : 'collapsed';\n\n const contextValue = React.useMemo(\n () => ({\n state,\n open,\n setOpen,\n toggleSidebar,\n }),\n [state, open, setOpen, toggleSidebar]\n );\n\n return (\n \n \n {children}\n
\n \n );\n}\n\nfunction McSidebar({\n side = 'left',\n variant = 'sidebar',\n collapsible = 'icon',\n className,\n children,\n ...props\n}: React.ComponentProps<'div'> & {\n side?: 'left' | 'right';\n variant?: 'sidebar' | 'floating' | 'inset';\n collapsible?: 'offcanvas' | 'icon' | 'none';\n}) {\n const { state } = useSidebar();\n\n return (\n
\n \n \n );\n}\n\nfunction McSidebarTrigger({ className, onClick, ...props }: React.ComponentProps) {\n const { toggleSidebar } = useSidebar();\n const handleClick: React.ComponentProps['onClick'] = (event) => {\n onClick?.(event);\n toggleSidebar();\n };\n\n return (\n \n \n Toggle Sidebar\n \n );\n}\n\nfunction McSidebarRail({ className, ...props }: React.ComponentProps<'button'>) {\n const { toggleSidebar } = useSidebar();\n\n return (\n