{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "sort-dropdown-glass", "type": "registry:component", "title": "Sort Dropdown Glass", "description": "Atomic sorting control built on DropdownMenuGlass primitives for data table and list sorting.\n * Provides intuitive sort field selection with visual indicators for active sort and direction.\n *\n * ## Features", "dependencies": [ "lucide-react", "react", "shadcn-glass-ui" ], "registryDependencies": [ "cn" ], "files": [ { "path": "components/glass/atomic/sort-dropdown-glass.tsx", "type": "registry:component", "content": "/**\n * SortDropdownGlass Component\n *\n * Atomic sorting control built on DropdownMenuGlass primitives for data table and list sorting.\n * Provides intuitive sort field selection with visual indicators for active sort and direction.\n *\n * ## Features\n * - **4 Sort Fields:** commits, stars, name, contribution (configurable via `options` prop)\n * - **2 Sort Orders:** ascending (asc) and descending (desc) with arrow indicators\n * - **Responsive Design:** Compact mode for mobile, full mode for desktop\n * - **Visual Indicators:** Check icon for selected field, arrow icons for sort direction\n * - **Theme-Aware:** Unified glass styling via DropdownMenuGlass\n * - **Smart Toggle:** Click same field to toggle order, click different field to switch\n * - **Accessible:** ARIA menu pattern with keyboard navigation\n * - **Compound API:** Built on shadcn/ui DropdownMenuGlass pattern\n * - **Customizable Options:** Filter available sort fields via `options` prop\n *\n * ## CSS Variables\n * Uses DropdownMenuGlass CSS variables (defined in `dropdown-content-styles.ts`):\n * - `--dropdown-bg` - Dropdown trigger and content background\n * - `--dropdown-border` - Dropdown border color\n * - `--dropdown-glow` - Hover glow effect\n * - `--dropdown-item-text` - Menu item text color\n * - `--select-item-selected-bg` - Selected item background\n * - `--text-accent` - Arrow icon color\n * - `--text-muted` - Chevron icon color\n *\n * @example Basic usage\n * ```tsx\n * import { SortDropdownGlass } from 'shadcn-glass-ui'\n *\n * function DataTable() {\n * const [sortBy, setSortBy] = useState('commits')\n * const [sortOrder, setSortOrder] = useState('desc')\n *\n * return (\n * {\n * setSortBy(field)\n * setSortOrder(order)\n * }}\n * />\n * )\n * }\n * ```\n *\n * @example Compact mode for mobile\n * ```tsx\n * \n * ```\n *\n * @example Limited sort options\n * ```tsx\n * \n * ```\n *\n * @example With custom styling\n * ```tsx\n * \n * ```\n *\n * @accessibility\n * - Uses ARIA `menu` role with `aria-haspopup` and `aria-label`\n * - Keyboard navigation via DropdownMenuGlass (Space, Enter, Arrow keys, Escape)\n * - Visual and semantic indication of selected state\n * - Focus management handled by Radix UI DropdownMenu\n * - Sort direction communicated via both icon and ARIA state\n * - WCAG 2.1 AA compliant color contrast\n *\n * @since v1.0.0\n */\n\n'use client';\n\nimport * as React from 'react';\nimport { ChevronDown, ArrowUp, ArrowDown, Check } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport {\n DropdownMenuGlass,\n DropdownMenuGlassTrigger,\n DropdownMenuGlassContent,\n DropdownMenuGlassItem,\n} from '@/components/glass/ui/dropdown-menu-glass';\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 = {\n commits: 'Commits',\n stars: 'Stars',\n name: 'Name',\n contribution: 'Contribution',\n};\n\n// ========================================\n// PROPS INTERFACE\n// ========================================\n\n/**\n * Props for SortDropdownGlass component.\n *\n * Extends standard div attributes (excluding `onChange`) to avoid conflicts with\n * the component's `onSortChange` callback.\n *\n * @example\n * ```tsx\n * const props: SortDropdownGlassProps = {\n * sortBy: 'commits',\n * sortOrder: 'desc',\n * onSortChange: (field, order) => {\n * console.log(`Sorting by ${field} in ${order} order`)\n * },\n * options: ['commits', 'stars'],\n * compact: false,\n * }\n * ```\n */\nexport interface SortDropdownGlassProps extends Omit<\n React.HTMLAttributes,\n 'onChange'\n> {\n /**\n * Current sort field.\n *\n * Must be one of the available sort fields: 'commits', 'stars', 'name', 'contribution'.\n *\n * @example\n * ```tsx\n * \n * ```\n */\n readonly sortBy: SortField;\n\n /**\n * Current sort order.\n *\n * - `'asc'`: Ascending (A-Z, 0-9, oldest-newest)\n * - `'desc'`: Descending (Z-A, 9-0, newest-oldest)\n *\n * @example\n * ```tsx\n * \n * ```\n */\n readonly sortOrder: SortOrder;\n\n /**\n * Callback when sort changes.\n *\n * Called when user selects a field or toggles order. Receives both the new\n * field and order. If user clicks the same field, order is toggled. If user\n * clicks a different field, order defaults to 'desc'.\n *\n * @example\n * ```tsx\n * {\n * setSortBy(field)\n * setSortOrder(order)\n * refetchData({ sortBy: field, sortOrder: order })\n * }}\n * />\n * ```\n */\n readonly onSortChange: (field: SortField, order: SortOrder) => void;\n\n /**\n * Available sort options.\n *\n * Filter which fields appear in the dropdown. Useful for limiting sort\n * options based on data type or user permissions.\n *\n * @default ['commits', 'stars', 'name', 'contribution']\n *\n * @example\n * ```tsx\n * \n * ```\n */\n readonly options?: readonly SortField[];\n\n /**\n * Compact mode for mobile.\n *\n * - `true`: Shows \"Sort\" label with arrow icon only\n * - `false`: Shows \"Sort: [Field]\" with arrow and chevron icons\n *\n * Recommended for mobile viewports to save space.\n *\n * @default false\n *\n * @example\n * ```tsx\n * \n * ```\n */\n readonly compact?: boolean;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const SortDropdownGlass = React.forwardRef(\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 handleFieldSelect = React.useCallback(\n (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 },\n [sortBy, sortOrder, onSortChange]\n );\n\n const SortIcon = sortOrder === 'asc' ? ArrowUp : ArrowDown;\n\n return (\n
\n \n \n \n {compact ? (\n <>\n Sort\n \n \n ) : (\n <>\n Sort:\n {fieldLabels[sortBy]}\n \n \n \n )}\n \n \n\n \n {options.map((field) => {\n const isSelected = field === sortBy;\n return (\n handleFieldSelect(field)}\n className={cn('justify-between', isSelected && 'bg-(--select-item-selected-bg)')}\n >\n {fieldLabels[field]}\n {isSelected && (\n
\n {sortOrder === 'asc' ? (\n \n ) : (\n \n )}\n \n
\n )}\n \n );\n })}\n
\n
\n
\n );\n }\n);\n\nSortDropdownGlass.displayName = 'SortDropdownGlass';\n" } ], "categories": [ "atomic" ] }