@hyddenlabs/hydn-ui 0.3.12 → 0.3.14
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/dist/components/data-display/code-block/code-block.js +1 -1
- package/dist/components/data-display/code-block/code-block.js.map +1 -1
- package/dist/components/data-display/data-grid/data-grid.js +2 -2
- package/dist/components/data-display/data-grid/data-grid.js.map +1 -1
- package/dist/components/feedback/tooltip/tooltip.d.ts +3 -1
- package/dist/components/feedback/tooltip/tooltip.d.ts.map +1 -1
- package/dist/components/feedback/tooltip/tooltip.js +3 -2
- package/dist/components/feedback/tooltip/tooltip.js.map +1 -1
- package/dist/components/forms/button/button.d.ts +2 -2
- package/dist/components/forms/button/button.d.ts.map +1 -1
- package/dist/components/forms/button/button.js +2 -0
- package/dist/components/forms/button/button.js.map +1 -1
- package/dist/components/forms/switch/switch.js +1 -1
- package/dist/components/forms/switch/switch.js.map +1 -1
- package/dist/components/layout/left-nav-layout/left-nav-item.d.ts.map +1 -1
- package/dist/components/layout/left-nav-layout/left-nav-item.js +1 -4
- package/dist/components/layout/left-nav-layout/left-nav-item.js.map +1 -1
- package/dist/components/typography/code/code.js +2 -2
- package/dist/components/typography/code/code.js.map +1 -1
- package/dist/fonts/Gilmer-Heavy.otf +0 -0
- package/dist/fonts/Gilmer-Light.otf +0 -0
- package/dist/fonts/Gilmer-Medium.otf +0 -0
- package/dist/fonts/Gilmer-Outline.otf +0 -0
- package/dist/fonts/Gilmer-Regular.otf +0 -0
- package/dist/style.css +1 -1
- package/dist/theme/tokens.d.ts +9 -1
- package/dist/theme/tokens.d.ts.map +1 -1
- package/dist/theme/tokens.js +17 -0
- package/dist/theme/tokens.js.map +1 -1
- package/package.json +1 -1
|
@@ -24,7 +24,7 @@ function CodeBlock({ code, className = "", showCopy = true }) {
|
|
|
24
24
|
children: copied ? "✓ Copied" : "Copy"
|
|
25
25
|
}
|
|
26
26
|
) }),
|
|
27
|
-
/* @__PURE__ */ jsx("pre", { className: `bg-muted/50 border rounded-lg p-4 overflow-x-auto text-sm ${showCopy ? "pr-24" : ""}`, children: /* @__PURE__ */ jsx("code", { className: "font-mono text-foreground", children: code }) })
|
|
27
|
+
/* @__PURE__ */ jsx("pre", { className: `bg-muted-active/50 border rounded-lg p-4 overflow-x-auto text-sm ${showCopy ? "pr-24" : ""}`, children: /* @__PURE__ */ jsx("code", { className: "font-mono text-foreground", children: code }) })
|
|
28
28
|
] });
|
|
29
29
|
}
|
|
30
30
|
CodeBlock.displayName = "CodeBlock";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"code-block.js","sources":["../../../../src/components/data-display/code-block/code-block.tsx"],"sourcesContent":["import { useState } from 'react';\n\nimport Button from '../../forms/button/button';\n\nexport type CodeBlockProps = {\n /** Code content to display */\n code: string;\n /** Additional CSS classes to apply */\n className?: string;\n /** Whether to show the copy-to-clipboard button */\n showCopy?: boolean;\n};\n\n/**\n * CodeBlock - Displays formatted code with syntax highlighting and copy functionality\n */\nfunction CodeBlock({ code, className = '', showCopy = true }: Readonly<CodeBlockProps>) {\n const [copied, setCopied] = useState(false);\n\n const handleCopy = async () => {\n try {\n await navigator.clipboard.writeText(code);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error('Failed to copy code:', err);\n }\n };\n\n return (\n <div className={`relative group ${className}`}>\n {showCopy && (\n <div className=\"absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity\">\n <Button\n variant=\"neutral\"\n style=\"ghost\"\n size=\"sm\"\n onClick={handleCopy}\n className=\"text-xs bg-background/80 hover:bg-background\"\n >\n {copied ? '✓ Copied' : 'Copy'}\n </Button>\n </div>\n )}\n <pre className={`bg-muted/50 border rounded-lg p-4 overflow-x-auto text-sm ${showCopy ? 'pr-24' : ''}`}>\n <code className=\"font-mono text-foreground\">{code}</code>\n </pre>\n </div>\n );\n}\n\nCodeBlock.displayName = 'CodeBlock';\n\nexport default CodeBlock;\n"],"names":[],"mappings":";;;AAgBA,SAAS,UAAU,EAAE,MAAM,YAAY,IAAI,WAAW,QAAkC;AACtF,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAE1C,QAAM,aAAa,YAAY;AAC7B,QAAI;AACF,YAAM,UAAU,UAAU,UAAU,IAAI;AACxC,gBAAU,IAAI;AACd,iBAAW,MAAM,UAAU,KAAK,GAAG,GAAI;AAAA,IACzC,SAAS,KAAK;AAEZ,cAAQ,MAAM,wBAAwB,GAAG;AAAA,IAC3C;AAAA,EACF;AAEA,SACE,qBAAC,OAAA,EAAI,WAAW,kBAAkB,SAAS,IACxC,UAAA;AAAA,IAAA,YACC,oBAAC,OAAA,EAAI,WAAU,+EACb,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,SAAQ;AAAA,QACR,OAAM;AAAA,QACN,MAAK;AAAA,QACL,SAAS;AAAA,QACT,WAAU;AAAA,QAET,mBAAS,aAAa;AAAA,MAAA;AAAA,IAAA,GAE3B;AAAA,IAEF,oBAAC,OAAA,EAAI,WAAW,
|
|
1
|
+
{"version":3,"file":"code-block.js","sources":["../../../../src/components/data-display/code-block/code-block.tsx"],"sourcesContent":["import { useState } from 'react';\n\nimport Button from '../../forms/button/button';\n\nexport type CodeBlockProps = {\n /** Code content to display */\n code: string;\n /** Additional CSS classes to apply */\n className?: string;\n /** Whether to show the copy-to-clipboard button */\n showCopy?: boolean;\n};\n\n/**\n * CodeBlock - Displays formatted code with syntax highlighting and copy functionality\n */\nfunction CodeBlock({ code, className = '', showCopy = true }: Readonly<CodeBlockProps>) {\n const [copied, setCopied] = useState(false);\n\n const handleCopy = async () => {\n try {\n await navigator.clipboard.writeText(code);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error('Failed to copy code:', err);\n }\n };\n\n return (\n <div className={`relative group ${className}`}>\n {showCopy && (\n <div className=\"absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity\">\n <Button\n variant=\"neutral\"\n style=\"ghost\"\n size=\"sm\"\n onClick={handleCopy}\n className=\"text-xs bg-background/80 hover:bg-background\"\n >\n {copied ? '✓ Copied' : 'Copy'}\n </Button>\n </div>\n )}\n <pre className={`bg-muted-active/50 border rounded-lg p-4 overflow-x-auto text-sm ${showCopy ? 'pr-24' : ''}`}>\n <code className=\"font-mono text-foreground\">{code}</code>\n </pre>\n </div>\n );\n}\n\nCodeBlock.displayName = 'CodeBlock';\n\nexport default CodeBlock;\n"],"names":[],"mappings":";;;AAgBA,SAAS,UAAU,EAAE,MAAM,YAAY,IAAI,WAAW,QAAkC;AACtF,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAE1C,QAAM,aAAa,YAAY;AAC7B,QAAI;AACF,YAAM,UAAU,UAAU,UAAU,IAAI;AACxC,gBAAU,IAAI;AACd,iBAAW,MAAM,UAAU,KAAK,GAAG,GAAI;AAAA,IACzC,SAAS,KAAK;AAEZ,cAAQ,MAAM,wBAAwB,GAAG;AAAA,IAC3C;AAAA,EACF;AAEA,SACE,qBAAC,OAAA,EAAI,WAAW,kBAAkB,SAAS,IACxC,UAAA;AAAA,IAAA,YACC,oBAAC,OAAA,EAAI,WAAU,+EACb,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,SAAQ;AAAA,QACR,OAAM;AAAA,QACN,MAAK;AAAA,QACL,SAAS;AAAA,QACT,WAAU;AAAA,QAET,mBAAS,aAAa;AAAA,MAAA;AAAA,IAAA,GAE3B;AAAA,IAEF,oBAAC,OAAA,EAAI,WAAW,oEAAoE,WAAW,UAAU,EAAE,IACzG,UAAA,oBAAC,QAAA,EAAK,WAAU,6BAA6B,gBAAK,EAAA,CACpD;AAAA,EAAA,GACF;AAEJ;AAEA,UAAU,cAAc;"}
|
|
@@ -323,7 +323,7 @@ function DataGrid({
|
|
|
323
323
|
},
|
|
324
324
|
actionIndex
|
|
325
325
|
);
|
|
326
|
-
return actionConfig.tooltip ? /* @__PURE__ */ jsx(Tooltip, { content: actionConfig.tooltip, children: button }, actionIndex) : button;
|
|
326
|
+
return actionConfig.tooltip ? /* @__PURE__ */ jsx(Tooltip, { content: actionConfig.tooltip, triggerClassName: "inline-flex items-center", children: button }, actionIndex) : button;
|
|
327
327
|
} else {
|
|
328
328
|
return /* @__PURE__ */ jsx("div", { "data-interactive": true, children: action }, actionIndex);
|
|
329
329
|
}
|
|
@@ -408,7 +408,7 @@ function DataGrid({
|
|
|
408
408
|
tabIndex: listRowClickProps.tabIndex,
|
|
409
409
|
children: [
|
|
410
410
|
hasHref && href && /* @__PURE__ */ jsx(Link, { to: href, className: "absolute inset-0 z-1", "aria-label": "View details", tabIndex: 0 }),
|
|
411
|
-
/* @__PURE__ */ jsx("div", { className: pointerClass
|
|
411
|
+
/* @__PURE__ */ jsx("div", { className: `relative z-10 ${pointerClass}`, children: listItemContent })
|
|
412
412
|
]
|
|
413
413
|
},
|
|
414
414
|
actualIndex
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-grid.js","sources":["../../../../src/components/data-display/data-grid/data-grid.tsx"],"sourcesContent":["import { ReactNode, useState, useEffect, useRef } from 'react';\nimport { Icon } from '../../system/icon/icon';\nimport IconButton from '../../forms/button/icon-button';\nimport Input from '../../forms/input/input';\nimport InputGroup from '../../forms/input-group/input-group';\n\nimport Checkbox from '../../forms/checkbox/checkbox';\nimport Button from '../../forms/button/button';\nimport Tooltip from '../../feedback/tooltip/tooltip';\nimport type { ButtonProps } from '../../forms/button/button';\nimport Stack from '../../layout/stack/stack';\nimport Badge from '../badge/badge';\nimport Text from '../../typography/text/text';\nimport { useTable, UseTableOptions } from '../data-table/use-table';\nimport { Size } from '@/theme/size-tokens';\nimport { ColorVariant } from '@/theme/tokens';\nimport EmptyState from '../empty-state/empty-state';\nimport StatusIndicator from '../status-indicator/status-indicator';\nimport { Link } from 'react-router-dom';\n\n/** Interactive element tags that should not trigger onRowClick callback */\nconst INTERACTIVE_TAGS = new Set(['A', 'BUTTON', 'INPUT', 'SELECT', 'TEXTAREA', 'LABEL']);\n\n/** Check if an element or any of its ancestors (up to container) is interactive - used for onRowClick only */\nfunction isInteractiveElement(element: HTMLElement | null, container: HTMLElement | null): boolean {\n let current = element;\n while (current && current !== container) {\n if (INTERACTIVE_TAGS.has(current.tagName)) return true;\n if (current.getAttribute('role') === 'button') return true;\n current = current.parentElement;\n }\n return false;\n}\n\nexport type DataGridAction<T> = {\n /** Icon name for the action button */\n icon: string;\n /** Size of the icon */\n iconSize?: Size;\n /** Color variant for the icon */\n iconColor?: ColorVariant;\n /** Icon to display on hover (optional) */\n hoverIcon?: string;\n /** Accessible label for the action (shown in tooltip or screen readers) */\n label: string;\n /** Tooltip text to display on hover. If not provided, label will be used for aria-label only. */\n tooltip?: string;\n /** Callback when the action is clicked */\n onClick: (row: T, index: number) => void;\n /** Visual variant for the action button */\n variant?: 'primary' | 'accent' | 'neutral' | 'success' | 'warning' | 'error' | 'info';\n};\n\nexport type DataGridActionItem<T> = DataGridAction<T> | ReactNode;\n\nexport type DataGridColumnDef<T> = {\n /** Key of the data property to display in this column */\n key: keyof T;\n /** Column header label */\n label: string;\n /** Whether this column can be sorted */\n sortable?: boolean;\n /** CSS width class or value for the column (e.g., 'w-48', '200px', 'flex-1') */\n width?: string;\n /**\n * Minimum width in pixels for the column. When set:\n * - Column truncates but never shrinks below this width\n * - Column auto-hides if container can't fit it at this width\n * - No need to also set hidePriority (though you can for explicit hide ordering)\n *\n * @example minWidth: 120 // Hide if email would be squished below 120px\n */\n minWidth?: number;\n /**\n * Maximum width in pixels for the column. Prevents column from growing too wide.\n * Use for columns like status/role that don't need much space.\n *\n * @example maxWidth: 100 // Cap role column at 100px\n */\n maxWidth?: number;\n /** Text alignment for cells in this column */\n align?: 'left' | 'center' | 'right';\n /** Whether text should wrap instead of truncating (useful for long strings) */\n wrapText?: boolean;\n /** Optional priority for responsive hiding. Lower values hide first when container width shrinks. Columns without hidePriority or minWidth are never hidden. */\n hidePriority?: number;\n /** Whether to show this column's label in mobile/vertical layout (default: true) */\n showLabelOnMobile?: boolean;\n /** Custom render function for cell content (use as escape hatch when config options don't cover your use case) */\n render?: (value: T[keyof T], row: T, index: number) => ReactNode;\n\n // Config-driven rendering options (preferred over custom render)\n /** Pre-built formatter for common data types */\n format?: 'date' | 'currency' | 'number' | 'percent';\n /** Options for Intl formatters (NumberFormat or DateTimeFormat) */\n formatOptions?: Intl.NumberFormatOptions | Intl.DateTimeFormatOptions;\n /** Map values to badge variants. Unmatched values render with 'neutral' variant. Use '*' key to override the default fallback. */\n badgeMap?: {\n [key: string]: { variant?: ColorVariant; label?: string };\n };\n /** Map values to status dot colors. Renders text with a colored dot to the left. Lighter alternative to badges for status indicators. */\n statusDotMap?: {\n [key: string]: { color?: ColorVariant; label?: string };\n };\n /**\n * Render cell values as badges. When the value is an array, each item is rendered\n * as an individual badge; for scalar values, the value is wrapped in a single badge.\n */\n renderAsBadges?: boolean;\n /** Variant for badge rendering */\n badgeVariant?: ColorVariant;\n /** Size for badge rendering */\n componentSize?: 'sm' | 'md' | 'lg';\n /** Fallback value to display when cell value is null, undefined, or empty string */\n fallback?: string;\n};\n\nexport type DataGridProps<T> = {\n /** Array of data objects to display in the grid */\n data: T[];\n /** Column definitions specifying how to render each column */\n columns: DataGridColumnDef<T>[];\n /** Additional CSS classes to apply */\n className?: string;\n\n // Styling options\n /** Whether to apply striped row styling (alternating background) */\n striped?: boolean;\n /** Whether rows have hover effects */\n hoverable?: boolean;\n /** Whether to use compact spacing for dense data */\n compact?: boolean;\n\n // Features\n /** Whether columns can be sorted (can be overridden per column) */\n sortable?: boolean;\n /** Whether to enable client-side pagination */\n paginated?: boolean;\n /** Number of rows per page when pagination is enabled */\n pageSize?: number;\n /** Whether to show checkboxes for row selection */\n selectable?: boolean;\n /** Whether to enable search/filter functionality */\n searchable?: boolean;\n /** Specific keys to search within. If not provided, searches all fields. */\n searchKeys?: (keyof T)[];\n /** Placeholder text for the search input */\n searchPlaceholder?: string;\n /** Callback when search query changes */\n onSearchChange?: (query: string) => void;\n\n // Actions - either array of action configs/components OR function returning actions/render\n /** Action buttons, function returning actions based on row data, or custom render function for the actions column */\n actions?: DataGridActionItem<T>[] | ((row: T, index: number) => DataGridActionItem<T>[] | ReactNode);\n /** Label for the actions column header */\n actionsLabel?: string;\n\n // Row link - makes entire row clickable for navigation\n /** Function returning href for row navigation. When provided, entire row becomes a link. */\n rowHref?: (row: T, index: number) => string | undefined;\n\n // Callbacks\n /** Callback when a row is clicked (if rowHref not provided) */\n onRowClick?: (row: T, index: number) => void;\n /** Callback when row selection changes (provides array of selected row indices) */\n onSelectionChange?: (selectedIndices: number[]) => void;\n\n // Empty state\n emptyState?: {\n /** Message to display when there is no data */\n title?: string;\n /** Description to display when there is no data */\n description?: string;\n /** Text for the button in the empty state */\n buttonText?: string;\n /** Click handler for the button in the empty state */\n onButtonClick?: () => void;\n /** React Router path — renders the empty state button as a <Link>. Use instead of onButtonClick for in-app navigation. */\n buttonTo?: string;\n /** Plain URL — renders the empty state button as an <a> tag. Use for external links. */\n buttonHref?: string;\n /** Link target (e.g. \"_blank\") — only used with buttonTo or buttonHref */\n buttonTarget?: ButtonProps['target'];\n /** Rel attribute for the anchor (defaults to \"noopener noreferrer\" when buttonTarget=\"_blank\") */\n buttonRel?: string;\n };\n\n // Initial state\n /** Initial sort configuration */\n initialSort?: { key: keyof T; direction: 'asc' | 'desc' };\n /** Title displayed above the grid (left side of header) */\n title?: string;\n /** Header actions displayed above the grid (right side of header). */\n headerActions?: HeaderAction[];\n\n // Responsive / list mode\n /**\n * Container width (px) below which the grid collapses into list mode.\n * Defaults to 480. Column hiding (via `hidePriority` / `minWidth`) still\n * runs for widths above this breakpoint.\n */\n listBreakpoint?: number;\n /**\n * Custom render function for list mode. When provided, each row is rendered\n * using this function instead of the default vertical-stack fallback.\n * Use this to create type-specific list layouts with the right fields,\n * actions, and visual hierarchy for the entity being displayed.\n *\n * @example\n * ```tsx\n * renderListItem={(row, index, { actions, isSelected, onToggleSelect }) => (\n * <div className=\"flex items-center gap-3\">\n * <Avatar name={row.name} />\n * <div className=\"flex-1 min-w-0\">\n * <Text weight=\"medium\" className=\"truncate\">{row.name}</Text>\n * <Text size=\"sm\" variant=\"muted\" className=\"truncate\">{row.email}</Text>\n * </div>\n * {actions}\n * </div>\n * )}\n * ```\n */\n renderListItem?: (row: T, index: number, context: DataGridListItemContext) => ReactNode;\n};\n\nexport type HeaderAction = {\n /** Button text; if omitted and `icon` provided, button will be icon-only */\n label?: string;\n /** Icon name to render inside the button (maps to library `Icon`) */\n icon?: string;\n /** Accessible label for screen readers. Required for icon-only buttons (when label is omitted). */\n ariaLabel?: string;\n /** Click handler invoked when the button is clicked */\n onClick?: () => void;\n /** React Router path — renders the button as a <Link>. Use instead of onClick for in-app navigation. */\n to?: string;\n /** Plain URL — renders the button as an <a> tag. Use for external links. */\n href?: string;\n /** Link target (e.g. \"_blank\") — only used with to or href */\n target?: ButtonProps['target'];\n /** Rel attribute for the anchor (defaults to \"noopener noreferrer\" when target=\"_blank\") */\n rel?: string;\n /** Button variant (maps to Button `variant` prop) */\n variant?: ButtonProps['variant'];\n /** Button style (maps to Button `style` prop) */\n style?: ButtonProps['style'];\n /** Button size (maps to Button `size` prop) */\n size?: ButtonProps['size'];\n /** Additional className forwarded to the Button */\n className?: string;\n};\n\n/** Breakpoint width (in px) below which we switch to list mode */\nconst DEFAULT_LIST_BREAKPOINT = 480;\n\n/**\n * Context passed to `renderListItem` so list-mode renders can embed grid actions\n * and selection state without needing to manage them independently.\n */\nexport type DataGridListItemContext = {\n /** Pre-rendered action buttons derived from the `actions` prop */\n actions: ReactNode;\n /** Whether this row is currently selected */\n isSelected: boolean;\n /** Toggle selection for this row. Defined only when `selectable` is true. */\n onToggleSelect?: () => void;\n};\n\n/**\n * DataGrid - Responsive data display component with Vercel-style layout\n *\n * Features:\n * - Generic typed data and columns (same API as DataTable)\n * - Responsive layout: horizontal rows on desktop, vertical card-like on mobile\n * - Clickable rows via rowHref with interactive elements taking priority\n * - Client-side sorting, pagination, selection, and search\n * - Custom cell rendering with DataGridCell for multi-element stacking\n *\n * @example\n * ```tsx\n * <DataGrid\n * data={deployments}\n * columns={[\n * {\n * key: 'name',\n * label: 'Deployment',\n * render: (value, row) => (\n * <DataGridCell>\n * <Text weight=\"medium\">{row.name}</Text>\n * <Text size=\"sm\" variant=\"muted\">{row.url}</Text>\n * </DataGridCell>\n * )\n * },\n * { key: 'status', label: 'Status', badgeMap: { ready: { variant: 'success' } } },\n * { key: 'createdAt', label: 'Created', format: 'date' }\n * ]}\n * rowHref={(row) => `/deployments/${row.id}`}\n * hoverable\n * />\n * ```\n */\nfunction DataGrid<T>({\n data,\n columns,\n className = '',\n striped = false,\n hoverable = true,\n compact = false,\n sortable = true,\n paginated = false,\n pageSize = 10,\n selectable = false,\n searchable = false,\n searchKeys,\n searchPlaceholder = 'Search...',\n onSearchChange,\n actions,\n actionsLabel = 'Actions',\n rowHref,\n onRowClick,\n onSelectionChange,\n emptyState = {\n title: 'No data available',\n description: undefined,\n buttonText: undefined,\n onButtonClick: undefined,\n buttonTo: undefined,\n buttonHref: undefined,\n buttonTarget: undefined,\n buttonRel: undefined\n },\n headerActions,\n title,\n initialSort,\n listBreakpoint = DEFAULT_LIST_BREAKPOINT,\n renderListItem\n}: DataGridProps<T>) {\n // Search state\n const [searchQuery, setSearchQuery] = useState('');\n\n // Layout state – true when container width is below listBreakpoint\n const [isListMode, setIsListMode] = useState(false);\n\n const handleSearchChange = (value: string) => {\n setSearchQuery(value);\n if (onSearchChange) {\n onSearchChange(value);\n }\n };\n\n // Container ref for responsive behavior\n const containerRef = useRef<HTMLDivElement>(null);\n const [visibleColumns, setVisibleColumns] = useState<DataGridColumnDef<T>[]>(() => columns);\n\n // Calculate minimum width needed for actions column based on max actions in any row\n const calculateActionsMinWidth = (): number => {\n if (!actions) return 0;\n\n let maxActionCount = 0;\n\n if (Array.isArray(actions)) {\n // Static actions - all rows have the same actions\n maxActionCount = actions.length;\n } else {\n // Dynamic actions - need to check all rows\n data.forEach((row, index) => {\n const result = actions(row, index);\n if (Array.isArray(result)) {\n maxActionCount = Math.max(maxActionCount, result.length);\n } else {\n // Custom render - assume it needs reasonable space (equivalent to 2 actions)\n maxActionCount = Math.max(maxActionCount, 2);\n }\n });\n }\n\n if (maxActionCount === 0) {\n // No actions, but we still need some minimum space in case actions appear\n return 60;\n }\n\n // Each action button takes ~36px (icon + button padding)\n // Stack spacing between actions is 8px (spacing=\"sm\")\n // Formula: (actionWidth * count) + (gap * (count - 1)) + extraPadding\n const actionWidth = 24;\n const gapWidth = 8;\n const extraPadding = 16; // breathing room\n\n return actionWidth * maxActionCount + gapWidth * Math.max(0, maxActionCount - 1) + extraPadding;\n };\n\n const actionsMinWidth = calculateActionsMinWidth();\n\n // ResizeObserver to handle responsive column hiding and mobile layout\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n // Check if any columns have hidePriority or minWidth set (both make columns hideable)\n const hasHideableColumns = columns.some((col) => col.hidePriority !== undefined || col.minWidth !== undefined);\n\n const updateLayout = () => {\n const containerWidth = container.offsetWidth;\n\n // Check if we should switch to list mode\n const nowListMode = containerWidth < listBreakpoint;\n setIsListMode(nowListMode);\n\n // In list mode, show all columns (either renderListItem handles layout\n // or the fallback vertical stack renders them all with labels).\n // Only apply responsive column hiding in grid mode.\n if (nowListMode || !hasHideableColumns) {\n setVisibleColumns(columns);\n return;\n }\n\n // Columns are hideable if they have hidePriority OR minWidth\n const isHideable = (col: DataGridColumnDef<T>) => col.hidePriority !== undefined || col.minWidth !== undefined;\n\n // Separate columns into hideable and non-hideable\n const hideableColumns = columns\n .map((col, index) => ({ col, originalIndex: index }))\n .filter(({ col }) => isHideable(col))\n .sort((a, b) => {\n // Sort by hidePriority if both have it (lower hides first)\n const aPriority = a.col.hidePriority;\n const bPriority = b.col.hidePriority;\n\n if (aPriority !== undefined && bPriority !== undefined) {\n if (aPriority !== bPriority) return aPriority - bPriority;\n }\n // Columns with explicit hidePriority hide before minWidth-only columns\n if (aPriority !== undefined && bPriority === undefined) return -1;\n if (aPriority === undefined && bPriority !== undefined) return 1;\n\n // For minWidth-only columns, larger minWidth hides first (needs more space)\n const aMin = a.col.minWidth ?? 0;\n const bMin = b.col.minWidth ?? 0;\n if (aMin !== bMin) return bMin - aMin;\n\n // Finally, later columns hide first (preserve leading columns)\n return b.originalIndex - a.originalIndex;\n });\n\n const nonHideableColumns = columns.filter((col) => !isHideable(col));\n\n // Estimate column width - use minWidth if set, otherwise parse width or default\n const estimateColumnWidth = (col: DataGridColumnDef<T>): number => {\n // Use minWidth if explicitly set (this is the minimum space the column needs)\n if (col.minWidth) return col.minWidth;\n\n if (col.width) {\n const match = col.width.match(/(\\d+)/);\n if (match) {\n const num = parseInt(match[1], 10);\n if (col.width.includes('px')) return num;\n return num * 4;\n }\n }\n return 150;\n };\n\n // Calculate horizontal padding (px-3 = 24px total, px-4 = 32px total)\n const horizontalPadding = compact ? 24 : 32;\n // Grid gap is 16px (gap-4) between each column\n const gridGap = 16;\n\n // Start with non-hideable columns\n let currentWidth = nonHideableColumns.reduce((sum, col) => sum + estimateColumnWidth(col), 0);\n let totalColumns = nonHideableColumns.length;\n\n // Account for selection and actions columns\n if (selectable) {\n currentWidth += 48;\n totalColumns += 1;\n }\n if (actions) {\n currentWidth += actionsMinWidth;\n totalColumns += 1;\n }\n\n // Add hideable columns until we run out of space\n const columnsToShow: DataGridColumnDef<T>[] = [...nonHideableColumns];\n\n for (const { col } of hideableColumns) {\n const colWidth = estimateColumnWidth(col);\n const newTotalColumns = totalColumns + 1;\n // Calculate total gaps needed with the new column (N columns = N-1 gaps)\n const totalGaps = Math.max(0, newTotalColumns - 1) * gridGap;\n const totalWidthNeeded = currentWidth + colWidth + totalGaps + horizontalPadding;\n\n if (totalWidthNeeded <= containerWidth) {\n columnsToShow.push(col);\n currentWidth += colWidth;\n totalColumns = newTotalColumns;\n }\n }\n\n // Restore original column order\n const orderedVisibleColumns = columns.filter((col) => columnsToShow.includes(col));\n setVisibleColumns(orderedVisibleColumns);\n };\n\n // Call once on mount\n updateLayout();\n\n // Set up observer\n const observer = new ResizeObserver(() => {\n updateLayout();\n });\n\n observer.observe(container);\n\n return () => {\n observer.disconnect();\n };\n }, [columns, selectable, actions, actionsMinWidth, compact, data, listBreakpoint]);\n\n // Use table hook for state management\n const effectiveSearchKeys = searchKeys || visibleColumns.map((col) => col.key);\n\n const tableOptions: UseTableOptions<T> = {\n data,\n initialSort,\n pageSize,\n searchQuery,\n searchKeys: effectiveSearchKeys\n };\n\n const {\n currentData,\n sortedData,\n filteredData,\n sortConfig,\n handleSort,\n currentPage,\n totalPages,\n nextPage,\n prevPage,\n canNextPage,\n canPrevPage,\n selectedRows,\n toggleRow,\n toggleAll,\n isRowSelected,\n isAllSelected\n } = useTable(tableOptions);\n\n // Display data (paginated uses currentData, non-paginated uses sortedData)\n const displayData = paginated ? currentData : sortedData;\n\n // Header title and actions\n const hasHeader = Boolean(\n (title && String(title).length > 0) ||\n (Array.isArray(headerActions) ? headerActions.length > 0 : headerActions) ||\n searchable\n );\n\n // Selection handlers\n const handleToggleRow = (index: number) => {\n toggleRow(index);\n if (onSelectionChange) {\n const newSelection = new Set(selectedRows);\n if (newSelection.has(index)) {\n newSelection.delete(index);\n } else {\n newSelection.add(index);\n }\n onSelectionChange(Array.from(newSelection));\n }\n };\n\n const handleToggleAll = () => {\n toggleAll();\n if (onSelectionChange) {\n if (isAllSelected) {\n onSelectionChange([]);\n } else {\n const startIndex = (currentPage - 1) * pageSize;\n const allIndices = displayData.map((_, idx) => startIndex + idx);\n onSelectionChange(allIndices);\n }\n }\n };\n\n // Format value based on column configuration\n const formatValue = (value: unknown, column: DataGridColumnDef<T>): string => {\n if (value == null) return '';\n\n switch (column.format) {\n case 'date': {\n const dateValue = value instanceof Date ? value : new Date(value as string | number);\n return new Intl.DateTimeFormat('en-US', column.formatOptions as Intl.DateTimeFormatOptions).format(dateValue);\n }\n\n case 'currency':\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n ...column.formatOptions\n } as Intl.NumberFormatOptions).format(Number(value));\n\n case 'number':\n return new Intl.NumberFormat('en-US', column.formatOptions as Intl.NumberFormatOptions).format(Number(value));\n\n case 'percent':\n return new Intl.NumberFormat('en-US', {\n style: 'percent',\n ...column.formatOptions\n } as Intl.NumberFormatOptions).format(Number(value));\n\n default:\n return String(value);\n }\n };\n\n const renderCellContent = (value: unknown, column: DataGridColumnDef<T>, row: T, rowIndex: number): ReactNode => {\n const isEmpty = value == null || value === '';\n\n if (isEmpty && column.fallback !== undefined) {\n return column.fallback;\n }\n\n if (column.render) {\n return column.render(value as T[keyof T], row, rowIndex);\n }\n\n if (column.badgeMap) {\n const stringValue = String(value);\n const badgeConfig = column.badgeMap[stringValue] || column.badgeMap['*'] || { variant: 'neutral' };\n\n return (\n <Badge variant={badgeConfig.variant || 'neutral'} size={column.componentSize || 'md'}>\n {badgeConfig.label || stringValue}\n </Badge>\n );\n }\n\n if (column.statusDotMap) {\n const stringValue = String(value);\n const dotConfig = column.statusDotMap[stringValue] || column.statusDotMap['*'] || { color: 'neutral' };\n\n return <StatusIndicator variant={dotConfig.color || 'neutral'}>{dotConfig.label || stringValue}</StatusIndicator>;\n }\n\n if (column.renderAsBadges) {\n if (Array.isArray(value)) {\n if (value.length === 0 && column.fallback !== undefined) {\n return column.fallback;\n }\n\n return (\n <Stack direction=\"horizontal\" spacing=\"xs\" wrap>\n {value.map((item, idx) => (\n <Badge key={idx} variant={column.badgeVariant || 'neutral'} size={column.componentSize || 'md'}>\n {String(item)}\n </Badge>\n ))}\n </Stack>\n );\n } else {\n return (\n <Badge variant={column.badgeVariant || 'neutral'} size={column.componentSize || 'md'}>\n {String(value)}\n </Badge>\n );\n }\n }\n\n if (column.format) {\n return formatValue(value, column);\n }\n\n return String(value ?? '');\n };\n\n // Render sort indicator\n const renderSortIcon = (columnKey: keyof T) => {\n if (!sortable) return null;\n\n const isSorted = sortConfig?.key === columnKey;\n\n if (!isSorted) {\n return <Icon name=\"selector\" size=\"xs\" color=\"neutral\" />;\n }\n\n if (sortConfig?.direction === 'asc') {\n return <Icon name=\"chevron-up\" size=\"xs\" color=\"primary\" />;\n }\n\n return <Icon name=\"chevron-down\" size=\"xs\" color=\"primary\" />;\n };\n\n // Render actions for a row\n const renderActions = (row: T, actualIndex: number) => {\n if (!actions) return null;\n\n let rowActions: DataGridActionItem<T>[] | ReactNode;\n\n if (Array.isArray(actions)) {\n rowActions = actions;\n } else {\n const result = actions(row, actualIndex);\n if (Array.isArray(result)) {\n rowActions = result;\n } else {\n return <div data-interactive>{result}</div>;\n }\n }\n\n return (\n <Stack direction=\"horizontal\" spacing=\"sm\" data-interactive>\n {(rowActions as DataGridActionItem<T>[]).map((action, actionIndex) => {\n if (action && typeof action === 'object' && 'onClick' in action) {\n const actionConfig = action as DataGridAction<T>;\n const button = (\n <IconButton\n key={actionIndex}\n icon={actionConfig.icon}\n iconSize={actionConfig.iconSize || 'md'}\n buttonStyle=\"ghost\"\n variant={actionConfig.variant || 'neutral'}\n iconColor={actionConfig.iconColor}\n hoverIcon={actionConfig.hoverIcon}\n ariaLabel={actionConfig.label}\n onClick={() => actionConfig.onClick(row, actualIndex)}\n noPadding\n />\n );\n\n return actionConfig.tooltip ? (\n <Tooltip key={actionIndex} content={actionConfig.tooltip}>\n {button}\n </Tooltip>\n ) : (\n button\n );\n } else {\n return (\n <div key={actionIndex} data-interactive>\n {action as ReactNode}\n </div>\n );\n }\n })}\n </Stack>\n );\n };\n\n // Row padding classes\n const rowPadding = compact ? 'py-2 px-3' : 'py-3 px-4';\n\n // Build grid template columns for desktop layout\n const buildGridTemplateColumns = () => {\n const parts: string[] = [];\n\n // Selection checkbox column - fixed width for consistency across rows\n if (selectable) {\n parts.push('24px');\n }\n\n // Data columns\n visibleColumns.forEach((col) => {\n if (col.width) {\n // Explicit width (Tailwind class won't work in grid-template-columns)\n // Try to extract pixel value or use minmax\n const widthMatch = col.width.match(/(\\d+)px/);\n if (widthMatch) {\n parts.push(`${widthMatch[1]}px`);\n } else {\n parts.push('1fr');\n }\n } else if (col.minWidth && col.maxWidth) {\n // Both min and max - constrained range\n parts.push(`minmax(${col.minWidth}px, ${col.maxWidth}px)`);\n } else if (col.minWidth) {\n // Min only - can grow but not shrink below\n parts.push(`minmax(${col.minWidth}px, 1fr)`);\n } else if (col.maxWidth) {\n // Max only - can shrink but not grow beyond\n parts.push(`minmax(auto, ${col.maxWidth}px)`);\n } else {\n parts.push('1fr');\n }\n });\n\n // Actions column - dynamically sized based on max actions across all rows\n // Allows growing to max-content to accommodate multiple action buttons\n if (actions) {\n parts.push(`minmax(${actionsMinWidth}px, max-content)`);\n }\n\n return parts.join(' ');\n };\n\n const gridTemplateColumns = !isListMode ? buildGridTemplateColumns() : undefined;\n\n // Render a single row\n const renderRow = (row: T, rowIndex: number) => {\n const actualIndex = paginated ? (currentPage - 1) * pageSize + rowIndex : rowIndex;\n const isSelected = isRowSelected(actualIndex);\n const href = rowHref?.(row, actualIndex);\n const hasHref = Boolean(href);\n const hasOnRowClick = Boolean(onRowClick);\n const isClickable = hasHref || hasOnRowClick;\n\n // Row styling - add relative for Link overlay positioning\n const rowClasses = [\n 'relative',\n 'border-b border-border/50',\n striped && rowIndex % 2 === 1 ? 'bg-muted/30' : '',\n hoverable && isClickable ? 'hover:bg-muted/50 transition-colors' : '',\n isSelected ? 'bg-primary/5' : ''\n ]\n .filter(Boolean)\n .join(' ');\n\n // Grid mode: CSS Grid for consistent column widths\n // List mode: vertical flex (fallback) or custom renderListItem\n const layoutClasses = isListMode ? 'flex flex-col gap-3' : 'grid items-center gap-4';\n\n // Grid style for grid mode only\n const rowStyle = !isListMode && gridTemplateColumns ? { gridTemplateColumns } : undefined;\n\n // ----------------------------------------------------------------\n // List mode with custom render – delegate layout to renderListItem\n // ----------------------------------------------------------------\n if (isListMode && renderListItem) {\n const actionsContent = renderActions(row, actualIndex);\n const listItemContent = renderListItem(row, actualIndex, {\n actions: actionsContent,\n isSelected,\n onToggleSelect: selectable ? () => handleToggleRow(actualIndex) : undefined\n });\n\n // Pointer-events wrapper mirrors the grid row behaviour\n const pointerClass = hasHref\n ? 'pointer-events-none [&_a]:pointer-events-auto [&_button]:pointer-events-auto [&_input]:pointer-events-auto [&_select]:pointer-events-auto [&_textarea]:pointer-events-auto [&_label]:pointer-events-auto [&_[data-interactive]]:pointer-events-auto'\n : '';\n\n const listRowClickProps =\n !hasHref && hasOnRowClick\n ? {\n onClick: (e: React.MouseEvent<HTMLDivElement>) => {\n if (isInteractiveElement(e.target as HTMLElement, e.currentTarget)) return;\n onRowClick?.(row, actualIndex);\n },\n onKeyDown: (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onRowClick?.(row, actualIndex);\n }\n },\n role: 'button' as const,\n tabIndex: 0,\n className: 'cursor-pointer'\n }\n : {};\n\n return (\n <div\n key={actualIndex}\n className={`relative ${rowPadding} ${rowClasses} ${listRowClickProps.className || ''}`}\n onClick={listRowClickProps.onClick}\n onKeyDown={listRowClickProps.onKeyDown}\n role={listRowClickProps.role}\n tabIndex={listRowClickProps.tabIndex}\n >\n {hasHref && href && (\n <Link to={href} className=\"absolute inset-0 z-1\" aria-label=\"View details\" tabIndex={0} />\n )}\n <div className={pointerClass}>{listItemContent}</div>\n </div>\n );\n }\n\n // ----------------------------------------------------------------\n // Props for onRowClick (when no href) - handlers are on the row div\n // Uses isInteractiveElement check to avoid triggering on links/buttons\n const rowClickProps =\n !hasHref && hasOnRowClick\n ? {\n onClick: (e: React.MouseEvent<HTMLDivElement>) => {\n // Don't trigger if clicked on an interactive element (links, buttons, etc.)\n if (isInteractiveElement(e.target as HTMLElement, e.currentTarget)) {\n return;\n }\n onRowClick?.(row, actualIndex);\n },\n onKeyDown: (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onRowClick?.(row, actualIndex);\n }\n },\n role: 'button' as const,\n tabIndex: 0,\n className: 'cursor-pointer'\n }\n : {};\n\n return (\n <div\n key={actualIndex}\n className={`${layoutClasses} ${rowPadding} ${rowClasses} ${rowClickProps.className || ''}`}\n style={rowStyle}\n onClick={rowClickProps.onClick}\n onKeyDown={rowClickProps.onKeyDown}\n role={rowClickProps.role}\n tabIndex={rowClickProps.tabIndex}\n >\n {/* Row link overlay - positioned behind content */}\n {hasHref && href && (\n <Link to={href} className=\"absolute inset-0 z-1\" aria-label={`View details`} tabIndex={0} />\n )}\n\n {/* Selection checkbox - above link layer */}\n {selectable && (\n <div className={`relative z-10 ${isListMode ? '' : 'shrink-0'}`}>\n <Checkbox\n checked={isSelected}\n onChange={() => handleToggleRow(actualIndex)}\n ariaLabel={`Select row ${actualIndex + 1}`}\n />\n </div>\n )}\n\n {/* Data cells - pointer-events-none lets clicks pass through to link layer */}\n {/* Interactive elements inside (links, buttons) keep their default pointer-events */}\n {visibleColumns.map((column) => {\n const value = row[column.key];\n const content = renderCellContent(value, column, row, actualIndex);\n const showLabel = isListMode && column.showLabelOnMobile !== false;\n\n // Alignment\n const alignClass =\n column.align === 'center' ? 'text-center' : column.align === 'right' ? 'text-right' : 'text-left';\n\n // Only disable pointer events when we have a link overlay\n const pointerClass = hasHref\n ? 'pointer-events-none [&_a]:pointer-events-auto [&_button]:pointer-events-auto [&_input]:pointer-events-auto [&_select]:pointer-events-auto [&_textarea]:pointer-events-auto [&_label]:pointer-events-auto [&_[data-interactive]]:pointer-events-auto'\n : '';\n\n return (\n <div\n key={String(column.key)}\n className={`${alignClass} ${isListMode ? 'flex flex-col gap-0.5' : 'min-w-0 overflow-hidden'} ${pointerClass}`}\n >\n {showLabel && (\n <Text size=\"xs\" variant=\"muted\" className=\"uppercase tracking-wide\">\n {column.label}\n </Text>\n )}\n <div className={column.wrapText ? '' : 'truncate'}>{content}</div>\n </div>\n );\n })}\n\n {/* Actions - above link layer */}\n {actions && (\n <div className={`relative z-10 shrink-0 ${isListMode ? 'flex justify-end pt-1' : 'justify-self-end'}`}>\n {renderActions(row, actualIndex)}\n </div>\n )}\n </div>\n );\n };\n\n return (\n <div ref={containerRef} className={`overflow-hidden ${className}`}>\n {/* Header with title, search, actions */}\n {hasHeader && (\n <div className=\"mb-3\">\n {searchable ? (\n <div className=\"flex flex-col gap-3\">\n {title && <h2 className=\"text-lg font-semibold text-foreground\">{title}</h2>}\n <div className=\"flex flex-col sm:flex-row items-stretch sm:items-center gap-3 sm:justify-between\">\n <div className=\"flex-1 sm:max-w-md\">\n <InputGroup prefix={<Icon name=\"search\" size=\"sm\" />} className=\"w-full\">\n <Input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => handleSearchChange(e.target.value)}\n placeholder={searchPlaceholder}\n />\n </InputGroup>\n </div>\n\n {Array.isArray(headerActions) && headerActions.length > 0 && (\n <div className=\"flex items-center gap-2 flex-wrap sm:flex-nowrap ml-auto\">\n {headerActions.map((act, idx) => (\n <Button\n key={idx}\n onClick={act.onClick}\n to={act.to}\n href={act.href}\n target={act.target}\n rel={act.rel}\n variant={act.variant}\n style={act.style}\n size={act.size}\n className={act.className}\n ariaLabel={act.ariaLabel || (!act.label && act.icon ? 'Action' : undefined)}\n >\n {act.icon ? <Icon name={act.icon} size=\"sm\" /> : null}\n {act.label}\n </Button>\n ))}\n </div>\n )}\n </div>\n </div>\n ) : (\n <div className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3\">\n <div className=\"flex-1\">\n {title && <h2 className=\"text-lg font-semibold text-foreground\">{title}</h2>}\n </div>\n\n {Array.isArray(headerActions) && headerActions.length > 0 && (\n <div className=\"flex items-center gap-2 flex-wrap sm:flex-nowrap\">\n {headerActions.map((act, idx) => (\n <Button\n key={idx}\n onClick={act.onClick}\n to={act.to}\n href={act.href}\n target={act.target}\n rel={act.rel}\n variant={act.variant}\n style={act.style}\n size={act.size}\n className={act.className}\n ariaLabel={act.ariaLabel || (!act.label && act.icon ? 'Action' : undefined)}\n >\n {act.icon ? <Icon name={act.icon} size=\"sm\" /> : null}\n {act.label}\n </Button>\n ))}\n </div>\n )}\n </div>\n )}\n </div>\n )}\n\n {/* Empty states */}\n {data.length === 0 ? (\n <div className=\"border border-border rounded-lg\">\n <EmptyState\n title={emptyState.title}\n description={emptyState.description}\n buttonText={emptyState.buttonText}\n onButtonClick={emptyState.onButtonClick}\n buttonTo={emptyState.buttonTo}\n buttonHref={emptyState.buttonHref}\n buttonTarget={emptyState.buttonTarget}\n buttonRel={emptyState.buttonRel}\n />\n </div>\n ) : filteredData.length === 0 && searchQuery.trim() ? (\n <div className=\"border border-border rounded-lg\">\n <EmptyState\n title=\"No results found\"\n description={`No items match \"${searchQuery}\". Try adjusting your search.`}\n />\n </div>\n ) : (\n <>\n {/* Column headers – grid mode only (hidden in list mode) */}\n {!isListMode && (\n <div\n className={`grid items-center gap-4 ${rowPadding} border-b border-border bg-muted/30`}\n style={gridTemplateColumns ? { gridTemplateColumns } : undefined}\n >\n {selectable && (\n <div>\n <Checkbox checked={isAllSelected} onChange={handleToggleAll} ariaLabel=\"Select all rows\" />\n </div>\n )}\n\n {visibleColumns.map((column) => {\n const alignClass =\n column.align === 'center' ? 'text-center' : column.align === 'right' ? 'text-right' : 'text-left';\n\n return (\n <div key={String(column.key)} className={`min-w-0 ${alignClass}`}>\n {column.sortable !== false && sortable ? (\n <button\n onClick={() => handleSort(column.key)}\n className=\"flex items-center gap-1 hover:text-foreground transition-colors font-medium text-sm text-muted-foreground\"\n type=\"button\"\n >\n {column.label}\n {renderSortIcon(column.key)}\n </button>\n ) : (\n <Text size=\"sm\" variant=\"muted\" weight=\"medium\">\n {column.label}\n </Text>\n )}\n </div>\n );\n })}\n\n {actions && (\n <div className=\"justify-self-end\">\n <Text size=\"sm\" variant=\"muted\" weight=\"medium\">\n {actionsLabel}\n </Text>\n </div>\n )}\n </div>\n )}\n\n {/* Select all checkbox (list mode fallback only – renderListItem handles its own selection UI) */}\n {isListMode && !renderListItem && selectable && (\n <div className={`flex items-center gap-2 ${rowPadding} border-b border-border`}>\n <Checkbox checked={isAllSelected} onChange={handleToggleAll} ariaLabel=\"Select all rows\" />\n <Text size=\"sm\" variant=\"muted\">\n Select all\n </Text>\n </div>\n )}\n\n {/* Rows */}\n <div className=\"divide-y divide-border/50\">{displayData.map((row, idx) => renderRow(row, idx))}</div>\n\n {/* Pagination controls */}\n {paginated && totalPages > 1 && (\n <div className=\"flex items-center justify-between px-4 py-3 border-t border-border\">\n <div className=\"text-sm text-muted-foreground\">\n Page {currentPage} of {totalPages} ({filteredData.length} {searchQuery.trim() ? 'filtered' : 'total'}{' '}\n rows\n {searchQuery.trim() && data.length !== filteredData.length ? ` of ${data.length}` : ''})\n </div>\n <div className=\"flex gap-2\">\n <Button size=\"sm\" style=\"outline\" onClick={prevPage} disabled={!canPrevPage}>\n Previous\n </Button>\n <Button size=\"sm\" style=\"outline\" onClick={nextPage} disabled={!canNextPage}>\n Next\n </Button>\n </div>\n </div>\n )}\n </>\n )}\n </div>\n );\n}\n\nDataGrid.displayName = 'DataGrid';\n\nexport default DataGrid;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"data-grid.js","sources":["../../../../src/components/data-display/data-grid/data-grid.tsx"],"sourcesContent":["import { ReactNode, useState, useEffect, useRef } from 'react';\nimport { Icon } from '../../system/icon/icon';\nimport IconButton from '../../forms/button/icon-button';\nimport Input from '../../forms/input/input';\nimport InputGroup from '../../forms/input-group/input-group';\n\nimport Checkbox from '../../forms/checkbox/checkbox';\nimport Button from '../../forms/button/button';\nimport Tooltip from '../../feedback/tooltip/tooltip';\nimport type { ButtonProps } from '../../forms/button/button';\nimport Stack from '../../layout/stack/stack';\nimport Badge from '../badge/badge';\nimport Text from '../../typography/text/text';\nimport { useTable, UseTableOptions } from '../data-table/use-table';\nimport { Size } from '@/theme/size-tokens';\nimport { ColorVariant } from '@/theme/tokens';\nimport EmptyState from '../empty-state/empty-state';\nimport StatusIndicator from '../status-indicator/status-indicator';\nimport { Link } from 'react-router-dom';\n\n/** Interactive element tags that should not trigger onRowClick callback */\nconst INTERACTIVE_TAGS = new Set(['A', 'BUTTON', 'INPUT', 'SELECT', 'TEXTAREA', 'LABEL']);\n\n/** Check if an element or any of its ancestors (up to container) is interactive - used for onRowClick only */\nfunction isInteractiveElement(element: HTMLElement | null, container: HTMLElement | null): boolean {\n let current = element;\n while (current && current !== container) {\n if (INTERACTIVE_TAGS.has(current.tagName)) return true;\n if (current.getAttribute('role') === 'button') return true;\n current = current.parentElement;\n }\n return false;\n}\n\nexport type DataGridAction<T> = {\n /** Icon name for the action button */\n icon: string;\n /** Size of the icon */\n iconSize?: Size;\n /** Color variant for the icon */\n iconColor?: ColorVariant;\n /** Icon to display on hover (optional) */\n hoverIcon?: string;\n /** Accessible label for the action (shown in tooltip or screen readers) */\n label: string;\n /** Tooltip text to display on hover. If not provided, label will be used for aria-label only. */\n tooltip?: string;\n /** Callback when the action is clicked */\n onClick: (row: T, index: number) => void;\n /** Visual variant for the action button */\n variant?: 'primary' | 'accent' | 'neutral' | 'success' | 'warning' | 'error' | 'info';\n};\n\nexport type DataGridActionItem<T> = DataGridAction<T> | ReactNode;\n\nexport type DataGridColumnDef<T> = {\n /** Key of the data property to display in this column */\n key: keyof T;\n /** Column header label */\n label: string;\n /** Whether this column can be sorted */\n sortable?: boolean;\n /** CSS width class or value for the column (e.g., 'w-48', '200px', 'flex-1') */\n width?: string;\n /**\n * Minimum width in pixels for the column. When set:\n * - Column truncates but never shrinks below this width\n * - Column auto-hides if container can't fit it at this width\n * - No need to also set hidePriority (though you can for explicit hide ordering)\n *\n * @example minWidth: 120 // Hide if email would be squished below 120px\n */\n minWidth?: number;\n /**\n * Maximum width in pixels for the column. Prevents column from growing too wide.\n * Use for columns like status/role that don't need much space.\n *\n * @example maxWidth: 100 // Cap role column at 100px\n */\n maxWidth?: number;\n /** Text alignment for cells in this column */\n align?: 'left' | 'center' | 'right';\n /** Whether text should wrap instead of truncating (useful for long strings) */\n wrapText?: boolean;\n /** Optional priority for responsive hiding. Lower values hide first when container width shrinks. Columns without hidePriority or minWidth are never hidden. */\n hidePriority?: number;\n /** Whether to show this column's label in mobile/vertical layout (default: true) */\n showLabelOnMobile?: boolean;\n /** Custom render function for cell content (use as escape hatch when config options don't cover your use case) */\n render?: (value: T[keyof T], row: T, index: number) => ReactNode;\n\n // Config-driven rendering options (preferred over custom render)\n /** Pre-built formatter for common data types */\n format?: 'date' | 'currency' | 'number' | 'percent';\n /** Options for Intl formatters (NumberFormat or DateTimeFormat) */\n formatOptions?: Intl.NumberFormatOptions | Intl.DateTimeFormatOptions;\n /** Map values to badge variants. Unmatched values render with 'neutral' variant. Use '*' key to override the default fallback. */\n badgeMap?: {\n [key: string]: { variant?: ColorVariant; label?: string };\n };\n /** Map values to status dot colors. Renders text with a colored dot to the left. Lighter alternative to badges for status indicators. */\n statusDotMap?: {\n [key: string]: { color?: ColorVariant; label?: string };\n };\n /**\n * Render cell values as badges. When the value is an array, each item is rendered\n * as an individual badge; for scalar values, the value is wrapped in a single badge.\n */\n renderAsBadges?: boolean;\n /** Variant for badge rendering */\n badgeVariant?: ColorVariant;\n /** Size for badge rendering */\n componentSize?: 'sm' | 'md' | 'lg';\n /** Fallback value to display when cell value is null, undefined, or empty string */\n fallback?: string;\n};\n\nexport type DataGridProps<T> = {\n /** Array of data objects to display in the grid */\n data: T[];\n /** Column definitions specifying how to render each column */\n columns: DataGridColumnDef<T>[];\n /** Additional CSS classes to apply */\n className?: string;\n\n // Styling options\n /** Whether to apply striped row styling (alternating background) */\n striped?: boolean;\n /** Whether rows have hover effects */\n hoverable?: boolean;\n /** Whether to use compact spacing for dense data */\n compact?: boolean;\n\n // Features\n /** Whether columns can be sorted (can be overridden per column) */\n sortable?: boolean;\n /** Whether to enable client-side pagination */\n paginated?: boolean;\n /** Number of rows per page when pagination is enabled */\n pageSize?: number;\n /** Whether to show checkboxes for row selection */\n selectable?: boolean;\n /** Whether to enable search/filter functionality */\n searchable?: boolean;\n /** Specific keys to search within. If not provided, searches all fields. */\n searchKeys?: (keyof T)[];\n /** Placeholder text for the search input */\n searchPlaceholder?: string;\n /** Callback when search query changes */\n onSearchChange?: (query: string) => void;\n\n // Actions - either array of action configs/components OR function returning actions/render\n /** Action buttons, function returning actions based on row data, or custom render function for the actions column */\n actions?: DataGridActionItem<T>[] | ((row: T, index: number) => DataGridActionItem<T>[] | ReactNode);\n /** Label for the actions column header */\n actionsLabel?: string;\n\n // Row link - makes entire row clickable for navigation\n /** Function returning href for row navigation. When provided, entire row becomes a link. */\n rowHref?: (row: T, index: number) => string | undefined;\n\n // Callbacks\n /** Callback when a row is clicked (if rowHref not provided) */\n onRowClick?: (row: T, index: number) => void;\n /** Callback when row selection changes (provides array of selected row indices) */\n onSelectionChange?: (selectedIndices: number[]) => void;\n\n // Empty state\n emptyState?: {\n /** Message to display when there is no data */\n title?: string;\n /** Description to display when there is no data */\n description?: string;\n /** Text for the button in the empty state */\n buttonText?: string;\n /** Click handler for the button in the empty state */\n onButtonClick?: () => void;\n /** React Router path — renders the empty state button as a <Link>. Use instead of onButtonClick for in-app navigation. */\n buttonTo?: string;\n /** Plain URL — renders the empty state button as an <a> tag. Use for external links. */\n buttonHref?: string;\n /** Link target (e.g. \"_blank\") — only used with buttonTo or buttonHref */\n buttonTarget?: ButtonProps['target'];\n /** Rel attribute for the anchor (defaults to \"noopener noreferrer\" when buttonTarget=\"_blank\") */\n buttonRel?: string;\n };\n\n // Initial state\n /** Initial sort configuration */\n initialSort?: { key: keyof T; direction: 'asc' | 'desc' };\n /** Title displayed above the grid (left side of header) */\n title?: string;\n /** Header actions displayed above the grid (right side of header). */\n headerActions?: HeaderAction[];\n\n // Responsive / list mode\n /**\n * Container width (px) below which the grid collapses into list mode.\n * Defaults to 480. Column hiding (via `hidePriority` / `minWidth`) still\n * runs for widths above this breakpoint.\n */\n listBreakpoint?: number;\n /**\n * Custom render function for list mode. When provided, each row is rendered\n * using this function instead of the default vertical-stack fallback.\n * Use this to create type-specific list layouts with the right fields,\n * actions, and visual hierarchy for the entity being displayed.\n *\n * @example\n * ```tsx\n * renderListItem={(row, index, { actions, isSelected, onToggleSelect }) => (\n * <div className=\"flex items-center gap-3\">\n * <Avatar name={row.name} />\n * <div className=\"flex-1 min-w-0\">\n * <Text weight=\"medium\" className=\"truncate\">{row.name}</Text>\n * <Text size=\"sm\" variant=\"muted\" className=\"truncate\">{row.email}</Text>\n * </div>\n * {actions}\n * </div>\n * )}\n * ```\n */\n renderListItem?: (row: T, index: number, context: DataGridListItemContext) => ReactNode;\n};\n\nexport type HeaderAction = {\n /** Button text; if omitted and `icon` provided, button will be icon-only */\n label?: string;\n /** Icon name to render inside the button (maps to library `Icon`) */\n icon?: string;\n /** Accessible label for screen readers. Required for icon-only buttons (when label is omitted). */\n ariaLabel?: string;\n /** Click handler invoked when the button is clicked */\n onClick?: () => void;\n /** React Router path — renders the button as a <Link>. Use instead of onClick for in-app navigation. */\n to?: string;\n /** Plain URL — renders the button as an <a> tag. Use for external links. */\n href?: string;\n /** Link target (e.g. \"_blank\") — only used with to or href */\n target?: ButtonProps['target'];\n /** Rel attribute for the anchor (defaults to \"noopener noreferrer\" when target=\"_blank\") */\n rel?: string;\n /** Button variant (maps to Button `variant` prop) */\n variant?: ButtonProps['variant'];\n /** Button style (maps to Button `style` prop) */\n style?: ButtonProps['style'];\n /** Button size (maps to Button `size` prop) */\n size?: ButtonProps['size'];\n /** Additional className forwarded to the Button */\n className?: string;\n};\n\n/** Breakpoint width (in px) below which we switch to list mode */\nconst DEFAULT_LIST_BREAKPOINT = 480;\n\n/**\n * Context passed to `renderListItem` so list-mode renders can embed grid actions\n * and selection state without needing to manage them independently.\n */\nexport type DataGridListItemContext = {\n /** Pre-rendered action buttons derived from the `actions` prop */\n actions: ReactNode;\n /** Whether this row is currently selected */\n isSelected: boolean;\n /** Toggle selection for this row. Defined only when `selectable` is true. */\n onToggleSelect?: () => void;\n};\n\n/**\n * DataGrid - Responsive data display component with Vercel-style layout\n *\n * Features:\n * - Generic typed data and columns (same API as DataTable)\n * - Responsive layout: horizontal rows on desktop, vertical card-like on mobile\n * - Clickable rows via rowHref with interactive elements taking priority\n * - Client-side sorting, pagination, selection, and search\n * - Custom cell rendering with DataGridCell for multi-element stacking\n *\n * @example\n * ```tsx\n * <DataGrid\n * data={deployments}\n * columns={[\n * {\n * key: 'name',\n * label: 'Deployment',\n * render: (value, row) => (\n * <DataGridCell>\n * <Text weight=\"medium\">{row.name}</Text>\n * <Text size=\"sm\" variant=\"muted\">{row.url}</Text>\n * </DataGridCell>\n * )\n * },\n * { key: 'status', label: 'Status', badgeMap: { ready: { variant: 'success' } } },\n * { key: 'createdAt', label: 'Created', format: 'date' }\n * ]}\n * rowHref={(row) => `/deployments/${row.id}`}\n * hoverable\n * />\n * ```\n */\nfunction DataGrid<T>({\n data,\n columns,\n className = '',\n striped = false,\n hoverable = true,\n compact = false,\n sortable = true,\n paginated = false,\n pageSize = 10,\n selectable = false,\n searchable = false,\n searchKeys,\n searchPlaceholder = 'Search...',\n onSearchChange,\n actions,\n actionsLabel = 'Actions',\n rowHref,\n onRowClick,\n onSelectionChange,\n emptyState = {\n title: 'No data available',\n description: undefined,\n buttonText: undefined,\n onButtonClick: undefined,\n buttonTo: undefined,\n buttonHref: undefined,\n buttonTarget: undefined,\n buttonRel: undefined\n },\n headerActions,\n title,\n initialSort,\n listBreakpoint = DEFAULT_LIST_BREAKPOINT,\n renderListItem\n}: DataGridProps<T>) {\n // Search state\n const [searchQuery, setSearchQuery] = useState('');\n\n // Layout state – true when container width is below listBreakpoint\n const [isListMode, setIsListMode] = useState(false);\n\n const handleSearchChange = (value: string) => {\n setSearchQuery(value);\n if (onSearchChange) {\n onSearchChange(value);\n }\n };\n\n // Container ref for responsive behavior\n const containerRef = useRef<HTMLDivElement>(null);\n const [visibleColumns, setVisibleColumns] = useState<DataGridColumnDef<T>[]>(() => columns);\n\n // Calculate minimum width needed for actions column based on max actions in any row\n const calculateActionsMinWidth = (): number => {\n if (!actions) return 0;\n\n let maxActionCount = 0;\n\n if (Array.isArray(actions)) {\n // Static actions - all rows have the same actions\n maxActionCount = actions.length;\n } else {\n // Dynamic actions - need to check all rows\n data.forEach((row, index) => {\n const result = actions(row, index);\n if (Array.isArray(result)) {\n maxActionCount = Math.max(maxActionCount, result.length);\n } else {\n // Custom render - assume it needs reasonable space (equivalent to 2 actions)\n maxActionCount = Math.max(maxActionCount, 2);\n }\n });\n }\n\n if (maxActionCount === 0) {\n // No actions, but we still need some minimum space in case actions appear\n return 60;\n }\n\n // Each action button takes ~36px (icon + button padding)\n // Stack spacing between actions is 8px (spacing=\"sm\")\n // Formula: (actionWidth * count) + (gap * (count - 1)) + extraPadding\n const actionWidth = 24;\n const gapWidth = 8;\n const extraPadding = 16; // breathing room\n\n return actionWidth * maxActionCount + gapWidth * Math.max(0, maxActionCount - 1) + extraPadding;\n };\n\n const actionsMinWidth = calculateActionsMinWidth();\n\n // ResizeObserver to handle responsive column hiding and mobile layout\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n // Check if any columns have hidePriority or minWidth set (both make columns hideable)\n const hasHideableColumns = columns.some((col) => col.hidePriority !== undefined || col.minWidth !== undefined);\n\n const updateLayout = () => {\n const containerWidth = container.offsetWidth;\n\n // Check if we should switch to list mode\n const nowListMode = containerWidth < listBreakpoint;\n setIsListMode(nowListMode);\n\n // In list mode, show all columns (either renderListItem handles layout\n // or the fallback vertical stack renders them all with labels).\n // Only apply responsive column hiding in grid mode.\n if (nowListMode || !hasHideableColumns) {\n setVisibleColumns(columns);\n return;\n }\n\n // Columns are hideable if they have hidePriority OR minWidth\n const isHideable = (col: DataGridColumnDef<T>) => col.hidePriority !== undefined || col.minWidth !== undefined;\n\n // Separate columns into hideable and non-hideable\n const hideableColumns = columns\n .map((col, index) => ({ col, originalIndex: index }))\n .filter(({ col }) => isHideable(col))\n .sort((a, b) => {\n // Sort by hidePriority if both have it (lower hides first)\n const aPriority = a.col.hidePriority;\n const bPriority = b.col.hidePriority;\n\n if (aPriority !== undefined && bPriority !== undefined) {\n if (aPriority !== bPriority) return aPriority - bPriority;\n }\n // Columns with explicit hidePriority hide before minWidth-only columns\n if (aPriority !== undefined && bPriority === undefined) return -1;\n if (aPriority === undefined && bPriority !== undefined) return 1;\n\n // For minWidth-only columns, larger minWidth hides first (needs more space)\n const aMin = a.col.minWidth ?? 0;\n const bMin = b.col.minWidth ?? 0;\n if (aMin !== bMin) return bMin - aMin;\n\n // Finally, later columns hide first (preserve leading columns)\n return b.originalIndex - a.originalIndex;\n });\n\n const nonHideableColumns = columns.filter((col) => !isHideable(col));\n\n // Estimate column width - use minWidth if set, otherwise parse width or default\n const estimateColumnWidth = (col: DataGridColumnDef<T>): number => {\n // Use minWidth if explicitly set (this is the minimum space the column needs)\n if (col.minWidth) return col.minWidth;\n\n if (col.width) {\n const match = col.width.match(/(\\d+)/);\n if (match) {\n const num = parseInt(match[1], 10);\n if (col.width.includes('px')) return num;\n return num * 4;\n }\n }\n return 150;\n };\n\n // Calculate horizontal padding (px-3 = 24px total, px-4 = 32px total)\n const horizontalPadding = compact ? 24 : 32;\n // Grid gap is 16px (gap-4) between each column\n const gridGap = 16;\n\n // Start with non-hideable columns\n let currentWidth = nonHideableColumns.reduce((sum, col) => sum + estimateColumnWidth(col), 0);\n let totalColumns = nonHideableColumns.length;\n\n // Account for selection and actions columns\n if (selectable) {\n currentWidth += 48;\n totalColumns += 1;\n }\n if (actions) {\n currentWidth += actionsMinWidth;\n totalColumns += 1;\n }\n\n // Add hideable columns until we run out of space\n const columnsToShow: DataGridColumnDef<T>[] = [...nonHideableColumns];\n\n for (const { col } of hideableColumns) {\n const colWidth = estimateColumnWidth(col);\n const newTotalColumns = totalColumns + 1;\n // Calculate total gaps needed with the new column (N columns = N-1 gaps)\n const totalGaps = Math.max(0, newTotalColumns - 1) * gridGap;\n const totalWidthNeeded = currentWidth + colWidth + totalGaps + horizontalPadding;\n\n if (totalWidthNeeded <= containerWidth) {\n columnsToShow.push(col);\n currentWidth += colWidth;\n totalColumns = newTotalColumns;\n }\n }\n\n // Restore original column order\n const orderedVisibleColumns = columns.filter((col) => columnsToShow.includes(col));\n setVisibleColumns(orderedVisibleColumns);\n };\n\n // Call once on mount\n updateLayout();\n\n // Set up observer\n const observer = new ResizeObserver(() => {\n updateLayout();\n });\n\n observer.observe(container);\n\n return () => {\n observer.disconnect();\n };\n }, [columns, selectable, actions, actionsMinWidth, compact, data, listBreakpoint]);\n\n // Use table hook for state management\n const effectiveSearchKeys = searchKeys || visibleColumns.map((col) => col.key);\n\n const tableOptions: UseTableOptions<T> = {\n data,\n initialSort,\n pageSize,\n searchQuery,\n searchKeys: effectiveSearchKeys\n };\n\n const {\n currentData,\n sortedData,\n filteredData,\n sortConfig,\n handleSort,\n currentPage,\n totalPages,\n nextPage,\n prevPage,\n canNextPage,\n canPrevPage,\n selectedRows,\n toggleRow,\n toggleAll,\n isRowSelected,\n isAllSelected\n } = useTable(tableOptions);\n\n // Display data (paginated uses currentData, non-paginated uses sortedData)\n const displayData = paginated ? currentData : sortedData;\n\n // Header title and actions\n const hasHeader = Boolean(\n (title && String(title).length > 0) ||\n (Array.isArray(headerActions) ? headerActions.length > 0 : headerActions) ||\n searchable\n );\n\n // Selection handlers\n const handleToggleRow = (index: number) => {\n toggleRow(index);\n if (onSelectionChange) {\n const newSelection = new Set(selectedRows);\n if (newSelection.has(index)) {\n newSelection.delete(index);\n } else {\n newSelection.add(index);\n }\n onSelectionChange(Array.from(newSelection));\n }\n };\n\n const handleToggleAll = () => {\n toggleAll();\n if (onSelectionChange) {\n if (isAllSelected) {\n onSelectionChange([]);\n } else {\n const startIndex = (currentPage - 1) * pageSize;\n const allIndices = displayData.map((_, idx) => startIndex + idx);\n onSelectionChange(allIndices);\n }\n }\n };\n\n // Format value based on column configuration\n const formatValue = (value: unknown, column: DataGridColumnDef<T>): string => {\n if (value == null) return '';\n\n switch (column.format) {\n case 'date': {\n const dateValue = value instanceof Date ? value : new Date(value as string | number);\n return new Intl.DateTimeFormat('en-US', column.formatOptions as Intl.DateTimeFormatOptions).format(dateValue);\n }\n\n case 'currency':\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n ...column.formatOptions\n } as Intl.NumberFormatOptions).format(Number(value));\n\n case 'number':\n return new Intl.NumberFormat('en-US', column.formatOptions as Intl.NumberFormatOptions).format(Number(value));\n\n case 'percent':\n return new Intl.NumberFormat('en-US', {\n style: 'percent',\n ...column.formatOptions\n } as Intl.NumberFormatOptions).format(Number(value));\n\n default:\n return String(value);\n }\n };\n\n const renderCellContent = (value: unknown, column: DataGridColumnDef<T>, row: T, rowIndex: number): ReactNode => {\n const isEmpty = value == null || value === '';\n\n if (isEmpty && column.fallback !== undefined) {\n return column.fallback;\n }\n\n if (column.render) {\n return column.render(value as T[keyof T], row, rowIndex);\n }\n\n if (column.badgeMap) {\n const stringValue = String(value);\n const badgeConfig = column.badgeMap[stringValue] || column.badgeMap['*'] || { variant: 'neutral' };\n\n return (\n <Badge variant={badgeConfig.variant || 'neutral'} size={column.componentSize || 'md'}>\n {badgeConfig.label || stringValue}\n </Badge>\n );\n }\n\n if (column.statusDotMap) {\n const stringValue = String(value);\n const dotConfig = column.statusDotMap[stringValue] || column.statusDotMap['*'] || { color: 'neutral' };\n\n return <StatusIndicator variant={dotConfig.color || 'neutral'}>{dotConfig.label || stringValue}</StatusIndicator>;\n }\n\n if (column.renderAsBadges) {\n if (Array.isArray(value)) {\n if (value.length === 0 && column.fallback !== undefined) {\n return column.fallback;\n }\n\n return (\n <Stack direction=\"horizontal\" spacing=\"xs\" wrap>\n {value.map((item, idx) => (\n <Badge key={idx} variant={column.badgeVariant || 'neutral'} size={column.componentSize || 'md'}>\n {String(item)}\n </Badge>\n ))}\n </Stack>\n );\n } else {\n return (\n <Badge variant={column.badgeVariant || 'neutral'} size={column.componentSize || 'md'}>\n {String(value)}\n </Badge>\n );\n }\n }\n\n if (column.format) {\n return formatValue(value, column);\n }\n\n return String(value ?? '');\n };\n\n // Render sort indicator\n const renderSortIcon = (columnKey: keyof T) => {\n if (!sortable) return null;\n\n const isSorted = sortConfig?.key === columnKey;\n\n if (!isSorted) {\n return <Icon name=\"selector\" size=\"xs\" color=\"neutral\" />;\n }\n\n if (sortConfig?.direction === 'asc') {\n return <Icon name=\"chevron-up\" size=\"xs\" color=\"primary\" />;\n }\n\n return <Icon name=\"chevron-down\" size=\"xs\" color=\"primary\" />;\n };\n\n // Render actions for a row\n const renderActions = (row: T, actualIndex: number) => {\n if (!actions) return null;\n\n let rowActions: DataGridActionItem<T>[] | ReactNode;\n\n if (Array.isArray(actions)) {\n rowActions = actions;\n } else {\n const result = actions(row, actualIndex);\n if (Array.isArray(result)) {\n rowActions = result;\n } else {\n return <div data-interactive>{result}</div>;\n }\n }\n\n return (\n <Stack direction=\"horizontal\" spacing=\"sm\" data-interactive>\n {(rowActions as DataGridActionItem<T>[]).map((action, actionIndex) => {\n if (action && typeof action === 'object' && 'onClick' in action) {\n const actionConfig = action as DataGridAction<T>;\n const button = (\n <IconButton\n key={actionIndex}\n icon={actionConfig.icon}\n iconSize={actionConfig.iconSize || 'md'}\n buttonStyle=\"ghost\"\n variant={actionConfig.variant || 'neutral'}\n iconColor={actionConfig.iconColor}\n hoverIcon={actionConfig.hoverIcon}\n ariaLabel={actionConfig.label}\n onClick={() => actionConfig.onClick(row, actualIndex)}\n noPadding\n />\n );\n\n return actionConfig.tooltip ? (\n <Tooltip key={actionIndex} content={actionConfig.tooltip} triggerClassName=\"inline-flex items-center\">\n {button}\n </Tooltip>\n ) : (\n button\n );\n } else {\n return (\n <div key={actionIndex} data-interactive>\n {action as ReactNode}\n </div>\n );\n }\n })}\n </Stack>\n );\n };\n\n // Row padding classes\n const rowPadding = compact ? 'py-2 px-3' : 'py-3 px-4';\n\n // Build grid template columns for desktop layout\n const buildGridTemplateColumns = () => {\n const parts: string[] = [];\n\n // Selection checkbox column - fixed width for consistency across rows\n if (selectable) {\n parts.push('24px');\n }\n\n // Data columns\n visibleColumns.forEach((col) => {\n if (col.width) {\n // Explicit width (Tailwind class won't work in grid-template-columns)\n // Try to extract pixel value or use minmax\n const widthMatch = col.width.match(/(\\d+)px/);\n if (widthMatch) {\n parts.push(`${widthMatch[1]}px`);\n } else {\n parts.push('1fr');\n }\n } else if (col.minWidth && col.maxWidth) {\n // Both min and max - constrained range\n parts.push(`minmax(${col.minWidth}px, ${col.maxWidth}px)`);\n } else if (col.minWidth) {\n // Min only - can grow but not shrink below\n parts.push(`minmax(${col.minWidth}px, 1fr)`);\n } else if (col.maxWidth) {\n // Max only - can shrink but not grow beyond\n parts.push(`minmax(auto, ${col.maxWidth}px)`);\n } else {\n parts.push('1fr');\n }\n });\n\n // Actions column - dynamically sized based on max actions across all rows\n // Allows growing to max-content to accommodate multiple action buttons\n if (actions) {\n parts.push(`minmax(${actionsMinWidth}px, max-content)`);\n }\n\n return parts.join(' ');\n };\n\n const gridTemplateColumns = !isListMode ? buildGridTemplateColumns() : undefined;\n\n // Render a single row\n const renderRow = (row: T, rowIndex: number) => {\n const actualIndex = paginated ? (currentPage - 1) * pageSize + rowIndex : rowIndex;\n const isSelected = isRowSelected(actualIndex);\n const href = rowHref?.(row, actualIndex);\n const hasHref = Boolean(href);\n const hasOnRowClick = Boolean(onRowClick);\n const isClickable = hasHref || hasOnRowClick;\n\n // Row styling - add relative for Link overlay positioning\n const rowClasses = [\n 'relative',\n 'border-b border-border/50',\n striped && rowIndex % 2 === 1 ? 'bg-muted/30' : '',\n hoverable && isClickable ? 'hover:bg-muted/50 transition-colors' : '',\n isSelected ? 'bg-primary/5' : ''\n ]\n .filter(Boolean)\n .join(' ');\n\n // Grid mode: CSS Grid for consistent column widths\n // List mode: vertical flex (fallback) or custom renderListItem\n const layoutClasses = isListMode ? 'flex flex-col gap-3' : 'grid items-center gap-4';\n\n // Grid style for grid mode only\n const rowStyle = !isListMode && gridTemplateColumns ? { gridTemplateColumns } : undefined;\n\n // ----------------------------------------------------------------\n // List mode with custom render – delegate layout to renderListItem\n // ----------------------------------------------------------------\n if (isListMode && renderListItem) {\n const actionsContent = renderActions(row, actualIndex);\n const listItemContent = renderListItem(row, actualIndex, {\n actions: actionsContent,\n isSelected,\n onToggleSelect: selectable ? () => handleToggleRow(actualIndex) : undefined\n });\n\n // Pointer-events wrapper mirrors the grid row behaviour\n const pointerClass = hasHref\n ? 'pointer-events-none [&_a]:pointer-events-auto [&_button]:pointer-events-auto [&_input]:pointer-events-auto [&_select]:pointer-events-auto [&_textarea]:pointer-events-auto [&_label]:pointer-events-auto [&_[data-interactive]]:pointer-events-auto'\n : '';\n\n const listRowClickProps =\n !hasHref && hasOnRowClick\n ? {\n onClick: (e: React.MouseEvent<HTMLDivElement>) => {\n if (isInteractiveElement(e.target as HTMLElement, e.currentTarget)) return;\n onRowClick?.(row, actualIndex);\n },\n onKeyDown: (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onRowClick?.(row, actualIndex);\n }\n },\n role: 'button' as const,\n tabIndex: 0,\n className: 'cursor-pointer'\n }\n : {};\n\n return (\n <div\n key={actualIndex}\n className={`relative ${rowPadding} ${rowClasses} ${listRowClickProps.className || ''}`}\n onClick={listRowClickProps.onClick}\n onKeyDown={listRowClickProps.onKeyDown}\n role={listRowClickProps.role}\n tabIndex={listRowClickProps.tabIndex}\n >\n {hasHref && href && (\n <Link to={href} className=\"absolute inset-0 z-1\" aria-label=\"View details\" tabIndex={0} />\n )}\n <div className={`relative z-10 ${pointerClass}`}>{listItemContent}</div>\n </div>\n );\n }\n\n // ----------------------------------------------------------------\n // Props for onRowClick (when no href) - handlers are on the row div\n // Uses isInteractiveElement check to avoid triggering on links/buttons\n const rowClickProps =\n !hasHref && hasOnRowClick\n ? {\n onClick: (e: React.MouseEvent<HTMLDivElement>) => {\n // Don't trigger if clicked on an interactive element (links, buttons, etc.)\n if (isInteractiveElement(e.target as HTMLElement, e.currentTarget)) {\n return;\n }\n onRowClick?.(row, actualIndex);\n },\n onKeyDown: (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onRowClick?.(row, actualIndex);\n }\n },\n role: 'button' as const,\n tabIndex: 0,\n className: 'cursor-pointer'\n }\n : {};\n\n return (\n <div\n key={actualIndex}\n className={`${layoutClasses} ${rowPadding} ${rowClasses} ${rowClickProps.className || ''}`}\n style={rowStyle}\n onClick={rowClickProps.onClick}\n onKeyDown={rowClickProps.onKeyDown}\n role={rowClickProps.role}\n tabIndex={rowClickProps.tabIndex}\n >\n {/* Row link overlay - positioned behind content */}\n {hasHref && href && (\n <Link to={href} className=\"absolute inset-0 z-1\" aria-label={`View details`} tabIndex={0} />\n )}\n\n {/* Selection checkbox - above link layer */}\n {selectable && (\n <div className={`relative z-10 ${isListMode ? '' : 'shrink-0'}`}>\n <Checkbox\n checked={isSelected}\n onChange={() => handleToggleRow(actualIndex)}\n ariaLabel={`Select row ${actualIndex + 1}`}\n />\n </div>\n )}\n\n {/* Data cells - pointer-events-none lets clicks pass through to link layer */}\n {/* Interactive elements inside (links, buttons) keep their default pointer-events */}\n {visibleColumns.map((column) => {\n const value = row[column.key];\n const content = renderCellContent(value, column, row, actualIndex);\n const showLabel = isListMode && column.showLabelOnMobile !== false;\n\n // Alignment\n const alignClass =\n column.align === 'center' ? 'text-center' : column.align === 'right' ? 'text-right' : 'text-left';\n\n // Only disable pointer events when we have a link overlay\n const pointerClass = hasHref\n ? 'pointer-events-none [&_a]:pointer-events-auto [&_button]:pointer-events-auto [&_input]:pointer-events-auto [&_select]:pointer-events-auto [&_textarea]:pointer-events-auto [&_label]:pointer-events-auto [&_[data-interactive]]:pointer-events-auto'\n : '';\n\n return (\n <div\n key={String(column.key)}\n className={`${alignClass} ${isListMode ? 'flex flex-col gap-0.5' : 'min-w-0 overflow-hidden'} ${pointerClass}`}\n >\n {showLabel && (\n <Text size=\"xs\" variant=\"muted\" className=\"uppercase tracking-wide\">\n {column.label}\n </Text>\n )}\n <div className={column.wrapText ? '' : 'truncate'}>{content}</div>\n </div>\n );\n })}\n\n {/* Actions - above link layer */}\n {actions && (\n <div className={`relative z-10 shrink-0 ${isListMode ? 'flex justify-end pt-1' : 'justify-self-end'}`}>\n {renderActions(row, actualIndex)}\n </div>\n )}\n </div>\n );\n };\n\n return (\n <div ref={containerRef} className={`overflow-hidden ${className}`}>\n {/* Header with title, search, actions */}\n {hasHeader && (\n <div className=\"mb-3\">\n {searchable ? (\n <div className=\"flex flex-col gap-3\">\n {title && <h2 className=\"text-lg font-semibold text-foreground\">{title}</h2>}\n <div className=\"flex flex-col sm:flex-row items-stretch sm:items-center gap-3 sm:justify-between\">\n <div className=\"flex-1 sm:max-w-md\">\n <InputGroup prefix={<Icon name=\"search\" size=\"sm\" />} className=\"w-full\">\n <Input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => handleSearchChange(e.target.value)}\n placeholder={searchPlaceholder}\n />\n </InputGroup>\n </div>\n\n {Array.isArray(headerActions) && headerActions.length > 0 && (\n <div className=\"flex items-center gap-2 flex-wrap sm:flex-nowrap ml-auto\">\n {headerActions.map((act, idx) => (\n <Button\n key={idx}\n onClick={act.onClick}\n to={act.to}\n href={act.href}\n target={act.target}\n rel={act.rel}\n variant={act.variant}\n style={act.style}\n size={act.size}\n className={act.className}\n ariaLabel={act.ariaLabel || (!act.label && act.icon ? 'Action' : undefined)}\n >\n {act.icon ? <Icon name={act.icon} size=\"sm\" /> : null}\n {act.label}\n </Button>\n ))}\n </div>\n )}\n </div>\n </div>\n ) : (\n <div className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3\">\n <div className=\"flex-1\">\n {title && <h2 className=\"text-lg font-semibold text-foreground\">{title}</h2>}\n </div>\n\n {Array.isArray(headerActions) && headerActions.length > 0 && (\n <div className=\"flex items-center gap-2 flex-wrap sm:flex-nowrap\">\n {headerActions.map((act, idx) => (\n <Button\n key={idx}\n onClick={act.onClick}\n to={act.to}\n href={act.href}\n target={act.target}\n rel={act.rel}\n variant={act.variant}\n style={act.style}\n size={act.size}\n className={act.className}\n ariaLabel={act.ariaLabel || (!act.label && act.icon ? 'Action' : undefined)}\n >\n {act.icon ? <Icon name={act.icon} size=\"sm\" /> : null}\n {act.label}\n </Button>\n ))}\n </div>\n )}\n </div>\n )}\n </div>\n )}\n\n {/* Empty states */}\n {data.length === 0 ? (\n <div className=\"border border-border rounded-lg\">\n <EmptyState\n title={emptyState.title}\n description={emptyState.description}\n buttonText={emptyState.buttonText}\n onButtonClick={emptyState.onButtonClick}\n buttonTo={emptyState.buttonTo}\n buttonHref={emptyState.buttonHref}\n buttonTarget={emptyState.buttonTarget}\n buttonRel={emptyState.buttonRel}\n />\n </div>\n ) : filteredData.length === 0 && searchQuery.trim() ? (\n <div className=\"border border-border rounded-lg\">\n <EmptyState\n title=\"No results found\"\n description={`No items match \"${searchQuery}\". Try adjusting your search.`}\n />\n </div>\n ) : (\n <>\n {/* Column headers – grid mode only (hidden in list mode) */}\n {!isListMode && (\n <div\n className={`grid items-center gap-4 ${rowPadding} border-b border-border bg-muted/30`}\n style={gridTemplateColumns ? { gridTemplateColumns } : undefined}\n >\n {selectable && (\n <div>\n <Checkbox checked={isAllSelected} onChange={handleToggleAll} ariaLabel=\"Select all rows\" />\n </div>\n )}\n\n {visibleColumns.map((column) => {\n const alignClass =\n column.align === 'center' ? 'text-center' : column.align === 'right' ? 'text-right' : 'text-left';\n\n return (\n <div key={String(column.key)} className={`min-w-0 ${alignClass}`}>\n {column.sortable !== false && sortable ? (\n <button\n onClick={() => handleSort(column.key)}\n className=\"flex items-center gap-1 hover:text-foreground transition-colors font-medium text-sm text-muted-foreground\"\n type=\"button\"\n >\n {column.label}\n {renderSortIcon(column.key)}\n </button>\n ) : (\n <Text size=\"sm\" variant=\"muted\" weight=\"medium\">\n {column.label}\n </Text>\n )}\n </div>\n );\n })}\n\n {actions && (\n <div className=\"justify-self-end\">\n <Text size=\"sm\" variant=\"muted\" weight=\"medium\">\n {actionsLabel}\n </Text>\n </div>\n )}\n </div>\n )}\n\n {/* Select all checkbox (list mode fallback only – renderListItem handles its own selection UI) */}\n {isListMode && !renderListItem && selectable && (\n <div className={`flex items-center gap-2 ${rowPadding} border-b border-border`}>\n <Checkbox checked={isAllSelected} onChange={handleToggleAll} ariaLabel=\"Select all rows\" />\n <Text size=\"sm\" variant=\"muted\">\n Select all\n </Text>\n </div>\n )}\n\n {/* Rows */}\n <div className=\"divide-y divide-border/50\">{displayData.map((row, idx) => renderRow(row, idx))}</div>\n\n {/* Pagination controls */}\n {paginated && totalPages > 1 && (\n <div className=\"flex items-center justify-between px-4 py-3 border-t border-border\">\n <div className=\"text-sm text-muted-foreground\">\n Page {currentPage} of {totalPages} ({filteredData.length} {searchQuery.trim() ? 'filtered' : 'total'}{' '}\n rows\n {searchQuery.trim() && data.length !== filteredData.length ? ` of ${data.length}` : ''})\n </div>\n <div className=\"flex gap-2\">\n <Button size=\"sm\" style=\"outline\" onClick={prevPage} disabled={!canPrevPage}>\n Previous\n </Button>\n <Button size=\"sm\" style=\"outline\" onClick={nextPage} disabled={!canNextPage}>\n Next\n </Button>\n </div>\n </div>\n )}\n </>\n )}\n </div>\n );\n}\n\nDataGrid.displayName = 'DataGrid';\n\nexport default DataGrid;\n"],"names":[],"mappings":"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"}
|
|
@@ -20,6 +20,8 @@ export type TooltipProps = {
|
|
|
20
20
|
showArrow?: boolean;
|
|
21
21
|
/** Max width of tooltip content in pixels */
|
|
22
22
|
maxWidth?: number;
|
|
23
|
+
/** Additional CSS classes for the trigger wrapper span. Defaults to 'inline-block'. Override with e.g. 'inline-flex items-center' inside flex containers to avoid layout disruption. */
|
|
24
|
+
triggerClassName?: string;
|
|
23
25
|
};
|
|
24
26
|
/**
|
|
25
27
|
* Tooltip - Industry-standard tooltip component
|
|
@@ -44,7 +46,7 @@ export type TooltipProps = {
|
|
|
44
46
|
* </Tooltip>
|
|
45
47
|
* ```
|
|
46
48
|
*/
|
|
47
|
-
declare function Tooltip({ children, content, position, variant, delayDuration, className, disabled, showArrow, maxWidth }: Readonly<TooltipProps>): import("react/jsx-runtime").JSX.Element;
|
|
49
|
+
declare function Tooltip({ children, content, position, variant, delayDuration, className, disabled, showArrow, maxWidth, triggerClassName }: Readonly<TooltipProps>): import("react/jsx-runtime").JSX.Element;
|
|
48
50
|
declare namespace Tooltip {
|
|
49
51
|
var displayName: string;
|
|
50
52
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooltip.d.ts","sourceRoot":"","sources":["../../../../src/components/feedback/tooltip/tooltip.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,SAAS,EAA4C,MAAM,OAAO,CAAC;AAEnF,OAAO,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AASrC,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAElE,MAAM,MAAM,YAAY,GAAG;IACzB,4DAA4D;IAC5D,QAAQ,EAAE,SAAS,CAAC;IACpB,2BAA2B;IAC3B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,mCAAmC;IACnC,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"tooltip.d.ts","sourceRoot":"","sources":["../../../../src/components/feedback/tooltip/tooltip.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,SAAS,EAA4C,MAAM,OAAO,CAAC;AAEnF,OAAO,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AASrC,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAElE,MAAM,MAAM,YAAY,GAAG;IACzB,4DAA4D;IAC5D,QAAQ,EAAE,SAAS,CAAC;IACpB,2BAA2B;IAC3B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,mCAAmC;IACnC,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wLAAwL;IACxL,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AA+CF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,iBAAS,OAAO,CAAC,EACf,QAAQ,EACR,OAAO,EACP,QAAgB,EAChB,OAAmB,EACnB,aAAmB,EACnB,SAAc,EACd,QAAgB,EAChB,SAAiB,EACjB,QAAc,EACd,gBAAiC,EAClC,EAAE,QAAQ,CAAC,YAAY,CAAC,2CAgLxB;kBA3LQ,OAAO;;;AA+LhB,eAAe,OAAO,CAAC"}
|
|
@@ -43,7 +43,8 @@ function Tooltip({
|
|
|
43
43
|
className = "",
|
|
44
44
|
disabled = false,
|
|
45
45
|
showArrow = false,
|
|
46
|
-
maxWidth = 300
|
|
46
|
+
maxWidth = 300,
|
|
47
|
+
triggerClassName = "inline-block"
|
|
47
48
|
}) {
|
|
48
49
|
const [isOpen, setIsOpen] = useState(false);
|
|
49
50
|
const [isPositioned, setIsPositioned] = useState(false);
|
|
@@ -160,7 +161,7 @@ function Tooltip({
|
|
|
160
161
|
onMouseLeave: handleMouseLeave,
|
|
161
162
|
onFocus: handleMouseEnter,
|
|
162
163
|
onBlur: handleMouseLeave,
|
|
163
|
-
className:
|
|
164
|
+
className: triggerClassName,
|
|
164
165
|
children
|
|
165
166
|
}
|
|
166
167
|
),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooltip.js","sources":["../../../../src/components/feedback/tooltip/tooltip.tsx"],"sourcesContent":["import React, { ReactNode, useState, useRef, useEffect, useCallback } from 'react';\nimport { createPortal } from 'react-dom';\nimport { ColorVariant } from '../..';\n\ntype ActiveTooltipController = {\n id: symbol;\n close: () => void;\n} | null;\n\nlet activeTooltipController: ActiveTooltipController = null;\n\nexport type TooltipPosition = 'top' | 'bottom' | 'left' | 'right';\n\nexport type TooltipProps = {\n /** The element that triggers the tooltip (child to wrap) */\n children: ReactNode;\n /** Tooltip text content */\n content: string | ReactNode;\n /** Position of the tooltip relative to the trigger element */\n position?: TooltipPosition;\n /** Color variant of the tooltip */\n variant?: ColorVariant;\n /** Delay before showing tooltip in milliseconds */\n delayDuration?: number;\n /** Additional CSS classes for the tooltip container */\n className?: string;\n /** Whether the tooltip is disabled */\n disabled?: boolean;\n /** Whether to show caret/arrow pointing to trigger element */\n showArrow?: boolean;\n /** Max width of tooltip content in pixels */\n maxWidth?: number;\n};\n\n/**\n * getVariantClasses - Returns Tailwind classes for a given color variant\n * Tooltips use a soft background with full-opacity foreground text\n */\nfunction getVariantClasses(variant: ColorVariant): string {\n const variants: Record<ColorVariant, string> = {\n neutral: 'bg-neutral/95 text-neutral-foreground',\n primary: 'bg-primary/95 text-primary-foreground',\n accent: 'bg-accent/95 text-accent-foreground',\n info: 'bg-info/95 text-info-foreground',\n success: 'bg-success/95 text-success-foreground',\n warning: 'bg-warning/95 text-warning-foreground',\n error: 'bg-error/95 text-error-foreground'\n };\n return variants[variant] || variants.neutral;\n}\n\n/**\n * getArrowClasses - Returns Tailwind classes for tooltip arrow based on position and variant\n */\nfunction getArrowClasses(position: TooltipPosition, variant: ColorVariant): string {\n const arrowSize = 'w-2 h-2';\n const baseClasses = `${arrowSize} absolute`;\n\n // Map variant to background color for arrow\n const bgColorMap: Record<ColorVariant, string> = {\n neutral: 'bg-neutral/95',\n primary: 'bg-primary/95',\n accent: 'bg-accent/95',\n info: 'bg-info/95',\n success: 'bg-success/95',\n warning: 'bg-warning/95',\n error: 'bg-error/95'\n };\n\n const positionClasses: Record<TooltipPosition, string> = {\n top: '-bottom-1 left-1/2 -translate-x-1/2 rotate-45',\n bottom: '-top-1 left-1/2 -translate-x-1/2 rotate-45',\n left: '-right-1 top-1/2 -translate-y-1/2 rotate-45',\n right: '-left-1 top-1/2 -translate-y-1/2 rotate-45'\n };\n\n return `${baseClasses} ${positionClasses[position]} ${bgColorMap[variant]}`;\n}\n\n/**\n * Tooltip - Industry-standard tooltip component\n *\n * Displays a tooltip message on hover over a trigger element.\n * Uses portal rendering to escape overflow constraints. Only one\n * tooltip is shown at a time globally.\n *\n * @example\n * ```tsx\n * <Tooltip content=\"Hello world\">\n * <Button>Hover me</Button>\n * </Tooltip>\n *\n * <Tooltip\n * content=\"Success!\"\n * position=\"right\"\n * variant=\"success\"\n * delayDuration={100}\n * >\n * <IconButton>✓</IconButton>\n * </Tooltip>\n * ```\n */\nfunction Tooltip({\n children,\n content,\n position = 'top',\n variant = 'neutral',\n delayDuration = 200,\n className = '',\n disabled = false,\n showArrow = false,\n maxWidth = 300\n}: Readonly<TooltipProps>) {\n const [isOpen, setIsOpen] = useState(false);\n const [isPositioned, setIsPositioned] = useState(false);\n const [coords, setCoords] = useState({ top: 0, left: 0 });\n const triggerRef = useRef<HTMLDivElement>(null);\n const tooltipRef = useRef<HTMLDivElement>(null);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const controllerIdRef = useRef(Symbol('tooltip'));\n\n const calculatePosition = useCallback(() => {\n if (!triggerRef.current || !tooltipRef.current || disabled) return;\n\n const triggerRect = triggerRef.current.getBoundingClientRect();\n const tooltipRect = tooltipRef.current.getBoundingClientRect();\n const offset = 8;\n const arrowOffset = showArrow ? 4 : 0;\n\n let top = 0;\n let left = 0;\n\n switch (position) {\n case 'top':\n top = triggerRect.top - tooltipRect.height - offset - arrowOffset;\n left = triggerRect.left + triggerRect.width / 2 - tooltipRect.width / 2;\n break;\n case 'bottom':\n top = triggerRect.bottom + offset + arrowOffset;\n left = triggerRect.left + triggerRect.width / 2 - tooltipRect.width / 2;\n break;\n case 'left':\n top = triggerRect.top + triggerRect.height / 2 - tooltipRect.height / 2;\n left = triggerRect.left - tooltipRect.width - offset - arrowOffset;\n break;\n case 'right':\n top = triggerRect.top + triggerRect.height / 2 - tooltipRect.height / 2;\n left = triggerRect.right + offset + arrowOffset;\n break;\n }\n\n // Viewport boundary clamping\n const padding = 8;\n if (left < padding) left = padding;\n if (left + tooltipRect.width > window.innerWidth - padding) {\n left = window.innerWidth - tooltipRect.width - padding;\n }\n if (top < padding) top = padding;\n if (top + tooltipRect.height > window.innerHeight - padding) {\n top = window.innerHeight - tooltipRect.height - padding;\n }\n\n setCoords({ top, left });\n setIsPositioned(true);\n }, [position, disabled, showArrow]);\n\n const handleMouseEnter = useCallback(() => {\n if (disabled) return;\n\n // Close any active tooltip\n if (activeTooltipController && activeTooltipController.id !== controllerIdRef.current) {\n activeTooltipController.close();\n }\n\n // Clear any pending timeout\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n // Set new timeout to show tooltip\n timeoutRef.current = setTimeout(() => {\n setIsOpen(true);\n activeTooltipController = {\n id: controllerIdRef.current,\n close: () => {\n setIsOpen(false);\n activeTooltipController = null;\n }\n };\n }, delayDuration);\n }, [disabled, delayDuration]);\n\n const handleMouseLeave = useCallback(() => {\n // Clear pending timeout\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n setIsOpen(false);\n setIsPositioned(false);\n\n // Clear active tooltip controller if this is the active one\n if (activeTooltipController?.id === controllerIdRef.current) {\n activeTooltipController = null;\n }\n }, []);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (e.key === 'Escape' && isOpen) {\n handleMouseLeave();\n }\n },\n [isOpen, handleMouseLeave]\n );\n\n // Position tooltip when it opens\n useEffect(() => {\n if (!isOpen || !tooltipRef.current) return;\n\n requestAnimationFrame(() => {\n calculatePosition();\n });\n\n document.addEventListener('keydown', handleKeyDown);\n window.addEventListener('scroll', calculatePosition, true);\n window.addEventListener('resize', calculatePosition);\n\n return () => {\n document.removeEventListener('keydown', handleKeyDown);\n window.removeEventListener('scroll', calculatePosition, true);\n window.removeEventListener('resize', calculatePosition);\n };\n }, [isOpen, calculatePosition, handleKeyDown]);\n\n // Cleanup timeout on unmount\n useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n if (activeTooltipController?.id === controllerIdRef.current) {\n activeTooltipController = null;\n }\n };\n }, []);\n\n return (\n <>\n <span\n ref={triggerRef}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n onFocus={handleMouseEnter}\n onBlur={handleMouseLeave}\n className=\"inline-block\"\n >\n {children}\n </span>\n\n {isOpen &&\n createPortal(\n <div\n ref={tooltipRef}\n role=\"tooltip\"\n style={\n {\n position: 'fixed',\n top: `${coords.top}px`,\n left: `${coords.left}px`,\n maxWidth: `${maxWidth}px`,\n visibility: isPositioned ? 'visible' : 'hidden'\n } as React.CSSProperties\n }\n className={`\n rounded-md px-3 py-2 text-sm font-medium shadow-lg z-50\n pointer-events-none\n ${getVariantClasses(variant)}\n ${className}\n `}\n >\n {content}\n {showArrow && <div className={getArrowClasses(position, variant)} />}\n </div>,\n document.body\n )}\n </>\n );\n}\n\nTooltip.displayName = 'Tooltip';\n\nexport default Tooltip;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tooltip.js","sources":["../../../../src/components/feedback/tooltip/tooltip.tsx"],"sourcesContent":["import React, { ReactNode, useState, useRef, useEffect, useCallback } from 'react';\nimport { createPortal } from 'react-dom';\nimport { ColorVariant } from '../..';\n\ntype ActiveTooltipController = {\n id: symbol;\n close: () => void;\n} | null;\n\nlet activeTooltipController: ActiveTooltipController = null;\n\nexport type TooltipPosition = 'top' | 'bottom' | 'left' | 'right';\n\nexport type TooltipProps = {\n /** The element that triggers the tooltip (child to wrap) */\n children: ReactNode;\n /** Tooltip text content */\n content: string | ReactNode;\n /** Position of the tooltip relative to the trigger element */\n position?: TooltipPosition;\n /** Color variant of the tooltip */\n variant?: ColorVariant;\n /** Delay before showing tooltip in milliseconds */\n delayDuration?: number;\n /** Additional CSS classes for the tooltip container */\n className?: string;\n /** Whether the tooltip is disabled */\n disabled?: boolean;\n /** Whether to show caret/arrow pointing to trigger element */\n showArrow?: boolean;\n /** Max width of tooltip content in pixels */\n maxWidth?: number;\n /** Additional CSS classes for the trigger wrapper span. Defaults to 'inline-block'. Override with e.g. 'inline-flex items-center' inside flex containers to avoid layout disruption. */\n triggerClassName?: string;\n};\n\n/**\n * getVariantClasses - Returns Tailwind classes for a given color variant\n * Tooltips use a soft background with full-opacity foreground text\n */\nfunction getVariantClasses(variant: ColorVariant): string {\n const variants: Record<ColorVariant, string> = {\n neutral: 'bg-neutral/95 text-neutral-foreground',\n primary: 'bg-primary/95 text-primary-foreground',\n accent: 'bg-accent/95 text-accent-foreground',\n info: 'bg-info/95 text-info-foreground',\n success: 'bg-success/95 text-success-foreground',\n warning: 'bg-warning/95 text-warning-foreground',\n error: 'bg-error/95 text-error-foreground'\n };\n return variants[variant] || variants.neutral;\n}\n\n/**\n * getArrowClasses - Returns Tailwind classes for tooltip arrow based on position and variant\n */\nfunction getArrowClasses(position: TooltipPosition, variant: ColorVariant): string {\n const arrowSize = 'w-2 h-2';\n const baseClasses = `${arrowSize} absolute`;\n\n // Map variant to background color for arrow\n const bgColorMap: Record<ColorVariant, string> = {\n neutral: 'bg-neutral/95',\n primary: 'bg-primary/95',\n accent: 'bg-accent/95',\n info: 'bg-info/95',\n success: 'bg-success/95',\n warning: 'bg-warning/95',\n error: 'bg-error/95'\n };\n\n const positionClasses: Record<TooltipPosition, string> = {\n top: '-bottom-1 left-1/2 -translate-x-1/2 rotate-45',\n bottom: '-top-1 left-1/2 -translate-x-1/2 rotate-45',\n left: '-right-1 top-1/2 -translate-y-1/2 rotate-45',\n right: '-left-1 top-1/2 -translate-y-1/2 rotate-45'\n };\n\n return `${baseClasses} ${positionClasses[position]} ${bgColorMap[variant]}`;\n}\n\n/**\n * Tooltip - Industry-standard tooltip component\n *\n * Displays a tooltip message on hover over a trigger element.\n * Uses portal rendering to escape overflow constraints. Only one\n * tooltip is shown at a time globally.\n *\n * @example\n * ```tsx\n * <Tooltip content=\"Hello world\">\n * <Button>Hover me</Button>\n * </Tooltip>\n *\n * <Tooltip\n * content=\"Success!\"\n * position=\"right\"\n * variant=\"success\"\n * delayDuration={100}\n * >\n * <IconButton>✓</IconButton>\n * </Tooltip>\n * ```\n */\nfunction Tooltip({\n children,\n content,\n position = 'top',\n variant = 'neutral',\n delayDuration = 200,\n className = '',\n disabled = false,\n showArrow = false,\n maxWidth = 300,\n triggerClassName = 'inline-block'\n}: Readonly<TooltipProps>) {\n const [isOpen, setIsOpen] = useState(false);\n const [isPositioned, setIsPositioned] = useState(false);\n const [coords, setCoords] = useState({ top: 0, left: 0 });\n const triggerRef = useRef<HTMLDivElement>(null);\n const tooltipRef = useRef<HTMLDivElement>(null);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const controllerIdRef = useRef(Symbol('tooltip'));\n\n const calculatePosition = useCallback(() => {\n if (!triggerRef.current || !tooltipRef.current || disabled) return;\n\n const triggerRect = triggerRef.current.getBoundingClientRect();\n const tooltipRect = tooltipRef.current.getBoundingClientRect();\n const offset = 8;\n const arrowOffset = showArrow ? 4 : 0;\n\n let top = 0;\n let left = 0;\n\n switch (position) {\n case 'top':\n top = triggerRect.top - tooltipRect.height - offset - arrowOffset;\n left = triggerRect.left + triggerRect.width / 2 - tooltipRect.width / 2;\n break;\n case 'bottom':\n top = triggerRect.bottom + offset + arrowOffset;\n left = triggerRect.left + triggerRect.width / 2 - tooltipRect.width / 2;\n break;\n case 'left':\n top = triggerRect.top + triggerRect.height / 2 - tooltipRect.height / 2;\n left = triggerRect.left - tooltipRect.width - offset - arrowOffset;\n break;\n case 'right':\n top = triggerRect.top + triggerRect.height / 2 - tooltipRect.height / 2;\n left = triggerRect.right + offset + arrowOffset;\n break;\n }\n\n // Viewport boundary clamping\n const padding = 8;\n if (left < padding) left = padding;\n if (left + tooltipRect.width > window.innerWidth - padding) {\n left = window.innerWidth - tooltipRect.width - padding;\n }\n if (top < padding) top = padding;\n if (top + tooltipRect.height > window.innerHeight - padding) {\n top = window.innerHeight - tooltipRect.height - padding;\n }\n\n setCoords({ top, left });\n setIsPositioned(true);\n }, [position, disabled, showArrow]);\n\n const handleMouseEnter = useCallback(() => {\n if (disabled) return;\n\n // Close any active tooltip\n if (activeTooltipController && activeTooltipController.id !== controllerIdRef.current) {\n activeTooltipController.close();\n }\n\n // Clear any pending timeout\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n // Set new timeout to show tooltip\n timeoutRef.current = setTimeout(() => {\n setIsOpen(true);\n activeTooltipController = {\n id: controllerIdRef.current,\n close: () => {\n setIsOpen(false);\n activeTooltipController = null;\n }\n };\n }, delayDuration);\n }, [disabled, delayDuration]);\n\n const handleMouseLeave = useCallback(() => {\n // Clear pending timeout\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n setIsOpen(false);\n setIsPositioned(false);\n\n // Clear active tooltip controller if this is the active one\n if (activeTooltipController?.id === controllerIdRef.current) {\n activeTooltipController = null;\n }\n }, []);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (e.key === 'Escape' && isOpen) {\n handleMouseLeave();\n }\n },\n [isOpen, handleMouseLeave]\n );\n\n // Position tooltip when it opens\n useEffect(() => {\n if (!isOpen || !tooltipRef.current) return;\n\n requestAnimationFrame(() => {\n calculatePosition();\n });\n\n document.addEventListener('keydown', handleKeyDown);\n window.addEventListener('scroll', calculatePosition, true);\n window.addEventListener('resize', calculatePosition);\n\n return () => {\n document.removeEventListener('keydown', handleKeyDown);\n window.removeEventListener('scroll', calculatePosition, true);\n window.removeEventListener('resize', calculatePosition);\n };\n }, [isOpen, calculatePosition, handleKeyDown]);\n\n // Cleanup timeout on unmount\n useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n if (activeTooltipController?.id === controllerIdRef.current) {\n activeTooltipController = null;\n }\n };\n }, []);\n\n return (\n <>\n <span\n ref={triggerRef}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n onFocus={handleMouseEnter}\n onBlur={handleMouseLeave}\n className={triggerClassName}\n >\n {children}\n </span>\n\n {isOpen &&\n createPortal(\n <div\n ref={tooltipRef}\n role=\"tooltip\"\n style={\n {\n position: 'fixed',\n top: `${coords.top}px`,\n left: `${coords.left}px`,\n maxWidth: `${maxWidth}px`,\n visibility: isPositioned ? 'visible' : 'hidden'\n } as React.CSSProperties\n }\n className={`\n rounded-md px-3 py-2 text-sm font-medium shadow-lg z-50\n pointer-events-none\n ${getVariantClasses(variant)}\n ${className}\n `}\n >\n {content}\n {showArrow && <div className={getArrowClasses(position, variant)} />}\n </div>,\n document.body\n )}\n </>\n );\n}\n\nTooltip.displayName = 'Tooltip';\n\nexport default Tooltip;\n"],"names":[],"mappings":"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"}
|
|
@@ -28,8 +28,8 @@ export type ButtonProps = {
|
|
|
28
28
|
iconPosition?: 'left' | 'right';
|
|
29
29
|
/** Color variant affecting button appearance */
|
|
30
30
|
variant?: ColorVariant;
|
|
31
|
-
/** Visual style modifier - solid (filled), outline (bordered), ghost (transparent), link (text-only),
|
|
32
|
-
style?: 'solid' | 'outline' | 'ghost' | 'link' | 'soft';
|
|
31
|
+
/** Visual style modifier - solid (filled), outline (bordered), ghost (transparent), link (text-only), soft (subtle background), or none (no background at all) */
|
|
32
|
+
style?: 'solid' | 'outline' | 'ghost' | 'link' | 'soft' | 'none';
|
|
33
33
|
/** Size variant - uses unified size system (xs, sm, md, lg, xl) */
|
|
34
34
|
size?: InteractiveSize;
|
|
35
35
|
/** Border radius style - default (rounded), pill (fully rounded), square (sharp corners), circle (for icon-only buttons) */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/button/button.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,EAAoB,eAAe,EAAE,UAAU,EAAmB,MAAM,4BAA4B,CAAC;AAC5G,OAAO,EAAE,SAAS,EAAoC,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAElG,MAAM,MAAM,WAAW,GAAG;IACxB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,0BAA0B;IAC1B,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,GAAG,iBAAiB,CAAC,KAAK,IAAI,CAAC;IAC/E,gCAAgC;IAChC,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,GAAG,iBAAiB,CAAC,KAAK,IAAI,CAAC;IACpF,gCAAgC;IAChC,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,GAAG,iBAAiB,CAAC,KAAK,IAAI,CAAC;IACpF,+BAA+B;IAC/B,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,GAAG,iBAAiB,CAAC,KAAK,IAAI,CAAC;IACnF,yBAAyB;IACzB,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,GAAG,iBAAiB,CAAC,KAAK,IAAI,CAAC;IAC9E,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;IACrC,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,gDAAgD;IAChD,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,
|
|
1
|
+
{"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/button/button.tsx"],"names":[],"mappings":"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"}
|
|
@@ -50,6 +50,8 @@ const Button = React.forwardRef(
|
|
|
50
50
|
return colorVariants.link[variantKey];
|
|
51
51
|
case "soft":
|
|
52
52
|
return colorVariants.soft[variantKey];
|
|
53
|
+
case "none":
|
|
54
|
+
return colorVariants.none[variantKey];
|
|
53
55
|
case "solid":
|
|
54
56
|
default:
|
|
55
57
|
return `${colorVariants.solid[variantKey]} shadow-sm hover:shadow-md`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.js","sources":["../../../../src/components/forms/button/button.tsx"],"sourcesContent":["import React from 'react';\nimport { Link } from 'react-router-dom';\n\nimport { interactiveSizes, InteractiveSize, InputWidth, inputWidthSizes } from '../../../theme/size-tokens';\nimport { Alignment, colorVariants, inputAlignClasses, ColorVariant } from '../../../theme/tokens';\n\nexport type ButtonProps = {\n /** Button label content - text, icons, or other elements */\n children?: React.ReactNode;\n /** Click event handler */\n onClick?: (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;\n /** Mouse enter event handler */\n onMouseEnter?: (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;\n /** Mouse leave event handler */\n onMouseLeave?: (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;\n /** Mouse down event handler */\n onMouseDown?: (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;\n /** Blur event handler */\n onBlur?: (e: React.FocusEvent<HTMLButtonElement | HTMLAnchorElement>) => void;\n /** Accessible label for icon-only buttons (required when no children) */\n ariaLabel?: string;\n /** Disables button interaction and applies disabled styling */\n disabled?: boolean;\n /** HTML button type attribute (ignored when `to` or `href` is set) */\n type?: 'button' | 'submit' | 'reset';\n /** Additional CSS classes applied to the button */\n className?: string;\n /** Icon element to display alongside button text */\n icon?: React.ReactNode;\n /** Position of the icon relative to button text */\n iconPosition?: 'left' | 'right';\n /** Color variant affecting button appearance */\n variant?: ColorVariant;\n /** Visual style modifier - solid (filled), outline (bordered), ghost (transparent), link (text-only), or soft (subtle background) */\n style?: 'solid' | 'outline' | 'ghost' | 'link' | 'soft';\n /** Size variant - uses unified size system (xs, sm, md, lg, xl) */\n size?: InteractiveSize;\n /** Border radius style - default (rounded), pill (fully rounded), square (sharp corners), circle (for icon-only buttons) */\n rounded?: 'default' | 'pill' | 'square' | 'circle';\n /** Shows loading spinner and disables interaction */\n loading?: boolean;\n /** Width of the button */\n width?: InputWidth;\n /** When true, button spans full width of container (same as width=\"full\") */\n fullWidth?: boolean;\n /** When true, button uses wider min-width (good for prominent actions) */\n wide?: boolean;\n /** Aligns the button within its container: left, center, or right */\n align?: Alignment;\n /** Applies active/pressed state styling */\n active?: boolean;\n /** ARIA expanded state - indicates if controlled element is expanded */\n 'aria-expanded'?: boolean;\n /** ARIA haspopup - indicates element has popup menu/dialog */\n 'aria-haspopup'?: boolean | 'false' | 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';\n /** ARIA controls - ID of element controlled by this button */\n 'aria-controls'?: string;\n /**\n * React Router path. When provided the component renders as a `<Link>` (anchor tag)\n * instead of a `<button>`, giving you router-aware navigation with full button styling.\n */\n to?: string;\n /**\n * Plain URL. When provided (and `to` is not set) the component renders as an `<a>` tag.\n * Use `to` instead when navigating within the app so React Router manages the history.\n */\n href?: string;\n /** Link target attribute (`_blank`, `_self`, etc.) – only used when `to` or `href` is set */\n target?: React.HTMLAttributeAnchorTarget;\n /** Rel attribute for the anchor – defaults to `\"noreferrer noopener\"` when `target=\"_blank\"` */\n rel?: string;\n};\n\n/**\n * Accessible Button component\n * - Color variants: neutral, primary, accent, info, success, warning, error\n * - Style modifiers: solid (default), outline, ghost, link, soft\n * - Sizes: xs, sm, md (default), lg, xl\n * - Supports icons with flexible positioning (left/right)\n * - Icon-only buttons require `ariaLabel` for accessibility\n * - Pass `to` to render as a React Router `<Link>`, or `href` for a plain `<a>`, keeping full button styling\n */\nconst Button = React.forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(\n (\n {\n children,\n onClick,\n onMouseEnter,\n onMouseLeave,\n onMouseDown,\n onBlur,\n ariaLabel,\n disabled = false,\n type = 'button',\n className = '',\n icon,\n iconPosition = 'left',\n variant = 'neutral',\n style = 'solid',\n size = 'md',\n rounded = 'default',\n loading = false,\n width = 'auto',\n fullWidth = false,\n wide = false,\n align,\n active = false,\n 'aria-expanded': ariaExpanded,\n 'aria-haspopup': ariaHaspopup,\n 'aria-controls': ariaControls,\n to,\n href,\n target,\n rel\n },\n ref\n ) => {\n const isIconOnly = icon && !children;\n\n // Icon-only buttons MUST have an aria-label for accessibility\n if (isIconOnly && !ariaLabel) {\n // eslint-disable-next-line no-console\n console.warn('Button: Icon-only buttons require an ariaLabel for accessibility');\n }\n\n // Style modifiers\n const getStyleClasses = () => {\n const variantKey = variant as keyof typeof colorVariants.solid;\n\n switch (style) {\n case 'outline':\n return `${colorVariants.outline[variantKey]}`;\n case 'ghost':\n return colorVariants.ghost[variantKey];\n case 'link':\n return colorVariants.link[variantKey];\n case 'soft':\n return colorVariants.soft[variantKey];\n case 'solid':\n default:\n return `${colorVariants.solid[variantKey]} shadow-sm hover:shadow-md`;\n }\n };\n\n // Sizes - from unified size system\n const sizeConfig = interactiveSizes[size];\n const sizeClasses = `${sizeConfig.height} ${sizeConfig.padding} ${sizeConfig.text}`;\n\n const roundedClasses = {\n default: 'rounded-md',\n pill: 'rounded-full',\n square: 'rounded-none aspect-square',\n circle: 'rounded-full aspect-square'\n };\n\n // Show loading spinner or icon\n const displayIcon = loading ? (\n <svg className=\"animate-spin h-4 w-4\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\"></circle>\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n ></path>\n </svg>\n ) : (\n icon\n );\n\n const styleClasses = getStyleClasses();\n const effectiveWidth = fullWidth ? 'full' : wide ? 'xl' : width;\n const widthClasses = inputWidthSizes[effectiveWidth];\n const alignmentClass = align ? inputAlignClasses[align] : '';\n const activeClasses = active ? 'active:scale-95' : '';\n\n const shouldRenderIconSlots = !isIconOnly && Boolean(displayIcon);\n const cloneIcon = () => {\n if (!displayIcon) return null;\n if (React.isValidElement(displayIcon)) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return React.cloneElement(displayIcon as React.ReactElement<any>, { 'aria-hidden': false });\n }\n return displayIcon;\n };\n\n const showLeftIcon = shouldRenderIconSlots && iconPosition === 'left';\n const showRightIcon = shouldRenderIconSlots && iconPosition === 'right';\n\n // Check if className contains display utilities to avoid conflicts\n const hasDisplayOverride =\n className.includes('hidden') ||\n className.includes('inline') ||\n className.includes('block') ||\n className.includes('flex');\n const baseDisplayClass = hasDisplayOverride ? '' : 'inline-flex';\n\n const isDisabled = disabled || loading;\n\n const combinedClassName = `${baseDisplayClass} items-center justify-center ${alignmentClass} ${roundedClasses[rounded]} font-medium transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50 ${styleClasses} ${sizeClasses} ${\n isIconOnly ? 'p-0' : ''\n } ${widthClasses} ${activeClasses} ${isDisabled && (to || href) ? 'pointer-events-none opacity-50' : ''} ${className}`;\n\n const innerContent = (\n <>\n {showLeftIcon && <span className=\"flex shrink-0 items-center mr-2\">{cloneIcon()}</span>}\n {isIconOnly ? (\n <span className=\"inline-flex items-center justify-center\">{displayIcon}</span>\n ) : (\n <span className=\"inline-flex flex-1 justify-center text-center\">{children}</span>\n )}\n {showRightIcon && <span className=\"flex shrink-0 items-center ml-2\">{cloneIcon()}</span>}\n </>\n );\n\n const sharedLinkProps = {\n 'aria-label': ariaLabel,\n 'aria-expanded': ariaExpanded,\n 'aria-haspopup': ariaHaspopup,\n 'aria-controls': ariaControls,\n 'aria-disabled': isDisabled || undefined,\n tabIndex: isDisabled ? -1 : undefined,\n target,\n rel: rel ?? (target === '_blank' ? 'noopener noreferrer' : undefined),\n className: combinedClassName,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onClick: onClick as any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onMouseEnter: onMouseEnter as any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onMouseLeave: onMouseLeave as any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onMouseDown: onMouseDown as any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onBlur: onBlur as any\n };\n\n if (to) {\n return (\n <Link to={to} ref={ref as React.Ref<HTMLAnchorElement>} {...sharedLinkProps}>\n {innerContent}\n </Link>\n );\n }\n\n if (href) {\n return (\n <a href={href} ref={ref as React.Ref<HTMLAnchorElement>} {...sharedLinkProps}>\n {innerContent}\n </a>\n );\n }\n\n return (\n <button\n ref={ref as React.Ref<HTMLButtonElement>}\n type={type}\n onClick={onClick}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n onMouseDown={onMouseDown}\n onBlur={onBlur}\n aria-label={ariaLabel}\n aria-expanded={ariaExpanded}\n aria-haspopup={ariaHaspopup}\n aria-controls={ariaControls}\n disabled={isDisabled}\n className={combinedClassName}\n >\n {innerContent}\n </button>\n );\n }\n);\n\nButton.displayName = 'Button';\n\nexport default Button;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"button.js","sources":["../../../../src/components/forms/button/button.tsx"],"sourcesContent":["import React from 'react';\nimport { Link } from 'react-router-dom';\n\nimport { interactiveSizes, InteractiveSize, InputWidth, inputWidthSizes } from '../../../theme/size-tokens';\nimport { Alignment, colorVariants, inputAlignClasses, ColorVariant } from '../../../theme/tokens';\n\nexport type ButtonProps = {\n /** Button label content - text, icons, or other elements */\n children?: React.ReactNode;\n /** Click event handler */\n onClick?: (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;\n /** Mouse enter event handler */\n onMouseEnter?: (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;\n /** Mouse leave event handler */\n onMouseLeave?: (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;\n /** Mouse down event handler */\n onMouseDown?: (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;\n /** Blur event handler */\n onBlur?: (e: React.FocusEvent<HTMLButtonElement | HTMLAnchorElement>) => void;\n /** Accessible label for icon-only buttons (required when no children) */\n ariaLabel?: string;\n /** Disables button interaction and applies disabled styling */\n disabled?: boolean;\n /** HTML button type attribute (ignored when `to` or `href` is set) */\n type?: 'button' | 'submit' | 'reset';\n /** Additional CSS classes applied to the button */\n className?: string;\n /** Icon element to display alongside button text */\n icon?: React.ReactNode;\n /** Position of the icon relative to button text */\n iconPosition?: 'left' | 'right';\n /** Color variant affecting button appearance */\n variant?: ColorVariant;\n /** Visual style modifier - solid (filled), outline (bordered), ghost (transparent), link (text-only), soft (subtle background), or none (no background at all) */\n style?: 'solid' | 'outline' | 'ghost' | 'link' | 'soft' | 'none';\n /** Size variant - uses unified size system (xs, sm, md, lg, xl) */\n size?: InteractiveSize;\n /** Border radius style - default (rounded), pill (fully rounded), square (sharp corners), circle (for icon-only buttons) */\n rounded?: 'default' | 'pill' | 'square' | 'circle';\n /** Shows loading spinner and disables interaction */\n loading?: boolean;\n /** Width of the button */\n width?: InputWidth;\n /** When true, button spans full width of container (same as width=\"full\") */\n fullWidth?: boolean;\n /** When true, button uses wider min-width (good for prominent actions) */\n wide?: boolean;\n /** Aligns the button within its container: left, center, or right */\n align?: Alignment;\n /** Applies active/pressed state styling */\n active?: boolean;\n /** ARIA expanded state - indicates if controlled element is expanded */\n 'aria-expanded'?: boolean;\n /** ARIA haspopup - indicates element has popup menu/dialog */\n 'aria-haspopup'?: boolean | 'false' | 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';\n /** ARIA controls - ID of element controlled by this button */\n 'aria-controls'?: string;\n /**\n * React Router path. When provided the component renders as a `<Link>` (anchor tag)\n * instead of a `<button>`, giving you router-aware navigation with full button styling.\n */\n to?: string;\n /**\n * Plain URL. When provided (and `to` is not set) the component renders as an `<a>` tag.\n * Use `to` instead when navigating within the app so React Router manages the history.\n */\n href?: string;\n /** Link target attribute (`_blank`, `_self`, etc.) – only used when `to` or `href` is set */\n target?: React.HTMLAttributeAnchorTarget;\n /** Rel attribute for the anchor – defaults to `\"noreferrer noopener\"` when `target=\"_blank\"` */\n rel?: string;\n};\n\n/**\n * Accessible Button component\n * - Color variants: neutral, primary, accent, info, success, warning, error\n * - Style modifiers: solid (default), outline, ghost, link, soft\n * - Sizes: xs, sm, md (default), lg, xl\n * - Supports icons with flexible positioning (left/right)\n * - Icon-only buttons require `ariaLabel` for accessibility\n * - Pass `to` to render as a React Router `<Link>`, or `href` for a plain `<a>`, keeping full button styling\n */\nconst Button = React.forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(\n (\n {\n children,\n onClick,\n onMouseEnter,\n onMouseLeave,\n onMouseDown,\n onBlur,\n ariaLabel,\n disabled = false,\n type = 'button',\n className = '',\n icon,\n iconPosition = 'left',\n variant = 'neutral',\n style = 'solid',\n size = 'md',\n rounded = 'default',\n loading = false,\n width = 'auto',\n fullWidth = false,\n wide = false,\n align,\n active = false,\n 'aria-expanded': ariaExpanded,\n 'aria-haspopup': ariaHaspopup,\n 'aria-controls': ariaControls,\n to,\n href,\n target,\n rel\n },\n ref\n ) => {\n const isIconOnly = icon && !children;\n\n // Icon-only buttons MUST have an aria-label for accessibility\n if (isIconOnly && !ariaLabel) {\n // eslint-disable-next-line no-console\n console.warn('Button: Icon-only buttons require an ariaLabel for accessibility');\n }\n\n // Style modifiers\n const getStyleClasses = () => {\n const variantKey = variant as keyof typeof colorVariants.solid;\n\n switch (style) {\n case 'outline':\n return `${colorVariants.outline[variantKey]}`;\n case 'ghost':\n return colorVariants.ghost[variantKey];\n case 'link':\n return colorVariants.link[variantKey];\n case 'soft':\n return colorVariants.soft[variantKey];\n case 'none':\n return colorVariants.none[variantKey];\n case 'solid':\n default:\n return `${colorVariants.solid[variantKey]} shadow-sm hover:shadow-md`;\n }\n };\n\n // Sizes - from unified size system\n const sizeConfig = interactiveSizes[size];\n const sizeClasses = `${sizeConfig.height} ${sizeConfig.padding} ${sizeConfig.text}`;\n\n const roundedClasses = {\n default: 'rounded-md',\n pill: 'rounded-full',\n square: 'rounded-none aspect-square',\n circle: 'rounded-full aspect-square'\n };\n\n // Show loading spinner or icon\n const displayIcon = loading ? (\n <svg className=\"animate-spin h-4 w-4\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\"></circle>\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n ></path>\n </svg>\n ) : (\n icon\n );\n\n const styleClasses = getStyleClasses();\n const effectiveWidth = fullWidth ? 'full' : wide ? 'xl' : width;\n const widthClasses = inputWidthSizes[effectiveWidth];\n const alignmentClass = align ? inputAlignClasses[align] : '';\n const activeClasses = active ? 'active:scale-95' : '';\n\n const shouldRenderIconSlots = !isIconOnly && Boolean(displayIcon);\n const cloneIcon = () => {\n if (!displayIcon) return null;\n if (React.isValidElement(displayIcon)) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return React.cloneElement(displayIcon as React.ReactElement<any>, { 'aria-hidden': false });\n }\n return displayIcon;\n };\n\n const showLeftIcon = shouldRenderIconSlots && iconPosition === 'left';\n const showRightIcon = shouldRenderIconSlots && iconPosition === 'right';\n\n // Check if className contains display utilities to avoid conflicts\n const hasDisplayOverride =\n className.includes('hidden') ||\n className.includes('inline') ||\n className.includes('block') ||\n className.includes('flex');\n const baseDisplayClass = hasDisplayOverride ? '' : 'inline-flex';\n\n const isDisabled = disabled || loading;\n\n const combinedClassName = `${baseDisplayClass} items-center justify-center ${alignmentClass} ${roundedClasses[rounded]} font-medium transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50 ${styleClasses} ${sizeClasses} ${\n isIconOnly ? 'p-0' : ''\n } ${widthClasses} ${activeClasses} ${isDisabled && (to || href) ? 'pointer-events-none opacity-50' : ''} ${className}`;\n\n const innerContent = (\n <>\n {showLeftIcon && <span className=\"flex shrink-0 items-center mr-2\">{cloneIcon()}</span>}\n {isIconOnly ? (\n <span className=\"inline-flex items-center justify-center\">{displayIcon}</span>\n ) : (\n <span className=\"inline-flex flex-1 justify-center text-center\">{children}</span>\n )}\n {showRightIcon && <span className=\"flex shrink-0 items-center ml-2\">{cloneIcon()}</span>}\n </>\n );\n\n const sharedLinkProps = {\n 'aria-label': ariaLabel,\n 'aria-expanded': ariaExpanded,\n 'aria-haspopup': ariaHaspopup,\n 'aria-controls': ariaControls,\n 'aria-disabled': isDisabled || undefined,\n tabIndex: isDisabled ? -1 : undefined,\n target,\n rel: rel ?? (target === '_blank' ? 'noopener noreferrer' : undefined),\n className: combinedClassName,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onClick: onClick as any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onMouseEnter: onMouseEnter as any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onMouseLeave: onMouseLeave as any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onMouseDown: onMouseDown as any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onBlur: onBlur as any\n };\n\n if (to) {\n return (\n <Link to={to} ref={ref as React.Ref<HTMLAnchorElement>} {...sharedLinkProps}>\n {innerContent}\n </Link>\n );\n }\n\n if (href) {\n return (\n <a href={href} ref={ref as React.Ref<HTMLAnchorElement>} {...sharedLinkProps}>\n {innerContent}\n </a>\n );\n }\n\n return (\n <button\n ref={ref as React.Ref<HTMLButtonElement>}\n type={type}\n onClick={onClick}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n onMouseDown={onMouseDown}\n onBlur={onBlur}\n aria-label={ariaLabel}\n aria-expanded={ariaExpanded}\n aria-haspopup={ariaHaspopup}\n aria-controls={ariaControls}\n disabled={isDisabled}\n className={combinedClassName}\n >\n {innerContent}\n </button>\n );\n }\n);\n\nButton.displayName = 'Button';\n\nexport default Button;\n"],"names":[],"mappings":"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"}
|