@galyan/ui 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/.turbo/turbo-build.log +20 -0
  2. package/dist/index.cjs +2291 -0
  3. package/dist/index.css +2558 -0
  4. package/dist/index.d.cts +498 -0
  5. package/dist/index.d.ts +498 -0
  6. package/dist/index.js +2235 -0
  7. package/eslint.config.mjs +4 -0
  8. package/package.json +43 -0
  9. package/src/accordion/Accordion.tsx +88 -0
  10. package/src/accordion/accordion.css +78 -0
  11. package/src/accordion/index.ts +2 -0
  12. package/src/animatednumber/AnimatedNumber.tsx +71 -0
  13. package/src/animatednumber/index.ts +2 -0
  14. package/src/banner/Banner.tsx +84 -0
  15. package/src/banner/banner.css +56 -0
  16. package/src/banner/index.ts +2 -0
  17. package/src/breadcrumb/Breadcrumb.tsx +94 -0
  18. package/src/breadcrumb/breadcrumb.css +58 -0
  19. package/src/breadcrumb/index.ts +2 -0
  20. package/src/button/Button.tsx +98 -0
  21. package/src/button/button.css +190 -0
  22. package/src/button/index.ts +2 -0
  23. package/src/button.tsx +20 -0
  24. package/src/calendar/Calendar.tsx +190 -0
  25. package/src/calendar/calendar.css +142 -0
  26. package/src/calendar/index.ts +2 -0
  27. package/src/card/Card.tsx +88 -0
  28. package/src/card/card.css +115 -0
  29. package/src/card/index.ts +2 -0
  30. package/src/card.tsx +27 -0
  31. package/src/chart/Chart.tsx +186 -0
  32. package/src/chart/index.ts +2 -0
  33. package/src/checkbox/Checkbox.tsx +101 -0
  34. package/src/checkbox/checkbox.css +76 -0
  35. package/src/checkbox/index.ts +2 -0
  36. package/src/chips/Chips.tsx +146 -0
  37. package/src/chips/chips.css +90 -0
  38. package/src/chips/index.ts +2 -0
  39. package/src/code.tsx +11 -0
  40. package/src/datepicker/DatePicker.tsx +104 -0
  41. package/src/datepicker/datepicker.css +22 -0
  42. package/src/datepicker/index.ts +2 -0
  43. package/src/dragdrop/DragDrop.tsx +168 -0
  44. package/src/dragdrop/dragdrop.css +122 -0
  45. package/src/dragdrop/index.ts +2 -0
  46. package/src/dropdown/Dropdown.tsx +176 -0
  47. package/src/dropdown/dropdown.css +118 -0
  48. package/src/dropdown/index.ts +2 -0
  49. package/src/fileupload/FileUpload.tsx +144 -0
  50. package/src/fileupload/fileupload.css +118 -0
  51. package/src/fileupload/index.ts +2 -0
  52. package/src/index.ts +28 -0
  53. package/src/input/Input.tsx +121 -0
  54. package/src/input/index.ts +2 -0
  55. package/src/input/input.css +120 -0
  56. package/src/menu/Menu.tsx +118 -0
  57. package/src/menu/index.ts +2 -0
  58. package/src/menu/menu.css +117 -0
  59. package/src/modal/Modal.tsx +103 -0
  60. package/src/modal/index.ts +2 -0
  61. package/src/modal/modal.css +79 -0
  62. package/src/progressbar/ProgressBar.tsx +68 -0
  63. package/src/progressbar/index.ts +2 -0
  64. package/src/progressbar/progressbar.css +73 -0
  65. package/src/radiogroup/RadioGroup.tsx +73 -0
  66. package/src/radiogroup/index.ts +2 -0
  67. package/src/radiogroup/radiogroup.css +80 -0
  68. package/src/skeleton/Skeleton.tsx +61 -0
  69. package/src/skeleton/index.ts +2 -0
  70. package/src/skeleton/skeleton.css +46 -0
  71. package/src/spinner/Spinner.tsx +29 -0
  72. package/src/spinner/index.ts +2 -0
  73. package/src/spinner/spinner.css +39 -0
  74. package/src/stepper/Stepper.tsx +127 -0
  75. package/src/stepper/index.ts +2 -0
  76. package/src/stepper/stepper.css +141 -0
  77. package/src/tab/Tabs.tsx +71 -0
  78. package/src/tab/index.ts +2 -0
  79. package/src/tab/tab.css +92 -0
  80. package/src/table/Table.tsx +205 -0
  81. package/src/table/index.ts +2 -0
  82. package/src/table/table.css +119 -0
  83. package/src/textarea/Textarea.tsx +102 -0
  84. package/src/textarea/index.ts +2 -0
  85. package/src/textarea/textarea.css +85 -0
  86. package/src/toaster/Toaster.tsx +108 -0
  87. package/src/toaster/index.ts +2 -0
  88. package/src/toaster/toaster.css +113 -0
  89. package/src/toggle/Toggle.tsx +88 -0
  90. package/src/toggle/index.ts +2 -0
  91. package/src/toggle/toggle.css +63 -0
  92. package/src/typography/Typography.tsx +58 -0
  93. package/src/typography/index.ts +2 -0
  94. package/src/typography/typography.css +102 -0
  95. package/tsconfig.json +13 -0
