shadcn-glass-ui 1.0.0 → 1.0.6
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 +123 -0
- package/README.md +128 -138
- package/dist/components.cjs +4 -4
- package/dist/components.js +1 -1
- package/dist/hooks.cjs +2 -2
- package/dist/index.cjs +5 -5
- package/dist/index.js +1 -1
- package/dist/r/ai-card-glass.json +24 -0
- package/dist/r/alert-glass.json +42 -0
- package/dist/r/avatar-glass.json +43 -0
- package/dist/r/badge-glass.json +41 -0
- package/dist/r/base-progress-glass.json +23 -0
- package/dist/r/button-glass.json +45 -0
- package/dist/r/career-stats-glass.json +24 -0
- package/dist/r/career-stats-header-glass.json +24 -0
- package/dist/r/checkbox-glass.json +42 -0
- package/dist/r/circular-metric-glass.json +23 -0
- package/dist/r/circular-progress-glass.json +40 -0
- package/dist/r/combobox-glass.json +41 -0
- package/dist/r/contribution-metrics-glass.json +23 -0
- package/dist/r/dropdown-glass.json +43 -0
- package/dist/r/expandable-header-glass.json +23 -0
- package/dist/r/flag-alert-glass.json +23 -0
- package/dist/r/flags-section-glass.json +24 -0
- package/dist/r/form-field-wrapper.json +23 -0
- package/dist/r/glass-card.json +42 -0
- package/dist/r/header-branding-glass.json +24 -0
- package/dist/r/header-nav-glass.json +25 -0
- package/dist/r/icon-button-glass.json +25 -0
- package/dist/r/input-glass.json +43 -0
- package/dist/r/interactive-card.json +24 -0
- package/dist/r/language-bar-glass.json +23 -0
- package/dist/r/metric-card-glass.json +24 -0
- package/dist/r/metrics-grid-glass.json +23 -0
- package/dist/r/modal-glass.json +43 -0
- package/dist/r/notification-glass.json +44 -0
- package/dist/r/popover-glass.json +40 -0
- package/dist/r/profile-avatar-glass.json +23 -0
- package/dist/r/profile-header-glass.json +24 -0
- package/dist/r/progress-glass.json +25 -0
- package/dist/r/projects-list-glass.json +24 -0
- package/dist/r/rainbow-progress-glass.json +23 -0
- package/dist/r/registry.json +337 -0
- package/dist/r/repository-card-glass.json +23 -0
- package/dist/r/repository-header-glass.json +24 -0
- package/dist/r/repository-metadata-glass.json +23 -0
- package/dist/r/search-box-glass.json +23 -0
- package/dist/r/segmented-control-glass.json +23 -0
- package/dist/r/skeleton-glass.json +41 -0
- package/dist/r/slider-glass.json +42 -0
- package/dist/r/sort-dropdown-glass.json +25 -0
- package/dist/r/stat-item-glass.json +25 -0
- package/dist/r/status-indicator-glass.json +23 -0
- package/dist/r/tabs-glass.json +38 -0
- package/dist/r/theme-toggle-glass.json +25 -0
- package/dist/r/toggle-glass.json +42 -0
- package/dist/r/tooltip-glass.json +42 -0
- package/dist/r/touch-target.json +23 -0
- package/dist/r/trust-score-card-glass.json +24 -0
- package/dist/r/trust-score-display-glass.json +24 -0
- package/dist/r/user-info-glass.json +24 -0
- package/dist/r/user-stats-line-glass.json +24 -0
- package/dist/r/year-card-glass.json +25 -0
- package/dist/shadcn-glass-ui.css +1 -1
- package/dist/{theme-context-DrLak65e.cjs → theme-context-e3yxC7A6.cjs} +2 -2
- package/dist/{theme-context-DrLak65e.cjs.map → theme-context-e3yxC7A6.cjs.map} +1 -1
- package/dist/themes.cjs +1 -1
- package/dist/{trust-score-card-glass-DqaCKo1w.cjs → trust-score-card-glass-CZeCRkHL.cjs} +19 -17
- package/dist/trust-score-card-glass-CZeCRkHL.cjs.map +1 -0
- package/dist/{trust-score-card-glass-tJnNNzeS.js → trust-score-card-glass-DWrcNoI2.js} +16 -14
- package/dist/trust-score-card-glass-DWrcNoI2.js.map +1 -0
- package/dist/{use-focus-6xqfE5s6.cjs → use-focus-CUkhhBRX.cjs} +2 -2
- package/dist/{use-focus-6xqfE5s6.cjs.map → use-focus-CUkhhBRX.cjs.map} +1 -1
- package/dist/{use-wallpaper-tint-D1f3UGGs.cjs → use-wallpaper-tint-b9KAZtoy.cjs} +2 -2
- package/dist/{use-wallpaper-tint-D1f3UGGs.cjs.map → use-wallpaper-tint-b9KAZtoy.cjs.map} +1 -1
- package/dist/{utils-BNzkwPwE.cjs → utils-Ba5INf7M.cjs} +2 -2
- package/dist/{utils-BNzkwPwE.cjs.map → utils-Ba5INf7M.cjs.map} +1 -1
- package/dist/utils.cjs +1 -1
- package/package.json +5 -2
- package/dist/trust-score-card-glass-DqaCKo1w.cjs.map +0 -1
- package/dist/trust-score-card-glass-tJnNNzeS.js.map +0 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "repository-card-glass",
|
|
4
|
+
"type": "registry:block",
|
|
5
|
+
"title": "Repository Card Glass",
|
|
6
|
+
"description": "Repository Card Glass component with glass effects",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"react"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"cn"
|
|
12
|
+
],
|
|
13
|
+
"files": [
|
|
14
|
+
{
|
|
15
|
+
"path": "components/glass/composite/repository-card-glass.tsx",
|
|
16
|
+
"type": "registry:component",
|
|
17
|
+
"content": "// ========================================\n// REPOSITORY CARD GLASS COMPONENT\n// Expandable repository card with metrics\n// ========================================\n\nimport { forwardRef, type CSSProperties } from \"react\";\nimport {\n ChevronDown,\n ChevronUp,\n Star,\n ExternalLink,\n Sparkles,\n AlertTriangle,\n} 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 = contribution > 0\n ? Math.round(commits / (contribution / 100))\n : 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\n className=\"font-medium text-sm\"\n style={{ color: \"var(--text-primary)\" }}\n >\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\n className=\"text-xs mt-1.5\"\n style={{ color: \"var(--text-muted)\" }}\n >\n {languages}\n </div>\n <div\n className=\"text-xs mt-0.5\"\n style={{ color: \"var(--text-secondary)\" }}\n >\n {commits} commits · {contribution}%\n </div>\n </div>\n\n {/* Expanded Section */}\n {expanded && (\n <div\n className=\"border-t p-3.5 space-y-3\"\n style={expandedStyles}\n >\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\n className=\"p-2.5 rounded-lg border\"\n style={metricCardStyles}\n >\n <div\n className=\"text-xs\"\n style={{ color: \"var(--text-muted)\" }}\n >\n Your Contribution\n </div>\n <div\n className=\"font-semibold\"\n style={{ color: \"var(--text-primary)\" }}\n >\n {commits} commits\n </div>\n <div\n className=\"text-xs\"\n style={{ color: \"var(--text-muted)\" }}\n >\n {contribution}%\n </div>\n </div>\n <div\n className=\"p-2.5 rounded-lg border\"\n style={metricCardStyles}\n >\n <div\n className=\"text-xs\"\n style={{ color: \"var(--text-muted)\" }}\n >\n Full Project\n </div>\n <div\n className=\"font-semibold\"\n style={{ color: \"var(--text-primary)\" }}\n >\n {totalProjectCommits} commits\n </div>\n <div\n className=\"text-xs\"\n style={{ color: \"var(--text-muted)\" }}\n >\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=\"primary\"\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
|
+
],
|
|
20
|
+
"categories": [
|
|
21
|
+
"composite"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "repository-header-glass",
|
|
4
|
+
"type": "registry:block",
|
|
5
|
+
"title": "Repository Header Glass",
|
|
6
|
+
"description": "Repository Header Glass component with glass effects",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"lucide-react",
|
|
9
|
+
"react"
|
|
10
|
+
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"cn"
|
|
13
|
+
],
|
|
14
|
+
"files": [
|
|
15
|
+
{
|
|
16
|
+
"path": "components/glass/composite/repository-header-glass.tsx",
|
|
17
|
+
"type": "registry:component",
|
|
18
|
+
"content": "// ========================================\n// REPOSITORY HEADER GLASS - COMPOSITE COMPONENT\n// Repository name with status indicator and stars\n// Level 3: Composite (extracted from RepositoryCardGlass)\n// ========================================\n\nimport { forwardRef, type HTMLAttributes, type CSSProperties } from 'react';\nimport { Star, ChevronDown, ChevronUp } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { StatusIndicatorGlass } from '../specialized/status-indicator-glass';\nimport '@/glass-theme.css';\n\nexport type RepositoryFlagType = 'green' | 'yellow' | 'red';\n\nexport interface RepositoryHeaderGlassProps extends HTMLAttributes<HTMLDivElement> {\n /** Repository name */\n readonly name: string;\n /** Flag/status type */\n readonly flagType?: RepositoryFlagType;\n /** Star count */\n readonly stars?: number;\n /** Is expanded state */\n readonly expanded?: boolean;\n /** Abbreviated star count for mobile (1.2k instead of 1234) */\n readonly abbreviatedStars?: boolean;\n}\n\nexport const RepositoryHeaderGlass = forwardRef<\n HTMLDivElement,\n RepositoryHeaderGlassProps\n>(\n (\n {\n name,\n flagType = 'green',\n stars = 0,\n expanded = false,\n abbreviatedStars = false,\n className,\n ...props\n },\n ref\n ) => {\n const titleStyles: CSSProperties = {\n color: 'var(--text-primary)',\n };\n\n const starStyles: CSSProperties = {\n color: 'var(--status-away)',\n };\n\n const mutedStyles: CSSProperties = {\n color: 'var(--text-muted)',\n };\n\n const formatStars = (count: number): string => {\n if (!abbreviatedStars) return String(count);\n if (count >= 1000000) return `${(count / 1000000).toFixed(1)}M`;\n if (count >= 1000) return `${(count / 1000).toFixed(1)}k`;\n return String(count);\n };\n\n return (\n <div\n ref={ref}\n className={cn('flex items-center justify-between', className)}\n {...props}\n >\n <div className=\"flex items-center gap-2.5\">\n <span className=\"font-medium text-sm\" style={titleStyles}>\n {name}\n </span>\n <StatusIndicatorGlass type={flagType} />\n </div>\n <div className=\"flex items-center gap-2\">\n {stars > 0 && (\n <span className=\"flex items-center gap-1 text-xs\" style={starStyles}>\n <Star className=\"w-3 h-3\" />\n {formatStars(stars)}\n </span>\n )}\n {expanded ? (\n <ChevronUp className=\"w-4 h-4\" style={mutedStyles} />\n ) : (\n <ChevronDown className=\"w-4 h-4\" style={mutedStyles} />\n )}\n </div>\n </div>\n );\n }\n);\n\nRepositoryHeaderGlass.displayName = 'RepositoryHeaderGlass';\n"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"categories": [
|
|
22
|
+
"composite"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "repository-metadata-glass",
|
|
4
|
+
"type": "registry:block",
|
|
5
|
+
"title": "Repository Metadata Glass",
|
|
6
|
+
"description": "Repository Metadata Glass component with glass effects",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"react"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"cn"
|
|
12
|
+
],
|
|
13
|
+
"files": [
|
|
14
|
+
{
|
|
15
|
+
"path": "components/glass/composite/repository-metadata-glass.tsx",
|
|
16
|
+
"type": "registry:component",
|
|
17
|
+
"content": "// ========================================\n// REPOSITORY METADATA GLASS - COMPOSITE COMPONENT\n// Repository languages, commits, and contribution info\n// Level 3: Composite (extracted from RepositoryCardGlass)\n// ========================================\n\nimport { forwardRef, type HTMLAttributes, type CSSProperties } from 'react';\nimport { cn } from '@/lib/utils';\nimport '@/glass-theme.css';\n\nexport interface RepositoryMetadataGlassProps extends HTMLAttributes<HTMLDivElement> {\n /** Programming languages used */\n readonly languages: string;\n /** Number of commits */\n readonly commits: number;\n /** Contribution percentage */\n readonly contribution: number;\n /** Stacked layout for mobile */\n readonly stacked?: boolean;\n}\n\nexport const RepositoryMetadataGlass = forwardRef<\n HTMLDivElement,\n RepositoryMetadataGlassProps\n>(\n (\n {\n languages,\n commits,\n contribution,\n stacked = false,\n className,\n ...props\n },\n ref\n ) => {\n const mutedStyles: CSSProperties = {\n color: 'var(--text-muted)',\n };\n\n const secondaryStyles: CSSProperties = {\n color: 'var(--text-secondary)',\n };\n\n return (\n <div\n ref={ref}\n className={cn(stacked ? 'space-y-0.5' : 'space-y-1', className)}\n {...props}\n >\n <div className=\"text-xs\" style={mutedStyles}>\n {languages}\n </div>\n <div className=\"text-xs\" style={secondaryStyles}>\n {commits.toLocaleString()} commits · {contribution}%\n </div>\n </div>\n );\n }\n);\n\nRepositoryMetadataGlass.displayName = 'RepositoryMetadataGlass';\n"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"categories": [
|
|
21
|
+
"composite"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "search-box-glass",
|
|
4
|
+
"type": "registry:component",
|
|
5
|
+
"title": "Search Box Glass",
|
|
6
|
+
"description": "Search Box Glass component with glass effects",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"lucide-react"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"cn"
|
|
12
|
+
],
|
|
13
|
+
"files": [
|
|
14
|
+
{
|
|
15
|
+
"path": "components/glass/atomic/search-box-glass.tsx",
|
|
16
|
+
"type": "registry:component",
|
|
17
|
+
"content": "// ========================================\n// SEARCH BOX GLASS - ATOMIC COMPONENT\n// Search input with submit button\n// Level 2: Atomic (extracted from HeaderNavGlass)\n// ========================================\n\nimport {\n forwardRef,\n useState,\n type InputHTMLAttributes,\n type CSSProperties,\n} from 'react';\nimport { Search } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport '@/glass-theme.css';\n\nexport interface SearchBoxGlassProps\n extends Omit<InputHTMLAttributes<HTMLInputElement>, 'onSubmit' | 'onChange'> {\n /** Callback when search is submitted (Enter key or button click) */\n readonly onSubmit?: (value: string) => void;\n /** Initial search value */\n readonly defaultValue?: string;\n /** Controlled value */\n readonly value?: string;\n /** Controlled change handler */\n readonly onChange?: (value: string) => void;\n /** Compact variant for mobile (icon only button) */\n readonly variant?: 'default' | 'compact';\n /** Input width class (default: w-48) */\n readonly inputWidth?: string;\n}\n\nexport const SearchBoxGlass = forwardRef<HTMLInputElement, SearchBoxGlassProps>(\n (\n {\n onSubmit,\n defaultValue = '',\n value: controlledValue,\n onChange: controlledOnChange,\n variant = 'default',\n inputWidth = 'w-32 sm:w-40 md:w-48',\n placeholder = 'Search username...',\n className,\n ...props\n },\n ref\n ) => {\n const [internalValue, setInternalValue] = useState(defaultValue);\n const [isFocused, setIsFocused] = useState(false);\n\n const isControlled = controlledValue !== undefined;\n const value = isControlled ? controlledValue : internalValue;\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const newValue = e.target.value;\n if (isControlled) {\n controlledOnChange?.(newValue);\n } else {\n setInternalValue(newValue);\n }\n };\n\n const handleSubmit = () => {\n onSubmit?.(value);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Enter') {\n handleSubmit();\n }\n };\n\n const containerStyles: CSSProperties = {\n boxShadow: isFocused ? 'var(--focus-glow)' : 'none',\n };\n\n const inputStyles: CSSProperties = {\n background: 'var(--search-bg)',\n color: 'var(--text-primary)',\n border: '1px solid var(--search-border)',\n borderRight: 'none',\n borderTopLeftRadius: '0.75rem',\n borderBottomLeftRadius: '0.75rem',\n };\n\n const buttonStyles: CSSProperties = {\n background: 'var(--search-btn-bg)',\n color: 'var(--search-btn-text)',\n borderTopRightRadius: '0.75rem',\n borderBottomRightRadius: '0.75rem',\n };\n\n return (\n <div\n className={cn('flex w-fit rounded-xl overflow-hidden', className)}\n style={containerStyles}\n >\n <input\n ref={ref}\n type=\"text\"\n value={value}\n onChange={handleChange}\n onFocus={() => setIsFocused(true)}\n onBlur={() => setIsFocused(false)}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n aria-label={placeholder}\n className={cn('px-4 py-2 text-sm outline-none transition-all', inputWidth)}\n style={inputStyles}\n {...props}\n />\n <button\n onClick={handleSubmit}\n type=\"button\"\n className={cn(\n 'px-5 py-2 text-sm font-medium flex items-center gap-2 hover:scale-[1.02] transition-transform',\n variant === 'compact' && 'px-3'\n )}\n style={buttonStyles}\n aria-label=\"Search\"\n >\n <Search className=\"w-4 h-4\" />\n {variant === 'default' && <span className=\"hidden sm:inline\">Search</span>}\n </button>\n </div>\n );\n }\n);\n\nSearchBoxGlass.displayName = 'SearchBoxGlass';\n"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"categories": [
|
|
21
|
+
"atomic"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "segmented-control-glass",
|
|
4
|
+
"type": "registry:component",
|
|
5
|
+
"title": "Segmented Control Glass",
|
|
6
|
+
"description": "Segmented Control Glass component with glass effects",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"react"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"cn"
|
|
12
|
+
],
|
|
13
|
+
"files": [
|
|
14
|
+
{
|
|
15
|
+
"path": "components/glass/specialized/segmented-control-glass.tsx",
|
|
16
|
+
"type": "registry:component",
|
|
17
|
+
"content": "// ========================================\n// SEGMENTED CONTROL GLASS COMPONENT\n// ========================================\n\nimport { forwardRef, type CSSProperties } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport \"@/glass-theme.css\";\n\nexport interface SegmentOption {\n readonly value: string;\n readonly label: string;\n}\n\nexport interface SegmentedControlGlassProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n readonly options: readonly SegmentOption[];\n readonly value: string;\n readonly onChange?: (value: string) => void;\n}\n\nexport const SegmentedControlGlass = forwardRef<HTMLDivElement, SegmentedControlGlassProps>(\n ({ options, value, onChange, className, ...props }, ref) => {\n const containerStyles: CSSProperties = {\n border: \"1px solid var(--segmented-container-border)\",\n background: \"var(--segmented-container-bg)\",\n };\n\n return (\n <div\n ref={ref}\n className={cn(\"flex rounded-xl overflow-hidden\", className)}\n style={containerStyles}\n role=\"tablist\"\n {...props}\n >\n {options.map((opt) => {\n const isActive = value === opt.value;\n const buttonStyles: CSSProperties = {\n background: isActive ? \"var(--segmented-active-bg)\" : \"transparent\",\n color: isActive ? \"var(--segmented-active-text)\" : \"var(--segmented-inactive-text)\",\n };\n\n return (\n <button\n key={opt.value}\n onClick={() => onChange?.(opt.value)}\n className=\"px-3 py-1.5 md:px-4 md:py-2 text-xs md:text-sm font-medium transition-all duration-300\"\n style={buttonStyles}\n type=\"button\"\n role=\"tab\"\n aria-selected={isActive}\n >\n {opt.label}\n </button>\n );\n })}\n </div>\n );\n }\n);\n\nSegmentedControlGlass.displayName = \"SegmentedControlGlass\";\n"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"categories": [
|
|
21
|
+
"specialized"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "skeleton-glass",
|
|
4
|
+
"type": "registry:ui",
|
|
5
|
+
"title": "Skeleton Glass",
|
|
6
|
+
"description": "Glass-themed loading skeleton with:",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"class-variance-authority",
|
|
9
|
+
"react"
|
|
10
|
+
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"cn",
|
|
13
|
+
"variants"
|
|
14
|
+
],
|
|
15
|
+
"files": [
|
|
16
|
+
{
|
|
17
|
+
"path": "components/glass/ui/skeleton-glass.tsx",
|
|
18
|
+
"type": "registry:component",
|
|
19
|
+
"content": "/**\n * SkeletonGlass Component\n *\n * Glass-themed loading skeleton with:\n * - Theme-aware styling (glass/light/aurora)\n * - Shimmer animation\n * - Variant presets (text, title, avatar, thumbnail, card)\n */\n\nimport { forwardRef, type CSSProperties } from 'react';\nimport { type VariantProps } from 'class-variance-authority';\nimport { cn } from '@/lib/utils';\nimport { skeletonVariants } from '@/lib/variants/skeleton-glass-variants';\nimport '@/glass-theme.css';\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\nexport interface SkeletonGlassProps\n extends React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof skeletonVariants> {\n readonly width?: string | number;\n readonly height?: string | number;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const SkeletonGlass = forwardRef<HTMLDivElement, SkeletonGlassProps>(\n (\n {\n className,\n variant = 'text',\n width,\n height,\n style,\n ...props\n },\n ref\n ) => {\n const skeletonStyles: CSSProperties = {\n width,\n height,\n background: 'linear-gradient(90deg, var(--skeleton-bg) 25%, var(--skeleton-shine) 50%, var(--skeleton-bg) 75%)',\n backgroundSize: '200% 100%',\n animation: 'skeleton-loading 1.5s infinite',\n ...style,\n };\n\n return (\n <div\n ref={ref}\n className={cn(skeletonVariants({ variant }), className)}\n style={skeletonStyles}\n aria-hidden=\"true\"\n {...props}\n />\n );\n }\n);\n\nSkeletonGlass.displayName = 'SkeletonGlass';\n"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"categories": [
|
|
23
|
+
"ui"
|
|
24
|
+
],
|
|
25
|
+
"cssVars": {
|
|
26
|
+
"light": {
|
|
27
|
+
"--glass-bg": "rgba(255, 255, 255, 0.1)",
|
|
28
|
+
"--glass-border": "rgba(255, 255, 255, 0.2)",
|
|
29
|
+
"--blur-sm": "8px",
|
|
30
|
+
"--blur-md": "16px",
|
|
31
|
+
"--blur-lg": "24px"
|
|
32
|
+
},
|
|
33
|
+
"dark": {
|
|
34
|
+
"--glass-bg": "rgba(255, 255, 255, 0.05)",
|
|
35
|
+
"--glass-border": "rgba(255, 255, 255, 0.1)",
|
|
36
|
+
"--blur-sm": "8px",
|
|
37
|
+
"--blur-md": "16px",
|
|
38
|
+
"--blur-lg": "24px"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "slider-glass",
|
|
4
|
+
"type": "registry:ui",
|
|
5
|
+
"title": "Slider Glass",
|
|
6
|
+
"description": "Glass-themed range slider with:",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"react"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"cn",
|
|
12
|
+
"primitives",
|
|
13
|
+
"use-focus",
|
|
14
|
+
"use-hover"
|
|
15
|
+
],
|
|
16
|
+
"files": [
|
|
17
|
+
{
|
|
18
|
+
"path": "components/glass/ui/slider-glass.tsx",
|
|
19
|
+
"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 */\n\nimport { forwardRef, useState, type CSSProperties } from 'react';\nimport { cn } from '@/lib/utils';\nimport { useHover } from '@/lib/hooks/use-hover';\nimport { useFocus } from '@/lib/hooks/use-focus';\nimport { FormFieldWrapper } from '@/components/glass/primitives';\nimport '@/glass-theme.css';\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\nexport interface SliderGlassProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value'> {\n readonly value: number;\n readonly onChange: (value: number) => void;\n readonly min?: number;\n readonly max?: number;\n readonly step?: number;\n readonly showValue?: boolean;\n readonly label?: string;\n readonly error?: string;\n readonly success?: string;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const SliderGlass = forwardRef<HTMLInputElement, SliderGlassProps>(\n (\n {\n className,\n value,\n onChange,\n min = 0,\n max = 100,\n step = 1,\n showValue,\n label,\n error,\n success,\n disabled,\n ...props\n },\n ref\n ) => {\n const { isHovered, hoverProps } = useHover();\n const { isFocusVisible, focusProps } = useFocus({ focusVisible: true });\n const [isDragging, setIsDragging] = useState(false);\n\n const percentage = ((value - min) / (max - min)) * 100;\n\n const trackStyles: CSSProperties = {\n background: 'var(--slider-track)',\n };\n\n const fillStyles: CSSProperties = {\n width: `${percentage}%`,\n background: 'var(--slider-fill)',\n boxShadow:\n isHovered || isDragging\n ? 'var(--slider-fill-glow)'\n : undefined,\n };\n\n const thumbStyles: CSSProperties = {\n left: `calc(${percentage}% - 10px)`,\n background: 'var(--slider-thumb)',\n border: '2px solid var(--slider-thumb-border)',\n boxShadow: isFocusVisible\n ? 'var(--focus-glow)'\n : isHovered || isDragging\n ? 'var(--slider-thumb-glow)'\n : 'var(--slider-thumb-shadow)',\n transform: isDragging\n ? 'scale(1.15)'\n : isHovered\n ? 'scale(1.05)'\n : 'scale(1)',\n };\n\n // Custom label with value display - only used when showValue is true\n // Otherwise, let FormFieldWrapper handle the label\n const customLabel = (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 {value}\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 <div\n className=\"relative w-full h-8 md:h-6 flex items-center\"\n onMouseEnter={hoverProps.onMouseEnter}\n onMouseLeave={hoverProps.onMouseLeave}\n >\n {/* Track */}\n <div\n className=\"absolute w-full h-2.5 md:h-2 rounded-full\"\n style={trackStyles}\n />\n {/* Fill */}\n <div\n className=\"absolute h-2.5 md:h-2 rounded-full transition-all duration-150\"\n style={fillStyles}\n />\n {/* Hidden input for accessibility */}\n <input\n ref={ref}\n type=\"range\"\n value={value}\n onChange={(e) => onChange(Number(e.target.value))}\n onMouseDown={() => setIsDragging(true)}\n onMouseUp={() => setIsDragging(false)}\n onTouchStart={() => setIsDragging(true)}\n onTouchEnd={() => setIsDragging(false)}\n onFocus={focusProps.onFocus}\n onBlur={focusProps.onBlur}\n min={min}\n max={max}\n step={step}\n disabled={disabled}\n aria-label={label || `Slider: ${value} (${min}-${max})`}\n aria-valuemin={min}\n aria-valuemax={max}\n aria-valuenow={value}\n aria-valuetext={`${value}`}\n className=\"absolute w-full h-8 md:h-6 opacity-0 cursor-pointer disabled:cursor-not-allowed z-10\"\n style={{ outline: 'none' }}\n {...props}\n />\n {/* Thumb - larger on mobile for touch */}\n <div\n className=\"absolute w-6 h-6 md:w-5 md:h-5 rounded-full transition-all duration-150 pointer-events-none\"\n style={thumbStyles}\n />\n </div>\n </FormFieldWrapper>\n );\n }\n);\n\nSliderGlass.displayName = 'SliderGlass';\n"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"categories": [
|
|
24
|
+
"ui"
|
|
25
|
+
],
|
|
26
|
+
"cssVars": {
|
|
27
|
+
"light": {
|
|
28
|
+
"--glass-bg": "rgba(255, 255, 255, 0.1)",
|
|
29
|
+
"--glass-border": "rgba(255, 255, 255, 0.2)",
|
|
30
|
+
"--blur-sm": "8px",
|
|
31
|
+
"--blur-md": "16px",
|
|
32
|
+
"--blur-lg": "24px"
|
|
33
|
+
},
|
|
34
|
+
"dark": {
|
|
35
|
+
"--glass-bg": "rgba(255, 255, 255, 0.05)",
|
|
36
|
+
"--glass-border": "rgba(255, 255, 255, 0.1)",
|
|
37
|
+
"--blur-sm": "8px",
|
|
38
|
+
"--blur-md": "16px",
|
|
39
|
+
"--blur-lg": "24px"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "sort-dropdown-glass",
|
|
4
|
+
"type": "registry:component",
|
|
5
|
+
"title": "Sort Dropdown Glass",
|
|
6
|
+
"description": "Atomic component for sorting controls with:",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"lucide-react",
|
|
9
|
+
"react"
|
|
10
|
+
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"cn",
|
|
13
|
+
"variants"
|
|
14
|
+
],
|
|
15
|
+
"files": [
|
|
16
|
+
{
|
|
17
|
+
"path": "components/glass/atomic/sort-dropdown-glass.tsx",
|
|
18
|
+
"type": "registry:component",
|
|
19
|
+
"content": "/**\n * SortDropdownGlass Component\n *\n * Atomic component for sorting controls with:\n * - Theme-aware glass styling\n * - Responsive design (compact/full mode)\n * - Sort field selection (commits, stars, name, contribution)\n * - Sort order toggle (asc/desc)\n */\n\nimport { forwardRef, useState, useRef, useEffect, useCallback, useMemo, type CSSProperties } from 'react';\nimport { ChevronDown, ArrowUp, ArrowDown, Check } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { getDropdownContentStyles } 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 SortField = 'commits' | 'stars' | 'name' | 'contribution';\nexport type SortOrder = 'asc' | 'desc';\n\n// ========================================\n// FIELD LABELS\n// ========================================\n\nconst fieldLabels: Record<SortField, string> = {\n commits: 'Commits',\n stars: 'Stars',\n name: 'Name',\n contribution: 'Contribution',\n};\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\nexport interface SortDropdownGlassProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {\n /** Current sort field */\n readonly sortBy: SortField;\n /** Current sort order */\n readonly sortOrder: SortOrder;\n /** Callback when sort changes */\n readonly onSortChange: (field: SortField, order: SortOrder) => void;\n /** Available sort options (default: all) */\n readonly options?: readonly SortField[];\n /** Compact mode for mobile */\n readonly compact?: boolean;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const SortDropdownGlass = forwardRef<HTMLDivElement, SortDropdownGlassProps>(\n (\n {\n sortBy,\n sortOrder,\n onSortChange,\n options = ['commits', 'stars', 'name', 'contribution'],\n compact = false,\n className,\n ...props\n },\n ref\n ) => {\n const [isOpen, setIsOpen] = useState(false);\n const internalRef = useRef<HTMLDivElement>(null);\n\n // Close on click outside or escape\n useEffect(() => {\n if (!isOpen) return;\n\n const handleClickOutside = (event: MouseEvent): void => {\n if (internalRef.current && !internalRef.current.contains(event.target as Node)) {\n setIsOpen(false);\n }\n };\n\n const handleEscape = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') {\n setIsOpen(false);\n }\n };\n\n document.addEventListener('mousedown', handleClickOutside);\n document.addEventListener('keydown', handleEscape);\n\n return () => {\n document.removeEventListener('mousedown', handleClickOutside);\n document.removeEventListener('keydown', handleEscape);\n };\n }, [isOpen]);\n\n const handleToggle = useCallback(() => {\n setIsOpen((prev) => !prev);\n }, []);\n\n const handleFieldSelect = useCallback((field: SortField) => {\n if (field === sortBy) {\n // Toggle order if same field\n onSortChange(field, sortOrder === 'asc' ? 'desc' : 'asc');\n } else {\n // New field, default to desc\n onSortChange(field, 'desc');\n }\n setIsOpen(false);\n }, [sortBy, sortOrder, onSortChange]);\n\n const handleKeyDown = (e: React.KeyboardEvent): void => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleToggle();\n }\n };\n\n // Styles\n const buttonStyles: CSSProperties = useMemo(() => ({\n background: 'var(--segmented-container-bg)',\n border: '1px solid var(--segmented-container-border)',\n color: 'var(--text-primary)',\n }), []);\n\n const dropdownStyles: CSSProperties = useMemo(() => ({\n ...getDropdownContentStyles(),\n animation: 'dropdownFadeIn 0.2s ease-out',\n }), []);\n\n const SortIcon = sortOrder === 'asc' ? ArrowUp : ArrowDown;\n\n return (\n <div\n ref={(node) => {\n // Handle both refs\n (internalRef as React.MutableRefObject<HTMLDivElement | null>).current = node;\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;\n }\n }}\n className={cn('relative inline-block', className)}\n style={{ zIndex: isOpen ? 50000 : 'auto' }}\n {...props}\n >\n {/* Trigger Button */}\n <button\n type=\"button\"\n onClick={handleToggle}\n onKeyDown={handleKeyDown}\n className={cn(\n 'flex items-center gap-1.5 px-3 py-1.5 rounded-xl text-xs font-medium transition-all duration-200',\n 'hover:opacity-80 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',\n 'sm:gap-2 sm:px-4 sm:py-2 sm:text-sm'\n )}\n style={buttonStyles}\n aria-expanded={isOpen}\n aria-haspopup=\"listbox\"\n >\n {compact ? (\n <>\n <span>Sort</span>\n <SortIcon className={ICON_SIZES.sm} style={{ color: 'var(--text-accent)' }} />\n </>\n ) : (\n <>\n <span className=\"hidden sm:inline\" style={{ color: 'var(--text-muted)' }}>Sort:</span>\n <span>{fieldLabels[sortBy]}</span>\n <SortIcon className={ICON_SIZES.sm} style={{ color: 'var(--text-accent)' }} />\n <ChevronDown\n className={cn(\n ICON_SIZES.sm,\n 'transition-transform duration-200',\n isOpen && 'rotate-180'\n )}\n style={{ color: 'var(--text-muted)' }}\n />\n </>\n )}\n </button>\n\n {/* Dropdown Menu */}\n {isOpen && (\n <>\n {/* Backdrop */}\n <div\n className=\"fixed inset-0\"\n style={{ zIndex: 50001 }}\n onClick={() => setIsOpen(false)}\n />\n {/* Menu */}\n <div\n className=\"absolute left-0 mt-2 min-w-[140px] py-1.5 rounded-xl overflow-hidden\"\n style={{ ...dropdownStyles, zIndex: 50002 }}\n role=\"listbox\"\n aria-label=\"Sort options\"\n >\n {options.map((field) => {\n const isSelected = field === sortBy;\n return (\n <button\n key={field}\n type=\"button\"\n onClick={() => handleFieldSelect(field)}\n className={cn(\n 'w-full px-3 py-2 text-xs sm:text-sm text-left flex items-center justify-between gap-2',\n 'transition-colors duration-150 hover:bg-white/5'\n )}\n style={{\n color: isSelected ? 'var(--text-accent)' : 'var(--text-primary)',\n background: isSelected ? 'var(--dropdown-item-hover)' : 'transparent',\n }}\n role=\"option\"\n aria-selected={isSelected}\n >\n <span className=\"font-medium\">{fieldLabels[field]}</span>\n {isSelected && (\n <div className=\"flex items-center gap-1\">\n {sortOrder === 'asc' ? (\n <ArrowUp className={ICON_SIZES.sm} />\n ) : (\n <ArrowDown className={ICON_SIZES.sm} />\n )}\n <Check className={ICON_SIZES.sm} />\n </div>\n )}\n </button>\n );\n })}\n </div>\n </>\n )}\n </div>\n );\n }\n);\n\nSortDropdownGlass.displayName = 'SortDropdownGlass';\n"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"categories": [
|
|
23
|
+
"atomic"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "stat-item-glass",
|
|
4
|
+
"type": "registry:component",
|
|
5
|
+
"title": "Stat Item Glass",
|
|
6
|
+
"description": "Stat Item Glass component with glass effects",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"class-variance-authority",
|
|
9
|
+
"lucide-react",
|
|
10
|
+
"react"
|
|
11
|
+
],
|
|
12
|
+
"registryDependencies": [
|
|
13
|
+
"cn"
|
|
14
|
+
],
|
|
15
|
+
"files": [
|
|
16
|
+
{
|
|
17
|
+
"path": "components/glass/atomic/stat-item-glass.tsx",
|
|
18
|
+
"type": "registry:component",
|
|
19
|
+
"content": "// ========================================\n// STAT ITEM GLASS - ATOMIC COMPONENT\n// Compact stat display with icon and label\n// Level 2: Atomic (extracted from ProfileHeaderGlass)\n// ========================================\n\nimport { forwardRef, type HTMLAttributes, type CSSProperties } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { type LucideIcon } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport '@/glass-theme.css';\n\nconst statItemVariants = cva('flex items-center gap-1', {\n variants: {\n size: {\n sm: 'text-xs',\n md: 'text-sm',\n lg: 'text-base',\n },\n layout: {\n horizontal: 'flex-row',\n vertical: 'flex-col items-start gap-0.5',\n },\n },\n defaultVariants: {\n size: 'md',\n layout: 'horizontal',\n },\n});\n\nexport interface StatItemGlassProps\n extends HTMLAttributes<HTMLSpanElement>,\n VariantProps<typeof statItemVariants> {\n /** Lucide icon component */\n readonly icon: LucideIcon;\n /** Stat value (number or formatted string) */\n readonly value: number | string;\n /** Stat label */\n readonly label: string;\n /** Icon size in pixels */\n readonly iconSize?: number;\n /** Abbreviated format for mobile (1.2k instead of 1234) */\n readonly abbreviated?: boolean;\n}\n\nexport const StatItemGlass = forwardRef<HTMLSpanElement, StatItemGlassProps>(\n (\n {\n icon: Icon,\n value,\n label,\n iconSize = 16,\n abbreviated = false,\n size,\n layout,\n className,\n ...props\n },\n ref\n ) => {\n const textStyles: CSSProperties = {\n color: 'var(--text-secondary)',\n };\n\n const iconStyles: CSSProperties = {\n color: 'var(--text-accent)',\n };\n\n const formatValue = (val: number | string): string => {\n if (!abbreviated || typeof val !== 'number') return String(val);\n\n if (val >= 1000000) return `${(val / 1000000).toFixed(1)}M`;\n if (val >= 1000) return `${(val / 1000).toFixed(1)}k`;\n return String(val);\n };\n\n return (\n <span\n ref={ref}\n className={cn(statItemVariants({ size, layout }), className)}\n style={textStyles}\n {...props}\n >\n <Icon size={iconSize} style={iconStyles} />\n <span className=\"font-medium\">\n {formatValue(value)} {label}\n </span>\n </span>\n );\n }\n);\n\nStatItemGlass.displayName = 'StatItemGlass';\n"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"categories": [
|
|
23
|
+
"atomic"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "status-indicator-glass",
|
|
4
|
+
"type": "registry:component",
|
|
5
|
+
"title": "Status Indicator Glass",
|
|
6
|
+
"description": "Status Indicator Glass component with glass effects",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"react"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"cn"
|
|
12
|
+
],
|
|
13
|
+
"files": [
|
|
14
|
+
{
|
|
15
|
+
"path": "components/glass/specialized/status-indicator-glass.tsx",
|
|
16
|
+
"type": "registry:component",
|
|
17
|
+
"content": "// ========================================\n// STATUS INDICATOR GLASS COMPONENT\n// Status dots with glow effect\n// ========================================\n\nimport { forwardRef, type CSSProperties } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport \"@/glass-theme.css\";\n\nexport type StatusType = \"green\" | \"yellow\" | \"red\";\nexport type StatusSize = \"normal\" | \"large\";\n\nexport interface StatusIndicatorGlassProps extends React.HTMLAttributes<HTMLDivElement> {\n readonly type?: StatusType;\n readonly size?: StatusSize;\n}\n\nconst sizeClasses: Record<StatusSize, string> = {\n normal: \"w-2 h-2 md:w-2.5 md:h-2.5\",\n large: \"w-3.5 h-3.5 md:w-4 md:h-4\",\n};\n\nconst statusSymbols: Record<StatusType, string> = {\n green: \"✓\",\n yellow: \"!\",\n red: \"✕\",\n};\n\n// CSS variable maps for status colors (using semantic naming)\nconst statusVarMap: Record<StatusType, { bg: string; glow: string }> = {\n green: { bg: \"var(--status-online)\", glow: \"var(--status-online-glow)\" },\n yellow: { bg: \"var(--status-away)\", glow: \"var(--status-away-glow)\" },\n red: { bg: \"var(--status-busy)\", glow: \"var(--status-busy-glow)\" },\n};\n\nexport const StatusIndicatorGlass = forwardRef<HTMLDivElement, StatusIndicatorGlassProps>(\n ({ type = \"green\", size = \"normal\", className, ...props }, ref) => {\n const colors = statusVarMap[type];\n\n const indicatorStyles: CSSProperties = {\n backgroundColor: colors.bg,\n boxShadow: colors.glow,\n };\n\n return (\n <div\n ref={ref}\n className={cn(\n \"rounded-full flex items-center justify-center\",\n sizeClasses[size],\n className\n )}\n style={indicatorStyles}\n role=\"status\"\n aria-label={`Status: ${type}`}\n {...props}\n >\n {size === \"large\" && (\n <span className=\"text-white text-[8px] md:text-[10px] font-bold\">\n {statusSymbols[type]}\n </span>\n )}\n </div>\n );\n }\n);\n\nStatusIndicatorGlass.displayName = \"StatusIndicatorGlass\";\n"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"categories": [
|
|
21
|
+
"specialized"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "tabs-glass",
|
|
4
|
+
"type": "registry:ui",
|
|
5
|
+
"title": "Tabs Glass",
|
|
6
|
+
"description": "TabsGlass Component (Compound API only)",
|
|
7
|
+
"dependencies": [],
|
|
8
|
+
"registryDependencies": [
|
|
9
|
+
"cn",
|
|
10
|
+
"use-focus"
|
|
11
|
+
],
|
|
12
|
+
"files": [
|
|
13
|
+
{
|
|
14
|
+
"path": "components/glass/ui/tabs-glass.tsx",
|
|
15
|
+
"type": "registry:component",
|
|
16
|
+
"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 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\ninterface TabsRootProps {\n /** Current active tab value */\n value: 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> = ({ value, onValueChange, children, className }) => {\n return (\n <TabsContext.Provider value={{ value, onValueChange }}>\n <div className={cn('tabs-glass-root', className)}>{children}</div>\n </TabsContext.Provider>\n );\n};\n\n// ========================================\n// COMPOUND COMPONENT: LIST\n// ========================================\n\ninterface 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\ninterface 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 return (\n <button\n ref={ref}\n type=\"button\"\n role=\"tab\"\n aria-selected={isActive}\n disabled={disabled}\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 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\ninterface 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 * <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 *\n * @since v1.0.0 - Legacy API removed (tabs/activeTab/onChange props)\n */\nexport const TabsGlass = {\n Root: TabsRoot,\n List: TabsList,\n Trigger: TabsTrigger,\n Content: TabsContent,\n};\n"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"categories": [
|
|
20
|
+
"ui"
|
|
21
|
+
],
|
|
22
|
+
"cssVars": {
|
|
23
|
+
"light": {
|
|
24
|
+
"--glass-bg": "rgba(255, 255, 255, 0.1)",
|
|
25
|
+
"--glass-border": "rgba(255, 255, 255, 0.2)",
|
|
26
|
+
"--blur-sm": "8px",
|
|
27
|
+
"--blur-md": "16px",
|
|
28
|
+
"--blur-lg": "24px"
|
|
29
|
+
},
|
|
30
|
+
"dark": {
|
|
31
|
+
"--glass-bg": "rgba(255, 255, 255, 0.05)",
|
|
32
|
+
"--glass-border": "rgba(255, 255, 255, 0.1)",
|
|
33
|
+
"--blur-sm": "8px",
|
|
34
|
+
"--blur-md": "16px",
|
|
35
|
+
"--blur-lg": "24px"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "theme-toggle-glass",
|
|
4
|
+
"type": "registry:component",
|
|
5
|
+
"title": "Theme Toggle Glass",
|
|
6
|
+
"description": "Theme Toggle Glass component with glass effects",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"lucide-react",
|
|
9
|
+
"react"
|
|
10
|
+
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"cn",
|
|
13
|
+
"theme-provider"
|
|
14
|
+
],
|
|
15
|
+
"files": [
|
|
16
|
+
{
|
|
17
|
+
"path": "components/glass/atomic/theme-toggle-glass.tsx",
|
|
18
|
+
"type": "registry:component",
|
|
19
|
+
"content": "// ========================================\n// THEME TOGGLE GLASS - ATOMIC COMPONENT\n// Theme switcher button with cycle animation\n// Level 2: Atomic (extracted from HeaderNavGlass)\n// ========================================\n\nimport { forwardRef, type ButtonHTMLAttributes, type CSSProperties } from 'react';\nimport { Sun, Moon, Palette } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { useTheme, type ThemeName } from '@/lib/theme-context';\nimport '@/glass-theme.css';\n\nconst themes: ThemeName[] = ['light', 'aurora', 'glass'];\n\nconst themeConfig: Record<ThemeName, { label: string; icon: typeof Sun }> = {\n light: { label: 'Light', icon: Sun },\n aurora: { label: 'Aurora', icon: Moon },\n glass: { label: 'Glass', icon: Palette },\n};\n\nexport interface ThemeToggleGlassProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** Custom theme toggle handler (overrides default cycleTheme) */\n readonly onToggle?: () => void;\n /** Icon size in pixels (default: 20) */\n readonly iconSize?: number;\n /** Show icon only (hide label on mobile) */\n readonly iconOnly?: boolean;\n}\n\nexport const ThemeToggleGlass = forwardRef<HTMLButtonElement, ThemeToggleGlassProps>(\n ({ onToggle, iconSize = 20, iconOnly = false, className, ...props }, ref) => {\n const { theme, cycleTheme } = useTheme();\n\n const nextTheme = themes[(themes.indexOf(theme) + 1) % themes.length];\n const NextIcon = themeConfig[nextTheme].icon;\n const nextLabel = themeConfig[nextTheme].label;\n\n const buttonStyles: CSSProperties = {\n background: 'var(--card-subtle-bg)',\n border: '1px solid var(--card-subtle-border)',\n };\n\n const iconStyles: CSSProperties = {\n color: 'var(--text-secondary)',\n };\n\n return (\n <button\n ref={ref}\n type=\"button\"\n onClick={onToggle ?? cycleTheme}\n aria-label={`Switch to ${nextLabel} theme`}\n className={cn(\n 'p-2.5 rounded-xl transition-all duration-300 hover:scale-105 focus:outline-none focus:ring-2 focus:ring-offset-2',\n iconOnly && 'md:px-4 md:gap-2',\n className\n )}\n style={buttonStyles}\n {...props}\n >\n <div className=\"flex items-center gap-2\">\n <NextIcon size={iconSize} style={iconStyles} />\n {!iconOnly && (\n <span className=\"hidden md:inline text-sm font-medium\" style={iconStyles}>\n {nextLabel}\n </span>\n )}\n </div>\n </button>\n );\n }\n);\n\nThemeToggleGlass.displayName = 'ThemeToggleGlass';\n"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"categories": [
|
|
23
|
+
"atomic"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "toggle-glass",
|
|
4
|
+
"type": "registry:ui",
|
|
5
|
+
"title": "Toggle Glass",
|
|
6
|
+
"description": "Glass-themed toggle switch with:",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"class-variance-authority",
|
|
9
|
+
"react"
|
|
10
|
+
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"cn",
|
|
13
|
+
"use-focus",
|
|
14
|
+
"variants"
|
|
15
|
+
],
|
|
16
|
+
"files": [
|
|
17
|
+
{
|
|
18
|
+
"path": "components/glass/ui/toggle-glass.tsx",
|
|
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 md: { 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 Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onChange'>,\n VariantProps<typeof toggleSizes> {\n readonly checked: boolean;\n readonly onChange?: (checked: boolean) => void;\n readonly label?: string;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const ToggleGlass = forwardRef<HTMLButtonElement, ToggleGlassProps>(\n (\n {\n className,\n size = 'md',\n checked,\n onChange,\n disabled,\n label,\n ...props\n },\n ref\n ) => {\n const { isFocusVisible, focusProps } = useFocus({ focusVisible: true });\n const s = sizesConfig[size ?? 'md'];\n\n const trackStyles: CSSProperties = {\n background: checked ? 'var(--toggle-active-bg)' : 'var(--toggle-bg)',\n boxShadow: isFocusVisible && !disabled\n ? 'var(--focus-glow)'\n : checked\n ? 'var(--toggle-glow)'\n : 'none',\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-checked={checked}\n aria-label={label || 'Toggle switch'}\n disabled={disabled}\n className={cn(\n toggleSizes({ size }),\n disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer',\n !label && className\n )}\n style={trackStyles}\n onClick={() => !disabled && onChange?.(!checked)}\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 checked && 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
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"categories": [
|
|
24
|
+
"ui"
|
|
25
|
+
],
|
|
26
|
+
"cssVars": {
|
|
27
|
+
"light": {
|
|
28
|
+
"--glass-bg": "rgba(255, 255, 255, 0.1)",
|
|
29
|
+
"--glass-border": "rgba(255, 255, 255, 0.2)",
|
|
30
|
+
"--blur-sm": "8px",
|
|
31
|
+
"--blur-md": "16px",
|
|
32
|
+
"--blur-lg": "24px"
|
|
33
|
+
},
|
|
34
|
+
"dark": {
|
|
35
|
+
"--glass-bg": "rgba(255, 255, 255, 0.05)",
|
|
36
|
+
"--glass-border": "rgba(255, 255, 255, 0.1)",
|
|
37
|
+
"--blur-sm": "8px",
|
|
38
|
+
"--blur-md": "16px",
|
|
39
|
+
"--blur-lg": "24px"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "tooltip-glass",
|
|
4
|
+
"type": "registry:ui",
|
|
5
|
+
"title": "Tooltip Glass",
|
|
6
|
+
"description": "Glass-themed tooltip with:",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"class-variance-authority",
|
|
9
|
+
"react"
|
|
10
|
+
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"cn",
|
|
13
|
+
"use-hover",
|
|
14
|
+
"variants"
|
|
15
|
+
],
|
|
16
|
+
"files": [
|
|
17
|
+
{
|
|
18
|
+
"path": "components/glass/ui/tooltip-glass.tsx",
|
|
19
|
+
"type": "registry:component",
|
|
20
|
+
"content": "/**\n * TooltipGlass Component\n *\n * Glass-themed tooltip with:\n * - Unified dark design (consistent UX across themes)\n * - Position variants (top/bottom/left/right)\n * - Smooth animation\n */\n\nimport { forwardRef, useId, type ReactNode, type CSSProperties } from 'react';\nimport { type VariantProps } from 'class-variance-authority';\nimport { cn } from '@/lib/utils';\nimport { useHover } from '@/lib/hooks/use-hover';\nimport { tooltipPositions, type TooltipPosition } from '@/lib/variants/tooltip-glass-variants';\nimport '@/glass-theme.css';\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\nexport interface TooltipGlassProps extends VariantProps<typeof tooltipPositions> {\n readonly children: ReactNode;\n readonly content: string;\n readonly position?: TooltipPosition;\n readonly className?: string;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const TooltipGlass = forwardRef<HTMLDivElement, TooltipGlassProps>(\n ({ children, content, position = 'top', className }, ref) => {\n const { isHovered, hoverProps } = useHover();\n const tooltipId = useId();\n\n // Glass tooltip with same background as modal (oklch(100% 0 0 / 0.06))\n const tooltipStyles: CSSProperties = {\n background: 'var(--tooltip-bg)',\n color: 'var(--tooltip-text)',\n border: '1px solid var(--tooltip-border)',\n boxShadow: 'var(--tooltip-shadow)',\n backdropFilter: 'blur(var(--blur-xl))',\n WebkitBackdropFilter: 'blur(var(--blur-xl))',\n };\n\n return (\n <div\n ref={ref}\n className={cn('relative inline-flex', className)}\n onMouseEnter={hoverProps.onMouseEnter}\n onMouseLeave={hoverProps.onMouseLeave}\n aria-describedby={isHovered ? tooltipId : undefined}\n >\n {children}\n {isHovered && (\n <div\n id={tooltipId}\n className={cn(tooltipPositions({ position }))}\n style={tooltipStyles}\n role=\"tooltip\"\n >\n {content}\n </div>\n )}\n </div>\n );\n }\n);\n\nTooltipGlass.displayName = 'TooltipGlass';\n"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"categories": [
|
|
24
|
+
"ui"
|
|
25
|
+
],
|
|
26
|
+
"cssVars": {
|
|
27
|
+
"light": {
|
|
28
|
+
"--glass-bg": "rgba(255, 255, 255, 0.1)",
|
|
29
|
+
"--glass-border": "rgba(255, 255, 255, 0.2)",
|
|
30
|
+
"--blur-sm": "8px",
|
|
31
|
+
"--blur-md": "16px",
|
|
32
|
+
"--blur-lg": "24px"
|
|
33
|
+
},
|
|
34
|
+
"dark": {
|
|
35
|
+
"--glass-bg": "rgba(255, 255, 255, 0.05)",
|
|
36
|
+
"--glass-border": "rgba(255, 255, 255, 0.1)",
|
|
37
|
+
"--blur-sm": "8px",
|
|
38
|
+
"--blur-md": "16px",
|
|
39
|
+
"--blur-lg": "24px"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "touch-target",
|
|
4
|
+
"type": "registry:lib",
|
|
5
|
+
"title": "Touch Target",
|
|
6
|
+
"description": "Ensures minimum touch target size compliance with Apple Human Interface Guidelines.\n * Wraps interactive elements to guarantee accessibility on touch devices.\n *\n * Apple HIG recommends minimum 44×44pt (44×44px) touch targets.\n * Material Design recommends 48×48dp for better accessibility.\n *\n * @see https://developer.apple.com/design/human-interface-guidelines/layout",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"react"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"cn"
|
|
12
|
+
],
|
|
13
|
+
"files": [
|
|
14
|
+
{
|
|
15
|
+
"path": "components/glass/primitives/touch-target.tsx",
|
|
16
|
+
"type": "registry:lib",
|
|
17
|
+
"content": "/**\n * TouchTarget Component\n *\n * Ensures minimum touch target size compliance with Apple Human Interface Guidelines.\n * Wraps interactive elements to guarantee accessibility on touch devices.\n *\n * Apple HIG recommends minimum 44×44pt (44×44px) touch targets.\n * Material Design recommends 48×48dp for better accessibility.\n *\n * @see https://developer.apple.com/design/human-interface-guidelines/layout\n */\n\nimport { forwardRef, type ReactNode, type HTMLAttributes } from 'react';\nimport { cn } from '@/lib/utils';\n\n/**\n * Props for the TouchTarget component\n */\nexport interface TouchTargetProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Child element(s) to wrap with touch target\n */\n children: ReactNode;\n\n /**\n * Minimum touch target size in pixels\n * @default 44 - Apple HIG minimum\n */\n minSize?: 44 | 48;\n\n /**\n * Center content within touch target\n * @default true\n */\n center?: boolean;\n}\n\n/**\n * TouchTarget wrapper component\n *\n * Ensures interactive elements meet accessibility standards for touch devices.\n * Automatically applies minimum dimensions and optional centering.\n *\n * @example\n * ```tsx\n * // Basic usage with default 44px minimum\n * <TouchTarget>\n * <button className=\"w-8 h-8\">×</button>\n * </TouchTarget>\n *\n * // Material Design 48px minimum\n * <TouchTarget minSize={48}>\n * <Checkbox />\n * </TouchTarget>\n *\n * // Custom alignment\n * <TouchTarget center={false} className=\"justify-start\">\n * <IconButton />\n * </TouchTarget>\n * ```\n */\nexport const TouchTarget = forwardRef<HTMLDivElement, TouchTargetProps>(\n ({ children, minSize = 44, center = true, className, ...props }, ref) => {\n // Map minSize to Tailwind classes\n // 44px = 11 × 4px (min-h-11, min-w-11)\n // 48px = 12 × 4px (min-h-12, min-w-12)\n const sizeClass = minSize === 44 ? 'min-h-11 min-w-11' : 'min-h-12 min-w-12';\n\n return (\n <div\n ref={ref}\n className={cn(\n sizeClass,\n center && 'flex items-center justify-center',\n className\n )}\n {...props}\n >\n {children}\n </div>\n );\n }\n);\n\nTouchTarget.displayName = 'TouchTarget';\n"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"categories": [
|
|
21
|
+
"primitives"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "trust-score-card-glass",
|
|
4
|
+
"type": "registry:block",
|
|
5
|
+
"title": "Trust Score Card Glass",
|
|
6
|
+
"description": "Trust Score Card Glass component with glass effects",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"lucide-react",
|
|
9
|
+
"react"
|
|
10
|
+
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"cn"
|
|
13
|
+
],
|
|
14
|
+
"files": [
|
|
15
|
+
{
|
|
16
|
+
"path": "components/glass/sections/trust-score-card-glass.tsx",
|
|
17
|
+
"type": "registry:component",
|
|
18
|
+
"content": "// ========================================\n// TRUST SCORE CARD GLASS COMPONENT\n// Overall trust score display with metrics\n// ========================================\n\nimport { forwardRef } from \"react\";\nimport { Target } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport { GlassCard } from \"../ui/glass-card\";\nimport { RainbowProgressGlass } from \"../specialized/rainbow-progress-glass\";\nimport { MetricCardGlass, type MetricColor } from \"../composite/metric-card-glass\";\nimport \"@/glass-theme.css\";\n\nexport interface MetricData {\n readonly label: string;\n readonly value: number;\n readonly color: MetricColor;\n}\n\nexport interface TrustScoreCardGlassProps extends React.HTMLAttributes<HTMLDivElement> {\n readonly score?: number;\n readonly metrics?: readonly MetricData[];\n}\n\nexport const TrustScoreCardGlass = forwardRef<HTMLDivElement, TrustScoreCardGlassProps>(\n ({ score = 72, metrics = [], className, ...props }, ref) => {\n return (\n <GlassCard\n ref={ref}\n className={cn(\"p-4 md:p-5\", className)}\n intensity=\"strong\"\n glow=\"cyan\"\n hover={false}\n {...props}\n >\n <div className=\"flex items-center justify-between mb-3 md:mb-4\">\n <h2\n className=\"font-semibold flex items-center gap-1.5 md:gap-2 text-base md:text-lg\"\n style={{ color: \"var(--text-primary)\" }}\n >\n <Target className=\"w-4 h-4 md:w-5 md:h-5\" style={{ color: \"var(--text-accent)\" }} />\n Overall Trust Score\n </h2>\n <div className=\"flex items-center gap-1.5 md:gap-2 animate-[score-pulse_2s_ease-in-out_infinite]\">\n <span className=\"text-3xl md:text-4xl font-bold bg-linear-to-r from-amber-400 via-emerald-400 to-cyan-400 bg-clip-text text-transparent\">\n {score}\n </span>\n <span className=\"text-lg md:text-xl\" style={{ color: \"var(--text-muted)\" }}>\n / 100\n </span>\n </div>\n </div>\n <RainbowProgressGlass value={score} size=\"lg\" showGlow />\n {metrics.length > 0 && (\n <div className=\"grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3 md:gap-4 mt-4 md:mt-5\">\n {metrics.map((m) => (\n <MetricCardGlass\n key={m.label}\n label={m.label}\n value={m.value}\n color={m.color}\n />\n ))}\n </div>\n )}\n </GlassCard>\n );\n }\n);\n\nTrustScoreCardGlass.displayName = \"TrustScoreCardGlass\";\n"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"categories": [
|
|
22
|
+
"sections"
|
|
23
|
+
]
|
|
24
|
+
}
|