`, or any markup
11 | - **No CSS** - Zero styling opinions, complete visual control
12 | - **Pure logic** - Column resizing, drag & drop, persistence, and table state management
13 |
14 | ## 📦 Installation
15 |
16 | ```bash
17 | npm install snaptable-react
18 | ```
19 |
20 | ## 🚀 Quick Start
21 |
22 | ```tsx
23 | import { useDataTable, useTable } from "snaptable-react";
24 |
25 | function MyTable() {
26 | // Configure your table behavior
27 | const dataTable = useDataTable({
28 | key: "my-table",
29 | columns: [
30 | {
31 | key: "name",
32 | label: "Name",
33 | Cell: ({ data }) =>
{data.name} ,
34 | resizeable: true,
35 | },
36 | {
37 | key: "email",
38 | label: "Email",
39 | Cell: ({ data }) =>
{data.email} ,
40 | resizeable: true,
41 | },
42 | ],
43 | hasDraggableColumns: true,
44 | isStickyHeader: true,
45 | saveLayoutView: true,
46 | });
47 |
48 | // Get table state and handlers
49 | const tableState = useTable(dataTable, myData);
50 |
51 | // Build your own table with complete control
52 | return (
53 |
54 |
55 |
56 | {tableState.columns.map((column, index) => {
57 | const props = tableState.getColumnProps(index);
58 | return (
59 |
67 | {column.label}
68 | {props.isResizable && (
69 | props.onResizeStart(e.nativeEvent)}
79 | />
80 | )}
81 |
82 | );
83 | })}
84 |
85 |
86 |
87 | {tableState.data.map((item) => {
88 | const rowProps = tableState.getRowProps(item);
89 | return (
90 |
91 | {tableState.columns.map(({ key, Cell }) => (
92 | |
93 | ))}
94 |
95 | );
96 | })}
97 |
98 |
99 | );
100 | }
101 | ```
102 |
103 | ## 🔧 Core Hooks
104 |
105 | ### `useDataTable(config)`
106 |
107 | Configure your table's behavior and structure.
108 |
109 | ```tsx
110 | const dataTable = useDataTable({
111 | key: 'unique-table-id', // For layout persistence
112 | columns: [...], // Column definitions
113 | hasDraggableColumns: true, // Enable column reordering
114 | isStickyHeader: true, // Sticky header behavior
115 | hasStickyColumns: true, // Enable sticky columns
116 | saveLayoutView: true, // Persist column widths/order
117 | onRowClick: ({ item }) => {...} // Row click handler
118 | });
119 | ```
120 |
121 | ### `useTable(dataTable, data)`
122 |
123 | Get table state and event handlers for your markup.
124 |
125 | ```tsx
126 | const tableState = useTable(dataTable, data);
127 |
128 | // Available properties:
129 | tableState.columns; // Column definitions
130 | tableState.data; // Table data
131 | tableState.config; // Table configuration
132 | tableState.columnWidths; // Current column widths
133 | tableState.stickyColumns; // Sticky column states
134 | tableState.stickyOffsets; // Sticky column positioning offsets
135 |
136 | // Available methods:
137 | tableState.getColumnProps(index); // Get all props for a column header
138 | tableState.getCellProps(columnIndex); // Get all props for a cell
139 | tableState.getRowProps(item); // Get all props for a row
140 | ```
141 |
142 | ## 📋 Column Definition
143 |
144 | ```tsx
145 | {
146 | key: 'field-name', // Data field key
147 | label: 'Display Name', // Column header text
148 | Cell: ({ data, ...props }) =>
{data.field} , // Cell renderer
149 | resizeable: true, // Enable column resizing
150 | sticky: false, // Make column sticky (requires hasStickyColumns: true)
151 | hidden: false, // Start column hidden (optional)
152 | width: 200, // Initial width (optional)
153 | minWidth: 100, // Minimum width (optional)
154 | maxWidth: 500 // Maximum width (optional)
155 | }
156 | ```
157 |
158 | ## 📌 Sticky Columns
159 |
160 | Enable sticky columns to pin important columns to the left side of the table during horizontal scrolling.
161 |
162 | ### Basic Sticky Columns Setup
163 |
164 | ```tsx
165 | const dataTable = useDataTable({
166 | key: "my-table",
167 | hasStickyColumns: true, // Enable sticky columns feature
168 | columns: [
169 | {
170 | key: "name",
171 | label: "Name",
172 | sticky: true, // Pin this column to the left
173 | Cell: ({ data }) =>
{data.name} ,
174 | resizeable: true,
175 | },
176 | {
177 | key: "id",
178 | label: "ID",
179 | sticky: true, // This will be the second sticky column
180 | Cell: ({ data }) =>
{data.id} ,
181 | resizeable: true,
182 | },
183 | {
184 | key: "email",
185 | label: "Email",
186 | Cell: ({ data }) =>
{data.email} ,
187 | resizeable: true,
188 | },
189 | // ... more columns
190 | ],
191 | });
192 | ```
193 |
194 | ### Implementing Sticky Columns in Your Table
195 |
196 | ```tsx
197 | function StickyTable() {
198 | const tableState = useTable(dataTable, data);
199 |
200 | return (
201 |
202 |
203 |
204 |
205 | {tableState.columns.map((column, index) => {
206 | const props = tableState.getColumnProps(index);
207 | return (
208 |
222 | {column.label}
223 | {/* Toggle sticky button */}
224 | props.onToggleSticky()}
226 | style={{ marginLeft: "8px" }}
227 | >
228 | {props.isSticky ? "📌" : "📍"}
229 |
230 | {/* Resize handle */}
231 | {props.isResizable && (
232 | props.onResizeStart(e.nativeEvent)}
242 | />
243 | )}
244 |
245 | );
246 | })}
247 |
248 |
249 |
250 | {tableState.data.map((item) => {
251 | const rowProps = tableState.getRowProps(item);
252 | return (
253 |
254 | {tableState.columns.map((column, columnIndex) => {
255 | const cellProps = tableState.getCellProps(columnIndex);
256 | return (
257 |
271 |
272 |
273 | );
274 | })}
275 |
276 | );
277 | })}
278 |
279 |
280 |
281 | );
282 | }
283 | ```
284 |
285 | ### Sticky Columns Features
286 |
287 | - **Multiple Sticky Columns** - Pin multiple columns that stack from left to right
288 | - **Dynamic Toggle** - Use `onToggleSticky()` to dynamically pin/unpin columns
289 | - **Automatic Positioning** - Precise positioning with `stickyOffset` values
290 | - **Resize Support** - Sticky columns work seamlessly with column resizing
291 | - **Drag & Drop Constraints** - Sticky columns can only be reordered among other sticky columns
292 | - **State Persistence** - Sticky states are saved to localStorage when `saveLayoutView` is enabled
293 |
294 | ### CSS Tips for Sticky Columns
295 |
296 | ```css
297 | /* Ensure smooth scrolling */
298 | .table-container {
299 | overflow-x: auto;
300 | scroll-behavior: smooth;
301 | }
302 |
303 | /* Add visual distinction for sticky columns */
304 | .sticky-column {
305 | background-color: #f8f9fa;
306 | border-right: 2px solid #dee2e6;
307 | box-shadow: 2px 0 4px rgba(0, 0, 0, 0.1);
308 | }
309 |
310 | /* Hover effects for sticky columns */
311 | .sticky-column:hover {
312 | background-color: #e9ecef;
313 | }
314 | ```
315 |
316 | ## 👁️ Show/Hide Columns
317 |
318 | Control column visibility dynamically with built-in state management and persistence.
319 |
320 | ### Basic Show/Hide Setup
321 |
322 | ```tsx
323 | const dataTable = useDataTable({
324 | key: "my-table",
325 | columns: [
326 | {
327 | key: "name",
328 | label: "Name",
329 | Cell: ({ data }) =>
{data.name} ,
330 | resizeable: true,
331 | },
332 | {
333 | key: "email",
334 | label: "Email",
335 | Cell: ({ data }) =>
{data.email} ,
336 | resizeable: true,
337 | hidden: true, // Start hidden
338 | },
339 | {
340 | key: "phone",
341 | label: "Phone",
342 | Cell: ({ data }) =>
{data.phone} ,
343 | resizeable: true,
344 | },
345 | ],
346 | saveLayoutView: true, // Persist hidden state
347 | });
348 | ```
349 |
350 | ### Implementing Show/Hide Controls
351 |
352 | ```tsx
353 | function TableWithHideShow() {
354 | const tableState = useTable(dataTable, data);
355 |
356 | return (
357 |
358 | {/* Hidden columns dropdown */}
359 |
360 |
364 | Show Hidden ({tableState.getHiddenColumns().length})
365 |
366 | {tableState.getHiddenColumns().length > 0 && (
367 |
368 | {tableState.getHiddenColumns().map((column) => (
369 | tableState.toggleColumnHidden(column.key)}
372 | >
373 | Show {column.label}
374 |
375 | ))}
376 |
377 | )}
378 |
379 |
380 |
381 |
382 |
383 | {tableState.columns.map((column, index) => {
384 | const props = tableState.getColumnProps(index);
385 | return (
386 |
387 | {column.label}
388 | {/* Hide column button */}
389 | props.onToggleHidden()}
391 | style={{ marginLeft: "8px" }}
392 | >
393 | 🙈 Hide
394 |
395 | {/* Resize handle */}
396 | {props.isResizable && (
397 | props.onResizeStart(e.nativeEvent)}
399 | style={{
400 | position: "absolute",
401 | right: 0,
402 | top: 0,
403 | width: "5px",
404 | height: "100%",
405 | cursor: "col-resize",
406 | }}
407 | />
408 | )}
409 |
410 | );
411 | })}
412 |
413 |
414 |
415 | {tableState.data.map((item) => {
416 | const rowProps = tableState.getRowProps(item);
417 | return (
418 |
419 | {tableState.columns.map(({ key, Cell }) => (
420 | |
421 | ))}
422 |
423 | );
424 | })}
425 |
426 |
427 |
428 | );
429 | }
430 | ```
431 |
432 | ### Show/Hide Features
433 |
434 | - **Hidden State Management** - Automatic state tracking for hidden columns
435 | - **Persistence** - Hidden states are saved to localStorage when `saveLayoutView` is enabled
436 | - **Dynamic Toggle** - Use `onToggleHidden()` to hide columns and `toggleColumnHidden()` to show them
437 | - **Hidden Columns List** - Get all hidden columns with `getHiddenColumns()`
438 | - **Flexible UI** - Build your own show/hide controls with complete styling control
439 | - **Integration** - Works seamlessly with sticky columns, resizing, and drag & drop
440 |
441 | ## 🎨 Styling Examples
442 |
443 | ### Basic Table
444 |
445 | ```tsx
446 | // Your CSS
447 | .my-table {
448 | width: 100%;
449 | border-collapse: collapse;
450 | }
451 |
452 | .my-header {
453 | background: #f5f5f5;
454 | padding: 12px;
455 | border: 1px solid #ddd;
456 | }
457 |
458 | .my-cell {
459 | padding: 12px;
460 | border: 1px solid #ddd;
461 | }
462 | ```
463 |
464 | ### Advanced Styling
465 |
466 | ```tsx
467 | // Complete control over appearance
468 | const StyledCell = ({ data, ...props }) => (
469 |
479 |
480 | {data.name}
481 | {data.description}
482 |
483 |
484 | );
485 | ```
486 |
487 | ### Grid Layout (Non-Table)
488 |
489 | ```tsx
490 | // Use divs instead of table elements
491 | return (
492 |
493 |
494 | {tableState.columns.map((column, index) => {
495 | const props = tableState.getColumnProps(index);
496 | return (
497 |
505 | {column.label}
506 |
507 | );
508 | })}
509 |
510 |
511 | {tableState.data.map((item) => (
512 |
513 | {tableState.columns.map(({ key, Cell }) => (
514 | |
515 | ))}
516 |
517 | ))}
518 |
519 |
520 | );
521 | ```
522 |
523 | ## ⚡ Features
524 |
525 | - **Column Resizing** - Drag column borders to resize
526 | - **Column Reordering** - Drag & drop column headers to reorder
527 | - **Sticky Headers** - Keep headers visible while scrolling
528 | - **Sticky Columns** - Pin columns to the left side during horizontal scrolling
529 | - **Show/Hide Columns** - Toggle column visibility with built-in state management
530 | - **Layout Persistence** - Save column widths, order, sticky states, and visibility to localStorage
531 | - **Row Click Handlers** - Handle row interactions
532 | - **Flexible Data** - Works with any data structure
533 | - **TypeScript** - Full TypeScript support with proper types
534 | - **Zero Dependencies** - No external dependencies except React
535 | - **Tiny Bundle** - Only the logic you need, no UI bloat
536 |
537 | ## 📋 Recent Changes
538 |
539 | ### v3.3.0 (Latest)
540 |
541 | **Developer Experience Improvements:**
542 |
543 | - 🎯 **Automated Z-Index Management** - Z-index calculations for sticky columns and headers are now handled automatically by the library
544 | - 🧹 **Cleaner User Code** - Users no longer need to implement complex z-index logic in their components
545 | - 📦 **Built-in Logic** - All sticky column layering logic is now internal to the hooks
546 | - 🔧 **Simplified Implementation** - Reduced boilerplate code for sticky column implementations
547 |
548 | **API Enhancements:**
549 |
550 | - `props.zIndex` - Column headers now include calculated z-index values
551 | - `cellProps.zIndex` - Table cells now include calculated z-index values
552 | - Automatic z-index calculation based on sticky column position and sticky header state
553 |
554 | ### v3.2.0
555 |
556 | **New Features:**
557 |
558 | - ✨ **Show/Hide Columns** - Toggle column visibility with built-in state management
559 | - 🔧 **Enhanced Layout Persistence** - Hidden column states are now saved to localStorage
560 | - 🎯 **Improved Developer Experience** - Better component architecture and naming conventions
561 |
562 | **API Additions:**
563 |
564 | - `tableState.getHiddenColumns()` - Get array of hidden columns
565 | - `tableState.toggleColumnHidden(columnKey)` - Toggle specific column visibility
566 | - `props.onToggleHidden()` - Hide a column from column header
567 | - `column.hidden` - Set initial hidden state in column definition
568 |
569 | ---
570 |
571 | 📖 **[View complete changelog](./CHANGELOG.md)** for all version history and detailed changes.
572 |
573 | ## 🔄 Migration from v2.x
574 |
575 | **v2.x had components:**
576 |
577 | ```tsx
578 | // OLD - Had built-in components
579 | import { SnapTable } from "snaptable-react";
580 |
;
581 | ```
582 |
583 | **v3.x is purely headless:**
584 |
585 | ```tsx
586 | // NEW - Only hooks, you build the UI
587 | import { useDataTable, useTable } from "snaptable-react";
588 | const tableState = useTable(dataTable, data);
589 | // Build your own
or structure
590 | ```
591 |
592 | ## 📚 Examples
593 |
594 | Check the `/examples` folder for complete implementation examples:
595 |
596 | - **Basic Table** - Simple table with resizing and drag & drop
597 | - **Advanced Styling** - Custom cell renderers and complex layouts
598 | - **Grid Layout** - Using divs instead of table elements
599 | - **Responsive Design** - Mobile-friendly implementations
600 |
601 | ## 🤝 Contributing
602 |
603 | 1. Fork the repository
604 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
605 | 3. Commit your changes (`git commit -m 'Add amazing feature'`)
606 | 4. Push to the branch (`git push origin feature/amazing-feature`)
607 | 5. Open a Pull Request
608 |
609 | ## 📄 License
610 |
611 | MIT License - see the [LICENSE](LICENSE) file for details.
612 |
613 | ---
614 |
615 | **Remember:** This is a headless library. We provide the logic, you provide the UI. Build tables that perfectly match your design system! 🎨
616 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | /* Reset and base styles */
2 | * {
3 | margin: 0;
4 | padding: 0;
5 | box-sizing: border-box;
6 | }
7 |
8 | body {
9 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
10 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
11 | sans-serif;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | line-height: 1.6;
15 | }
16 |
17 | /* Landing Page Container */
18 | .landing-page {
19 | min-height: 100vh;
20 | width: 100vw;
21 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
22 | position: relative;
23 | overflow-x: hidden;
24 | }
25 |
26 | /* Hero Section */
27 | .hero-section {
28 | min-height: 100vh;
29 | width: 100vw;
30 | display: flex;
31 | align-items: center;
32 | justify-content: center;
33 | position: relative;
34 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
35 | padding: 2rem 1rem;
36 | }
37 |
38 | .hero-section::before {
39 | content: '';
40 | position: absolute;
41 | top: 0;
42 | left: 0;
43 | right: 0;
44 | bottom: 0;
45 | background:
46 | radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
47 | radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3) 0%, transparent 50%);
48 | pointer-events: none;
49 | }
50 |
51 | .hero-content {
52 | text-align: center;
53 | max-width: 1060px;
54 | width: 100%;
55 | padding: 1rem;
56 | position: relative;
57 | z-index: 1;
58 | }
59 |
60 | .hero-badge {
61 | display: flex;
62 | align-items: center;
63 | justify-content: center;
64 | gap: 0.5rem;
65 | margin-bottom: 2rem;
66 | flex-wrap: wrap;
67 | }
68 |
69 | .version-badge {
70 | background: rgba(255, 255, 255, 0.2);
71 | backdrop-filter: blur(10px);
72 | border: 1px solid rgba(255, 255, 255, 0.3);
73 | padding: 0.5rem 1rem;
74 | border-radius: 50px;
75 | color: white;
76 | font-weight: 600;
77 | font-size: 0.9rem;
78 | }
79 |
80 | .new-badge {
81 | background: linear-gradient(135deg, #ff6b6b, #ee5a24);
82 | padding: 0.5rem 1rem;
83 | border-radius: 50px;
84 | color: white;
85 | font-weight: 600;
86 | font-size: 0.9rem;
87 | box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3);
88 | animation: pulse 2s infinite;
89 | }
90 |
91 | @keyframes pulse {
92 | 0%, 100% { transform: scale(1); }
93 | 50% { transform: scale(1.05); }
94 | }
95 |
96 | .hero-title {
97 | font-size: clamp(2.5rem, 8vw, 6rem);
98 | font-weight: 800;
99 | margin-bottom: 1rem;
100 | line-height: 1.1;
101 | position: relative;
102 | display: inline-block;
103 | }
104 |
105 | .gradient-text {
106 | background: linear-gradient(45deg,
107 | #ff6b6b 0%,
108 | #4ecdc4 14%,
109 | #45b7d1 28%,
110 | #96ceb4 42%,
111 | #feca57 56%,
112 | #ff9ff3 70%,
113 | #54a0ff 84%,
114 | #5f27cd 100%);
115 | background-size: 400% 400%;
116 | -webkit-background-clip: text;
117 | -webkit-text-fill-color: transparent;
118 | background-clip: text;
119 | animation: rainbowShift 4s ease infinite, textGlow 2s ease-in-out infinite alternate;
120 | position: relative;
121 | z-index: 3;
122 | }
123 |
124 | .gradient-text::before {
125 | content: 'SnapTable';
126 | position: absolute;
127 | top: 0;
128 | left: 0;
129 | right: 0;
130 | background: linear-gradient(45deg,
131 | #ff6b6b 0%,
132 | #4ecdc4 14%,
133 | #45b7d1 28%,
134 | #96ceb4 42%,
135 | #feca57 56%,
136 | #ff9ff3 70%,
137 | #54a0ff 84%,
138 | #5f27cd 100%);
139 | background-size: 400% 400%;
140 | -webkit-background-clip: text;
141 | -webkit-text-fill-color: transparent;
142 | background-clip: text;
143 | animation: rainbowShift 4s ease infinite reverse;
144 | filter: blur(3px);
145 | opacity: 0.6;
146 | z-index: 1;
147 | }
148 |
149 | .gradient-text::after {
150 | content: '';
151 | position: absolute;
152 | top: -15px;
153 | left: -15px;
154 | right: -15px;
155 | bottom: -15px;
156 | background: linear-gradient(45deg,
157 | #ff6b6b 0%,
158 | #4ecdc4 14%,
159 | #45b7d1 28%,
160 | #96ceb4 42%,
161 | #feca57 56%,
162 | #ff9ff3 70%,
163 | #54a0ff 84%,
164 | #5f27cd 100%);
165 | background-size: 400% 400%;
166 | animation: rainbowShift 4s ease infinite;
167 | filter: blur(30px);
168 | opacity: 0.4;
169 | z-index: 0;
170 | border-radius: 30px;
171 | }
172 |
173 | @keyframes rainbowShift {
174 | 0% { background-position: 0% 50%; }
175 | 50% { background-position: 100% 50%; }
176 | 100% { background-position: 0% 50%; }
177 | }
178 |
179 | @keyframes textGlow {
180 | 0% {
181 | filter: drop-shadow(0 0 5px rgba(255, 107, 107, 0.5))
182 | drop-shadow(0 0 10px rgba(78, 205, 196, 0.3))
183 | drop-shadow(0 0 15px rgba(69, 183, 209, 0.2));
184 | }
185 | 100% {
186 | filter: drop-shadow(0 0 20px rgba(255, 107, 107, 0.8))
187 | drop-shadow(0 0 30px rgba(78, 205, 196, 0.6))
188 | drop-shadow(0 0 40px rgba(69, 183, 209, 0.4));
189 | }
190 | }
191 |
192 | .hero-subtitle {
193 | font-size: clamp(1.1rem, 4vw, 1.8rem);
194 | color: rgba(255, 255, 255, 0.9);
195 | margin-bottom: 1rem;
196 | font-weight: 600;
197 | }
198 |
199 | .hero-description {
200 | font-size: clamp(0.95rem, 3vw, 1.2rem);
201 | color: rgba(255, 255, 255, 0.8);
202 | margin-bottom: 2rem;
203 | max-width: 600px;
204 | margin-left: auto;
205 | margin-right: auto;
206 | line-height: 1.7;
207 | }
208 |
209 | .hero-description p {
210 | margin: 0;
211 | margin-bottom: 0.5rem;
212 | }
213 |
214 | .hero-description p:last-child {
215 | margin-bottom: 0;
216 | }
217 |
218 | .hero-features {
219 | display: flex;
220 | flex-wrap: wrap;
221 | justify-content: center;
222 | align-items: center;
223 | /* display: grid; */
224 | /* grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); */
225 | gap: 1rem;
226 | margin-bottom: 2rem;
227 | width: 700px;
228 | margin-left: auto;
229 | margin-right: auto;
230 | }
231 |
232 | .feature-pill {
233 | background: rgba(255, 255, 255, 0.15);
234 | backdrop-filter: blur(10px);
235 | border: 1px solid rgba(255, 255, 255, 0.2);
236 | padding: 0.6rem 1rem;
237 | border-radius: 50px;
238 | color: white;
239 | font-weight: 500;
240 | font-size: 0.85rem;
241 | text-align: center;
242 | align-content: center;
243 | height: 60px;
244 | width: 160px;
245 | }
246 |
247 | .feature-pill:hover {
248 | background: rgba(255, 255, 255, 0.25);
249 | transform: translateY(-2px);
250 | box-shadow: 0 8px 25px rgba(255, 255, 255, 0.1);
251 | }
252 |
253 | .hero-actions {
254 | display: flex;
255 | align-items: center;
256 | justify-content: center;
257 | gap: 1rem;
258 | max-width: 800px;
259 | margin: 0 auto;
260 | }
261 |
262 | .primary-button, .secondary-button {
263 | display: flex;
264 | align-items: center;
265 | justify-content: center;
266 | gap: 0.5rem;
267 | padding: 1rem 2rem;
268 | border-radius: 50px;
269 | font-weight: 600;
270 | font-size: 1rem;
271 | border: none;
272 | cursor: pointer;
273 | text-decoration: none;
274 | width: 100%;
275 | max-width: 320px;
276 | }
277 |
278 | .primary-button {
279 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
280 | color: white;
281 | box-shadow: 0 8px 30px rgba(102, 126, 234, 0.4);
282 | border: 2px solid rgba(255, 255, 255, 0.3);
283 | }
284 |
285 | .primary-button:hover {
286 | transform: translateY(-3px);
287 | box-shadow: 0 12px 40px rgba(102, 126, 234, 0.6);
288 | }
289 |
290 | .secondary-button {
291 | background: rgba(255, 255, 255, 0.1);
292 | color: white;
293 | border: 2px solid rgba(255, 255, 255, 0.3);
294 | backdrop-filter: blur(10px);
295 | }
296 |
297 | .secondary-button:hover {
298 | background: rgba(255, 255, 255, 0.2);
299 | transform: translateY(-3px);
300 | box-shadow: 0 8px 25px rgba(255, 255, 255, 0.2);
301 | }
302 |
303 | .button-icon {
304 | font-size: 1.2rem;
305 | }
306 |
307 | /* Demo Section */
308 | .demo-section {
309 | background: linear-gradient(180deg, #f8f9ff 0%, #ffffff 100%);
310 | padding: 2rem;
311 | position: relative;
312 | width: 100vw;
313 | min-height: 100vh;
314 | }
315 |
316 | .demo-header {
317 | width: 100%;
318 | margin-bottom: 24px;
319 | }
320 |
321 | .demo-title {
322 | font-size: clamp(2rem, 5vw, 3rem);
323 | font-weight: 700;
324 | margin-bottom: 1rem;
325 | color: #1e293b;
326 | }
327 |
328 | .demo-title .gradient-text {
329 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
330 | -webkit-background-clip: text;
331 | -webkit-text-fill-color: transparent;
332 | background-clip: text;
333 | animation: none !important;
334 | filter: none !important;
335 | position: static !important;
336 | z-index: auto !important;
337 | }
338 |
339 | .demo-title .gradient-text::before,
340 | .demo-title .gradient-text::after {
341 | display: none !important;
342 | }
343 |
344 | .demo-description {
345 | font-size: clamp(1rem, 3vw, 1.2rem);
346 | color: #64748b;
347 | border-bottom: 1px solid #e5e7eb;
348 | padding-bottom: 18px;
349 | max-width: 700px;
350 | line-height: 1.6;
351 | }
352 |
353 | .demo-description p {
354 | margin: 0;
355 | margin-bottom: 0.5rem;
356 | }
357 |
358 | .demo-description p:last-child {
359 | margin-bottom: 0;
360 | }
361 |
362 |
363 |
364 | /* Table Wrapper */
365 | .table-wrapper {
366 | max-width: 1400px;
367 | margin: 0 auto;
368 | position: relative;
369 | }
370 |
371 | /* Table Container */
372 | .table-container {
373 | background: white;
374 | border-radius: 12px;
375 | box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
376 | border: 1px solid #e2e8f0;
377 | max-height: 70vh;
378 | overflow: auto;
379 | position: relative;
380 | }
381 |
382 | /* Table Styles */
383 | .demo-table {
384 | width: max-content;
385 | min-width: 100%;
386 | border-collapse: separate;
387 | border-spacing: 0;
388 | font-size: 0.9rem;
389 | table-layout: fixed;
390 | }
391 |
392 | .demo-thead {
393 | background: #f8fafc;
394 | position: sticky;
395 | top: 0;
396 | z-index: 10;
397 | }
398 |
399 | .demo-thead.sticky {
400 | position: sticky;
401 | top: 0;
402 | z-index: 10;
403 | }
404 |
405 | .demo-header {
406 | background: #f8fafc;
407 | color: #374151;
408 | padding: 1rem 0.75rem;
409 | font-weight: 600;
410 | text-align: left;
411 | position: relative;
412 | user-select: none;
413 | font-size: 0.875rem;
414 | text-transform: uppercase;
415 | letter-spacing: 0.05em;
416 | white-space: nowrap;
417 | overflow: hidden;
418 | text-overflow: ellipsis;
419 | box-sizing: border-box;
420 | }
421 |
422 | .demo-header.sticky {
423 | position: sticky;
424 | top: 0;
425 | z-index: 10;
426 | }
427 |
428 | .demo-header.dragging {
429 | opacity: 0.5;
430 | transform: scale(0.95);
431 | }
432 |
433 | .demo-header.hovered {
434 | background: #f1f5f9;
435 | }
436 |
437 | .header-content {
438 | display: flex;
439 | align-items: center;
440 | justify-content: space-between;
441 | width: 100%;
442 | min-width: 0; /* Allow content to shrink */
443 | max-width: 100%; /* Prevent content overflow */
444 | }
445 |
446 | .header-label {
447 | font-weight: 600;
448 | font-size: 0.875rem;
449 | text-transform: uppercase;
450 | letter-spacing: 0.05em;
451 | color: #374151;
452 | min-width: 0; /* Allow text to shrink */
453 | overflow: hidden;
454 | text-overflow: ellipsis;
455 | flex: 1; /* Take available space */
456 | }
457 |
458 | .header-actions {
459 | display: flex;
460 | align-items: center;
461 | gap: 0.5rem;
462 | flex-shrink: 0; /* Prevent actions from shrinking */
463 | }
464 |
465 | .sticky-toggle {
466 | background: none;
467 | border: none;
468 | cursor: pointer;
469 | font-size: 0.9rem;
470 | opacity: 0.6;
471 | transition: opacity 0.2s ease;
472 | padding: 0.25rem;
473 | border-radius: 3px;
474 | }
475 |
476 | .sticky-toggle:hover {
477 | opacity: 1;
478 | background: rgba(0, 0, 0, 0.05);
479 | }
480 |
481 | .sticky-toggle.active {
482 | opacity: 1;
483 | }
484 |
485 | /* Header cell borders */
486 | .demo-header.has-border {
487 | border-right: 1px solid #e5e7eb;
488 | }
489 |
490 | /* Sticky column styling */
491 | .demo-header.column-sticky {
492 | background: #f1f5f9;
493 | box-shadow: 2px 0 4px rgba(0, 0, 0, 0.1);
494 | min-width: 0; /* Prevent content overflow */
495 | }
496 |
497 | .demo-cell.cell-sticky {
498 | background: #f8fafc;
499 | box-shadow: 2px 0 4px rgba(0, 0, 0, 0.05);
500 | min-width: 0; /* Prevent content overflow */
501 | }
502 |
503 | /* Ensure sticky columns don't overlap */
504 | .demo-header.column-sticky,
505 | .demo-cell.cell-sticky {
506 | box-sizing: border-box;
507 | flex-shrink: 0;
508 | position: relative;
509 | }
510 |
511 | .demo-cell.has-border {
512 | border-right: 1px solid #f1f5f9;
513 | }
514 |
515 | .drag-indicator {
516 | opacity: 0.4;
517 | font-size: 0.875rem;
518 | cursor: grab;
519 | color: #9ca3af;
520 | }
521 |
522 | .demo-header:hover .drag-indicator {
523 | opacity: 0.8;
524 | }
525 |
526 | .resize-handle {
527 | position: absolute;
528 | right: 0;
529 | top: 0;
530 | width: 4px;
531 | height: 100%;
532 | cursor: col-resize;
533 | background: transparent;
534 | opacity: 0;
535 | }
536 |
537 | .demo-header:hover .resize-handle {
538 | opacity: 1;
539 | background: #d1d5db;
540 | }
541 |
542 | .resize-handle:hover {
543 | background: #9ca3af;
544 | }
545 |
546 | /* Table Body */
547 | .demo-tbody {
548 | background: white;
549 | }
550 |
551 | .demo-row {
552 | cursor: pointer;
553 | border-bottom: 1px solid #f1f5f9;
554 | }
555 |
556 | .demo-row:hover {
557 | background: #f8fafc;
558 | }
559 |
560 | .demo-row:last-child {
561 | border-bottom: none;
562 | }
563 |
564 | .demo-cell {
565 | padding: 0.875rem 0.75rem;
566 | vertical-align: middle;
567 | border-bottom: 1px solid #f1f5f9;
568 | white-space: nowrap;
569 | overflow: hidden;
570 | text-overflow: ellipsis;
571 | box-sizing: border-box;
572 | }
573 |
574 | /* Employee Info Styles */
575 | .employee-info {
576 | display: flex;
577 | align-items: center;
578 | gap: 0.75rem;
579 | }
580 |
581 | .employee-avatar {
582 | width: 36px;
583 | height: 36px;
584 | border-radius: 50%;
585 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
586 | display: flex;
587 | align-items: center;
588 | justify-content: center;
589 | color: white;
590 | font-weight: bold;
591 | font-size: 0.8rem;
592 | flex-shrink: 0;
593 | }
594 |
595 | .employee-details {
596 | min-width: 0;
597 | flex: 1;
598 | }
599 |
600 | .employee-name {
601 | font-weight: 600;
602 | color: #1e293b;
603 | margin-bottom: 0.25rem;
604 | font-size: 0.9rem;
605 | }
606 |
607 | .employee-position {
608 | font-size: 0.8rem;
609 | color: #64748b;
610 | }
611 |
612 | /* Department Badges */
613 | .department-badge {
614 | padding: 0.25rem 0.75rem;
615 | border-radius: 12px;
616 | font-size: 0.75rem;
617 | font-weight: 600;
618 | text-transform: uppercase;
619 | letter-spacing: 0.025em;
620 | white-space: nowrap;
621 | }
622 |
623 | .department-badge.engineering {
624 | background: #dbeafe;
625 | color: #1e40af;
626 | }
627 |
628 | .department-badge.design {
629 | background: #fce7f3;
630 | color: #be185d;
631 | }
632 |
633 | .department-badge.marketing {
634 | background: #fef3c7;
635 | color: #d97706;
636 | }
637 |
638 | .department-badge.sales {
639 | background: #d1fae5;
640 | color: #059669;
641 | }
642 |
643 | .department-badge.hr {
644 | background: #e9d5ff;
645 | color: #7c3aed;
646 | }
647 |
648 | /* Salary Cell */
649 | .salary-cell {
650 | font-weight: 600;
651 | color: #059669;
652 | font-family: 'SF Mono', 'Monaco', 'Inconsolata', monospace;
653 | font-size: 0.875rem;
654 | }
655 |
656 | /* Location Info */
657 | .location-info {
658 | display: flex;
659 | align-items: center;
660 | gap: 0.5rem;
661 | font-size: 0.875rem;
662 | color: #64748b;
663 | }
664 |
665 | .location-icon {
666 | font-size: 0.875rem;
667 | flex-shrink: 0;
668 | }
669 |
670 | /* Experience Cell */
671 | .experience-cell {
672 | color: #64748b;
673 | font-weight: 500;
674 | font-size: 0.875rem;
675 | }
676 |
677 | /* Status Badge */
678 | .status-badge {
679 | width: fit-content;
680 | display: flex;
681 | align-items: center;
682 | gap: 0.375rem;
683 | padding: 0.25rem 0.75rem;
684 | border-radius: 12px;
685 | font-size: 0.75rem;
686 | font-weight: 600;
687 | white-space: nowrap;
688 | }
689 |
690 | .status-badge.active {
691 | background: #d1fae5;
692 | color: #059669;
693 | }
694 |
695 | .status-dot {
696 | width: 6px;
697 | height: 6px;
698 | border-radius: 50%;
699 | background: currentColor;
700 | flex-shrink: 0;
701 | }
702 |
703 | /* Features Section */
704 | .features-section {
705 | background: linear-gradient(180deg, #ffffff 0%, #f8f9ff 100%);
706 | padding: 4rem 2rem;
707 | width: 100vw;
708 | }
709 |
710 | .features-title {
711 | text-align: center;
712 | font-size: clamp(2rem, 6vw, 4rem);
713 | font-weight: 800;
714 | margin-bottom: 3rem;
715 | }
716 |
717 | .features-title .gradient-text {
718 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
719 | -webkit-background-clip: text;
720 | -webkit-text-fill-color: transparent;
721 | background-clip: text;
722 | animation: none !important;
723 | filter: none !important;
724 | position: static !important;
725 | z-index: auto !important;
726 | }
727 |
728 | .features-title .gradient-text::before,
729 | .features-title .gradient-text::after {
730 | display: none !important;
731 | }
732 |
733 | .features-grid {
734 | display: grid;
735 | grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
736 | gap: 1.5rem;
737 | max-width: 1100px;
738 | margin: 0 auto;
739 | }
740 |
741 | .feature-card {
742 | background: white;
743 | padding: 2rem;
744 | border-radius: 20px;
745 | text-align: center;
746 | box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
747 | border: 1px solid rgba(102, 126, 234, 0.1);
748 | }
749 |
750 | .feature-card:hover {
751 | transform: translateY(-10px);
752 | box-shadow: 0 20px 60px rgba(102, 126, 234, 0.15);
753 | }
754 |
755 | .feature-icon {
756 | font-size: clamp(2rem, 6vw, 3rem);
757 | margin-bottom: 1rem;
758 | display: block;
759 | }
760 |
761 | .feature-card h3 {
762 | font-size: clamp(1.2rem, 4vw, 1.5rem);
763 | font-weight: 700;
764 | margin-bottom: 1rem;
765 | color: #1e293b;
766 | }
767 |
768 | .feature-card p {
769 | color: #64748b;
770 | line-height: 1.7;
771 | font-size: clamp(0.9rem, 3vw, 1rem);
772 | }
773 |
774 | /* Footer */
775 | .footer {
776 | background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
777 | color: white;
778 | text-align: center;
779 | padding: 2rem 1rem;
780 | width: 100vw;
781 | }
782 |
783 | .footer p {
784 | margin-bottom: 1rem;
785 | opacity: 0.9;
786 | font-size: clamp(0.85rem, 3vw, 1rem);
787 | }
788 |
789 | .footer-link {
790 | color: #67e8f9;
791 | text-decoration: none;
792 | font-weight: 500;
793 | }
794 |
795 | .footer-link:hover {
796 | color: #22d3ee;
797 | }
798 |
799 | .footer-version {
800 | color: #94a3b8;
801 | font-family: 'SF Mono', 'Monaco', 'Inconsolata', monospace;
802 | }
803 |
804 | /* Mobile Specific Improvements */
805 | @media (max-width: 768px) {
806 | .hero-section {
807 | min-height: auto;
808 | padding: 3rem 1rem;
809 | }
810 |
811 | .hero-content {
812 | padding: 0;
813 | }
814 |
815 | .hero-badge {
816 | gap: 0.5rem;
817 | }
818 |
819 | .version-badge, .new-badge {
820 | padding: 0.4rem 0.8rem;
821 | font-size: 0.8rem;
822 | }
823 |
824 | .hero-description {
825 | margin-bottom: 1.5rem;
826 | }
827 |
828 | .hero-features {
829 | grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
830 | gap: 0.5rem;
831 | margin-bottom: 1.5rem;
832 | }
833 |
834 | .feature-pill {
835 | padding: 0.5rem 0.8rem;
836 | font-size: 0.75rem;
837 | }
838 |
839 | .demo-section {
840 | padding: 3rem 1rem;
841 | }
842 |
843 | .table-container {
844 | border-radius: 8px;
845 | max-height: 60vh;
846 | margin: 0 1rem;
847 | }
848 |
849 | .demo-table {
850 | min-width: 600px;
851 | }
852 |
853 | .demo-header {
854 | padding: 0.75rem 0.5rem;
855 | font-size: 0.75rem;
856 | }
857 |
858 | .demo-cell {
859 | padding: 0.75rem 0.5rem;
860 | }
861 |
862 | .employee-avatar {
863 | width: 32px;
864 | height: 32px;
865 | font-size: 0.75rem;
866 | }
867 |
868 | .employee-info {
869 | gap: 0.5rem;
870 | }
871 |
872 | .features-section {
873 | padding: 3rem 1rem;
874 | }
875 |
876 | .features-grid {
877 | grid-template-columns: 1fr;
878 | gap: 1rem;
879 | }
880 |
881 | .feature-card {
882 | padding: 1.5rem;
883 | }
884 |
885 | .footer {
886 | padding: 1.5rem 1rem;
887 | }
888 | }
889 |
890 | @media (max-width: 480px) {
891 | .hero-section {
892 | padding: 2rem 0.5rem;
893 | }
894 |
895 | .hero-features {
896 | grid-template-columns: repeat(2, 1fr);
897 | }
898 |
899 | .demo-section, .features-section {
900 | padding: 2rem 0.5rem;
901 | }
902 |
903 | .table-container {
904 | border-radius: 6px;
905 | max-height: 50vh;
906 | margin: 0 0.5rem;
907 | }
908 |
909 | .demo-table {
910 | min-width: 500px;
911 | }
912 |
913 | .demo-header {
914 | padding: 0.5rem 0.375rem;
915 | }
916 |
917 | .demo-cell {
918 | padding: 0.5rem 0.375rem;
919 | }
920 |
921 | .employee-info {
922 | flex-direction: row;
923 | align-items: center;
924 | }
925 |
926 | .employee-details {
927 | text-align: left;
928 | }
929 |
930 | .location-info {
931 | flex-direction: column;
932 | align-items: flex-start;
933 | gap: 0.2rem;
934 | }
935 | }
936 |
937 | /* Scrollbar Styling */
938 | .table-container::-webkit-scrollbar {
939 | width: 6px;
940 | height: 6px;
941 | }
942 |
943 | .table-container::-webkit-scrollbar-track {
944 | background: #f1f5f9;
945 | }
946 |
947 | .table-container::-webkit-scrollbar-thumb {
948 | background: #cbd5e1;
949 | border-radius: 10px;
950 | }
951 |
952 | .table-container::-webkit-scrollbar-thumb:hover {
953 | background: #94a3b8;
954 | }
955 |
956 | /* Hidden columns dropdown */
957 | .hidden-columns-dropdown {
958 | position: absolute;
959 | top: -40px;
960 | right: 0;
961 | z-index: 9999;
962 | }
963 |
964 | /* Portal dropdown base styles */
965 | .dropdown-portal {
966 | background: white;
967 | border: 1px solid #e2e8f0;
968 | border-radius: 8px;
969 | box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
970 | overflow: hidden;
971 | }
972 |
973 | .hidden-columns-toggle {
974 | background: rgba(255, 255, 255, 0.95);
975 | border: 1px solid #e0e0e0;
976 | border-radius: 8px;
977 | padding: 8px 12px;
978 | font-size: 12px;
979 | font-weight: 500;
980 | color: #666;
981 | cursor: pointer;
982 | transition: all 0.2s ease;
983 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
984 | }
985 |
986 | .hidden-columns-toggle:hover:not(.disabled) {
987 | background: white;
988 | border-color: #667eea;
989 | color: #667eea;
990 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
991 | }
992 |
993 | .hidden-columns-toggle.disabled {
994 | background: rgba(255, 255, 255, 0.7);
995 | border-color: #e0e0e0;
996 | color: #999;
997 | cursor: not-allowed;
998 | opacity: 0.6;
999 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
1000 | }
1001 |
1002 | .hidden-columns-toggle:disabled {
1003 | cursor: not-allowed;
1004 | }
1005 |
1006 | .hidden-columns-menu {
1007 | position: absolute;
1008 | top: 100%;
1009 | right: 0;
1010 | margin-top: 4px;
1011 | background: white;
1012 | border: 1px solid #e0e0e0;
1013 | border-radius: 8px;
1014 | box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
1015 | min-width: 130px;
1016 | z-index: 10000;
1017 | overflow: hidden;
1018 | }
1019 |
1020 | .hidden-column-item {
1021 | display: block;
1022 | width: 100%;
1023 | padding: 10px 12px;
1024 | background: none;
1025 | border: none;
1026 | text-align: left;
1027 | font-size: 13px;
1028 | color: #333;
1029 | cursor: pointer;
1030 | transition: background-color 0.2s ease;
1031 | }
1032 |
1033 | .hidden-column-item:hover {
1034 | background-color: #f8f9fa;
1035 | }
1036 |
1037 | .hidden-column-item:not(:last-child) {
1038 | border-bottom: 1px solid #f0f0f0;
1039 | }
1040 |
1041 | /* Kebab menu styles */
1042 | .kebab-menu {
1043 | position: relative;
1044 | display: inline-block;
1045 | }
1046 |
1047 | .kebab-toggle {
1048 | background: none;
1049 | border: none;
1050 | font-size: 14px;
1051 | color: #666;
1052 | cursor: pointer;
1053 | padding: 4px 6px;
1054 | border-radius: 4px;
1055 | transition: all 0.2s ease;
1056 | line-height: 1;
1057 | }
1058 |
1059 | .kebab-toggle:hover {
1060 | background-color: rgba(102, 126, 234, 0.1);
1061 | color: #667eea;
1062 | }
1063 |
1064 | .kebab-dropdown {
1065 | position: absolute;
1066 | top: 100%;
1067 | right: 0;
1068 | margin-top: 4px;
1069 | background: white;
1070 | border: 1px solid #e0e0e0;
1071 | border-radius: 8px;
1072 | box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
1073 | min-width: 120px;
1074 | z-index: 10000;
1075 | overflow: hidden;
1076 | }
1077 |
1078 | .kebab-item {
1079 | display: block;
1080 | width: 100%;
1081 | padding: 8px 12px;
1082 | background: none;
1083 | border: none;
1084 | text-align: left;
1085 | font-size: 13px;
1086 | color: #333;
1087 | cursor: pointer;
1088 | transition: background-color 0.2s ease;
1089 | }
1090 |
1091 | .kebab-item:hover {
1092 | background-color: #f8f9fa;
1093 | }
1094 |
1095 | .kebab-item:not(:last-child) {
1096 | border-bottom: 1px solid #f0f0f0;
1097 | }
1098 |
1099 | /* Update header actions to accommodate kebab menu */
1100 | .header-actions {
1101 | display: flex;
1102 | align-items: center;
1103 | gap: 4px;
1104 | }
1105 |
1106 | /* Remove old sticky-toggle styles since we're using kebab menu now */
1107 | .sticky-toggle {
1108 | display: none;
1109 | }
--------------------------------------------------------------------------------