uifolio 0.5.4 → 0.5.5

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/README.md CHANGED
@@ -90,7 +90,7 @@ only marks the installation as complete when that is relevant.
90
90
  | ResizablePanels | `src/components/ui/resizable.tsx` | horizontal/vertical groups, handles, min-size, keyboard |
91
91
  | OtpInput | `src/components/ui/otp-input.tsx` | 4 fields, paste, rotate + success check |
92
92
  | ColorPicker | `src/components/ui/color-picker.tsx` | advanced panel, saturation/lightness, hue, alpha, eyedropper, RGB channels, presets, HEX/RGB/HSL/RGBA |
93
- | Table | `src/components/ui/table.tsx` | typed data table, uniform `px-4` cells and a wider gap before Actions, selection and row actions |
93
+ | Table | `src/components/ui/table.tsx` | shadcn-style typed table, uniform `px-4` cells and a wider gap before Actions, select-all checkbox, row actions |
94
94
 
95
95
  Copyable helpers (the CLI follows them automatically): `src/lib/utils.ts`,
96
96
  `src/lib/video.ts`, `src/lib/vtt.ts`, `src/lib/date.ts`, `src/lib/uploader.ts`, `src/lib/color.ts`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uifolio",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "description": "CLI that copies React components (calendar, video/audio players, OTP, editor, upload) and their helpers into your project, with Tailwind CSS 4 tokens.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import type { ReactNode } from 'react'
3
+ import { useEffect, useRef, type ReactNode } from 'react'
4
4
  import { cn } from '@/lib/utils'
5
5
  import { useControllableValue } from '@/lib/use-controllable'
6
6
 
@@ -8,7 +8,7 @@ export interface TableColumn<T> {
8
8
  /** Stable identifier, also used as the React key of the column. */
9
9
  key: string
10
10
  header: ReactNode
11
- cell: (row: T) => ReactNode
11
+ cell: (row: T, index: number) => ReactNode
12
12
  /** Applied to the header and body cells: width, nowrap… */
13
13
  className?: string
14
14
  align?: 'left' | 'center' | 'right'
@@ -36,9 +36,10 @@ function alignClass(align: TableColumn<unknown>['align']): string {
36
36
  }
37
37
 
38
38
  /**
39
- * Data table with a shadcn-like API: typed columns, optional selection and a
40
- * dedicated actions column. Every cell on the left shares the same `px-4`
41
- * padding; the actions column keeps a wider `pl-10` gap with the last column.
39
+ * Data table with a shadcn-like API: typed columns, an optional select-all
40
+ * checkbox, and a dedicated actions column. Every cell on the left shares the
41
+ * same `px-4` padding; the actions column keeps a wider `pl-10` gap with the
42
+ * last column.
42
43
  */
43
44
  export function Table<T>({
44
45
  columns,
@@ -59,6 +60,17 @@ export function Table<T>({
59
60
  defaultSelectedKeys,
60
61
  onSelectionChange,
61
62
  )
63
+ const selectAllRef = useRef<HTMLInputElement>(null)
64
+
65
+ const keys = rows.map((row) => String(rowKey(row)))
66
+ const allSelected = keys.length > 0 && keys.every((key) => selection.includes(key))
67
+ const someSelected = keys.some((key) => selection.includes(key))
68
+
69
+ useEffect(() => {
70
+ if (selectAllRef.current) {
71
+ selectAllRef.current.indeterminate = someSelected && !allSelected
72
+ }
73
+ }, [someSelected, allSelected])
62
74
 
63
75
  const toggle = (key: string, checked: boolean) => {
64
76
  setSelection((current) =>
@@ -70,13 +82,29 @@ export function Table<T>({
70
82
  )
71
83
  }
72
84
 
73
- const columnsCount = columns.length + (actions ? 1 : 0)
85
+ const toggleAll = (checked: boolean) => {
86
+ setSelection(checked ? keys : [])
87
+ }
88
+
89
+ const columnsCount = columns.length + (selectable ? 1 : 0) + (actions ? 1 : 0)
74
90
 
75
91
  return (
76
92
  <div className={cn('w-full overflow-x-auto rounded-sm border bg-card', className)}>
77
93
  <table className="w-full min-w-[860px] border-collapse text-left text-xs">
78
94
  <thead>
79
95
  <tr className="border-b bg-muted/50">
96
+ {selectable && (
97
+ <th scope="col" className="w-10 px-4 py-3.5">
98
+ <input
99
+ ref={selectAllRef}
100
+ type="checkbox"
101
+ checked={allSelected}
102
+ onChange={(event) => toggleAll(event.target.checked)}
103
+ aria-label="Select all rows"
104
+ className="size-4 cursor-pointer rounded-sm border-border accent-primary"
105
+ />
106
+ </th>
107
+ )}
80
108
  {columns.map((column) => (
81
109
  <th
82
110
  key={column.key}
@@ -108,7 +136,7 @@ export function Table<T>({
108
136
  </td>
109
137
  </tr>
110
138
  ) : (
111
- rows.map((row) => {
139
+ rows.map((row, rowIndex) => {
112
140
  const key = String(rowKey(row))
113
141
  const selected = selection.includes(key)
114
142
 
@@ -122,7 +150,19 @@ export function Table<T>({
122
150
  selected ? 'bg-primary/5' : 'hover:bg-muted/30',
123
151
  )}
124
152
  >
125
- {columns.map((column, index) => (
153
+ {selectable && (
154
+ <td className="w-10 px-4 py-3.5 align-middle">
155
+ <input
156
+ type="checkbox"
157
+ checked={selected}
158
+ onChange={(event) => toggle(key, event.target.checked)}
159
+ onClick={(event) => event.stopPropagation()}
160
+ aria-label={`Select row ${key}`}
161
+ className="size-4 cursor-pointer rounded-sm border-border accent-primary"
162
+ />
163
+ </td>
164
+ )}
165
+ {columns.map((column) => (
126
166
  <td
127
167
  key={column.key}
128
168
  className={cn(
@@ -131,21 +171,7 @@ export function Table<T>({
131
171
  column.className,
132
172
  )}
133
173
  >
134
- {selectable && index === 0 ? (
135
- <div className="flex items-center gap-3">
136
- <input
137
- type="checkbox"
138
- checked={selected}
139
- onChange={(event) => toggle(key, event.target.checked)}
140
- onClick={(event) => event.stopPropagation()}
141
- aria-label={`Select row ${key}`}
142
- className="size-4 shrink-0 cursor-pointer rounded-sm border-border accent-primary"
143
- />
144
- {column.cell(row)}
145
- </div>
146
- ) : (
147
- column.cell(row)
148
- )}
174
+ {column.cell(row, rowIndex)}
149
175
  </td>
150
176
  ))}
151
177
  {actions && (