shadcn-glass-ui 2.3.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/dist/cli/index.cjs +1 -1
- package/dist/components/glass/index.d.ts +7 -5
- package/dist/components/glass/index.d.ts.map +1 -1
- package/dist/components/glass/specialized/flag-alert-glass.d.ts.map +1 -1
- package/dist/components/glass/specialized/progress-glass.d.ts.map +1 -1
- package/dist/components/glass/ui/avatar-glass.d.ts.map +1 -1
- package/dist/components/glass/ui/checkbox-glass.d.ts +21 -46
- package/dist/components/glass/ui/checkbox-glass.d.ts.map +1 -1
- package/dist/components/glass/ui/circular-progress-glass.d.ts.map +1 -1
- package/dist/components/glass/ui/dropdown-menu-glass.d.ts +11 -5
- package/dist/components/glass/ui/dropdown-menu-glass.d.ts.map +1 -1
- package/dist/components/glass/ui/glass-card.d.ts.map +1 -1
- package/dist/components/glass/ui/index.d.ts +7 -1
- package/dist/components/glass/ui/index.d.ts.map +1 -1
- package/dist/components/glass/ui/notification-glass.d.ts.map +1 -1
- package/dist/components/glass/ui/popover-glass.d.ts +6 -3
- package/dist/components/glass/ui/popover-glass.d.ts.map +1 -1
- package/dist/components/glass/ui/skeleton-glass.d.ts.map +1 -1
- package/dist/components/glass/ui/slider-glass.d.ts.map +1 -1
- package/dist/components/glass/ui/stepper-glass.d.ts.map +1 -1
- package/dist/components/glass/ui/tabs-glass.d.ts +93 -142
- package/dist/components/glass/ui/tabs-glass.d.ts.map +1 -1
- package/dist/components/glass/ui/toggle-glass.d.ts.map +1 -1
- package/dist/components.cjs +4 -4
- package/dist/components.mjs +1 -1
- package/dist/hooks.cjs +2 -2
- package/dist/index.cjs +12 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +8 -1
- package/dist/index.mjs.map +1 -1
- package/dist/lib/variants/skeleton-glass-variants.d.ts +1 -1
- package/dist/r/avatar-glass.json +1 -1
- package/dist/r/checkbox-glass.json +4 -5
- package/dist/r/circular-progress-glass.json +1 -1
- package/dist/r/dropdown-glass.json +1 -1
- package/dist/r/dropdown-menu-glass.json +1 -1
- package/dist/r/flag-alert-glass.json +1 -1
- package/dist/r/glass-card.json +3 -2
- package/dist/r/notification-glass.json +1 -1
- package/dist/r/popover-glass.json +1 -1
- package/dist/r/progress-glass.json +1 -1
- package/dist/r/registry.json +2 -2
- package/dist/r/skeleton-glass.json +1 -1
- package/dist/r/slider-glass.json +1 -1
- package/dist/r/stepper-glass.json +1 -1
- package/dist/r/tabs-glass.json +5 -4
- package/dist/r/toggle-glass.json +1 -1
- package/dist/shadcn-glass-ui.css +1 -1
- package/dist/{theme-context-DmTETrFi.cjs → theme-context-7NcW0KZL.cjs} +2 -2
- package/dist/{theme-context-DmTETrFi.cjs.map → theme-context-7NcW0KZL.cjs.map} +1 -1
- package/dist/themes.cjs +1 -1
- package/dist/{trust-score-card-glass-EfMB5l5J.mjs → trust-score-card-glass-BGqBcdyJ.mjs} +120 -177
- package/dist/trust-score-card-glass-BGqBcdyJ.mjs.map +1 -0
- package/dist/{trust-score-card-glass-3VBi9soW.cjs → trust-score-card-glass-DtgFygh5.cjs} +124 -179
- package/dist/trust-score-card-glass-DtgFygh5.cjs.map +1 -0
- package/dist/{use-focus-CswOSq71.cjs → use-focus-BFBcpBh1.cjs} +2 -2
- package/dist/{use-focus-CswOSq71.cjs.map → use-focus-BFBcpBh1.cjs.map} +1 -1
- package/dist/{use-wallpaper-tint-WtRWtupA.cjs → use-wallpaper-tint-DTTStm5f.cjs} +2 -2
- package/dist/{use-wallpaper-tint-WtRWtupA.cjs.map → use-wallpaper-tint-DTTStm5f.cjs.map} +1 -1
- package/dist/{utils-DX6rdBol.cjs → utils-CiuCe_Aq.cjs} +2 -2
- package/dist/{utils-DX6rdBol.cjs.map → utils-CiuCe_Aq.cjs.map} +1 -1
- package/dist/utils.cjs +1 -1
- package/package.json +5 -1
- package/dist/trust-score-card-glass-3VBi9soW.cjs.map +0 -1
- package/dist/trust-score-card-glass-EfMB5l5J.mjs.map +0 -1
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
{
|
|
17
17
|
"path": "components/glass/ui/stepper-glass.tsx",
|
|
18
18
|
"type": "registry:component",
|
|
19
|
-
"content": "/* eslint-disable react-refresh/only-export-components */\n/**\n * StepperGlass Component (Compound API)\n *\n * Glass-themed step indicator with:\n * - Theme-aware styling (glass/light/aurora)\n * - Horizontal and vertical orientations\n * - Numbered, icon, and dots variants\n * - Linear mode (lock future steps)\n * - Animated connector lines\n * - Compound component API for advanced composition\n *\n * @example\n * ```tsx\n * <StepperGlass.Root value=\"step2\" onValueChange={setStep}>\n * <StepperGlass.List>\n * <StepperGlass.Step value=\"step1\" label=\"Account\" description=\"Create your account\" />\n * <StepperGlass.Step value=\"step2\" label=\"Profile\" description=\"Setup your profile\" />\n * <StepperGlass.Step value=\"step3\" label=\"Complete\" description=\"Finish setup\" />\n * </StepperGlass.List>\n * <StepperGlass.Content value=\"step1\">Step 1 content</StepperGlass.Content>\n * <StepperGlass.Content value=\"step2\">Step 2 content</StepperGlass.Content>\n * <StepperGlass.Content value=\"step3\">Step 3 content</StepperGlass.Content>\n * </StepperGlass.Root>\n * ```\n *\n * @accessibility\n * - **Keyboard Navigation:** Arrow keys navigate between steps (WCAG 2.1.1)\n * - **Focus Management:** Visible focus ring using `--focus-glow` (WCAG 2.4.7)\n * - **Screen Readers:** Uses `role=\"tablist\"`, `role=\"tab\"` (WCAG 4.1.3)\n * - **ARIA Attributes:** `aria-current=\"step\"`, `aria-disabled` for state\n * - **Touch Targets:** 44x44px minimum touch targets (WCAG 2.5.5)\n * - **Color Contrast:** All states meet WCAG AA 4.5:1 ratio\n * - **Motion:** Respects `prefers-reduced-motion`\n */\n\nimport {\n forwardRef,\n createContext,\n useContext,\n useMemo,\n useState,\n useCallback,\n useLayoutEffect,\n type CSSProperties,\n type FC,\n type ReactNode,\n} from 'react';\nimport { cn } from '@/lib/utils';\nimport { useFocus } from '@/lib/hooks/use-focus';\nimport { Check } from 'lucide-react';\nimport {\n stepperRootVariants,\n stepperListVariants,\n stepperStepContainerVariants,\n stepperIndicatorVariants,\n stepperConnectorVariants,\n stepperLabelVariants,\n stepperDescriptionVariants,\n stepperContentVariants,\n type StepperOrientation,\n type StepperVariant,\n type StepperSize,\n type StepStatus,\n} from '@/lib/variants/stepper-glass-variants';\nimport '@/glass-theme.css';\n\n// ========================================\n// CONTEXT\n// ========================================\n\ninterface StepperContextValue {\n value: string;\n onValueChange?: (value: string) => void;\n orientation: StepperOrientation;\n variant: StepperVariant;\n size: StepperSize;\n linear: boolean;\n steps: string[];\n registerStep: (value: string, index: number) => void;\n unregisterStep: (value: string) => void;\n}\n\nconst StepperContext = createContext<StepperContextValue | null>(null);\n\nconst useStepperContext = () => {\n const context = useContext(StepperContext);\n if (!context) {\n throw new Error('Stepper compound components must be used within StepperGlass.Root');\n }\n return context;\n};\n\n// ========================================\n// UTILITY: GET STEP STATUS\n// ========================================\n\nfunction getStepStatus(\n stepValue: string,\n currentValue: string,\n steps: string[],\n linear: boolean,\n disabled?: boolean\n): StepStatus {\n if (disabled) return 'disabled';\n\n const stepIndex = steps.indexOf(stepValue);\n const currentIndex = steps.indexOf(currentValue);\n\n if (stepIndex === -1 || currentIndex === -1) return 'pending';\n if (stepIndex === currentIndex) return 'active';\n if (stepIndex < currentIndex) return 'completed';\n if (linear && stepIndex > currentIndex) return 'disabled';\n return 'pending';\n}\n\n// ========================================\n// ROOT COMPONENT\n// ========================================\n\ninterface StepperRootProps {\n /** Current active step value */\n value: string;\n /** Callback when step value changes */\n onValueChange?: (value: string) => void;\n /** Orientation of the stepper */\n orientation?: StepperOrientation;\n /** Visual variant */\n variant?: StepperVariant;\n /** Size of step indicators */\n size?: StepperSize;\n /** Lock future steps (require sequential completion) */\n linear?: boolean;\n /** Child components */\n children: ReactNode;\n /** Optional className */\n className?: string;\n}\n\nconst StepperRoot: FC<StepperRootProps> = ({\n value,\n onValueChange,\n orientation = 'horizontal',\n variant = 'numbered',\n size = 'md',\n linear = false,\n children,\n className,\n}) => {\n const [steps, setSteps] = useState<string[]>([]);\n\n const registerStep = useCallback((stepValue: string, index: number) => {\n setSteps((prev) => {\n if (prev.includes(stepValue)) return prev;\n const newSteps = [...prev];\n // Insert at correct position to maintain order\n newSteps.splice(index, 0, stepValue);\n return newSteps;\n });\n }, []);\n\n const unregisterStep = useCallback((stepValue: string) => {\n setSteps((prev) => prev.filter((s) => s !== stepValue));\n }, []);\n\n const contextValue = useMemo(\n () => ({\n value,\n onValueChange,\n orientation,\n variant,\n size,\n linear,\n steps,\n registerStep,\n unregisterStep,\n }),\n [value, onValueChange, orientation, variant, size, linear, steps, registerStep, unregisterStep]\n );\n\n return (\n <StepperContext.Provider value={contextValue}>\n <div\n className={cn(stepperRootVariants({ orientation }), className)}\n aria-label=\"Progress steps\"\n >\n {children}\n </div>\n </StepperContext.Provider>\n );\n};\n\n// ========================================\n// LIST COMPONENT\n// ========================================\n\ninterface StepperListProps extends React.HTMLAttributes<HTMLDivElement> {\n children: ReactNode;\n className?: string;\n}\n\nconst StepperList = forwardRef<HTMLDivElement, StepperListProps>(\n ({ children, className, ...props }, ref) => {\n const { orientation } = useStepperContext();\n\n return (\n <div\n ref={ref}\n role=\"tablist\"\n aria-orientation={orientation}\n className={cn(stepperListVariants({ orientation }), className)}\n {...props}\n >\n {children}\n </div>\n );\n }\n);\n\nStepperList.displayName = 'StepperList';\n\n// ========================================\n// STEP COMPONENT\n// ========================================\n\ninterface StepperStepProps {\n /** Unique value for this step */\n value: string;\n /** Step label (required for accessibility) */\n label: string;\n /** Optional description */\n description?: string;\n /** Custom icon (for icon variant) */\n icon?: ReactNode;\n /** Completed icon override */\n completedIcon?: ReactNode;\n /** Force disabled state */\n disabled?: boolean;\n /** Optional className */\n className?: string;\n /** Step index for ordering (auto-detected) */\n index?: number;\n}\n\nconst StepperStep = forwardRef<HTMLButtonElement, StepperStepProps>(\n (\n {\n value: stepValue,\n label,\n description,\n icon,\n completedIcon,\n disabled: forcedDisabled,\n className,\n index: providedIndex,\n },\n ref\n ) => {\n const {\n value: currentValue,\n onValueChange,\n orientation,\n variant,\n size,\n linear,\n steps,\n registerStep,\n unregisterStep,\n } = useStepperContext();\n\n const { isFocusVisible, focusProps } = useFocus({ focusVisible: true });\n\n // Track mount order for step registration\n const [mountIndex] = useState(() => providedIndex ?? Date.now());\n\n // Register step on mount\n // Use useLayoutEffect to register before paint\n useLayoutEffect(() => {\n registerStep(stepValue, mountIndex);\n return () => unregisterStep(stepValue);\n }, [stepValue, mountIndex, registerStep, unregisterStep]);\n\n const status = getStepStatus(stepValue, currentValue, steps, linear, forcedDisabled);\n const stepIndex = steps.indexOf(stepValue);\n const isLast = stepIndex === steps.length - 1;\n const isClickable = status !== 'disabled';\n\n // Styles based on status\n const indicatorStyles: CSSProperties = {\n background:\n status === 'completed'\n ? 'var(--stepper-step-completed-bg)'\n : status === 'active'\n ? 'var(--stepper-step-active-bg)'\n : status === 'disabled'\n ? 'var(--stepper-step-disabled-bg)'\n : 'var(--stepper-step-bg)',\n border: `2px solid ${\n status === 'completed'\n ? 'var(--stepper-step-completed-border)'\n : status === 'active'\n ? 'var(--stepper-step-active-border)'\n : status === 'disabled'\n ? 'var(--stepper-step-disabled-border)'\n : 'var(--stepper-step-border)'\n }`,\n color:\n status === 'completed'\n ? 'var(--stepper-step-completed-text)'\n : status === 'active'\n ? 'var(--stepper-step-active-text)'\n : status === 'disabled'\n ? 'var(--stepper-step-disabled-text)'\n : 'var(--stepper-step-text)',\n boxShadow:\n status === 'active'\n ? 'var(--stepper-step-active-glow)'\n : status === 'completed'\n ? 'var(--stepper-step-glow)'\n : isFocusVisible\n ? 'var(--focus-glow)'\n : 'none',\n backdropFilter: 'blur(var(--blur-sm))',\n };\n\n const connectorStyles: CSSProperties = {\n background:\n stepIndex < steps.indexOf(currentValue)\n ? 'var(--stepper-connector-active-bg)'\n : 'var(--stepper-connector-bg)',\n };\n\n const labelStyles: CSSProperties = {\n color:\n status === 'active' || status === 'completed'\n ? 'var(--stepper-label-text)'\n : 'var(--stepper-description-text)',\n };\n\n const descriptionStyles: CSSProperties = {\n color: 'var(--stepper-description-text)',\n };\n\n // Render indicator content\n const renderIndicatorContent = () => {\n if (status === 'completed') {\n if (completedIcon) return completedIcon;\n return <Check className=\"w-4 h-4\" />;\n }\n if (variant === 'icon' && icon) return icon;\n if (variant === 'dots') return null;\n // Numbered variant\n return stepIndex >= 0 ? stepIndex + 1 : '';\n };\n\n const handleClick = () => {\n if (isClickable && onValueChange) {\n onValueChange(stepValue);\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (!isClickable) return;\n\n const stepList = e.currentTarget.closest('[role=\"tablist\"]');\n if (!stepList) return;\n\n const allSteps = Array.from(\n stepList.querySelectorAll('[role=\"tab\"]:not([aria-disabled=\"true\"])')\n ) as HTMLButtonElement[];\n const currentIdx = allSteps.indexOf(e.currentTarget as HTMLButtonElement);\n\n let nextIdx = currentIdx;\n const isHorizontal = orientation === 'horizontal';\n\n switch (e.key) {\n case isHorizontal ? 'ArrowRight' : 'ArrowDown':\n e.preventDefault();\n nextIdx = (currentIdx + 1) % allSteps.length;\n break;\n case isHorizontal ? 'ArrowLeft' : 'ArrowUp':\n e.preventDefault();\n nextIdx = currentIdx - 1 < 0 ? allSteps.length - 1 : currentIdx - 1;\n break;\n case 'Home':\n e.preventDefault();\n nextIdx = 0;\n break;\n case 'End':\n e.preventDefault();\n nextIdx = allSteps.length - 1;\n break;\n default:\n return;\n }\n\n const nextStep = allSteps[nextIdx];\n if (nextStep) {\n nextStep.focus();\n const nextValue = nextStep.getAttribute('data-value');\n if (nextValue && onValueChange) {\n onValueChange(nextValue);\n }\n }\n };\n\n // For horizontal, we need step + connector inline\n // For vertical, step is a row with connector below\n if (orientation === 'horizontal') {\n return (\n <>\n <div className={cn(stepperStepContainerVariants({ orientation }), className)}>\n <button\n ref={ref}\n type=\"button\"\n role=\"tab\"\n aria-selected={status === 'active'}\n aria-disabled={!isClickable}\n aria-current={status === 'active' ? 'step' : undefined}\n data-value={stepValue}\n data-status={status}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n onFocus={focusProps.onFocus}\n onBlur={focusProps.onBlur}\n disabled={!isClickable}\n className={cn(\n stepperIndicatorVariants({ size, variant }),\n !isClickable && 'cursor-not-allowed opacity-60',\n isClickable && 'cursor-pointer hover:scale-105',\n // Ensure minimum touch target\n 'min-w-[44px] min-h-[44px]'\n )}\n style={indicatorStyles}\n >\n {renderIndicatorContent()}\n </button>\n\n {label && (\n <div className=\"flex flex-col items-center\">\n <span\n className={cn(stepperLabelVariants({ size, orientation }))}\n style={labelStyles}\n >\n {label}\n </span>\n {description && (\n <span\n className={cn(stepperDescriptionVariants({ size, orientation }))}\n style={descriptionStyles}\n >\n {description}\n </span>\n )}\n </div>\n )}\n </div>\n\n {/* Connector line between steps */}\n {!isLast && (\n <div\n className={cn(stepperConnectorVariants({ orientation }))}\n style={connectorStyles}\n aria-hidden=\"true\"\n />\n )}\n </>\n );\n }\n\n // Vertical orientation\n return (\n <div className=\"flex flex-col\">\n <div className={cn(stepperStepContainerVariants({ orientation }), className)}>\n <button\n ref={ref}\n type=\"button\"\n role=\"tab\"\n aria-selected={status === 'active'}\n aria-disabled={!isClickable}\n aria-current={status === 'active' ? 'step' : undefined}\n data-value={stepValue}\n data-status={status}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n onFocus={focusProps.onFocus}\n onBlur={focusProps.onBlur}\n disabled={!isClickable}\n className={cn(\n stepperIndicatorVariants({ size, variant }),\n !isClickable && 'cursor-not-allowed opacity-60',\n isClickable && 'cursor-pointer hover:scale-105',\n 'min-w-[44px] min-h-[44px]'\n )}\n style={indicatorStyles}\n >\n {renderIndicatorContent()}\n </button>\n\n {label && (\n <div className=\"flex flex-col justify-center\">\n <span className={cn(stepperLabelVariants({ size, orientation }))} style={labelStyles}>\n {label}\n </span>\n {description && (\n <span\n className={cn(stepperDescriptionVariants({ size, orientation }))}\n style={descriptionStyles}\n >\n {description}\n </span>\n )}\n </div>\n )}\n </div>\n\n {/* Vertical connector */}\n {!isLast && (\n <div\n className={cn(stepperConnectorVariants({ orientation }))}\n style={connectorStyles}\n aria-hidden=\"true\"\n />\n )}\n </div>\n );\n }\n);\n\nStepperStep.displayName = 'StepperStep';\n\n// ========================================\n// CONTENT COMPONENT\n// ========================================\n\ninterface StepperContentProps {\n /** Value of the step this content belongs to */\n value: string;\n /** Content to display when step is active */\n children: ReactNode;\n /** Optional className */\n className?: string;\n}\n\nconst StepperContent: FC<StepperContentProps> = ({ value, children, className }) => {\n const { value: currentValue, orientation } = useStepperContext();\n const isActive = currentValue === value;\n\n if (!isActive) return null;\n\n return (\n <div\n role=\"tabpanel\"\n aria-hidden={!isActive}\n className={cn(stepperContentVariants({ orientation }), className)}\n >\n {children}\n </div>\n );\n};\n\n// ========================================\n// EXPORT COMPOUND COMPONENT\n// ========================================\n\nexport const StepperGlass = {\n Root: StepperRoot,\n List: StepperList,\n Step: StepperStep,\n Content: StepperContent,\n};\n\n// Also export individual components for flexibility\nexport { StepperRoot, StepperList, StepperStep, StepperContent };\n\n// Re-export types\nexport type {\n StepperRootProps,\n StepperListProps,\n StepperStepProps,\n StepperContentProps,\n StepperOrientation,\n StepperVariant,\n StepperSize,\n StepStatus,\n};\n"
|
|
19
|
+
"content": "/* eslint-disable react-refresh/only-export-components */\n/**\n * StepperGlass Component (Compound API)\n *\n * Glass-themed step indicator with:\n * - Theme-aware styling (glass/light/aurora)\n * - Horizontal and vertical orientations\n * - Numbered, icon, and dots variants\n * - Linear mode (lock future steps)\n * - Animated connector lines\n * - Compound component API for advanced composition\n *\n * @example\n * ```tsx\n * <StepperGlass.Root value=\"step2\" onValueChange={setStep}>\n * <StepperGlass.List>\n * <StepperGlass.Step value=\"step1\" label=\"Account\" description=\"Create your account\" />\n * <StepperGlass.Step value=\"step2\" label=\"Profile\" description=\"Setup your profile\" />\n * <StepperGlass.Step value=\"step3\" label=\"Complete\" description=\"Finish setup\" />\n * </StepperGlass.List>\n * <StepperGlass.Content value=\"step1\">Step 1 content</StepperGlass.Content>\n * <StepperGlass.Content value=\"step2\">Step 2 content</StepperGlass.Content>\n * <StepperGlass.Content value=\"step3\">Step 3 content</StepperGlass.Content>\n * </StepperGlass.Root>\n * ```\n *\n * @accessibility\n * - **Keyboard Navigation:** Arrow keys navigate between steps (WCAG 2.1.1)\n * - **Focus Management:** Visible focus ring using `--focus-glow` (WCAG 2.4.7)\n * - **Screen Readers:** Uses `role=\"tablist\"`, `role=\"tab\"` (WCAG 4.1.3)\n * - **ARIA Attributes:** `aria-current=\"step\"`, `aria-disabled` for state\n * - **Touch Targets:** 44x44px minimum touch targets (WCAG 2.5.5)\n * - **Color Contrast:** All states meet WCAG AA 4.5:1 ratio\n * - **Motion:** Respects `prefers-reduced-motion`\n */\n\nimport {\n forwardRef,\n createContext,\n useContext,\n useMemo,\n useState,\n useCallback,\n useLayoutEffect,\n type CSSProperties,\n type FC,\n type ReactNode,\n} from 'react';\nimport { cn } from '@/lib/utils';\nimport { useFocus } from '@/lib/hooks/use-focus';\nimport { Check } from 'lucide-react';\nimport {\n stepperRootVariants,\n stepperListVariants,\n stepperStepContainerVariants,\n stepperIndicatorVariants,\n stepperConnectorVariants,\n stepperLabelVariants,\n stepperDescriptionVariants,\n stepperContentVariants,\n type StepperOrientation,\n type StepperVariant,\n type StepperSize,\n type StepStatus,\n} from '@/lib/variants/stepper-glass-variants';\nimport '@/glass-theme.css';\n\n// ========================================\n// CONTEXT\n// ========================================\n\ninterface StepperContextValue {\n value: string;\n onValueChange?: (value: string) => void;\n orientation: StepperOrientation;\n variant: StepperVariant;\n size: StepperSize;\n linear: boolean;\n steps: string[];\n registerStep: (value: string, index: number) => void;\n unregisterStep: (value: string) => void;\n}\n\nconst StepperContext = createContext<StepperContextValue | null>(null);\n\nconst useStepperContext = () => {\n const context = useContext(StepperContext);\n if (!context) {\n throw new Error('Stepper compound components must be used within StepperGlass.Root');\n }\n return context;\n};\n\n// ========================================\n// UTILITY: GET STEP STATUS\n// ========================================\n\nfunction getStepStatus(\n stepValue: string,\n currentValue: string,\n steps: string[],\n linear: boolean,\n disabled?: boolean\n): StepStatus {\n if (disabled) return 'disabled';\n\n const stepIndex = steps.indexOf(stepValue);\n const currentIndex = steps.indexOf(currentValue);\n\n if (stepIndex === -1 || currentIndex === -1) return 'pending';\n if (stepIndex === currentIndex) return 'active';\n if (stepIndex < currentIndex) return 'completed';\n if (linear && stepIndex > currentIndex) return 'disabled';\n return 'pending';\n}\n\n// ========================================\n// ROOT COMPONENT\n// ========================================\n\ninterface StepperRootProps {\n /** Current active step value */\n value: string;\n /** Callback when step value changes */\n onValueChange?: (value: string) => void;\n /** Orientation of the stepper */\n orientation?: StepperOrientation;\n /** Visual variant */\n variant?: StepperVariant;\n /** Size of step indicators */\n size?: StepperSize;\n /** Lock future steps (require sequential completion) */\n linear?: boolean;\n /** Child components */\n children: ReactNode;\n /** Optional className */\n className?: string;\n}\n\nconst StepperRoot: FC<StepperRootProps> = ({\n value,\n onValueChange,\n orientation = 'horizontal',\n variant = 'numbered',\n size = 'md',\n linear = false,\n children,\n className,\n}) => {\n const [steps, setSteps] = useState<string[]>([]);\n\n const registerStep = useCallback((stepValue: string, index: number) => {\n setSteps((prev) => {\n if (prev.includes(stepValue)) return prev;\n const newSteps = [...prev];\n // Insert at correct position to maintain order\n newSteps.splice(index, 0, stepValue);\n return newSteps;\n });\n }, []);\n\n const unregisterStep = useCallback((stepValue: string) => {\n setSteps((prev) => prev.filter((s) => s !== stepValue));\n }, []);\n\n const contextValue = useMemo(\n () => ({\n value,\n onValueChange,\n orientation,\n variant,\n size,\n linear,\n steps,\n registerStep,\n unregisterStep,\n }),\n [value, onValueChange, orientation, variant, size, linear, steps, registerStep, unregisterStep]\n );\n\n return (\n <StepperContext.Provider value={contextValue}>\n <div\n data-slot=\"stepper\"\n className={cn(stepperRootVariants({ orientation }), className)}\n aria-label=\"Progress steps\"\n >\n {children}\n </div>\n </StepperContext.Provider>\n );\n};\n\n// ========================================\n// LIST COMPONENT\n// ========================================\n\ninterface StepperListProps extends React.HTMLAttributes<HTMLDivElement> {\n children: ReactNode;\n className?: string;\n}\n\nconst StepperList = forwardRef<HTMLDivElement, StepperListProps>(\n ({ children, className, ...props }, ref) => {\n const { orientation } = useStepperContext();\n\n return (\n <div\n ref={ref}\n data-slot=\"stepper-list\"\n role=\"tablist\"\n aria-orientation={orientation}\n className={cn(stepperListVariants({ orientation }), className)}\n {...props}\n >\n {children}\n </div>\n );\n }\n);\n\nStepperList.displayName = 'StepperList';\n\n// ========================================\n// STEP COMPONENT\n// ========================================\n\ninterface StepperStepProps {\n /** Unique value for this step */\n value: string;\n /** Step label (required for accessibility) */\n label: string;\n /** Optional description */\n description?: string;\n /** Custom icon (for icon variant) */\n icon?: ReactNode;\n /** Completed icon override */\n completedIcon?: ReactNode;\n /** Force disabled state */\n disabled?: boolean;\n /** Optional className */\n className?: string;\n /** Step index for ordering (auto-detected) */\n index?: number;\n}\n\nconst StepperStep = forwardRef<HTMLButtonElement, StepperStepProps>(\n (\n {\n value: stepValue,\n label,\n description,\n icon,\n completedIcon,\n disabled: forcedDisabled,\n className,\n index: providedIndex,\n },\n ref\n ) => {\n const {\n value: currentValue,\n onValueChange,\n orientation,\n variant,\n size,\n linear,\n steps,\n registerStep,\n unregisterStep,\n } = useStepperContext();\n\n const { isFocusVisible, focusProps } = useFocus({ focusVisible: true });\n\n // Track mount order for step registration\n const [mountIndex] = useState(() => providedIndex ?? Date.now());\n\n // Register step on mount\n // Use useLayoutEffect to register before paint\n useLayoutEffect(() => {\n registerStep(stepValue, mountIndex);\n return () => unregisterStep(stepValue);\n }, [stepValue, mountIndex, registerStep, unregisterStep]);\n\n const status = getStepStatus(stepValue, currentValue, steps, linear, forcedDisabled);\n const stepIndex = steps.indexOf(stepValue);\n const isLast = stepIndex === steps.length - 1;\n const isClickable = status !== 'disabled';\n\n // Styles based on status\n const indicatorStyles: CSSProperties = {\n background:\n status === 'completed'\n ? 'var(--stepper-step-completed-bg)'\n : status === 'active'\n ? 'var(--stepper-step-active-bg)'\n : status === 'disabled'\n ? 'var(--stepper-step-disabled-bg)'\n : 'var(--stepper-step-bg)',\n border: `2px solid ${\n status === 'completed'\n ? 'var(--stepper-step-completed-border)'\n : status === 'active'\n ? 'var(--stepper-step-active-border)'\n : status === 'disabled'\n ? 'var(--stepper-step-disabled-border)'\n : 'var(--stepper-step-border)'\n }`,\n color:\n status === 'completed'\n ? 'var(--stepper-step-completed-text)'\n : status === 'active'\n ? 'var(--stepper-step-active-text)'\n : status === 'disabled'\n ? 'var(--stepper-step-disabled-text)'\n : 'var(--stepper-step-text)',\n boxShadow:\n status === 'active'\n ? 'var(--stepper-step-active-glow)'\n : status === 'completed'\n ? 'var(--stepper-step-glow)'\n : isFocusVisible\n ? 'var(--focus-glow)'\n : 'none',\n backdropFilter: 'blur(var(--blur-sm))',\n };\n\n const connectorStyles: CSSProperties = {\n background:\n stepIndex < steps.indexOf(currentValue)\n ? 'var(--stepper-connector-active-bg)'\n : 'var(--stepper-connector-bg)',\n };\n\n const labelStyles: CSSProperties = {\n color:\n status === 'active' || status === 'completed'\n ? 'var(--stepper-label-text)'\n : 'var(--stepper-description-text)',\n };\n\n const descriptionStyles: CSSProperties = {\n color: 'var(--stepper-description-text)',\n };\n\n // Render indicator content\n const renderIndicatorContent = () => {\n if (status === 'completed') {\n if (completedIcon) return completedIcon;\n return <Check className=\"w-4 h-4\" />;\n }\n if (variant === 'icon' && icon) return icon;\n if (variant === 'dots') return null;\n // Numbered variant\n return stepIndex >= 0 ? stepIndex + 1 : '';\n };\n\n const handleClick = () => {\n if (isClickable && onValueChange) {\n onValueChange(stepValue);\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (!isClickable) return;\n\n const stepList = e.currentTarget.closest('[role=\"tablist\"]');\n if (!stepList) return;\n\n const allSteps = Array.from(\n stepList.querySelectorAll('[role=\"tab\"]:not([aria-disabled=\"true\"])')\n ) as HTMLButtonElement[];\n const currentIdx = allSteps.indexOf(e.currentTarget as HTMLButtonElement);\n\n let nextIdx = currentIdx;\n const isHorizontal = orientation === 'horizontal';\n\n switch (e.key) {\n case isHorizontal ? 'ArrowRight' : 'ArrowDown':\n e.preventDefault();\n nextIdx = (currentIdx + 1) % allSteps.length;\n break;\n case isHorizontal ? 'ArrowLeft' : 'ArrowUp':\n e.preventDefault();\n nextIdx = currentIdx - 1 < 0 ? allSteps.length - 1 : currentIdx - 1;\n break;\n case 'Home':\n e.preventDefault();\n nextIdx = 0;\n break;\n case 'End':\n e.preventDefault();\n nextIdx = allSteps.length - 1;\n break;\n default:\n return;\n }\n\n const nextStep = allSteps[nextIdx];\n if (nextStep) {\n nextStep.focus();\n const nextValue = nextStep.getAttribute('data-value');\n if (nextValue && onValueChange) {\n onValueChange(nextValue);\n }\n }\n };\n\n // For horizontal, we need step + connector inline\n // For vertical, step is a row with connector below\n if (orientation === 'horizontal') {\n return (\n <>\n <div\n data-slot=\"stepper-item\"\n className={cn(stepperStepContainerVariants({ orientation }), className)}\n >\n <button\n ref={ref}\n type=\"button\"\n role=\"tab\"\n aria-selected={status === 'active'}\n aria-disabled={!isClickable}\n aria-current={status === 'active' ? 'step' : undefined}\n data-value={stepValue}\n data-status={status}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n onFocus={focusProps.onFocus}\n onBlur={focusProps.onBlur}\n disabled={!isClickable}\n className={cn(\n stepperIndicatorVariants({ size, variant }),\n !isClickable && 'cursor-not-allowed opacity-60',\n isClickable && 'cursor-pointer hover:scale-105',\n // Ensure minimum touch target\n 'min-w-[44px] min-h-[44px]'\n )}\n style={indicatorStyles}\n >\n {renderIndicatorContent()}\n </button>\n\n {label && (\n <div className=\"flex flex-col items-center\">\n <span\n className={cn(stepperLabelVariants({ size, orientation }))}\n style={labelStyles}\n >\n {label}\n </span>\n {description && (\n <span\n className={cn(stepperDescriptionVariants({ size, orientation }))}\n style={descriptionStyles}\n >\n {description}\n </span>\n )}\n </div>\n )}\n </div>\n\n {/* Connector line between steps */}\n {!isLast && (\n <div\n data-slot=\"stepper-separator\"\n className={cn(stepperConnectorVariants({ orientation }))}\n style={connectorStyles}\n aria-hidden=\"true\"\n />\n )}\n </>\n );\n }\n\n // Vertical orientation\n return (\n <div className=\"flex flex-col\">\n <div\n data-slot=\"stepper-item\"\n className={cn(stepperStepContainerVariants({ orientation }), className)}\n >\n <button\n ref={ref}\n type=\"button\"\n role=\"tab\"\n aria-selected={status === 'active'}\n aria-disabled={!isClickable}\n aria-current={status === 'active' ? 'step' : undefined}\n data-value={stepValue}\n data-status={status}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n onFocus={focusProps.onFocus}\n onBlur={focusProps.onBlur}\n disabled={!isClickable}\n className={cn(\n stepperIndicatorVariants({ size, variant }),\n !isClickable && 'cursor-not-allowed opacity-60',\n isClickable && 'cursor-pointer hover:scale-105',\n 'min-w-[44px] min-h-[44px]'\n )}\n style={indicatorStyles}\n >\n {renderIndicatorContent()}\n </button>\n\n {label && (\n <div className=\"flex flex-col justify-center\">\n <span className={cn(stepperLabelVariants({ size, orientation }))} style={labelStyles}>\n {label}\n </span>\n {description && (\n <span\n className={cn(stepperDescriptionVariants({ size, orientation }))}\n style={descriptionStyles}\n >\n {description}\n </span>\n )}\n </div>\n )}\n </div>\n\n {/* Vertical connector */}\n {!isLast && (\n <div\n data-slot=\"stepper-separator\"\n className={cn(stepperConnectorVariants({ orientation }))}\n style={connectorStyles}\n aria-hidden=\"true\"\n />\n )}\n </div>\n );\n }\n);\n\nStepperStep.displayName = 'StepperStep';\n\n// ========================================\n// CONTENT COMPONENT\n// ========================================\n\ninterface StepperContentProps {\n /** Value of the step this content belongs to */\n value: string;\n /** Content to display when step is active */\n children: ReactNode;\n /** Optional className */\n className?: string;\n}\n\nconst StepperContent: FC<StepperContentProps> = ({ value, children, className }) => {\n const { value: currentValue, orientation } = useStepperContext();\n const isActive = currentValue === value;\n\n if (!isActive) return null;\n\n return (\n <div\n role=\"tabpanel\"\n data-slot=\"stepper-content\"\n aria-hidden={!isActive}\n className={cn(stepperContentVariants({ orientation }), className)}\n >\n {children}\n </div>\n );\n};\n\n// ========================================\n// EXPORT COMPOUND COMPONENT\n// ========================================\n\nexport const StepperGlass = {\n Root: StepperRoot,\n List: StepperList,\n Step: StepperStep,\n Content: StepperContent,\n};\n\n// Also export individual components for flexibility\nexport { StepperRoot, StepperList, StepperStep, StepperContent };\n\n// Re-export types\nexport type {\n StepperRootProps,\n StepperListProps,\n StepperStepProps,\n StepperContentProps,\n StepperOrientation,\n StepperVariant,\n StepperSize,\n StepStatus,\n};\n"
|
|
20
20
|
}
|
|
21
21
|
],
|
|
22
22
|
"categories": [
|
package/dist/r/tabs-glass.json
CHANGED
|
@@ -3,19 +3,20 @@
|
|
|
3
3
|
"name": "tabs-glass",
|
|
4
4
|
"type": "registry:ui",
|
|
5
5
|
"title": "Tabs Glass",
|
|
6
|
-
"description": "TabsGlass Component (
|
|
6
|
+
"description": "TabsGlass Component (Radix UI based)",
|
|
7
7
|
"dependencies": [
|
|
8
|
+
"@radix-ui/react-tabs",
|
|
9
|
+
"react",
|
|
8
10
|
"shadcn-glass-ui"
|
|
9
11
|
],
|
|
10
12
|
"registryDependencies": [
|
|
11
|
-
"cn"
|
|
12
|
-
"use-focus"
|
|
13
|
+
"cn"
|
|
13
14
|
],
|
|
14
15
|
"files": [
|
|
15
16
|
{
|
|
16
17
|
"path": "components/glass/ui/tabs-glass.tsx",
|
|
17
18
|
"type": "registry:component",
|
|
18
|
-
"content": "/* eslint-disable react-refresh/only-export-components */\n/**\n * TabsGlass Component (Compound API only)\n *\n * Glass-themed tab navigation with:\n * - Theme-aware styling (glass/light/aurora)\n * - Active tab indicator\n * - Smooth transitions\n * - Compound component API for advanced composition\n *\n * @example\n * ```tsx\n * <TabsGlass.Root value={activeTab} onValueChange={setActiveTab}>\n * <TabsGlass.List>\n * <TabsGlass.Trigger value=\"overview\">Overview</TabsGlass.Trigger>\n * <TabsGlass.Trigger value=\"analytics\">Analytics</TabsGlass.Trigger>\n * <TabsGlass.Trigger value=\"settings\">Settings</TabsGlass.Trigger>\n * </TabsGlass.List>\n * <TabsGlass.Content value=\"overview\">\n * Overview content\n * </TabsGlass.Content>\n * <TabsGlass.Content value=\"analytics\">\n * Analytics content\n * </TabsGlass.Content>\n * <TabsGlass.Content value=\"settings\">\n * Settings content\n * </TabsGlass.Content>\n * </TabsGlass.Root>\n * ```\n *\n * @since v1.0.0 - Legacy API removed (tabs/activeTab/onChange props)\n */\n\nimport {\n forwardRef,\n createContext,\n useContext,\n useState,\n type CSSProperties,\n type FC,\n type ReactNode,\n} from 'react';\nimport { cn } from '@/lib/utils';\nimport { useFocus } from '@/lib/hooks/use-focus';\nimport '@/glass-theme.css';\n\n// ========================================\n// TYPES\n// ========================================\n\nexport interface TabItem {\n readonly id: string;\n readonly label: string;\n}\n\n// ========================================\n// CONTEXT FOR COMPOUND COMPONENTS\n// ========================================\n\ninterface TabsContextValue {\n value: string;\n onValueChange?: (value: string) => void;\n}\n\nconst TabsContext = createContext<TabsContextValue | null>(null);\n\nconst useTabsContext = () => {\n const context = useContext(TabsContext);\n if (!context) {\n throw new Error('Tabs compound components must be used within TabsGlass.Root');\n }\n return context;\n};\n\n// ========================================\n// COMPOUND COMPONENT: ROOT\n// ========================================\n\n/**\n * Props for TabsGlass.Root component\n *\n * Root component that manages tab state and provides context for child components.\n * Features accessible keyboard navigation and ARIA attributes.\n *\n * @accessibility\n * - **Keyboard Navigation:** Arrow keys navigate between tabs, Tab moves to tab panel content (WCAG 2.1.1)\n * - **Focus Management:** Visible focus ring on active tab using `--focus-glow` CSS variable (WCAG 2.4.7)\n * - **Screen Readers:** Uses `role=\"tablist\"`, `role=\"tab\"`, `role=\"tabpanel\"` for proper tab semantics (WCAG 4.1.3)\n * - **ARIA Attributes:** Tabs marked with `aria-selected`, panels with `aria-hidden` for state announcement\n * - **Active State:** Visual indicator (underline) plus color change for multi-modal feedback\n * - **Touch Targets:** Tab triggers meet minimum 44x44px touch target (WCAG 2.5.5)\n * - **Color Contrast:** Active and inactive tab text meet WCAG AA contrast ratio 4.5:1\n * - **Motion:** Transitions and indicator animations respect `prefers-reduced-motion` settings\n *\n * @example\n * ```tsx\n * // Basic tabs\n * <TabsGlass.Root value={activeTab} onValueChange={setActiveTab}>\n * <TabsGlass.List>\n * <TabsGlass.Trigger value=\"overview\">Overview</TabsGlass.Trigger>\n * <TabsGlass.Trigger value=\"analytics\">Analytics</TabsGlass.Trigger>\n * <TabsGlass.Trigger value=\"settings\">Settings</TabsGlass.Trigger>\n * </TabsGlass.List>\n * <TabsGlass.Content value=\"overview\">\n * <h2>Overview</h2>\n * <p>Overview content here</p>\n * </TabsGlass.Content>\n * <TabsGlass.Content value=\"analytics\">\n * <h2>Analytics</h2>\n * <p>Analytics content here</p>\n * </TabsGlass.Content>\n * <TabsGlass.Content value=\"settings\">\n * <h2>Settings</h2>\n * <p>Settings content here</p>\n * </TabsGlass.Content>\n * </TabsGlass.Root>\n *\n * // Tabs with icons (ensure accessible labels)\n * <TabsGlass.Root value={activeTab} onValueChange={setActiveTab}>\n * <TabsGlass.List>\n * <TabsGlass.Trigger value=\"home\" aria-label=\"Home dashboard\">\n * <Home className=\"w-4 h-4\" />\n * </TabsGlass.Trigger>\n * <TabsGlass.Trigger value=\"search\" aria-label=\"Search\">\n * <Search className=\"w-4 h-4\" />\n * </TabsGlass.Trigger>\n * </TabsGlass.List>\n * <TabsGlass.Content value=\"home\">Dashboard content</TabsGlass.Content>\n * <TabsGlass.Content value=\"search\">Search content</TabsGlass.Content>\n * </TabsGlass.Root>\n *\n * // Disabled tab (announced to screen readers)\n * <TabsGlass.Root value={activeTab} onValueChange={setActiveTab}>\n * <TabsGlass.List>\n * <TabsGlass.Trigger value=\"tab1\">Available Tab</TabsGlass.Trigger>\n * <TabsGlass.Trigger value=\"tab2\" disabled>\n * Locked Tab\n * </TabsGlass.Trigger>\n * </TabsGlass.List>\n * <TabsGlass.Content value=\"tab1\">Content 1</TabsGlass.Content>\n * </TabsGlass.Root>\n *\n * // Form tabs with proper focus management\n * <TabsGlass.Root value={currentStep} onValueChange={setCurrentStep}>\n * <TabsGlass.List aria-label=\"Registration steps\">\n * <TabsGlass.Trigger value=\"account\">Account Info</TabsGlass.Trigger>\n * <TabsGlass.Trigger value=\"profile\">Profile Details</TabsGlass.Trigger>\n * <TabsGlass.Trigger value=\"confirm\">Confirmation</TabsGlass.Trigger>\n * </TabsGlass.List>\n * <TabsGlass.Content value=\"account\">\n * <InputGlass label=\"Email\" type=\"email\" />\n * </TabsGlass.Content>\n * <TabsGlass.Content value=\"profile\">\n * <InputGlass label=\"Name\" />\n * </TabsGlass.Content>\n * <TabsGlass.Content value=\"confirm\">\n * <p>Review your information</p>\n * </TabsGlass.Content>\n * </TabsGlass.Root>\n * ```\n */\n/**\n * Props for TabsGlass Root component (shadcn/ui compatible)\n */\nexport interface TabsRootProps {\n /** Current active tab value (controlled) */\n value?: string;\n /** Default active tab value (uncontrolled) */\n defaultValue?: string;\n /** Callback when tab value changes */\n onValueChange?: (value: string) => void;\n /** Child components */\n children: ReactNode;\n /** Optional className for container */\n className?: string;\n}\n\nconst TabsRoot: FC<TabsRootProps> = ({\n value: controlledValue,\n defaultValue,\n onValueChange,\n children,\n className,\n}) => {\n // Support both controlled and uncontrolled modes (shadcn/ui pattern)\n const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue ?? '');\n const isControlled = controlledValue !== undefined;\n const value = isControlled ? controlledValue : uncontrolledValue;\n\n const handleValueChange = (newValue: string) => {\n if (!isControlled) {\n setUncontrolledValue(newValue);\n }\n onValueChange?.(newValue);\n };\n\n return (\n <TabsContext.Provider value={{ value, onValueChange: handleValueChange }}>\n <div className={cn('tabs-glass-root', className)}>{children}</div>\n </TabsContext.Provider>\n );\n};\n\n// ========================================\n// COMPOUND COMPONENT: LIST\n// ========================================\n\nexport interface TabsListProps extends React.HTMLAttributes<HTMLDivElement> {\n children: ReactNode;\n className?: string;\n}\n\nconst TabsList = forwardRef<HTMLDivElement, TabsListProps>(\n ({ children, className, ...props }, ref) => {\n const containerStyles: CSSProperties = {\n background: 'var(--tab-container-bg)',\n border: '1px solid var(--tab-container-border)',\n };\n\n return (\n <div\n ref={ref}\n className={cn('inline-flex gap-0.5 md:gap-1 p-0.5 md:p-1 rounded-xl', className)}\n style={containerStyles}\n role=\"tablist\"\n {...props}\n >\n {children}\n </div>\n );\n }\n);\n\nTabsList.displayName = 'TabsList';\n\n// ========================================\n// COMPOUND COMPONENT: TRIGGER\n// ========================================\n\nexport interface TabsTriggerProps {\n /** Value of this tab */\n value: string;\n /** Tab label/content */\n children: ReactNode;\n /** Optional className */\n className?: string;\n /** Disabled state */\n disabled?: boolean;\n}\n\nconst TabsTrigger = forwardRef<HTMLButtonElement, TabsTriggerProps>(\n ({ value, children, className, disabled }, ref) => {\n const { value: activeValue, onValueChange } = useTabsContext();\n const { isFocusVisible, focusProps } = useFocus({ focusVisible: true });\n const isActive = activeValue === value;\n\n const tabStyles: CSSProperties = {\n background: isActive ? 'var(--tab-active-bg)' : 'var(--tab-bg)',\n color: isActive ? 'var(--tab-active-text)' : 'var(--text-secondary)',\n boxShadow: isFocusVisible && !disabled ? 'var(--focus-glow)' : 'none',\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {\n if (disabled) return;\n\n const tablist = e.currentTarget.closest('[role=\"tablist\"]');\n if (!tablist) return;\n\n const tabs = Array.from(\n tablist.querySelectorAll('[role=\"tab\"]:not([disabled])')\n ) as HTMLButtonElement[];\n const currentIndex = tabs.indexOf(e.currentTarget);\n\n let nextIndex = currentIndex;\n\n switch (e.key) {\n case 'ArrowRight':\n e.preventDefault();\n nextIndex = currentIndex + 1 >= tabs.length ? 0 : currentIndex + 1;\n break;\n case 'ArrowLeft':\n e.preventDefault();\n nextIndex = currentIndex - 1 < 0 ? tabs.length - 1 : currentIndex - 1;\n break;\n case 'Home':\n e.preventDefault();\n nextIndex = 0;\n break;\n case 'End':\n e.preventDefault();\n nextIndex = tabs.length - 1;\n break;\n default:\n return;\n }\n\n const nextTab = tabs[nextIndex];\n if (nextTab) {\n nextTab.focus();\n // Get the value from the button's data attribute or find it in context\n const nextValue = nextTab.getAttribute('data-value');\n if (nextValue && onValueChange) {\n onValueChange(nextValue);\n }\n }\n };\n\n return (\n <button\n ref={ref}\n type=\"button\"\n role=\"tab\"\n aria-selected={isActive}\n disabled={disabled}\n data-value={value}\n className={cn(\n 'relative px-2.5 py-1.5 md:px-4 md:py-2 rounded-lg text-xs md:text-sm font-medium transition-[background-color,color,opacity] duration-300',\n disabled && 'opacity-50 cursor-not-allowed',\n className\n )}\n style={tabStyles}\n onClick={() => !disabled && onValueChange?.(value)}\n onKeyDown={handleKeyDown}\n onFocus={focusProps.onFocus}\n onBlur={focusProps.onBlur}\n >\n {children}\n {isActive && (\n <div\n className=\"absolute bottom-0 left-1/2 -translate-x-1/2 w-6 md:w-8 h-0.5 rounded-full\"\n style={{ background: 'var(--tab-indicator)' }}\n />\n )}\n </button>\n );\n }\n);\n\nTabsTrigger.displayName = 'TabsTrigger';\n\n// ========================================\n// COMPOUND COMPONENT: CONTENT\n// ========================================\n\nexport interface TabsContentProps {\n /** Value of the tab this content belongs to */\n value: string;\n /** Content to display when tab is active */\n children: ReactNode;\n /** Optional className */\n className?: string;\n}\n\nconst TabsContent: FC<TabsContentProps> = ({ value, children, className }) => {\n const { value: activeValue } = useTabsContext();\n const isActive = activeValue === value;\n\n if (!isActive) return null;\n\n return (\n <div\n role=\"tabpanel\"\n aria-hidden={!isActive}\n className={cn('animate-in fade-in-0 duration-200', className)}\n >\n {children}\n </div>\n );\n};\n\n// ========================================\n// EXPORT COMPOUND COMPONENT (v1.0.0+)\n// ========================================\n\n/**\n * TabsGlass - Compound Component API\n *\n * @example\n * ```tsx\n * // Compound API (TabsGlass.Root pattern)\n * <TabsGlass.Root value={activeTab} onValueChange={setActiveTab}>\n * <TabsGlass.List>\n * <TabsGlass.Trigger value=\"tab1\">Overview</TabsGlass.Trigger>\n * <TabsGlass.Trigger value=\"tab2\">Analytics</TabsGlass.Trigger>\n * </TabsGlass.List>\n * <TabsGlass.Content value=\"tab1\">\n * <p>Overview content</p>\n * </TabsGlass.Content>\n * <TabsGlass.Content value=\"tab2\">\n * <p>Analytics content</p>\n * </TabsGlass.Content>\n * </TabsGlass.Root>\n *\n * // shadcn/ui compatible API (separate imports)\n * import { Tabs, TabsList, TabsTrigger, TabsContent } from 'shadcn-glass-ui'\n * <Tabs defaultValue=\"tab1\">\n * <TabsList>\n * <TabsTrigger value=\"tab1\">Overview</TabsTrigger>\n * <TabsTrigger value=\"tab2\">Analytics</TabsTrigger>\n * </TabsList>\n * <TabsContent value=\"tab1\">Overview content</TabsContent>\n * <TabsContent value=\"tab2\">Analytics content</TabsContent>\n * </Tabs>\n * ```\n *\n * @since v1.0.0 - Legacy API removed (tabs/activeTab/onChange props)\n * @since v2.3.0 - Added shadcn/ui compatible separate exports\n */\nexport const TabsGlass = {\n Root: TabsRoot,\n List: TabsList,\n Trigger: TabsTrigger,\n Content: TabsContent,\n};\n\n// ========================================\n// SHADCN/UI COMPATIBLE EXPORTS (v2.3.0+)\n// ========================================\n\n/**\n * Tabs - shadcn/ui compatible alias for TabsGlass.Root\n *\n * @example\n * ```tsx\n * import { Tabs, TabsList, TabsTrigger, TabsContent } from 'shadcn-glass-ui'\n *\n * <Tabs defaultValue=\"account\">\n * <TabsList>\n * <TabsTrigger value=\"account\">Account</TabsTrigger>\n * <TabsTrigger value=\"password\">Password</TabsTrigger>\n * </TabsList>\n * <TabsContent value=\"account\">Account settings</TabsContent>\n * <TabsContent value=\"password\">Password settings</TabsContent>\n * </Tabs>\n * ```\n */\nexport const Tabs = TabsRoot;\n\n/**\n * TabsList - shadcn/ui compatible alias for TabsGlass.List\n */\nexport { TabsList };\n\n/**\n * TabsTrigger - shadcn/ui compatible alias for TabsGlass.Trigger\n */\nexport { TabsTrigger };\n\n/**\n * TabsContent - shadcn/ui compatible alias for TabsGlass.Content\n */\nexport { TabsContent };\n"
|
|
19
|
+
"content": "/* eslint-disable react-refresh/only-export-components */\n/**\n * TabsGlass Component (Radix UI based)\n *\n * Glass-themed tab navigation built on Radix UI primitives with:\n * - 100% shadcn/ui type compatibility\n * - Theme-aware styling (glass/light/aurora)\n * - Active tab indicator\n * - Full keyboard navigation (Arrow keys, Home, End)\n * - Support for orientation, dir, activationMode\n *\n * @since v2.2.6 - Migrated to Radix UI primitives for full type compatibility\n */\n\nimport * as React from 'react';\nimport * as TabsPrimitive from '@radix-ui/react-tabs';\nimport { cn } from '@/lib/utils';\nimport '@/glass-theme.css';\n\n// ========================================\n// TYPES\n// ========================================\n\n/**\n * Legacy TabItem interface for backwards compatibility\n */\nexport interface TabItem {\n readonly id: string;\n readonly label: string;\n}\n\n/**\n * Props for TabsGlass Root component\n *\n * Extends Radix UI Tabs.Root props for 100% shadcn/ui compatibility.\n * All Radix props are supported including: value, defaultValue, onValueChange,\n * orientation, dir, activationMode.\n *\n * **Type Compatibility (v2.3.1+):**\n * - Extends `React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>`\n * - No more `as unknown as` type assertions needed\n * - Full IntelliSense for all Radix props\n *\n * **New props from Radix:**\n * - `orientation`: 'horizontal' | 'vertical' - Tab layout direction\n * - `dir`: 'ltr' | 'rtl' - Text direction for RTL support\n * - `activationMode`: 'automatic' | 'manual' - Tab activation behavior\n *\n * @accessibility\n * - **Keyboard Navigation:** Arrow keys navigate between tabs (respects orientation)\n * - **Focus Management:** Visible focus ring using CSS variables\n * - **Screen Readers:** Radix UI handles all ARIA attributes automatically\n * - **RTL Support:** Full RTL support via `dir` prop\n *\n * @example\n * ```tsx\n * // Basic usage\n * <Tabs defaultValue=\"tab1\">\n * <TabsList>\n * <TabsTrigger value=\"tab1\">Tab 1</TabsTrigger>\n * <TabsTrigger value=\"tab2\">Tab 2</TabsTrigger>\n * </TabsList>\n * <TabsContent value=\"tab1\">Content 1</TabsContent>\n * <TabsContent value=\"tab2\">Content 2</TabsContent>\n * </Tabs>\n *\n * // Vertical tabs\n * <Tabs defaultValue=\"tab1\" orientation=\"vertical\">\n * <TabsList>\n * <TabsTrigger value=\"tab1\">Tab 1</TabsTrigger>\n * <TabsTrigger value=\"tab2\">Tab 2</TabsTrigger>\n * </TabsList>\n * <TabsContent value=\"tab1\">Content 1</TabsContent>\n * </Tabs>\n *\n * // Manual activation (click required, not focus)\n * <Tabs defaultValue=\"tab1\" activationMode=\"manual\">\n * ...\n * </Tabs>\n * ```\n *\n * @since v2.3.0 - Added shadcn/ui compatible separate exports\n * @since v2.2.6 - Migrated to Radix UI primitives\n */\nexport type TabsRootProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>;\n\n/**\n * Props for TabsList component\n */\nexport type TabsListProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>;\n\n/**\n * Props for TabsTrigger component\n */\nexport type TabsTriggerProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>;\n\n/**\n * Props for TabsContent component\n */\nexport type TabsContentProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>;\n\n// ========================================\n// COMPONENTS\n// ========================================\n\n/**\n * TabsRoot - Root container for tabs\n */\nfunction TabsRoot({ className, ...props }: TabsRootProps) {\n return (\n <TabsPrimitive.Root data-slot=\"tabs\" className={cn('tabs-glass-root', className)} {...props} />\n );\n}\n\n/**\n * TabsList - Container for tab triggers\n */\nconst TabsList = React.forwardRef<React.ElementRef<typeof TabsPrimitive.List>, TabsListProps>(\n ({ className, ...props }, ref) => (\n <TabsPrimitive.List\n ref={ref}\n data-slot=\"tabs-list\"\n className={cn(\n 'inline-flex gap-0.5 md:gap-1 p-0.5 md:p-1 rounded-xl',\n 'bg-(--tab-container-bg) border border-(--tab-container-border)',\n className\n )}\n {...props}\n />\n )\n);\n\nTabsList.displayName = 'TabsList';\n\n/**\n * TabsTrigger - Individual tab button\n */\nconst TabsTrigger = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Trigger>,\n TabsTriggerProps\n>(({ className, children, ...props }, ref) => (\n <TabsPrimitive.Trigger\n ref={ref}\n data-slot=\"tabs-trigger\"\n className={cn(\n 'relative px-2.5 py-1.5 md:px-4 md:py-2 rounded-lg',\n 'text-xs md:text-sm font-medium',\n 'transition-[background-color,color,opacity,box-shadow] duration-300',\n // Inactive state\n 'bg-(--tab-bg) text-(--text-secondary)',\n // Active state\n 'data-[state=active]:bg-(--tab-active-bg) data-[state=active]:text-(--tab-active-text)',\n // Focus state\n 'focus-visible:outline-none focus-visible:shadow-(--focus-glow)',\n // Disabled state\n 'disabled:opacity-50 disabled:cursor-not-allowed',\n // Indicator (using ::after pseudo-element)\n 'after:absolute after:bottom-0 after:left-1/2 after:-translate-x-1/2',\n 'after:w-6 md:after:w-8 after:h-0.5 after:rounded-full',\n 'after:bg-(--tab-indicator)',\n 'after:opacity-0 data-[state=active]:after:opacity-100',\n 'after:transition-opacity after:duration-300',\n className\n )}\n {...props}\n >\n {children}\n </TabsPrimitive.Trigger>\n));\n\nTabsTrigger.displayName = 'TabsTrigger';\n\n/**\n * TabsContent - Content panel for a tab\n */\nconst TabsContent = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Content>,\n TabsContentProps\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.Content\n ref={ref}\n data-slot=\"tabs-content\"\n className={cn(\n 'animate-in fade-in-0 duration-200 outline-none',\n 'focus-visible:outline-none',\n className\n )}\n {...props}\n />\n));\n\nTabsContent.displayName = 'TabsContent';\n\n// ========================================\n// COMPOUND COMPONENT EXPORT\n// ========================================\n\n/**\n * TabsGlass - Compound Component API\n *\n * @example\n * ```tsx\n * // Compound API (TabsGlass.Root pattern)\n * <TabsGlass.Root value={activeTab} onValueChange={setActiveTab}>\n * <TabsGlass.List>\n * <TabsGlass.Trigger value=\"tab1\">Overview</TabsGlass.Trigger>\n * <TabsGlass.Trigger value=\"tab2\">Analytics</TabsGlass.Trigger>\n * </TabsGlass.List>\n * <TabsGlass.Content value=\"tab1\">\n * <p>Overview content</p>\n * </TabsGlass.Content>\n * <TabsGlass.Content value=\"tab2\">\n * <p>Analytics content</p>\n * </TabsGlass.Content>\n * </TabsGlass.Root>\n *\n * // shadcn/ui compatible API (separate imports)\n * import { Tabs, TabsList, TabsTrigger, TabsContent } from 'shadcn-glass-ui'\n * <Tabs defaultValue=\"tab1\">\n * <TabsList>\n * <TabsTrigger value=\"tab1\">Overview</TabsTrigger>\n * <TabsTrigger value=\"tab2\">Analytics</TabsTrigger>\n * </TabsList>\n * <TabsContent value=\"tab1\">Overview content</TabsContent>\n * <TabsContent value=\"tab2\">Analytics content</TabsContent>\n * </Tabs>\n * ```\n *\n * @since v1.0.0 - Legacy API removed (tabs/activeTab/onChange props)\n * @since v2.3.0 - Added shadcn/ui compatible separate exports\n * @since v2.2.6 - Migrated to Radix UI primitives\n */\nexport const TabsGlass = {\n Root: TabsRoot,\n List: TabsList,\n Trigger: TabsTrigger,\n Content: TabsContent,\n};\n\n// ========================================\n// SHADCN/UI COMPATIBLE EXPORTS\n// ========================================\n\n/**\n * Tabs - shadcn/ui compatible alias for TabsGlass.Root\n */\nexport const Tabs = TabsRoot;\n\nexport { TabsList, TabsTrigger, TabsContent };\n"
|
|
19
20
|
}
|
|
20
21
|
],
|
|
21
22
|
"categories": [
|
package/dist/r/toggle-glass.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
{
|
|
18
18
|
"path": "components/glass/ui/toggle-glass.tsx",
|
|
19
19
|
"type": "registry:component",
|
|
20
|
-
"content": "/**\n * ToggleGlass Component\n *\n * Glass-themed toggle switch with shadcn/ui compatible API:\n * - Theme-aware styling (glass/light/aurora)\n * - Glow effect when active\n * - Size variants (default, sm, lg)\n * - Variant styles (default, outline)\n * - Optional label\n *\n * **shadcn/ui compatible props:**\n * - `pressed` / `defaultPressed` - Control pressed state\n * - `onPressedChange` - Callback when state changes\n * - `variant` - 'default' | 'outline'\n * - `size` - 'default' | 'sm' | 'lg'\n *\n * @example\n * ```tsx\n * // Controlled\n * <ToggleGlass pressed={isOn} onPressedChange={setIsOn} />\n *\n * // Uncontrolled\n * <ToggleGlass defaultPressed={true} />\n *\n * // With variant\n * <ToggleGlass variant=\"outline\" pressed={isOn} onPressedChange={setIsOn} />\n * ```\n */\n\nimport { forwardRef, useState, type CSSProperties } from 'react';\nimport { type VariantProps } from 'class-variance-authority';\nimport { cn } from '@/lib/utils';\nimport { useFocus } from '@/lib/hooks/use-focus';\nimport { toggleSizes, type ToggleGlassVariant } from '@/lib/variants/toggle-glass-variants';\nimport '@/glass-theme.css';\n\n// ========================================\n// SIZE CONFIG\n// ========================================\n\nconst sizesConfig = {\n sm: { track: 'w-8 h-4', knob: 'w-3 h-3', translate: 'translate-x-4' },\n default: { track: 'w-11 h-6', knob: 'w-5 h-5', translate: 'translate-x-5' },\n lg: { track: 'w-14 h-7', knob: 'w-6 h-6', translate: 'translate-x-7' },\n} as const;\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\nexport interface ToggleGlassProps\n extends\n Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onChange' | 'defaultValue'>,\n VariantProps<typeof toggleSizes> {\n /**\n * Controlled pressed state (shadcn/ui compatible)\n */\n readonly pressed?: boolean;\n /**\n * Default pressed state for uncontrolled usage\n */\n readonly defaultPressed?: boolean;\n /**\n * Callback when pressed state changes (shadcn/ui compatible)\n */\n readonly onPressedChange?: (pressed: boolean) => void;\n /**\n * Visual variant (shadcn/ui compatible)\n * @default \"default\"\n */\n readonly variant?: ToggleGlassVariant;\n /**\n * Optional label text\n */\n readonly label?: string;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const ToggleGlass = forwardRef<HTMLButtonElement, ToggleGlassProps>(\n (\n {\n className,\n size = 'default',\n variant = 'default',\n pressed: controlledPressed,\n defaultPressed = false,\n onPressedChange,\n disabled,\n label,\n ...props\n },\n ref\n ) => {\n // Support both controlled and uncontrolled modes\n const [uncontrolledPressed, setUncontrolledPressed] = useState(defaultPressed);\n const isControlled = controlledPressed !== undefined;\n const isPressed = isControlled ? controlledPressed : uncontrolledPressed;\n\n const { isFocusVisible, focusProps } = useFocus({ focusVisible: true });\n const s = sizesConfig[size ?? 'default'];\n\n const handleToggle = () => {\n if (disabled) return;\n const newValue = !isPressed;\n if (!isControlled) {\n setUncontrolledPressed(newValue);\n }\n onPressedChange?.(newValue);\n };\n\n const getTrackStyles = (): CSSProperties => {\n if (variant === 'outline') {\n return {\n background: isPressed ? 'var(--toggle-outline-active-bg)' : 'transparent',\n borderColor: isPressed\n ? 'var(--toggle-outline-active-border)'\n : 'var(--toggle-outline-border)',\n boxShadow: isFocusVisible && !disabled ? 'var(--focus-glow)' : 'none',\n };\n }\n return {\n background: isPressed ? 'var(--toggle-active-bg)' : 'var(--toggle-bg)',\n boxShadow:\n isFocusVisible && !disabled\n ? 'var(--focus-glow)'\n : isPressed\n ? 'var(--toggle-glow)'\n : 'none',\n };\n };\n\n const knobStyles: CSSProperties = {\n background: 'var(--toggle-knob)',\n };\n\n // Touch area wrapper ensures 44px minimum touch target (Apple HIG)\n const toggle = (\n <span className=\"inline-flex items-center justify-center min-h-11\">\n <button\n ref={ref}\n type=\"button\"\n role=\"switch\"\n aria-pressed={isPressed}\n aria-label={label || 'Toggle switch'}\n disabled={disabled}\n className={cn(\n toggleSizes({ size, variant }),\n disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer',\n !label && className\n )}\n style={getTrackStyles()}\n onClick={handleToggle}\n onFocus={focusProps.onFocus}\n onBlur={focusProps.onBlur}\n {...props}\n >\n <div\n className={cn(\n 'absolute top-0.5 left-0.5 rounded-full shadow-md transition-all duration-300',\n s.knob,\n isPressed && s.translate\n )}\n style={knobStyles}\n />\n </button>\n </span>\n );\n\n if (label) {\n return (\n <label\n className={cn(\n 'inline-flex items-center gap-2 md:gap-2.5',\n disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer',\n className\n )}\n >\n {toggle}\n <span className=\"text-xs md:text-sm\" style={{ color: 'var(--text-secondary)' }}>\n {label}\n </span>\n </label>\n );\n }\n\n return toggle;\n }\n);\n\nToggleGlass.displayName = 'ToggleGlass';\n"
|
|
20
|
+
"content": "/**\n * ToggleGlass Component\n *\n * Glass-themed toggle switch with shadcn/ui compatible API:\n * - Theme-aware styling (glass/light/aurora)\n * - Glow effect when active\n * - Size variants (default, sm, lg)\n * - Variant styles (default, outline)\n * - Optional label\n *\n * **shadcn/ui compatible props:**\n * - `pressed` / `defaultPressed` - Control pressed state\n * - `onPressedChange` - Callback when state changes\n * - `variant` - 'default' | 'outline'\n * - `size` - 'default' | 'sm' | 'lg'\n *\n * @example\n * ```tsx\n * // Controlled\n * <ToggleGlass pressed={isOn} onPressedChange={setIsOn} />\n *\n * // Uncontrolled\n * <ToggleGlass defaultPressed={true} />\n *\n * // With variant\n * <ToggleGlass variant=\"outline\" pressed={isOn} onPressedChange={setIsOn} />\n * ```\n */\n\nimport { forwardRef, useState, type CSSProperties } from 'react';\nimport { type VariantProps } from 'class-variance-authority';\nimport { cn } from '@/lib/utils';\nimport { useFocus } from '@/lib/hooks/use-focus';\nimport { toggleSizes, type ToggleGlassVariant } from '@/lib/variants/toggle-glass-variants';\nimport '@/glass-theme.css';\n\n// ========================================\n// SIZE CONFIG\n// ========================================\n\nconst sizesConfig = {\n sm: { track: 'w-8 h-4', knob: 'w-3 h-3', translate: 'translate-x-4' },\n default: { track: 'w-11 h-6', knob: 'w-5 h-5', translate: 'translate-x-5' },\n lg: { track: 'w-14 h-7', knob: 'w-6 h-6', translate: 'translate-x-7' },\n} as const;\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\nexport interface ToggleGlassProps\n extends\n Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onChange' | 'defaultValue'>,\n VariantProps<typeof toggleSizes> {\n /**\n * Controlled pressed state (shadcn/ui compatible)\n */\n readonly pressed?: boolean;\n /**\n * Default pressed state for uncontrolled usage\n */\n readonly defaultPressed?: boolean;\n /**\n * Callback when pressed state changes (shadcn/ui compatible)\n */\n readonly onPressedChange?: (pressed: boolean) => void;\n /**\n * Visual variant (shadcn/ui compatible)\n * @default \"default\"\n */\n readonly variant?: ToggleGlassVariant;\n /**\n * Optional label text\n */\n readonly label?: string;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const ToggleGlass = forwardRef<HTMLButtonElement, ToggleGlassProps>(\n (\n {\n className,\n size = 'default',\n variant = 'default',\n pressed: controlledPressed,\n defaultPressed = false,\n onPressedChange,\n disabled,\n label,\n ...props\n },\n ref\n ) => {\n // Support both controlled and uncontrolled modes\n const [uncontrolledPressed, setUncontrolledPressed] = useState(defaultPressed);\n const isControlled = controlledPressed !== undefined;\n const isPressed = isControlled ? controlledPressed : uncontrolledPressed;\n\n const { isFocusVisible, focusProps } = useFocus({ focusVisible: true });\n const s = sizesConfig[size ?? 'default'];\n\n const handleToggle = () => {\n if (disabled) return;\n const newValue = !isPressed;\n if (!isControlled) {\n setUncontrolledPressed(newValue);\n }\n onPressedChange?.(newValue);\n };\n\n const getTrackStyles = (): CSSProperties => {\n if (variant === 'outline') {\n return {\n background: isPressed ? 'var(--toggle-outline-active-bg)' : 'transparent',\n borderColor: isPressed\n ? 'var(--toggle-outline-active-border)'\n : 'var(--toggle-outline-border)',\n boxShadow: isFocusVisible && !disabled ? 'var(--focus-glow)' : 'none',\n };\n }\n return {\n background: isPressed ? 'var(--toggle-active-bg)' : 'var(--toggle-bg)',\n boxShadow:\n isFocusVisible && !disabled\n ? 'var(--focus-glow)'\n : isPressed\n ? 'var(--toggle-glow)'\n : 'none',\n };\n };\n\n const knobStyles: CSSProperties = {\n background: 'var(--toggle-knob)',\n };\n\n // Touch area wrapper ensures 44px minimum touch target (Apple HIG)\n const toggle = (\n <span className=\"inline-flex items-center justify-center min-h-11\">\n <button\n ref={ref}\n data-slot=\"toggle\"\n type=\"button\"\n role=\"switch\"\n aria-pressed={isPressed}\n aria-label={label || 'Toggle switch'}\n disabled={disabled}\n className={cn(\n toggleSizes({ size, variant }),\n disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer',\n !label && className\n )}\n style={getTrackStyles()}\n onClick={handleToggle}\n onFocus={focusProps.onFocus}\n onBlur={focusProps.onBlur}\n {...props}\n >\n <div\n className={cn(\n 'absolute top-0.5 left-0.5 rounded-full shadow-md transition-all duration-300',\n s.knob,\n isPressed && s.translate\n )}\n style={knobStyles}\n />\n </button>\n </span>\n );\n\n if (label) {\n return (\n <label\n className={cn(\n 'inline-flex items-center gap-2 md:gap-2.5',\n disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer',\n className\n )}\n >\n {toggle}\n <span className=\"text-xs md:text-sm\" style={{ color: 'var(--text-secondary)' }}>\n {label}\n </span>\n </label>\n );\n }\n\n return toggle;\n }\n);\n\nToggleGlass.displayName = 'ToggleGlass';\n"
|
|
21
21
|
}
|
|
22
22
|
],
|
|
23
23
|
"categories": [
|