shadcn-glass-ui 2.0.12 → 2.1.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 +218 -5
- package/README.md +132 -43
- package/context7.json +2 -1
- package/dist/cli/index.cjs +1 -1
- package/dist/components.cjs +4 -4
- package/dist/components.d.ts +103 -29
- package/dist/components.js +1 -1
- package/dist/demo-screenshot-aurora.png +0 -0
- package/dist/demo-screenshot.png +0 -0
- package/dist/demo-screenshot.png.zip +0 -0
- package/dist/hooks.cjs +2 -2
- package/dist/index.cjs +5 -5
- package/dist/index.js +28 -28
- package/dist/index.js.map +1 -1
- package/dist/r/ai-card-glass.json +1 -1
- package/dist/r/avatar-glass.json +1 -1
- package/dist/r/badge-glass.json +1 -1
- package/dist/r/button-glass.json +1 -1
- package/dist/r/combobox-glass.json +1 -1
- package/dist/r/registry.json +2 -2
- package/dist/r/repository-card-glass.json +2 -1
- package/dist/r/slider-glass.json +4 -5
- package/dist/r/toggle-glass.json +2 -2
- package/dist/r/year-card-glass.json +1 -1
- package/dist/shadcn-glass-ui.css +1 -1
- package/dist/{theme-context-DNe_2vWJ.cjs → theme-context-BHXYJ4RE.cjs} +2 -2
- package/dist/{theme-context-DNe_2vWJ.cjs.map → theme-context-BHXYJ4RE.cjs.map} +1 -1
- package/dist/themes.cjs +1 -1
- package/dist/{trust-score-card-glass-Dgu46oWI.cjs → trust-score-card-glass-CGXmOIfq.cjs} +850 -150
- package/dist/trust-score-card-glass-CGXmOIfq.cjs.map +1 -0
- package/dist/{trust-score-card-glass-A7kas5OS.js → trust-score-card-glass-L9g0qamo.js} +1182 -482
- package/dist/trust-score-card-glass-L9g0qamo.js.map +1 -0
- package/dist/{use-focus-BRkQtQCj.cjs → use-focus-CeNHOiBa.cjs} +2 -2
- package/dist/{use-focus-BRkQtQCj.cjs.map → use-focus-CeNHOiBa.cjs.map} +1 -1
- package/dist/{use-wallpaper-tint-CfShPBo2.cjs → use-wallpaper-tint-Bt2G3g1v.cjs} +2 -2
- package/dist/{use-wallpaper-tint-CfShPBo2.cjs.map → use-wallpaper-tint-Bt2G3g1v.cjs.map} +1 -1
- package/dist/{utils-BXN7AcRu.cjs → utils-LYxxWvUn.cjs} +2 -2
- package/dist/{utils-BXN7AcRu.cjs.map → utils-LYxxWvUn.cjs.map} +1 -1
- package/dist/utils.cjs +1 -1
- package/docs/ADVANCED_PATTERNS.md +584 -0
- package/docs/AI_USAGE.md +135 -611
- package/docs/BEST_PRACTICES.md +2 -2
- package/docs/BREAKING_CHANGES.md +242 -0
- package/docs/COMPONENTS_CATALOG.md +8 -8
- package/docs/EXPORTS_STRUCTURE.md +3 -3
- package/docs/GETTING_STARTED.md +13 -8
- package/docs/PUBLISHING.md +1 -1
- package/docs/REGISTRY_SUMMARY.md +2 -2
- package/docs/REGISTRY_USAGE.md +1 -1
- package/docs/api/README.md +11 -11
- package/docs/api/interfaces/BadgeGlassProps.md +21 -14
- package/docs/api/interfaces/ButtonGlassProps.md +37 -30
- package/package.json +4 -3
- package/dist/trust-score-card-glass-A7kas5OS.js.map +0 -1
- package/dist/trust-score-card-glass-Dgu46oWI.cjs.map +0 -1
- package/dist/vite.svg +0 -1
- package/docs/migration/modal-glass-compound-api.md +0 -458
- package/docs/migration/select-to-combobox.md +0 -386
- package/docs/migration/tabs-glass-compound-api.md +0 -579
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
{
|
|
17
17
|
"path": "components/glass/ui/combobox-glass.tsx",
|
|
18
18
|
"type": "registry:component",
|
|
19
|
-
"content": "/**\n * ComboBoxGlass Component\n *\n * Glass-themed combobox (searchable select) with:\n * - Keyboard navigation support\n * - Search/filter functionality\n * - Custom rendering options\n * - Async data loading support\n * - Glass styling with theme support\n * - Accessibility features\n * - Form field wrapper support (label, error, success)\n * - Size variants (sm, md, lg, xl)\n * - Optional searchable mode\n * - Icon support for trigger and options\n */\n\n'use client';\n\nimport { useState, useMemo, useCallback, forwardRef, useId } from 'react';\nimport { CheckIcon, ChevronsUpDownIcon, type LucideIcon } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { ButtonGlass } from './button-glass';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from '@/components/ui/command';\nimport { FormFieldWrapper } from '../primitives/form-field-wrapper';\nimport { inputVariants, type InputGlassSize } from '@/lib/variants/input-glass-variants';\nimport {\n getDropdownContentStyles,\n dropdownContentClasses,\n getDropdownItemClasses,\n} from '@/lib/variants/dropdown-content-styles';\nimport { ICON_SIZES } from '../primitives/style-utils';\nimport '@/glass-theme.css';\n\n// ========================================\n// TYPES\n// ========================================\n\nexport type GlassVariant = 'glass' | 'frosted' | 'fluted' | 'crystal';\n\nexport interface ComboBoxOption<T = string> {\n readonly value: T;\n readonly label: string;\n readonly disabled?: boolean;\n /** Optional icon component for the option */\n readonly icon?: LucideIcon;\n}\n\nexport interface ComboBoxGlassProps<T = string> {\n /** Available options */\n readonly options: readonly ComboBoxOption<T>[];\n /** Currently selected value */\n readonly value?: T;\n /** Callback when value changes */\n readonly
|
|
19
|
+
"content": "/**\n * ComboBoxGlass Component\n *\n * Glass-themed combobox (searchable select) with:\n * - Keyboard navigation support\n * - Search/filter functionality\n * - Custom rendering options\n * - Async data loading support\n * - Glass styling with theme support\n * - Accessibility features\n * - Form field wrapper support (label, error, success)\n * - Size variants (sm, md, lg, xl)\n * - Optional searchable mode\n * - Icon support for trigger and options\n */\n\n'use client';\n\nimport { useState, useMemo, useCallback, forwardRef, useId } from 'react';\nimport { CheckIcon, ChevronsUpDownIcon, type LucideIcon } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { ButtonGlass } from './button-glass';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from '@/components/ui/command';\nimport { FormFieldWrapper } from '../primitives/form-field-wrapper';\nimport { inputVariants, type InputGlassSize } from '@/lib/variants/input-glass-variants';\nimport {\n getDropdownContentStyles,\n dropdownContentClasses,\n getDropdownItemClasses,\n} from '@/lib/variants/dropdown-content-styles';\nimport { ICON_SIZES } from '../primitives/style-utils';\nimport '@/glass-theme.css';\n\n// ========================================\n// TYPES\n// ========================================\n\nexport type GlassVariant = 'glass' | 'frosted' | 'fluted' | 'crystal';\n\nexport interface ComboBoxOption<T = string> {\n readonly value: T;\n readonly label: string;\n readonly disabled?: boolean;\n /** Optional icon component for the option */\n readonly icon?: LucideIcon;\n}\n\nexport interface ComboBoxGlassProps<T = string> {\n /** Available options */\n readonly options: readonly ComboBoxOption<T>[];\n /** Currently selected value */\n readonly value?: T;\n /** Callback when value changes */\n readonly onValueChange?: (value: T | undefined) => void;\n /** Placeholder text for trigger button */\n readonly placeholder?: string;\n /** Text shown when no results found */\n readonly emptyText?: string;\n /** Placeholder for search input */\n readonly searchPlaceholder?: string;\n /** Glass variant style */\n readonly glassVariant?: GlassVariant;\n /** Disabled state */\n readonly disabled?: boolean;\n /** Custom className for container */\n readonly className?: string;\n /** Custom className for popover content */\n readonly popoverClassName?: string;\n /** Allow clearing selection */\n readonly clearable?: boolean;\n /** Popover side */\n readonly side?: 'top' | 'right' | 'bottom' | 'left';\n /** Popover alignment */\n readonly align?: 'start' | 'center' | 'end';\n\n // ========================================\n // NEW PROPS (Week 3 Enhancement)\n // ========================================\n\n /** Label text displayed above the field */\n readonly label?: string;\n /** Error message - displays in red below the field */\n readonly error?: string;\n /** Success message - displays in green if no error */\n readonly success?: string;\n /** Shows required asterisk (*) next to label */\n readonly required?: boolean;\n /** Size variant (affects trigger button height and padding) */\n readonly size?: InputGlassSize;\n /** Enable/disable search functionality */\n readonly searchable?: boolean;\n /** Optional icon for trigger button (displayed before text) */\n readonly icon?: LucideIcon;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nfunction ComboBoxGlassInner<T = string>(\n {\n options,\n value,\n onValueChange,\n placeholder = 'Select option...',\n emptyText = 'No results found.',\n searchPlaceholder = 'Search...',\n glassVariant = 'glass',\n disabled = false,\n className,\n popoverClassName,\n clearable = false,\n side = 'bottom',\n align = 'start',\n // NEW PROPS\n label,\n error,\n success,\n required = false,\n size = 'md',\n searchable = true,\n icon: TriggerIcon,\n }: ComboBoxGlassProps<T>,\n ref: React.ForwardedRef<HTMLButtonElement>\n) {\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState('');\n const fieldId = useId();\n\n // Find selected option\n const selectedOption = useMemo(\n () => options.find((opt) => opt.value === value),\n [options, value]\n );\n\n // Filter options based on search\n const filteredOptions = useMemo(() => {\n if (!search) return options;\n const searchLower = search.toLowerCase();\n return options.filter((opt) => opt.label.toLowerCase().includes(searchLower));\n }, [options, search]);\n\n // Handle option selection\n const handleSelect = useCallback(\n (optionValue: T) => {\n if (clearable && value === optionValue) {\n onValueChange?.(undefined);\n } else {\n onValueChange?.(optionValue);\n }\n setOpen(false);\n setSearch('');\n },\n [value, onValueChange, clearable]\n );\n\n // Get glass variant class\n const getGlassClass = () => {\n const variants: Record<GlassVariant, string> = {\n glass: 'glass',\n frosted: 'frosted',\n fluted: 'fluted',\n crystal: 'crystal',\n };\n return variants[glassVariant];\n };\n\n // Render content\n const comboboxContent = (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <ButtonGlass\n ref={ref}\n variant=\"secondary\"\n role=\"combobox\"\n aria-expanded={open}\n aria-haspopup=\"listbox\"\n aria-label={selectedOption?.label || placeholder}\n aria-describedby={error ? `${fieldId}-error` : success ? `${fieldId}-success` : undefined}\n disabled={disabled}\n className={cn(\n 'w-full justify-between',\n // Apply size variant via inputVariants\n inputVariants({ size }),\n !selectedOption && 'text-muted-foreground',\n className\n )}\n >\n <span className=\"flex items-center gap-2 truncate\">\n {TriggerIcon && <TriggerIcon className={ICON_SIZES.md} />}\n {selectedOption ? selectedOption.label : placeholder}\n </span>\n <ChevronsUpDownIcon className={cn(ICON_SIZES.md, 'shrink-0 opacity-50')} />\n </ButtonGlass>\n </PopoverTrigger>\n <PopoverContent\n side={side}\n align={align}\n className={cn(\n dropdownContentClasses,\n 'w-[--radix-popover-trigger-width] p-0 border-0',\n getGlassClass(),\n popoverClassName\n )}\n style={getDropdownContentStyles()}\n >\n <Command\n shouldFilter={false}\n className={cn(\n 'bg-transparent',\n // Hide default border, use subtle divider instead\n '[&_[data-slot=command-input-wrapper]]:border-b-0',\n // Fix search icon - use glass theme color with better visibility\n '[&_[data-slot=command-input-wrapper]_svg]:text-[var(--text-muted)]',\n '[&_[data-slot=command-input-wrapper]_svg]:opacity-80'\n )}\n >\n {searchable && (\n <CommandInput\n placeholder={searchPlaceholder}\n value={search}\n onValueChange={setSearch}\n className=\"text-[var(--text-primary)] placeholder:text-[var(--text-muted)] h-10 font-medium\"\n />\n )}\n <CommandList className=\"p-1.5 scrollbar-hide\">\n <CommandEmpty className=\"text-[var(--text-muted)] py-4\">{emptyText}</CommandEmpty>\n <CommandGroup className=\"text-[var(--text-primary)] p-0\">\n {filteredOptions.map((option) => {\n const OptionIcon = option.icon;\n const isSelected = value === option.value;\n\n return (\n <CommandItem\n key={String(option.value)}\n value={String(option.value)}\n disabled={option.disabled}\n onSelect={() => handleSelect(option.value)}\n className={cn(\n getDropdownItemClasses({ selected: isSelected }),\n option.disabled && 'cursor-not-allowed opacity-50'\n )}\n >\n <CheckIcon\n className={cn(\n ICON_SIZES.md,\n 'shrink-0',\n isSelected ? 'opacity-100 text-[var(--text-accent)]' : 'opacity-0'\n )}\n />\n {OptionIcon && (\n <OptionIcon\n className={cn(ICON_SIZES.md, 'shrink-0 text-[var(--dropdown-icon)]')}\n />\n )}\n <span className=\"truncate\">{option.label}</span>\n </CommandItem>\n );\n })}\n </CommandGroup>\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n );\n\n // Wrap with FormFieldWrapper if label/error/success provided\n if (label || error || success) {\n return (\n <FormFieldWrapper\n label={label}\n error={error}\n success={success}\n required={required}\n htmlFor={fieldId}\n >\n {comboboxContent}\n </FormFieldWrapper>\n );\n }\n\n return comboboxContent;\n}\n\n// Export with generic type support\nexport const ComboBoxGlass = forwardRef(ComboBoxGlassInner) as <T = string>(\n props: ComboBoxGlassProps<T> & { ref?: React.ForwardedRef<HTMLButtonElement> }\n) => ReturnType<typeof ComboBoxGlassInner>;\n\n// Add display name for debugging\nComboBoxGlassInner.displayName = 'ComboBoxGlass';\n"
|
|
20
20
|
}
|
|
21
21
|
],
|
|
22
22
|
"categories": [
|
package/dist/r/registry.json
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"name": "toggle-glass",
|
|
14
14
|
"type": "registry:ui",
|
|
15
15
|
"title": "Toggle Glass",
|
|
16
|
-
"description": "Glass-themed toggle switch with:"
|
|
16
|
+
"description": "Glass-themed toggle switch with shadcn/ui compatible API:"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"name": "tabs-glass",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"name": "slider-glass",
|
|
32
32
|
"type": "registry:ui",
|
|
33
33
|
"title": "Slider Glass",
|
|
34
|
-
"description": "Glass-themed range slider with:"
|
|
34
|
+
"description": "Glass-themed range slider built on Radix UI with shadcn/ui compatible API:"
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"name": "skeleton-glass",
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
"title": "Repository Card Glass",
|
|
6
6
|
"description": "Repository Card Glass component with glass effects",
|
|
7
7
|
"dependencies": [
|
|
8
|
+
"lucide-react",
|
|
8
9
|
"react"
|
|
9
10
|
],
|
|
10
11
|
"registryDependencies": [
|
|
@@ -14,7 +15,7 @@
|
|
|
14
15
|
{
|
|
15
16
|
"path": "components/glass/composite/repository-card-glass.tsx",
|
|
16
17
|
"type": "registry:component",
|
|
17
|
-
"content": "// ========================================\n// REPOSITORY CARD GLASS COMPONENT\n// Expandable repository card with metrics\n// ========================================\n\nimport { forwardRef, type CSSProperties } from
|
|
18
|
+
"content": "// ========================================\n// REPOSITORY CARD GLASS COMPONENT\n// Expandable repository card with metrics\n// ========================================\n\nimport { forwardRef, type CSSProperties } from 'react';\nimport { ChevronDown, ChevronUp, Star, ExternalLink, Sparkles, AlertTriangle } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { StatusIndicatorGlass } from '../specialized/status-indicator-glass';\nimport { ButtonGlass } from '../ui/button-glass';\nimport { InteractiveCard } from '../primitives';\nimport '@/glass-theme.css';\n\nexport type RepositoryFlagType = 'green' | 'yellow' | 'red';\n\nexport interface RepositoryCardGlassProps extends React.HTMLAttributes<HTMLDivElement> {\n readonly name: string;\n readonly languages: string;\n readonly commits: number;\n readonly contribution: number;\n readonly stars?: number;\n readonly flagType?: RepositoryFlagType;\n readonly issues?: readonly string[];\n readonly expanded?: boolean;\n readonly onToggle?: () => void;\n readonly onGitHubClick?: () => void;\n readonly onAIAnalysisClick?: () => void;\n}\n\nexport const RepositoryCardGlass = forwardRef<HTMLDivElement, RepositoryCardGlassProps>(\n (\n {\n name,\n languages,\n commits,\n contribution,\n stars = 0,\n flagType = 'green',\n issues = [],\n expanded = false,\n onToggle,\n onGitHubClick,\n onAIAnalysisClick,\n className,\n ...props\n },\n ref\n ) => {\n // Calculate total project commits from contribution percentage\n const totalProjectCommits =\n contribution > 0 ? Math.round(commits / (contribution / 100)) : commits;\n const estimatedLines = Math.round(commits * 12);\n\n const expandedStyles: CSSProperties = {\n background: 'var(--expanded-bg)',\n borderColor: 'var(--card-border)',\n };\n\n const metricCardStyles: CSSProperties = {\n background: 'var(--card-bg)',\n borderColor: 'var(--card-border)',\n };\n\n return (\n <InteractiveCard\n ref={ref}\n baseBg=\"var(--card-bg)\"\n hoverBg=\"var(--card-hover-bg)\"\n borderColor=\"var(--card-border)\"\n hoverLift={false}\n blur=\"sm\"\n rounded=\"rounded-xl\"\n className={cn('overflow-hidden', className)}\n {...props}\n >\n {/* Main Card Content */}\n <div\n className=\"p-3 md:p-3.5 cursor-pointer\"\n onClick={onToggle}\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onToggle?.();\n }\n }}\n aria-expanded={expanded}\n >\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2.5\">\n <span className=\"font-medium text-sm\" style={{ color: 'var(--text-primary)' }}>\n {name}\n </span>\n <StatusIndicatorGlass type={flagType} />\n </div>\n <div className=\"flex items-center gap-2\">\n {stars > 0 && (\n <span\n className=\"flex items-center gap-1 text-xs\"\n style={{ color: 'var(--status-away)' }}\n >\n <Star className=\"w-3 h-3\" />\n {stars}\n </span>\n )}\n {expanded ? (\n <ChevronUp className=\"w-4 h-4\" style={{ color: 'var(--text-muted)' }} />\n ) : (\n <ChevronDown className=\"w-4 h-4\" style={{ color: 'var(--text-muted)' }} />\n )}\n </div>\n </div>\n <div className=\"text-xs mt-1.5\" style={{ color: 'var(--text-muted)' }}>\n {languages}\n </div>\n <div className=\"text-xs mt-0.5\" style={{ color: 'var(--text-secondary)' }}>\n {commits} commits · {contribution}%\n </div>\n </div>\n\n {/* Expanded Section */}\n {expanded && (\n <div className=\"border-t p-3.5 space-y-3\" style={expandedStyles}>\n {/* Issues Alert */}\n {issues.length > 0 && (\n <div\n className=\"p-3 rounded-xl border\"\n style={{\n background: 'var(--alert-danger-bg)',\n borderColor: 'var(--alert-danger-border)',\n }}\n >\n <div\n className=\"text-xs font-semibold flex items-center gap-1.5 mb-1.5\"\n style={{ color: 'var(--alert-danger-text)' }}\n >\n <AlertTriangle className=\"w-3.5 h-3.5\" />\n Issues\n </div>\n {issues.map((issue, index) => (\n <div\n key={index}\n className=\"text-xs opacity-70\"\n style={{ color: 'var(--alert-danger-text)' }}\n >\n • {issue}\n </div>\n ))}\n </div>\n )}\n\n {/* Contribution Metrics */}\n <div className=\"grid grid-cols-2 gap-2\">\n <div className=\"p-2.5 rounded-lg border\" style={metricCardStyles}>\n <div className=\"text-xs\" style={{ color: 'var(--text-muted)' }}>\n Your Contribution\n </div>\n <div className=\"font-semibold\" style={{ color: 'var(--text-primary)' }}>\n {commits} commits\n </div>\n <div className=\"text-xs\" style={{ color: 'var(--text-muted)' }}>\n {contribution}%\n </div>\n </div>\n <div className=\"p-2.5 rounded-lg border\" style={metricCardStyles}>\n <div className=\"text-xs\" style={{ color: 'var(--text-muted)' }}>\n Full Project\n </div>\n <div className=\"font-semibold\" style={{ color: 'var(--text-primary)' }}>\n {totalProjectCommits} commits\n </div>\n <div className=\"text-xs\" style={{ color: 'var(--text-muted)' }}>\n ~{estimatedLines} lines\n </div>\n </div>\n </div>\n\n {/* Action Buttons */}\n <div className=\"flex flex-col sm:flex-row gap-2\">\n <ButtonGlass\n variant=\"secondary\"\n size=\"sm\"\n icon={ExternalLink}\n onClick={(e) => {\n e.stopPropagation();\n onGitHubClick?.();\n }}\n className=\"flex-1\"\n >\n GitHub\n </ButtonGlass>\n <ButtonGlass\n variant=\"default\"\n size=\"sm\"\n icon={Sparkles}\n onClick={(e) => {\n e.stopPropagation();\n onAIAnalysisClick?.();\n }}\n className=\"flex-1\"\n >\n AI Analysis\n </ButtonGlass>\n </div>\n </div>\n )}\n </InteractiveCard>\n );\n }\n);\n\nRepositoryCardGlass.displayName = 'RepositoryCardGlass';\n"
|
|
18
19
|
}
|
|
19
20
|
],
|
|
20
21
|
"categories": [
|
package/dist/r/slider-glass.json
CHANGED
|
@@ -3,21 +3,20 @@
|
|
|
3
3
|
"name": "slider-glass",
|
|
4
4
|
"type": "registry:ui",
|
|
5
5
|
"title": "Slider Glass",
|
|
6
|
-
"description": "Glass-themed range slider with:",
|
|
6
|
+
"description": "Glass-themed range slider built on Radix UI with shadcn/ui compatible API:",
|
|
7
7
|
"dependencies": [
|
|
8
|
+
"@radix-ui/react-slider",
|
|
8
9
|
"react"
|
|
9
10
|
],
|
|
10
11
|
"registryDependencies": [
|
|
11
12
|
"cn",
|
|
12
|
-
"primitives"
|
|
13
|
-
"use-focus",
|
|
14
|
-
"use-hover"
|
|
13
|
+
"primitives"
|
|
15
14
|
],
|
|
16
15
|
"files": [
|
|
17
16
|
{
|
|
18
17
|
"path": "components/glass/ui/slider-glass.tsx",
|
|
19
18
|
"type": "registry:component",
|
|
20
|
-
"content": "/**\n * SliderGlass Component\n *\n * Glass-themed range slider with:\n * - Theme-aware styling (glass/light/aurora)\n * - Glow effect on hover/drag\n * - Gradient fill (glass theme)\n * - Optional label and value display\n
|
|
19
|
+
"content": "/**\n * SliderGlass Component\n *\n * Glass-themed range slider built on Radix UI with shadcn/ui compatible API:\n * - Theme-aware styling (glass/light/aurora)\n * - Glow effect on hover/drag\n * - Gradient fill (glass theme)\n * - Range slider support (multiple thumbs)\n * - Optional label and value display\n *\n * **shadcn/ui compatible props:**\n * - `value` / `defaultValue` - Array of values (supports range)\n * - `onValueChange` - Callback when values change\n * - `onValueCommit` - Callback when interaction ends\n *\n * @example\n * ```tsx\n * // Single value (controlled)\n * <SliderGlass value={[50]} onValueChange={setValue} />\n *\n * // Range slider\n * <SliderGlass defaultValue={[25, 75]} />\n *\n * // With label and value display\n * <SliderGlass value={[50]} onValueChange={setValue} label=\"Volume\" showValue />\n * ```\n */\n\nimport { forwardRef, type CSSProperties, type ComponentPropsWithoutRef } from 'react';\nimport * as SliderPrimitive from '@radix-ui/react-slider';\nimport { cn } from '@/lib/utils';\nimport { FormFieldWrapper } from '@/components/glass/primitives';\nimport '@/glass-theme.css';\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\nexport interface SliderGlassProps extends Omit<\n ComponentPropsWithoutRef<typeof SliderPrimitive.Root>,\n 'value' | 'defaultValue' | 'onValueChange'\n> {\n /**\n * Controlled value (shadcn/ui compatible - array for range support)\n */\n readonly value?: number[];\n /**\n * Default value for uncontrolled usage (array for range support)\n */\n readonly defaultValue?: number[];\n /**\n * Callback when value changes (shadcn/ui compatible)\n */\n readonly onValueChange?: (value: number[]) => void;\n /**\n * Callback when interaction ends (mouse up / touch end)\n */\n readonly onValueCommit?: (value: number[]) => void;\n /**\n * Show current value(s) next to label\n */\n readonly showValue?: boolean;\n /**\n * Optional label text\n */\n readonly label?: string;\n /**\n * Error message to display\n */\n readonly error?: string;\n /**\n * Success message to display\n */\n readonly success?: string;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const SliderGlass = forwardRef<\n React.ComponentRef<typeof SliderPrimitive.Root>,\n SliderGlassProps\n>(\n (\n {\n className,\n value,\n defaultValue,\n onValueChange,\n onValueCommit,\n min = 0,\n max = 100,\n step = 1,\n showValue,\n label,\n error,\n success,\n disabled,\n orientation = 'horizontal',\n ...props\n },\n ref\n ) => {\n // Determine current values for rendering thumbs and display\n const currentValue = value ?? defaultValue ?? [min];\n\n // Format value display\n const formatValueDisplay = (values: number[]) => {\n if (values.length === 1) {\n return `${values[0]}`;\n }\n return `${values[0]} - ${values[values.length - 1]}`;\n };\n\n // Track styles via CSS variables\n const trackStyles: CSSProperties = {\n background: 'var(--slider-track)',\n };\n\n // Range (fill) styles via CSS variables\n const rangeStyles: CSSProperties = {\n background: 'var(--slider-fill)',\n };\n\n // Thumb styles via CSS variables\n const thumbStyles: CSSProperties = {\n background: 'var(--slider-thumb)',\n border: '2px solid var(--slider-thumb-border)',\n };\n\n // Custom label with value display - only used when showValue is true\n const customLabel =\n (label && showValue) || (!label && showValue) ? (\n <div className=\"flex justify-between mb-1.5 md:mb-2\">\n {label && (\n <label\n className=\"text-xs md:text-sm font-medium\"\n style={{ color: 'var(--text-secondary)' }}\n >\n {label}\n </label>\n )}\n <span\n className=\"text-xs md:text-sm font-medium tabular-nums\"\n style={{ color: 'var(--text-secondary)' }}\n >\n {formatValueDisplay(currentValue)}\n </span>\n </div>\n ) : undefined;\n\n return (\n <FormFieldWrapper\n label={showValue ? undefined : label}\n error={error}\n success={success}\n className={cn('w-full', className)}\n >\n {customLabel}\n <SliderPrimitive.Root\n ref={ref}\n value={value}\n defaultValue={defaultValue}\n onValueChange={onValueChange}\n onValueCommit={onValueCommit}\n min={min}\n max={max}\n step={step}\n disabled={disabled}\n orientation={orientation}\n className={cn(\n 'relative flex touch-none select-none',\n orientation === 'horizontal'\n ? 'w-full h-8 md:h-6 items-center'\n : 'flex-col h-full w-8 md:w-6 justify-center',\n disabled && 'opacity-50 cursor-not-allowed',\n 'group'\n )}\n {...props}\n >\n <SliderPrimitive.Track\n className={cn(\n 'relative grow rounded-full',\n orientation === 'horizontal' ? 'h-2.5 md:h-2 w-full' : 'w-2.5 md:w-2 h-full'\n )}\n style={trackStyles}\n >\n <SliderPrimitive.Range\n className={cn(\n 'absolute rounded-full transition-shadow duration-150',\n orientation === 'horizontal' ? 'h-full' : 'w-full',\n 'group-hover:shadow-(--slider-fill-glow)',\n 'group-active:shadow-(--slider-fill-glow)'\n )}\n style={rangeStyles}\n />\n </SliderPrimitive.Track>\n {currentValue.map((_, index) => (\n <SliderPrimitive.Thumb\n key={index}\n className={cn(\n 'block rounded-full shadow-md transition-all duration-150',\n 'w-6 h-6 md:w-5 md:h-5',\n 'hover:scale-105',\n 'focus-visible:outline-none focus-visible:shadow-(--focus-glow)',\n 'active:scale-110',\n disabled && 'pointer-events-none'\n )}\n style={thumbStyles}\n aria-label={\n label\n ? currentValue.length > 1\n ? `${label} thumb ${index + 1}`\n : label\n : `Slider thumb ${index + 1}`\n }\n />\n ))}\n </SliderPrimitive.Root>\n </FormFieldWrapper>\n );\n }\n);\n\nSliderGlass.displayName = 'SliderGlass';\n"
|
|
21
20
|
}
|
|
22
21
|
],
|
|
23
22
|
"categories": [
|
package/dist/r/toggle-glass.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "toggle-glass",
|
|
4
4
|
"type": "registry:ui",
|
|
5
5
|
"title": "Toggle Glass",
|
|
6
|
-
"description": "Glass-themed toggle switch with:",
|
|
6
|
+
"description": "Glass-themed toggle switch with shadcn/ui compatible API:",
|
|
7
7
|
"dependencies": [
|
|
8
8
|
"class-variance-authority",
|
|
9
9
|
"react"
|
|
@@ -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:\n * - Theme-aware styling (glass/light/aurora)\n * - Glow effect when active\n * - Size variants\n * - Optional label\n */\n\nimport { forwardRef, 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 } 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
|
|
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"
|
|
21
21
|
}
|
|
22
22
|
],
|
|
23
23
|
"categories": [
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
{
|
|
17
17
|
"path": "components/glass/composite/year-card-glass.tsx",
|
|
18
18
|
"type": "registry:component",
|
|
19
|
-
"content": "// ========================================\n// YEAR CARD GLASS COMPONENT\n// Year card for career timeline / analytics\n// Domain-specific composite component following shadcn/ui patterns\n// ========================================\n\nimport { forwardRef, type CSSProperties, type ReactNode } from 'react';\nimport { ChevronDown, ChevronUp } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { BadgeGlass } from '../ui/badge-glass';\nimport { ProgressGlass } from '../specialized/progress-glass';\nimport { SparklineGlass } from '../specialized/sparkline-glass';\nimport { ButtonGlass } from '../ui/button-glass';\nimport { InteractiveCard } from '../primitives';\nimport { InsightCardGlass } from '../atomic/insight-card-glass';\nimport type { ProgressGradient } from '@/lib/variants/progress-glass-variants';\nimport '@/glass-theme.css';\n\n// ========================================\n// TYPES\n// ========================================\n\nexport interface YearCardGlassInsight {\n readonly variant?: 'default' | 'tip' | 'highlight' | 'warning' | 'stat' | 'growth' | 'decline';\n readonly emoji?: string;\n readonly text: string;\n readonly detail?: string;\n}\n\nexport interface YearCardGlassStat {\n readonly label: string;\n readonly value: string | number;\n readonly icon?: ReactNode;\n}\n\nexport interface YearCardGlassProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Year to display (string or number) */\n readonly year: string | number;\n /** Emoji for the badge */\n readonly emoji: string;\n /** Label for the badge (e.g., \"Peak Year\") */\n readonly label: string;\n /** Primary metric value (e.g., \"629 commits\") */\n readonly commits: string;\n /** Progress percentage (0-100) */\n readonly progress: number;\n /** Whether the card is expanded to show details */\n readonly isExpanded?: boolean;\n /** Progress bar gradient color */\n readonly gradient?: ProgressGradient;\n /** Number of PRs (shown in expanded view) */\n readonly prs?: number;\n /** Number of repos (shown in expanded view) */\n readonly repos?: number;\n /** Callback when \"Show repos\" button is clicked */\n readonly onShowYear?: () => void;\n /** Monthly activity data for sparkline */\n readonly sparklineData?: readonly number[];\n /** Labels for sparkline (e.g., month names) */\n readonly sparklineLabels?: readonly string[];\n /** Insights to display in expanded view */\n readonly insights?: readonly YearCardGlassInsight[];\n /** Custom stats for expanded view (replaces default commits/prs/repos grid) */\n readonly stats?: readonly YearCardGlassStat[];\n /** Custom action button text */\n readonly actionLabel?: string;\n /** Show sparkline in collapsed view */\n readonly showSparklineCollapsed?: boolean;\n /** Custom value formatter for commits display */\n readonly valueFormatter?: (commits: string) => string;\n /** Additional content for expanded section */\n readonly children?: ReactNode;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const YearCardGlass = forwardRef<HTMLDivElement, YearCardGlassProps>(\n (\n {\n year,\n emoji,\n label,\n commits,\n progress,\n isExpanded = false,\n gradient = 'blue',\n prs = 0,\n repos = 0,\n onShowYear,\n sparklineData,\n sparklineLabels,\n insights,\n stats,\n actionLabel,\n showSparklineCollapsed = true,\n valueFormatter,\n children,\n className,\n onClick,\n ...props\n },\n ref\n ) => {\n const expandedStyles: CSSProperties = {\n background: 'var(--expanded-bg)',\n borderColor: 'var(--expanded-border)',\n };\n\n const metricCardStyles: CSSProperties = {\n background: 'var(--card-bg)',\n borderColor: 'var(--card-border)',\n };\n\n // Format commits display\n const displayCommits = valueFormatter ? valueFormatter(commits) : commits;\n\n // Default stats if none provided\n const displayStats = stats || [\n { label: 'Commits', value: commits },\n { label: 'PRs', value: prs },\n { label: 'Repos', value: repos },\n ];\n\n // Default action label\n const buttonLabel = actionLabel || `Show repos from ${year}`;\n\n return (\n <InteractiveCard\n ref={ref}\n baseBg=\"var(--year-card-bg)\"\n hoverBg=\"var(--card-hover-bg)\"\n borderColor=\"var(--year-card-border)\"\n hoverGlow=\"var(--year-card-hover-glow)\"\n hoverLift\n rounded=\"rounded-xl\"\n className={cn('w-full max-w-2xl p-2.5 md:p-3 cursor-pointer', className)}\n onClick={onClick}\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.(e as unknown as React.MouseEvent<HTMLDivElement>);\n }\n }}\n aria-expanded={isExpanded}\n aria-label={`${year} year: ${label}, ${commits} commits. ${isExpanded ? 'Collapse' : 'Expand'} details`}\n {...props}\n >\n {/* Header */}\n <div className=\"flex items-center justify-between mb-1.5 md:mb-2\">\n <div className=\"flex items-center gap-1.5 md:gap-2\">\n <span\n className=\"font-semibold text-sm md:text-base\"\n style={{ color: 'var(--text-primary)' }}\n >\n {year}\n </span>\n <BadgeGlass>\n {emoji} {label}\n </BadgeGlass>\n </div>\n <span\n className=\"text-xs md:text-sm flex items-center gap-0.5 md:gap-1\"\n style={{ color: 'var(--text-secondary)' }}\n >\n {displayCommits}\n {isExpanded ? (\n <ChevronUp className=\"w-3.5 h-3.5 md:w-4 md:h-4\" aria-hidden=\"true\" />\n ) : (\n <ChevronDown className=\"w-3.5 h-3.5 md:w-4 md:h-4\" aria-hidden=\"true\" />\n )}\n </span>\n </div>\n\n {/* Progress + Sparkline (collapsed view) */}\n <div className=\"flex items-center gap-2\">\n <div className=\"flex-1\">\n <ProgressGlass value={progress} gradient={gradient} size=\"sm\" />\n </div>\n {showSparklineCollapsed && sparklineData && sparklineData.length > 0 && (\n <SparklineGlass\n data={sparklineData}\n labels={sparklineLabels}\n height=\"sm\"\n gap=\"sm\"\n className=\"w-16 md:w-20\"\n aria-label={`Activity trend for ${year}`}\n />\n )}\n </div>\n\n {/* Expanded Section */}\n {isExpanded && (\n <div className=\"mt-3 pt-3 border-t space-y-3\" style={expandedStyles}>\n {/* Stats Grid */}\n <div\n className=\"grid gap-2 md:gap-3\"\n style={{ gridTemplateColumns: `repeat(${Math.min(displayStats.length, 4)}, 1fr)` }}\n >\n {displayStats.map((stat, index) => (\n <div\n key={index}\n className=\"p-2 md:p-2.5 rounded-lg border text-center\"\n style={metricCardStyles}\n >\n {stat.icon && (\n <div className=\"mb-1 flex justify-center text-[var(--text-muted)]\">\n {stat.icon}\n </div>\n )}\n <div\n className=\"text-base md:text-xl font-bold\"\n style={{ color: 'var(--text-primary)' }}\n >\n {stat.value}\n </div>\n <div className=\"text-(length:--font-size-2xs) md:text-xs text-(--text-muted)\">\n {stat.label}\n </div>\n </div>\n ))}\n </div>\n\n {/* Expanded Sparkline with labels (if not shown in collapsed) */}\n {!showSparklineCollapsed && sparklineData && sparklineData.length > 0 && (\n <SparklineGlass\n data={sparklineData}\n labels={sparklineLabels}\n showLabels={!!sparklineLabels}\n height=\"md\"\n gap=\"sm\"\n highlightMax\n className=\"w-full\"\n aria-label={`Monthly activity for ${year}`}\n />\n )}\n\n {/* Insights */}\n {insights && insights.length > 0 && (\n <div className=\"space-y-2\">\n {insights.map((insight, index) => (\n <InsightCardGlass\n key={index}\n variant={insight.variant}\n emoji={insight.emoji}\n text={insight.text}\n detail={insight.detail}\n inline={false}\n />\n ))}\n </div>\n )}\n\n {/* Custom children content */}\n {children}\n\n {/* Show Year Button */}\n {onShowYear && (\n <ButtonGlass\n variant=\"
|
|
19
|
+
"content": "// ========================================\n// YEAR CARD GLASS COMPONENT\n// Year card for career timeline / analytics\n// Domain-specific composite component following shadcn/ui patterns\n// ========================================\n\nimport { forwardRef, type CSSProperties, type ReactNode } from 'react';\nimport { ChevronDown, ChevronUp } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { BadgeGlass } from '../ui/badge-glass';\nimport { ProgressGlass } from '../specialized/progress-glass';\nimport { SparklineGlass } from '../specialized/sparkline-glass';\nimport { ButtonGlass } from '../ui/button-glass';\nimport { InteractiveCard } from '../primitives';\nimport { InsightCardGlass } from '../atomic/insight-card-glass';\nimport type { ProgressGradient } from '@/lib/variants/progress-glass-variants';\nimport '@/glass-theme.css';\n\n// ========================================\n// TYPES\n// ========================================\n\nexport interface YearCardGlassInsight {\n readonly variant?: 'default' | 'tip' | 'highlight' | 'warning' | 'stat' | 'growth' | 'decline';\n readonly emoji?: string;\n readonly text: string;\n readonly detail?: string;\n}\n\nexport interface YearCardGlassStat {\n readonly label: string;\n readonly value: string | number;\n readonly icon?: ReactNode;\n}\n\nexport interface YearCardGlassProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Year to display (string or number) */\n readonly year: string | number;\n /** Emoji for the badge */\n readonly emoji: string;\n /** Label for the badge (e.g., \"Peak Year\") */\n readonly label: string;\n /** Primary metric value (e.g., \"629 commits\") */\n readonly commits: string;\n /** Progress percentage (0-100) */\n readonly progress: number;\n /** Whether the card is expanded to show details */\n readonly isExpanded?: boolean;\n /** Progress bar gradient color */\n readonly gradient?: ProgressGradient;\n /** Number of PRs (shown in expanded view) */\n readonly prs?: number;\n /** Number of repos (shown in expanded view) */\n readonly repos?: number;\n /** Callback when \"Show repos\" button is clicked */\n readonly onShowYear?: () => void;\n /** Monthly activity data for sparkline */\n readonly sparklineData?: readonly number[];\n /** Labels for sparkline (e.g., month names) */\n readonly sparklineLabels?: readonly string[];\n /** Insights to display in expanded view */\n readonly insights?: readonly YearCardGlassInsight[];\n /** Custom stats for expanded view (replaces default commits/prs/repos grid) */\n readonly stats?: readonly YearCardGlassStat[];\n /** Custom action button text */\n readonly actionLabel?: string;\n /** Show sparkline in collapsed view */\n readonly showSparklineCollapsed?: boolean;\n /** Custom value formatter for commits display */\n readonly valueFormatter?: (commits: string) => string;\n /** Additional content for expanded section */\n readonly children?: ReactNode;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const YearCardGlass = forwardRef<HTMLDivElement, YearCardGlassProps>(\n (\n {\n year,\n emoji,\n label,\n commits,\n progress,\n isExpanded = false,\n gradient = 'blue',\n prs = 0,\n repos = 0,\n onShowYear,\n sparklineData,\n sparklineLabels,\n insights,\n stats,\n actionLabel,\n showSparklineCollapsed = true,\n valueFormatter,\n children,\n className,\n onClick,\n ...props\n },\n ref\n ) => {\n const expandedStyles: CSSProperties = {\n background: 'var(--expanded-bg)',\n borderColor: 'var(--expanded-border)',\n };\n\n const metricCardStyles: CSSProperties = {\n background: 'var(--card-bg)',\n borderColor: 'var(--card-border)',\n };\n\n // Format commits display\n const displayCommits = valueFormatter ? valueFormatter(commits) : commits;\n\n // Default stats if none provided\n const displayStats = stats || [\n { label: 'Commits', value: commits },\n { label: 'PRs', value: prs },\n { label: 'Repos', value: repos },\n ];\n\n // Default action label\n const buttonLabel = actionLabel || `Show repos from ${year}`;\n\n return (\n <InteractiveCard\n ref={ref}\n baseBg=\"var(--year-card-bg)\"\n hoverBg=\"var(--card-hover-bg)\"\n borderColor=\"var(--year-card-border)\"\n hoverGlow=\"var(--year-card-hover-glow)\"\n hoverLift\n rounded=\"rounded-xl\"\n className={cn('w-full max-w-2xl p-2.5 md:p-3 cursor-pointer', className)}\n onClick={onClick}\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.(e as unknown as React.MouseEvent<HTMLDivElement>);\n }\n }}\n aria-expanded={isExpanded}\n aria-label={`${year} year: ${label}, ${commits} commits. ${isExpanded ? 'Collapse' : 'Expand'} details`}\n {...props}\n >\n {/* Header */}\n <div className=\"flex items-center justify-between mb-1.5 md:mb-2\">\n <div className=\"flex items-center gap-1.5 md:gap-2\">\n <span\n className=\"font-semibold text-sm md:text-base\"\n style={{ color: 'var(--text-primary)' }}\n >\n {year}\n </span>\n <BadgeGlass>\n {emoji} {label}\n </BadgeGlass>\n </div>\n <span\n className=\"text-xs md:text-sm flex items-center gap-0.5 md:gap-1\"\n style={{ color: 'var(--text-secondary)' }}\n >\n {displayCommits}\n {isExpanded ? (\n <ChevronUp className=\"w-3.5 h-3.5 md:w-4 md:h-4\" aria-hidden=\"true\" />\n ) : (\n <ChevronDown className=\"w-3.5 h-3.5 md:w-4 md:h-4\" aria-hidden=\"true\" />\n )}\n </span>\n </div>\n\n {/* Progress + Sparkline (collapsed view) */}\n <div className=\"flex items-center gap-2\">\n <div className=\"flex-1\">\n <ProgressGlass value={progress} gradient={gradient} size=\"sm\" />\n </div>\n {showSparklineCollapsed && sparklineData && sparklineData.length > 0 && (\n <SparklineGlass\n data={sparklineData}\n labels={sparklineLabels}\n height=\"sm\"\n gap=\"sm\"\n className=\"w-16 md:w-20\"\n aria-label={`Activity trend for ${year}`}\n />\n )}\n </div>\n\n {/* Expanded Section */}\n {isExpanded && (\n <div className=\"mt-3 pt-3 border-t space-y-3\" style={expandedStyles}>\n {/* Stats Grid */}\n <div\n className=\"grid gap-2 md:gap-3\"\n style={{ gridTemplateColumns: `repeat(${Math.min(displayStats.length, 4)}, 1fr)` }}\n >\n {displayStats.map((stat, index) => (\n <div\n key={index}\n className=\"p-2 md:p-2.5 rounded-lg border text-center\"\n style={metricCardStyles}\n >\n {stat.icon && (\n <div className=\"mb-1 flex justify-center text-[var(--text-muted)]\">\n {stat.icon}\n </div>\n )}\n <div\n className=\"text-base md:text-xl font-bold\"\n style={{ color: 'var(--text-primary)' }}\n >\n {stat.value}\n </div>\n <div className=\"text-(length:--font-size-2xs) md:text-xs text-(--text-muted)\">\n {stat.label}\n </div>\n </div>\n ))}\n </div>\n\n {/* Expanded Sparkline with labels (if not shown in collapsed) */}\n {!showSparklineCollapsed && sparklineData && sparklineData.length > 0 && (\n <SparklineGlass\n data={sparklineData}\n labels={sparklineLabels}\n showLabels={!!sparklineLabels}\n height=\"md\"\n gap=\"sm\"\n highlightMax\n className=\"w-full\"\n aria-label={`Monthly activity for ${year}`}\n />\n )}\n\n {/* Insights */}\n {insights && insights.length > 0 && (\n <div className=\"space-y-2\">\n {insights.map((insight, index) => (\n <InsightCardGlass\n key={index}\n variant={insight.variant}\n emoji={insight.emoji}\n text={insight.text}\n detail={insight.detail}\n inline={false}\n />\n ))}\n </div>\n )}\n\n {/* Custom children content */}\n {children}\n\n {/* Show Year Button */}\n {onShowYear && (\n <ButtonGlass\n variant=\"default\"\n size=\"sm\"\n onClick={(e) => {\n e.stopPropagation();\n onShowYear();\n }}\n className=\"w-full\"\n >\n {buttonLabel}\n </ButtonGlass>\n )}\n </div>\n )}\n </InteractiveCard>\n );\n }\n);\n\nYearCardGlass.displayName = 'YearCardGlass';\n"
|
|
20
20
|
}
|
|
21
21
|
],
|
|
22
22
|
"categories": [
|