@harismawan/stamp-ui 0.1.1 → 0.2.0

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.
@@ -0,0 +1,273 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import styled from 'styled-components';
4
+ import { Search } from 'lucide-react';
5
+ const defaultFilter = (item, query) => {
6
+ const q = query.trim().toLowerCase();
7
+ if (!q)
8
+ return true;
9
+ if (item.label.toLowerCase().includes(q))
10
+ return true;
11
+ return (item.keywords ?? []).some((k) => k.toLowerCase().includes(q));
12
+ };
13
+ const Overlay = styled.div `
14
+ position: fixed;
15
+ inset: 0;
16
+ background: ${(p) => p.theme.colors.overlay};
17
+ display: flex;
18
+ justify-content: center;
19
+ align-items: flex-start;
20
+ z-index: 50;
21
+ padding: ${(p) => p.theme.space[9]} ${(p) => p.theme.space[5]} ${(p) => p.theme.space[5]};
22
+ animation: cfade 120ms ${(p) => p.theme.easing.out};
23
+
24
+ @keyframes cfade {
25
+ from {
26
+ opacity: 0;
27
+ }
28
+ to {
29
+ opacity: 1;
30
+ }
31
+ }
32
+ `;
33
+ const Panel = styled.div `
34
+ background: ${(p) => p.theme.colors.surface};
35
+ border: 2px solid ${(p) => p.theme.colors.border};
36
+ border-radius: ${(p) => p.theme.radii.lg};
37
+ box-shadow: ${(p) => p.theme.shadow.stampLg};
38
+ width: 100%;
39
+ max-width: 560px;
40
+ display: flex;
41
+ flex-direction: column;
42
+ overflow: hidden;
43
+ animation: crise 140ms ${(p) => p.theme.easing.out};
44
+
45
+ @keyframes crise {
46
+ from {
47
+ transform: translate(-4px, -4px);
48
+ }
49
+ to {
50
+ transform: none;
51
+ }
52
+ }
53
+ `;
54
+ const SearchRow = styled.div `
55
+ display: flex;
56
+ align-items: center;
57
+ gap: ${(p) => p.theme.space[3]};
58
+ padding: ${(p) => p.theme.space[4]} ${(p) => p.theme.space[5]};
59
+ border-bottom: 2px solid ${(p) => p.theme.colors.border};
60
+ color: ${(p) => p.theme.colors.textSubtle};
61
+ `;
62
+ const SearchInput = styled.input `
63
+ font-family: ${(p) => p.theme.font.body};
64
+ font-size: 1rem;
65
+ font-weight: 600;
66
+ width: 100%;
67
+ min-width: 0;
68
+ background: transparent;
69
+ color: ${(p) => p.theme.colors.text};
70
+ border: none;
71
+ padding: 0;
72
+
73
+ &::placeholder {
74
+ color: ${(p) => p.theme.colors.textSubtle};
75
+ }
76
+
77
+ &:focus {
78
+ outline: none;
79
+ }
80
+ `;
81
+ const List = styled.ul `
82
+ list-style: none;
83
+ margin: 0;
84
+ padding: ${(p) => p.theme.space[2]};
85
+ max-height: 340px;
86
+ overflow-y: auto;
87
+ display: flex;
88
+ flex-direction: column;
89
+ gap: ${(p) => p.theme.space[1]};
90
+ `;
91
+ const GroupLabel = styled.div `
92
+ font-family: ${(p) => p.theme.font.body};
93
+ font-size: 0.75rem;
94
+ font-weight: 800;
95
+ text-transform: uppercase;
96
+ letter-spacing: 0.04em;
97
+ color: ${(p) => p.theme.colors.textSubtle};
98
+ padding: ${(p) => p.theme.space[2]} ${(p) => p.theme.space[3]} ${(p) => p.theme.space[1]};
99
+ `;
100
+ const Option = styled.li `
101
+ display: flex;
102
+ align-items: center;
103
+ gap: ${(p) => p.theme.space[3]};
104
+ font-family: ${(p) => p.theme.font.body};
105
+ font-size: 14px;
106
+ font-weight: 700;
107
+ color: ${(p) => p.theme.colors.text};
108
+ background: ${(p) => (p.$active ? p.theme.colors.primarySoft : 'transparent')};
109
+ border-radius: ${(p) => p.theme.radii.sm};
110
+ padding: ${(p) => p.theme.space[2]} ${(p) => p.theme.space[3]};
111
+ cursor: ${(p) => (p.$disabled ? 'not-allowed' : 'pointer')};
112
+ user-select: none;
113
+ opacity: ${(p) => (p.$disabled ? 0.55 : 1)};
114
+ transition: background 80ms ${(p) => p.theme.easing.out};
115
+ `;
116
+ const OptionIcon = styled.span `
117
+ display: grid;
118
+ place-items: center;
119
+ color: ${(p) => p.theme.colors.textMuted};
120
+ `;
121
+ const OptionLabel = styled.span `
122
+ flex: 1;
123
+ min-width: 0;
124
+ overflow: hidden;
125
+ text-overflow: ellipsis;
126
+ white-space: nowrap;
127
+ `;
128
+ const Shortcut = styled.span `
129
+ font-family: ${(p) => p.theme.font.mono};
130
+ font-size: 0.75rem;
131
+ font-weight: 600;
132
+ color: ${(p) => p.theme.colors.textSubtle};
133
+ `;
134
+ const Empty = styled.div `
135
+ font-family: ${(p) => p.theme.font.body};
136
+ font-size: 14px;
137
+ font-weight: 600;
138
+ color: ${(p) => p.theme.colors.textSubtle};
139
+ padding: ${(p) => p.theme.space[3]};
140
+ text-align: center;
141
+ `;
142
+ const Group = styled.li `
143
+ list-style: none;
144
+ `;
145
+ const GroupOptions = styled.ul `
146
+ list-style: none;
147
+ margin: 0;
148
+ padding: 0;
149
+ display: flex;
150
+ flex-direction: column;
151
+ gap: ${(p) => p.theme.space[1]};
152
+ `;
153
+ export function Command({ open, onClose, items, placeholder, emptyText = 'No results', filter = defaultFilter, }) {
154
+ const [query, setQuery] = React.useState('');
155
+ const [activeIndex, setActiveIndex] = React.useState(0);
156
+ const inputRef = React.useRef(null);
157
+ const baseId = React.useId();
158
+ const listId = `${baseId}-list`;
159
+ // Reset query + active index whenever the palette transitions to open.
160
+ React.useEffect(() => {
161
+ if (open) {
162
+ setQuery('');
163
+ setActiveIndex(0);
164
+ }
165
+ }, [open]);
166
+ // Autofocus the input when opened.
167
+ React.useEffect(() => {
168
+ if (open && inputRef.current) {
169
+ inputRef.current.focus();
170
+ }
171
+ }, [open]);
172
+ // Escape closes via document keydown (matches Modal).
173
+ React.useEffect(() => {
174
+ if (!open)
175
+ return;
176
+ const onKey = (e) => {
177
+ if (e.key === 'Escape')
178
+ onClose();
179
+ };
180
+ document.addEventListener('keydown', onKey);
181
+ return () => document.removeEventListener('keydown', onKey);
182
+ }, [open, onClose]);
183
+ const visible = React.useMemo(() => items.filter((item) => filter(item, query)), [items, query, filter]);
184
+ // Build the render structure: ungrouped first, then groups in first-appearance
185
+ // order. `ordered` flattens this in the SAME order rows are rendered, so keyboard
186
+ // navigation order matches the visual order.
187
+ const { ungrouped, groups, ordered } = React.useMemo(() => {
188
+ const groupOrder = [];
189
+ const byGroup = new Map();
190
+ const ungroupedItems = [];
191
+ for (const item of visible) {
192
+ if (item.group == null) {
193
+ ungroupedItems.push(item);
194
+ }
195
+ else {
196
+ let bucket = byGroup.get(item.group);
197
+ if (bucket == null) {
198
+ bucket = [];
199
+ byGroup.set(item.group, bucket);
200
+ groupOrder.push(item.group);
201
+ }
202
+ bucket.push(item);
203
+ }
204
+ }
205
+ const groupRows = groupOrder.map((group, i) => ({
206
+ group,
207
+ labelId: `${baseId}-group-${i}`,
208
+ items: byGroup.get(group) ?? [],
209
+ }));
210
+ const orderedItems = [...ungroupedItems];
211
+ for (const row of groupRows)
212
+ orderedItems.push(...row.items);
213
+ return { ungrouped: ungroupedItems, groups: groupRows, ordered: orderedItems };
214
+ }, [visible, baseId]);
215
+ // Navigable items are the non-disabled ones, in rendered (visual) order.
216
+ const navigable = React.useMemo(() => ordered.filter((i) => !i.disabled), [ordered]);
217
+ // O(1) lookup from item -> its index within `navigable` (used per rendered option).
218
+ const navIndexByItem = React.useMemo(() => {
219
+ const map = new Map();
220
+ navigable.forEach((item, i) => map.set(item, i));
221
+ return map;
222
+ }, [navigable]);
223
+ // Keep activeIndex within the navigable range when the filtered set changes.
224
+ React.useEffect(() => {
225
+ setActiveIndex((prev) => {
226
+ if (navigable.length === 0)
227
+ return 0;
228
+ return Math.min(prev, navigable.length - 1);
229
+ });
230
+ }, [navigable.length]);
231
+ const activeId = navigable.length > 0 ? `${baseId}-opt-${navigable[activeIndex]?.id}` : undefined;
232
+ const selectItem = (item) => {
233
+ if (item.disabled)
234
+ return;
235
+ item.onSelect();
236
+ onClose();
237
+ };
238
+ const handleKeyDown = (e) => {
239
+ if (navigable.length === 0)
240
+ return;
241
+ if (e.key === 'ArrowDown') {
242
+ e.preventDefault();
243
+ setActiveIndex((prev) => (prev + 1) % navigable.length);
244
+ }
245
+ else if (e.key === 'ArrowUp') {
246
+ e.preventDefault();
247
+ setActiveIndex((prev) => (prev - 1 + navigable.length) % navigable.length);
248
+ }
249
+ else if (e.key === 'Enter') {
250
+ e.preventDefault();
251
+ const item = navigable[activeIndex];
252
+ if (item)
253
+ selectItem(item);
254
+ }
255
+ };
256
+ const renderOption = (item) => {
257
+ const navIndex = navIndexByItem.get(item) ?? -1;
258
+ const isActive = navIndex !== -1 && navIndex === activeIndex;
259
+ return (_jsxs(Option, { id: `${baseId}-opt-${item.id}`, role: "option", "aria-selected": isActive, "aria-disabled": item.disabled || undefined, "$active": isActive, "$disabled": !!item.disabled, onMouseDown: (e) => {
260
+ // Prevent the input from losing focus on click.
261
+ e.preventDefault();
262
+ }, onClick: () => selectItem(item), onMouseEnter: () => {
263
+ if (navIndex !== -1)
264
+ setActiveIndex(navIndex);
265
+ }, children: [item.icon != null && _jsx(OptionIcon, { "aria-hidden": "true", children: item.icon }), _jsx(OptionLabel, { children: item.label }), item.shortcut != null && _jsx(Shortcut, { children: item.shortcut })] }, item.id));
266
+ };
267
+ if (!open)
268
+ return null;
269
+ return (_jsx(Overlay, { onMouseDown: () => onClose(), children: _jsxs(Panel, { onMouseDown: (e) => e.stopPropagation(), children: [_jsxs(SearchRow, { children: [_jsx(Search, { size: 18, strokeWidth: 2.5, "aria-hidden": "true" }), _jsx(SearchInput, { ref: inputRef, type: "text", role: "combobox", "aria-expanded": true, "aria-controls": listId, "aria-activedescendant": activeId, "aria-autocomplete": "list", autoComplete: "off", placeholder: placeholder ?? 'Type a command...', value: query, onChange: (e) => {
270
+ setQuery(e.target.value);
271
+ setActiveIndex(0);
272
+ }, onKeyDown: handleKeyDown })] }), _jsxs(List, { id: listId, role: "listbox", children: [ungrouped.map((item) => renderOption(item)), groups.map(({ group, labelId, items: groupItems }) => (_jsxs(Group, { role: "group", "aria-labelledby": labelId, children: [_jsx(GroupLabel, { id: labelId, children: group }), _jsx(GroupOptions, { children: groupItems.map((item) => renderOption(item)) })] }, labelId)))] }), ordered.length === 0 && _jsx(Empty, { role: "status", children: emptyText })] }) }));
273
+ }
@@ -0,0 +1,37 @@
1
+ import * as React from 'react';
2
+ export interface DataTableColumn<T> {
3
+ key: string;
4
+ header: React.ReactNode;
5
+ /** Default: `String(row[key])`. */
6
+ render?: (row: T) => React.ReactNode;
7
+ sortable?: boolean;
8
+ /**
9
+ * Default: `row[key]`. A column sorts numerically when every present value is
10
+ * a number (or a numeric string); missing values (null/undefined/empty) sort
11
+ * to the end in both directions. Any non-numeric present value falls back to a
12
+ * locale string comparison for the whole column.
13
+ */
14
+ sortAccessor?: (row: T) => string | number;
15
+ align?: 'left' | 'right' | 'center';
16
+ width?: string;
17
+ }
18
+ export interface DataTableSort {
19
+ key: string;
20
+ dir: 'asc' | 'desc';
21
+ }
22
+ export interface DataTableProps<T> {
23
+ columns: DataTableColumn<T>[];
24
+ data: T[];
25
+ rowKey: (row: T) => string;
26
+ selectable?: boolean;
27
+ selectedKeys?: string[];
28
+ defaultSelectedKeys?: string[];
29
+ onSelectionChange?: (keys: string[]) => void;
30
+ /** Enables internal pagination. */
31
+ pageSize?: number;
32
+ defaultSort?: DataTableSort;
33
+ onSortChange?: (sort: DataTableSort | null) => void;
34
+ emptyText?: React.ReactNode;
35
+ caption?: string;
36
+ }
37
+ export declare function DataTable<T>(props: DataTableProps<T>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,229 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import styled from 'styled-components';
4
+ import { ChevronUp, ChevronDown } from 'lucide-react';
5
+ import { Table, THead, TBody, Tr, Th, Td } from './Table';
6
+ import { Checkbox } from './Checkbox';
7
+ import { Pagination } from './Pagination';
8
+ const SortButton = styled.button `
9
+ display: inline-flex;
10
+ align-items: center;
11
+ gap: ${(p) => p.theme.space[1]};
12
+ width: 100%;
13
+ justify-content: ${(p) => p.$align === 'right' ? 'flex-end' : p.$align === 'center' ? 'center' : 'flex-start'};
14
+ font-family: inherit;
15
+ font-size: inherit;
16
+ font-weight: 800;
17
+ color: ${(p) => p.theme.colors.text};
18
+ background: transparent;
19
+ border: none;
20
+ padding: 0;
21
+ cursor: pointer;
22
+ transition: color 80ms ${(p) => p.theme.easing.out};
23
+
24
+ &:focus-visible {
25
+ outline: 2px solid ${(p) => p.theme.colors.accent};
26
+ outline-offset: 2px;
27
+ }
28
+ `;
29
+ const FooterCell = styled.td `
30
+ padding: ${(p) => p.theme.space[3]};
31
+ border-top: 2px solid ${(p) => p.theme.colors.border};
32
+ `;
33
+ const FooterInner = styled.div `
34
+ display: flex;
35
+ justify-content: flex-end;
36
+ `;
37
+ const Caption = styled.caption `
38
+ caption-side: top;
39
+ text-align: left;
40
+ font-family: ${(p) => p.theme.font.body};
41
+ font-weight: 800;
42
+ color: ${(p) => p.theme.colors.text};
43
+ padding: ${(p) => p.theme.space[2]} ${(p) => p.theme.space[3]};
44
+ `;
45
+ function defaultAccessor(column) {
46
+ if (column.sortAccessor)
47
+ return column.sortAccessor;
48
+ return (row) => {
49
+ const v = row[column.key];
50
+ return typeof v === 'number' ? v : String(v ?? '');
51
+ };
52
+ }
53
+ function defaultRender(column, row) {
54
+ if (column.render)
55
+ return column.render(row);
56
+ return String(row[column.key] ?? '');
57
+ }
58
+ /** A value that should sort to the end of the column regardless of direction. */
59
+ function isMissing(v) {
60
+ return v === '' || (typeof v === 'number' && Number.isNaN(v));
61
+ }
62
+ /**
63
+ * Parses a sort value to a finite number, or `null` if it is not numeric.
64
+ * Numeric strings (e.g. `"10"`) are accepted so a column backed by strings of
65
+ * digits still sorts numerically.
66
+ */
67
+ function toNumber(v) {
68
+ if (typeof v === 'number')
69
+ return Number.isFinite(v) ? v : null;
70
+ if (v.trim() === '')
71
+ return null;
72
+ const n = Number(v);
73
+ return Number.isFinite(n) ? n : null;
74
+ }
75
+ /**
76
+ * A column is treated as numeric when at least one present value is numeric and
77
+ * no present value is non-numeric. Missing values (null/undefined/empty) do not
78
+ * disqualify numeric mode — they simply sort to the end.
79
+ */
80
+ function isNumericColumn(values) {
81
+ let sawNumber = false;
82
+ for (const v of values) {
83
+ if (isMissing(v))
84
+ continue;
85
+ if (toNumber(v) === null)
86
+ return false;
87
+ sawNumber = true;
88
+ }
89
+ return sawNumber;
90
+ }
91
+ /**
92
+ * Compares two sort values. In numeric mode both are coerced to numbers and
93
+ * missing values are pushed to the end (always last, independent of asc/desc).
94
+ * Otherwise a case-insensitive locale comparison is used.
95
+ */
96
+ function compareValues(a, b, numeric) {
97
+ const aMissing = isMissing(a);
98
+ const bMissing = isMissing(b);
99
+ // Missing values always sort last; this branch is direction-independent and
100
+ // the caller must not negate its result (see sortedData).
101
+ if (aMissing || bMissing) {
102
+ if (aMissing && bMissing)
103
+ return 0;
104
+ return aMissing ? 1 : -1;
105
+ }
106
+ if (numeric) {
107
+ const na = toNumber(a);
108
+ const nb = toNumber(b);
109
+ if (na !== null && nb !== null)
110
+ return na - nb;
111
+ }
112
+ return String(a).localeCompare(String(b));
113
+ }
114
+ /** Cycles a column's sort state: unsorted -> asc -> desc -> unsorted. */
115
+ function nextSort(current, key) {
116
+ if (!current || current.key !== key)
117
+ return { key, dir: 'asc' };
118
+ if (current.dir === 'asc')
119
+ return { key, dir: 'desc' };
120
+ return null;
121
+ }
122
+ export function DataTable(props) {
123
+ const { columns, data, rowKey, selectable = false, selectedKeys, defaultSelectedKeys, onSelectionChange, pageSize, defaultSort, onSortChange, emptyText = 'No data', caption, } = props;
124
+ // --- Sort state (internal; `onSortChange` reports every change). ---
125
+ const [sort, setSort] = React.useState(defaultSort ?? null);
126
+ // --- Selection state (controlled when `selectedKeys` provided). ---
127
+ const selectionControlled = selectedKeys !== undefined;
128
+ const [uncontrolledSelection, setUncontrolledSelection] = React.useState(() => defaultSelectedKeys ?? []);
129
+ const selection = selectionControlled ? selectedKeys : uncontrolledSelection;
130
+ const commitSelection = React.useCallback((next) => {
131
+ if (!selectionControlled)
132
+ setUncontrolledSelection(next);
133
+ onSelectionChange?.(next);
134
+ }, [selectionControlled, onSelectionChange]);
135
+ // --- Pagination state. ---
136
+ const [page, setPage] = React.useState(1);
137
+ // --- Sorted data (client-side). ---
138
+ const sortedData = React.useMemo(() => {
139
+ if (!sort)
140
+ return data;
141
+ const column = columns.find((c) => c.key === sort.key);
142
+ if (!column)
143
+ return data;
144
+ const accessor = defaultAccessor(column);
145
+ const values = data.map(accessor);
146
+ const numeric = isNumericColumn(values);
147
+ const copy = [...data];
148
+ copy.sort((a, b) => {
149
+ const av = accessor(a);
150
+ const bv = accessor(b);
151
+ // Missing values stay last in both directions, so handle them before the
152
+ // direction flip rather than letting it invert their placement.
153
+ const aMissing = isMissing(av);
154
+ const bMissing = isMissing(bv);
155
+ if (aMissing || bMissing) {
156
+ if (aMissing && bMissing)
157
+ return 0;
158
+ return aMissing ? 1 : -1;
159
+ }
160
+ const result = compareValues(av, bv, numeric);
161
+ return sort.dir === 'asc' ? result : -result;
162
+ });
163
+ return copy;
164
+ }, [data, sort, columns]);
165
+ // --- Page slice. ---
166
+ const pageCount = pageSize ? Math.max(1, Math.ceil(sortedData.length / pageSize)) : 1;
167
+ const clampedPage = Math.min(page, pageCount);
168
+ const pageRows = React.useMemo(() => {
169
+ if (!pageSize)
170
+ return sortedData;
171
+ const start = (clampedPage - 1) * pageSize;
172
+ return sortedData.slice(start, start + pageSize);
173
+ }, [sortedData, pageSize, clampedPage]);
174
+ const handleSort = (key) => {
175
+ const next = nextSort(sort, key);
176
+ setSort(next);
177
+ setPage(1); // reset to first page when the sort changes
178
+ onSortChange?.(next);
179
+ };
180
+ const selectionSet = React.useMemo(() => new Set(selection), [selection]);
181
+ const pageKeys = React.useMemo(() => pageRows.map(rowKey), [pageRows, rowKey]);
182
+ const selectedOnPage = pageKeys.filter((k) => selectionSet.has(k));
183
+ const allOnPageSelected = pageKeys.length > 0 && selectedOnPage.length === pageKeys.length;
184
+ const someOnPageSelected = selectedOnPage.length > 0 && !allOnPageSelected;
185
+ const toggleAllOnPage = (checked) => {
186
+ if (checked) {
187
+ const next = new Set(selectionSet);
188
+ for (const k of pageKeys)
189
+ next.add(k);
190
+ commitSelection([...next]);
191
+ }
192
+ else {
193
+ const pageSet = new Set(pageKeys);
194
+ commitSelection(selection.filter((k) => !pageSet.has(k)));
195
+ }
196
+ };
197
+ const toggleRow = (key, checked) => {
198
+ if (checked) {
199
+ if (selectionSet.has(key))
200
+ return;
201
+ commitSelection([...selection, key]);
202
+ }
203
+ else {
204
+ commitSelection(selection.filter((k) => k !== key));
205
+ }
206
+ };
207
+ const totalColumns = columns.length + (selectable ? 1 : 0);
208
+ return (_jsxs(Table, { children: [caption ? _jsx(Caption, { children: caption }) : null, _jsx(THead, { children: _jsxs(Tr, { children: [selectable ? (_jsx(Th, { style: { width: '1%' }, children: _jsx(Checkbox, { checked: allOnPageSelected, indeterminate: someOnPageSelected, onChange: toggleAllOnPage, "aria-label": "Select all rows on this page" }) })) : null, columns.map((column) => {
209
+ const isSorted = sort?.key === column.key;
210
+ const ariaSort = !column.sortable
211
+ ? undefined
212
+ : isSorted
213
+ ? sort.dir === 'asc'
214
+ ? 'ascending'
215
+ : 'descending'
216
+ : 'none';
217
+ return (_jsx(Th, { "aria-sort": ariaSort, style: {
218
+ textAlign: column.align ?? 'left',
219
+ width: column.width,
220
+ }, children: column.sortable ? (_jsxs(SortButton, { type: "button", "$align": column.align, onClick: () => handleSort(column.key), children: [column.header, isSorted ? (sort.dir === 'asc' ? (_jsx(ChevronUp, { size: 16, strokeWidth: 3, "aria-hidden": "true" })) : (_jsx(ChevronDown, { size: 16, strokeWidth: 3, "aria-hidden": "true" }))) : null] })) : (column.header) }, column.key));
221
+ })] }) }), _jsx(TBody, { children: pageRows.length === 0 ? (_jsx(Tr, { children: _jsx(Td, { colSpan: totalColumns, style: { textAlign: 'center' }, children: emptyText }) })) : (pageRows.map((row) => {
222
+ const key = rowKey(row);
223
+ const rowSelected = selectionSet.has(key);
224
+ return (_jsxs(Tr, { children: [selectable ? (_jsx(Td, { style: { width: '1%' }, children: _jsx(Checkbox, { checked: rowSelected, onChange: (checked) => toggleRow(key, checked), "aria-label": `Select row ${key}` }) })) : null, columns.map((column) => (_jsx(Td, { style: {
225
+ textAlign: column.align ?? 'left',
226
+ width: column.width,
227
+ }, children: defaultRender(column, row) }, column.key)))] }, key));
228
+ })) }), pageSize && sortedData.length > 0 ? (_jsx("tfoot", { children: _jsx("tr", { children: _jsx(FooterCell, { colSpan: totalColumns, children: _jsx(FooterInner, { children: _jsx(Pagination, { page: clampedPage, pageCount: pageCount, onChange: setPage }) }) }) }) })) : null] }));
229
+ }
@@ -0,0 +1,15 @@
1
+ import * as React from 'react';
2
+ export interface DatePickerProps {
3
+ value?: Date | null;
4
+ defaultValue?: Date | null;
5
+ onChange?: (date: Date | null) => void;
6
+ min?: Date;
7
+ max?: Date;
8
+ placeholder?: string;
9
+ disabled?: boolean;
10
+ clearable?: boolean;
11
+ format?: (date: Date) => string;
12
+ weekStartsOn?: 0 | 1;
13
+ id?: string;
14
+ }
15
+ export declare function DatePicker(props: DatePickerProps): React.ReactElement;