@@ -0,0 +1,141 @@
1
+ /* ── Stepper ──────────────────────────────────────────────────────────────── */
2
+ .gy-stepper {
3
+ display: flex;
4
+ align-items: flex-start;
5
+ width: 100%;
6
+ }
7
+
8
+ .gy-stepper--vertical {
9
+ flex-direction: column;
10
+ }
11
+
12
+ .gy-stepper-step {
13
+ display: flex;
14
+ align-items: center;
15
+ flex: 1;
16
+ position: relative;
17
+ }
18
+
19
+ .gy-stepper--vertical .gy-stepper-step {
20
+ flex-direction: column;
21
+ align-items: flex-start;
22
+ flex: none;
23
+ width: 100%;
24
+ }
25
+
26
+ /* Connector line */
27
+ .gy-stepper-connector {
28
+ flex: 1;
29
+ height: 2px;
30
+ background: var(--gy-border);
31
+ margin: 0 var(--gy-spacing-2);
32
+ transition: background-color var(--gy-duration-slow);
33
+ }
34
+
35
+ .gy-stepper-connector--completed { background: var(--gy-primary); }
36
+
37
+ .gy-stepper--vertical .gy-stepper-connector {
38
+ width: 2px;
39
+ height: 2.5rem;
40
+ flex: none;
41
+ margin: var(--gy-spacing-1) 0;
42
+ margin-left: 19px;
43
+ }
44
+
45
+ /* Step indicator */
46
+ .gy-stepper-indicator {
47
+ display: flex;
48
+ align-items: center;
49
+ justify-content: center;
50
+ width: 2.25rem;
51
+ height: 2.25rem;
52
+ border-radius: 50%;
53
+ border: 2px solid var(--gy-border-strong);
54
+ background: var(--gy-surface);
55
+ font-size: var(--gy-font-size-sm);
56
+ font-weight: var(--gy-font-weight-semibold);
57
+ color: var(--gy-text-muted);
58
+ flex-shrink: 0;
59
+ transition:
60
+ background-color var(--gy-duration-normal),
61
+ border-color var(--gy-duration-normal),
62
+ color var(--gy-duration-normal);
63
+ cursor: default;
64
+ position: relative;
65
+ z-index: 1;
66
+ }
67
+
68
+ .gy-stepper-indicator--active {
69
+ border-color: var(--gy-primary);
70
+ color: var(--gy-primary);
71
+ background: var(--gy-primary-subtle);
72
+ box-shadow: 0 0 0 4px var(--gy-focus-ring);
73
+ }
74
+
75
+ .gy-stepper-indicator--completed {
76
+ border-color: var(--gy-primary);
77
+ background: var(--gy-primary);
78
+ color: var(--gy-primary-fg);
79
+ }
80
+
81
+ .gy-stepper-indicator--error {
82
+ border-color: var(--gy-danger);
83
+ background: var(--gy-danger);
84
+ color: white;
85
+ }
86
+
87
+ .gy-stepper-indicator--clickable {
88
+ cursor: pointer;
89
+ }
90
+
91
+ .gy-stepper-indicator--clickable:hover {
92
+ border-color: var(--gy-primary);
93
+ color: var(--gy-primary);
94
+ }
95
+
96
+ /* Step content */
97
+ .gy-stepper-item {
98
+ display: flex;
99
+ flex-direction: column;
100
+ align-items: center;
101
+ text-align: center;
102
+ }
103
+
104
+ .gy-stepper--vertical .gy-stepper-item {
105
+ flex-direction: row;
106
+ align-items: center;
107
+ gap: var(--gy-spacing-3);
108
+ text-align: left;
109
+ }
110
+
111
+ .gy-stepper-label {
112
+ font-size: var(--gy-font-size-xs);
113
+ font-weight: var(--gy-font-weight-semibold);
114
+ color: var(--gy-text-muted);
115
+ margin-top: var(--gy-spacing-2);
116
+ white-space: nowrap;
117
+ }
118
+
119
+ .gy-stepper--vertical .gy-stepper-label { margin-top: 0; }
120
+
121
+ .gy-stepper-label--active { color: var(--gy-primary); }
122
+ .gy-stepper-label--completed { color: var(--gy-text); }
123
+
124
+ .gy-stepper-description {
125
+ font-size: var(--gy-font-size-xs);
126
+ color: var(--gy-text-subtle);
127
+ margin-top: 2px;
128
+ }
129
+
130
+ /* Panel */
131
+ .gy-stepper-panel {
132
+ margin-top: var(--gy-spacing-6);
133
+ animation: gy-fade-in var(--gy-duration-normal) var(--gy-ease-out);
134
+ }
135
+
136
+ .gy-stepper-actions {
137
+ display: flex;
138
+ gap: var(--gy-spacing-3);
139
+ margin-top: var(--gy-spacing-4);
140
+ flex-wrap: wrap;
141
+ }
@@ -0,0 +1,71 @@
1
+ 'use client';
2
+
3
+ import React, { useState } from 'react';
4
+ import './tab.css';
5
+
6
+ export interface TabItem {
7
+ id: string;
8
+ label: React.ReactNode;
9
+ content: React.ReactNode;
10
+ disabled?: boolean;
11
+ badge?: string | number;
12
+ icon?: React.ReactNode;
13
+ }
14
+
15
+ export interface TabsProps {
16
+ items: TabItem[];
17
+ defaultTab?: string;
18
+ activeTab?: string;
19
+ onChange?: (id: string) => void;
20
+ orientation?: 'horizontal' | 'vertical';
21
+ className?: string;
22
+ }
23
+
24
+ export function Tabs({ items, defaultTab, activeTab, onChange, orientation = 'horizontal', className = '' }: TabsProps) {
25
+ const [internalActive, setInternalActive] = useState<string>(defaultTab ?? items[0]?.id ?? '');
26
+ const current = activeTab ?? internalActive;
27
+
28
+ const handleSelect = (id: string) => {
29
+ setInternalActive(id);
30
+ onChange?.(id);
31
+ };
32
+
33
+ const activeItem = items.find((i) => i.id === current);
34
+
35
+ return (
36
+ <div className={`gy-tabs ${orientation === 'vertical' ? 'gy-tabs--vertical' : ''} ${className}`}>
37
+ <div className="gy-tabs-list" role="tablist" aria-orientation={orientation}>
38
+ {items.map((item) => (
39
+ <button
40
+ key={item.id}
41
+ role="tab"
42
+ id={`gy-tab-${item.id}`}
43
+ aria-controls={`gy-panel-${item.id}`}
44
+ aria-selected={current === item.id}
45
+ disabled={item.disabled}
46
+ className={`gy-tabs-trigger ${current === item.id ? 'gy-tabs-trigger--active' : ''}`}
47
+ onClick={() => !item.disabled && handleSelect(item.id)}
48
+ >
49
+ {item.icon && <span aria-hidden="true">{item.icon}</span>}
50
+ {item.label}
51
+ {item.badge !== undefined && (
52
+ <span className="gy-tabs-trigger-badge">{item.badge}</span>
53
+ )}
54
+ </button>
55
+ ))}
56
+ </div>
57
+
58
+ {activeItem && (
59
+ <div
60
+ key={current}
61
+ id={`gy-panel-${current}`}
62
+ role="tabpanel"
63
+ aria-labelledby={`gy-tab-${current}`}
64
+ className="gy-tabs-panel"
65
+ >
66
+ {activeItem.content}
67
+ </div>
68
+ )}
69
+ </div>
70
+ );
71
+ }
@@ -0,0 +1,2 @@
1
+ export { Tabs } from './Tabs';
2
+ export type { TabsProps, TabItem } from './Tabs';
@@ -0,0 +1,92 @@
1
+ /* ── Tabs ─────────────────────────────────────────────────────────────────── */
2
+ .gy-tabs {
3
+ display: flex;
4
+ flex-direction: column;
5
+ }
6
+
7
+ .gy-tabs--vertical {
8
+ flex-direction: row;
9
+ gap: var(--gy-spacing-4);
10
+ }
11
+
12
+ .gy-tabs-list {
13
+ position: relative;
14
+ display: flex;
15
+ align-items: center;
16
+ border-bottom: 2px solid var(--gy-border);
17
+ overflow-x: auto;
18
+ scrollbar-width: none;
19
+ }
20
+
21
+ .gy-tabs-list::-webkit-scrollbar { display: none; }
22
+
23
+ .gy-tabs--vertical .gy-tabs-list {
24
+ flex-direction: column;
25
+ border-bottom: none;
26
+ border-right: 2px solid var(--gy-border);
27
+ min-width: 160px;
28
+ align-items: stretch;
29
+ }
30
+
31
+ .gy-tabs-trigger {
32
+ display: inline-flex;
33
+ align-items: center;
34
+ gap: var(--gy-spacing-2);
35
+ padding: var(--gy-spacing-2-5) var(--gy-spacing-4);
36
+ font-size: var(--gy-font-size-sm);
37
+ font-weight: var(--gy-font-weight-medium);
38
+ color: var(--gy-text-muted);
39
+ white-space: nowrap;
40
+ cursor: pointer;
41
+ background: none;
42
+ border: none;
43
+ position: relative;
44
+ transition: color var(--gy-duration-fast);
45
+ border-bottom: 2px solid transparent;
46
+ margin-bottom: -2px;
47
+ }
48
+
49
+ .gy-tabs--vertical .gy-tabs-trigger {
50
+ border-bottom: none;
51
+ border-right: 2px solid transparent;
52
+ margin-bottom: 0;
53
+ margin-right: -2px;
54
+ border-radius: var(--gy-radius-md) 0 0 var(--gy-radius-md);
55
+ }
56
+
57
+ .gy-tabs-trigger:hover { color: var(--gy-text); }
58
+ .gy-tabs-trigger:focus-visible { outline: none; color: var(--gy-primary); }
59
+
60
+ .gy-tabs-trigger--active {
61
+ color: var(--gy-primary) !important;
62
+ border-color: var(--gy-primary);
63
+ }
64
+
65
+ .gy-tabs-trigger:disabled {
66
+ opacity: 0.4;
67
+ cursor: not-allowed;
68
+ }
69
+
70
+ .gy-tabs-trigger-badge {
71
+ display: inline-flex;
72
+ align-items: center;
73
+ justify-content: center;
74
+ min-width: 18px;
75
+ height: 18px;
76
+ padding: 0 5px;
77
+ font-size: 0.65rem;
78
+ font-weight: var(--gy-font-weight-semibold);
79
+ background: var(--gy-primary-muted);
80
+ color: var(--gy-primary-hover);
81
+ border-radius: var(--gy-radius-full);
82
+ }
83
+
84
+ .gy-tabs-panel {
85
+ padding: var(--gy-spacing-4) 0;
86
+ animation: gy-fade-in var(--gy-duration-normal) var(--gy-ease-out);
87
+ }
88
+
89
+ .gy-tabs--vertical .gy-tabs-panel {
90
+ flex: 1;
91
+ padding: 0 var(--gy-spacing-4);
92
+ }
@@ -0,0 +1,205 @@
1
+ 'use client';
2
+
3
+ import React, { useMemo, useState } from 'react';
4
+ import { Checkbox } from '../checkbox/Checkbox';
5
+ import './table.css';
6
+
7
+ export type SortDirection = 'asc' | 'desc';
8
+
9
+ export interface Column<T> {
10
+ key: string;
11
+ header: React.ReactNode;
12
+ accessor: (row: T) => React.ReactNode;
13
+ sortable?: boolean;
14
+ width?: string;
15
+ align?: 'left' | 'center' | 'right';
16
+ }
17
+
18
+ export interface TableProps<T> {
19
+ columns: Column<T>[];
20
+ data: T[];
21
+ rowKey: (row: T) => string;
22
+ selectable?: boolean;
23
+ selectedRows?: string[];
24
+ onSelectionChange?: (keys: string[]) => void;
25
+ pageSize?: number;
26
+ emptyMessage?: React.ReactNode;
27
+ className?: string;
28
+ stickyHeader?: boolean;
29
+ }
30
+
31
+ function SortIcon({ dir }: { dir?: SortDirection }) {
32
+ return (
33
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor" className={`gy-table-sort-icon ${dir ? 'gy-table-sort-icon--active' : ''}`}>
34
+ {dir === 'asc' ? (
35
+ <path d="M6 2L10 8H2L6 2z" />
36
+ ) : dir === 'desc' ? (
37
+ <path d="M6 10L2 4H10L6 10z" />
38
+ ) : (
39
+ <>
40
+ <path d="M6 1L9 5H3L6 1z" opacity=".5" />
41
+ <path d="M6 11L3 7H9L6 11z" opacity=".5" />
42
+ </>
43
+ )}
44
+ </svg>
45
+ );
46
+ }
47
+
48
+ export function Table<T>({
49
+ columns,
50
+ data,
51
+ rowKey,
52
+ selectable = false,
53
+ selectedRows = [],
54
+ onSelectionChange,
55
+ pageSize = 10,
56
+ emptyMessage = 'No data available',
57
+ className = '',
58
+ stickyHeader = false,
59
+ }: TableProps<T>) {
60
+ const [sortKey, setSortKey] = useState<string | null>(null);
61
+ const [sortDir, setSortDir] = useState<SortDirection>('asc');
62
+ const [page, setPage] = useState(1);
63
+
64
+ const sortedData = useMemo(() => {
65
+ if (!sortKey) return data;
66
+ const col = columns.find((c) => c.key === sortKey);
67
+ if (!col) return data;
68
+ return [...data].sort((a, b) => {
69
+ const av = String(col.accessor(a) ?? '');
70
+ const bv = String(col.accessor(b) ?? '');
71
+ const cmp = av.localeCompare(bv, undefined, { numeric: true });
72
+ return sortDir === 'asc' ? cmp : -cmp;
73
+ });
74
+ }, [data, sortKey, sortDir, columns]);
75
+
76
+ const totalPages = Math.max(1, Math.ceil(sortedData.length / pageSize));
77
+ const paged = sortedData.slice((page - 1) * pageSize, page * pageSize);
78
+
79
+ const allSelected = paged.length > 0 && paged.every((r) => selectedRows.includes(rowKey(r)));
80
+ const someSelected = paged.some((r) => selectedRows.includes(rowKey(r)));
81
+
82
+ const toggleSort = (key: string) => {
83
+ if (sortKey === key) {
84
+ setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'));
85
+ } else {
86
+ setSortKey(key);
87
+ setSortDir('asc');
88
+ }
89
+ setPage(1);
90
+ };
91
+
92
+ const toggleRow = (key: string) => {
93
+ const next = selectedRows.includes(key)
94
+ ? selectedRows.filter((k) => k !== key)
95
+ : [...selectedRows, key];
96
+ onSelectionChange?.(next);
97
+ };
98
+
99
+ const toggleAll = () => {
100
+ const pageKeys = paged.map(rowKey);
101
+ if (allSelected) {
102
+ onSelectionChange?.(selectedRows.filter((k) => !pageKeys.includes(k)));
103
+ } else {
104
+ const combined = [...new Set([...selectedRows, ...pageKeys])];
105
+ onSelectionChange?.(combined);
106
+ }
107
+ };
108
+
109
+ const pageNums = Array.from({ length: totalPages }, (_, i) => i + 1);
110
+ const visiblePages = pageNums.filter(
111
+ (p) => p === 1 || p === totalPages || Math.abs(p - page) <= 1
112
+ );
113
+
114
+ return (
115
+ <div className={`gy-table-wrapper ${className}`}>
116
+ <table className="gy-table" aria-label="Data table">
117
+ <thead className="gy-table-header">
118
+ <tr>
119
+ {selectable && (
120
+ <th className="gy-table-th gy-table-th--checkbox">
121
+ <Checkbox
122
+ checked={allSelected}
123
+ indeterminate={!allSelected && someSelected}
124
+ onChange={toggleAll}
125
+ size="sm"
126
+ />
127
+ </th>
128
+ )}
129
+ {columns.map((col) => (
130
+ <th
131
+ key={col.key}
132
+ className={`gy-table-th ${col.sortable ? 'gy-table-th--sortable' : ''}`}
133
+ style={{ width: col.width, textAlign: col.align }}
134
+ onClick={col.sortable ? () => toggleSort(col.key) : undefined}
135
+ aria-sort={sortKey === col.key ? (sortDir === 'asc' ? 'ascending' : 'descending') : undefined}
136
+ >
137
+ <span className="gy-table-th-inner">
138
+ {col.header}
139
+ {col.sortable && <SortIcon dir={sortKey === col.key ? sortDir : undefined} />}
140
+ </span>
141
+ </th>
142
+ ))}
143
+ </tr>
144
+ </thead>
145
+ <tbody>
146
+ {paged.length === 0 ? (
147
+ <tr>
148
+ <td colSpan={columns.length + (selectable ? 1 : 0)} className="gy-table-empty">
149
+ {emptyMessage}
150
+ </td>
151
+ </tr>
152
+ ) : (
153
+ paged.map((row) => {
154
+ const key = rowKey(row);
155
+ const isSelected = selectedRows.includes(key);
156
+ return (
157
+ <tr
158
+ key={key}
159
+ className={`gy-table-tr ${isSelected ? 'gy-table-tr--selected' : ''}`}
160
+ aria-selected={selectable ? isSelected : undefined}
161
+ >
162
+ {selectable && (
163
+ <td className="gy-table-td gy-table-td--checkbox">
164
+ <Checkbox checked={isSelected} onChange={() => toggleRow(key)} size="sm" />
165
+ </td>
166
+ )}
167
+ {columns.map((col) => (
168
+ <td key={col.key} className="gy-table-td" style={{ textAlign: col.align }}>
169
+ {col.accessor(row)}
170
+ </td>
171
+ ))}
172
+ </tr>
173
+ );
174
+ })
175
+ )}
176
+ </tbody>
177
+ </table>
178
+
179
+ {totalPages > 1 && (
180
+ <div className="gy-table-pagination">
181
+ <span className="gy-table-pagination-info">
182
+ Showing {(page - 1) * pageSize + 1}–{Math.min(page * pageSize, sortedData.length)} of {sortedData.length}
183
+ </span>
184
+ <div className="gy-table-pagination-controls">
185
+ <button className="gy-table-pagination-btn" onClick={() => setPage((p) => p - 1)} disabled={page === 1} aria-label="Previous page">‹</button>
186
+ {visiblePages.map((p, i, arr) => (
187
+ <React.Fragment key={p}>
188
+ {i > 0 && arr[i - 1] !== p - 1 && <span style={{ padding: '0 4px', color: 'var(--gy-text-subtle)' }}>…</span>}
189
+ <button
190
+ className={`gy-table-pagination-btn ${page === p ? 'gy-table-pagination-btn--active' : ''}`}
191
+ onClick={() => setPage(p)}
192
+ aria-label={`Page ${p}`}
193
+ aria-current={page === p ? 'page' : undefined}
194
+ >
195
+ {p}
196
+ </button>
197
+ </React.Fragment>
198
+ ))}
199
+ <button className="gy-table-pagination-btn" onClick={() => setPage((p) => p + 1)} disabled={page === totalPages} aria-label="Next page">›</button>
200
+ </div>
201
+ </div>
202
+ )}
203
+ </div>
204
+ );
205
+ }
@@ -0,0 +1,2 @@
1
+ export { Table } from './Table';
2
+ export type { TableProps, Column, SortDirection } from './Table';
@@ -0,0 +1,119 @@
1
+ /* ── Table ───────────────────────────────────────────────────────────────── */
2
+ .gy-table-wrapper {
3
+ width: 100%;
4
+ overflow-x: auto;
5
+ border-radius: var(--gy-radius-xl);
6
+ border: 1px solid var(--gy-border);
7
+ }
8
+
9
+ .gy-table {
10
+ width: 100%;
11
+ border-collapse: collapse;
12
+ font-size: var(--gy-font-size-sm);
13
+ }
14
+
15
+ .gy-table-header { background: var(--gy-background-subtle); }
16
+
17
+ .gy-table-th {
18
+ padding: var(--gy-spacing-3) var(--gy-spacing-4);
19
+ text-align: left;
20
+ font-weight: var(--gy-font-weight-semibold);
21
+ color: var(--gy-text-muted);
22
+ white-space: nowrap;
23
+ letter-spacing: var(--gy-letter-spacing-wide);
24
+ font-size: var(--gy-font-size-xs);
25
+ text-transform: uppercase;
26
+ border-bottom: 1px solid var(--gy-border);
27
+ user-select: none;
28
+ position: relative;
29
+ }
30
+
31
+ .gy-table-th--sortable { cursor: pointer; }
32
+ .gy-table-th--sortable:hover { color: var(--gy-text); background: var(--gy-background-muted); }
33
+
34
+ .gy-table-th-inner {
35
+ display: inline-flex;
36
+ align-items: center;
37
+ gap: var(--gy-spacing-1-5);
38
+ }
39
+
40
+ .gy-table-sort-icon { color: var(--gy-text-subtle); }
41
+ .gy-table-sort-icon--active { color: var(--gy-primary); }
42
+
43
+ .gy-table-td {
44
+ padding: var(--gy-spacing-3) var(--gy-spacing-4);
45
+ color: var(--gy-text);
46
+ border-bottom: 1px solid var(--gy-border);
47
+ vertical-align: middle;
48
+ }
49
+
50
+ .gy-table-tr:last-child .gy-table-td { border-bottom: none; }
51
+
52
+ .gy-table-tr { transition: background-color var(--gy-duration-fast); }
53
+ .gy-table-tr:hover { background: var(--gy-background-subtle); }
54
+ .gy-table-tr--selected { background: var(--gy-primary-subtle); }
55
+
56
+ /* Selection checkbox column */
57
+ .gy-table-th--checkbox,
58
+ .gy-table-td--checkbox {
59
+ width: 40px;
60
+ padding-right: var(--gy-spacing-2);
61
+ }
62
+
63
+ /* Empty state */
64
+ .gy-table-empty {
65
+ text-align: center;
66
+ padding: var(--gy-spacing-12) var(--gy-spacing-4);
67
+ color: var(--gy-text-subtle);
68
+ }
69
+
70
+ /* Pagination */
71
+ .gy-table-pagination {
72
+ display: flex;
73
+ align-items: center;
74
+ justify-content: space-between;
75
+ padding: var(--gy-spacing-3) var(--gy-spacing-4);
76
+ border-top: 1px solid var(--gy-border);
77
+ background: var(--gy-background-subtle);
78
+ font-size: var(--gy-font-size-sm);
79
+ }
80
+
81
+ .gy-table-pagination-info { color: var(--gy-text-muted); }
82
+
83
+ .gy-table-pagination-controls {
84
+ display: flex;
85
+ align-items: center;
86
+ gap: var(--gy-spacing-1);
87
+ }
88
+
89
+ .gy-table-pagination-btn {
90
+ display: flex;
91
+ align-items: center;
92
+ justify-content: center;
93
+ width: 2rem;
94
+ height: 2rem;
95
+ border-radius: var(--gy-radius-md);
96
+ font-size: var(--gy-font-size-sm);
97
+ font-weight: var(--gy-font-weight-medium);
98
+ color: var(--gy-text-muted);
99
+ border: 1px solid transparent;
100
+ cursor: pointer;
101
+ background: none;
102
+ transition: background var(--gy-duration-fast), color var(--gy-duration-fast);
103
+ }
104
+
105
+ .gy-table-pagination-btn:hover:not(:disabled) {
106
+ background: var(--gy-background-muted);
107
+ color: var(--gy-text);
108
+ }
109
+
110
+ .gy-table-pagination-btn--active {
111
+ background: var(--gy-primary);
112
+ color: var(--gy-primary-fg);
113
+ border-color: var(--gy-primary);
114
+ }
115
+
116
+ .gy-table-pagination-btn:disabled {
117
+ opacity: 0.4;
118
+ cursor: not-allowed;
119
+ }