@photon-ai/pho-ui 2.6.0 → 2.7.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 (78) hide show
  1. package/dist/chunks/IconChevronDown-CZilB71T.js +11 -0
  2. package/dist/chunks/IconChevronDown-CZilB71T.js.map +1 -0
  3. package/dist/chunks/IconSearch-BKAHkLyX.js +14 -0
  4. package/dist/chunks/IconSearch-BKAHkLyX.js.map +1 -0
  5. package/dist/chunks/animated-height-CCO-Ocoz.js +48 -0
  6. package/dist/chunks/animated-height-CCO-Ocoz.js.map +1 -0
  7. package/dist/chunks/basic-page-BF4jnCa1.js +1373 -0
  8. package/dist/chunks/basic-page-BF4jnCa1.js.map +1 -0
  9. package/dist/chunks/{button-DNNs5RsT.js → button-CdqS_t5a.js} +62 -65
  10. package/dist/chunks/button-CdqS_t5a.js.map +1 -0
  11. package/dist/chunks/checkbox-Cdm08HWG.js +129 -0
  12. package/dist/chunks/checkbox-Cdm08HWG.js.map +1 -0
  13. package/dist/chunks/{choice-card-CunOnY9l.js → choice-card-BVNP1NB6.js} +2 -2
  14. package/dist/chunks/{choice-card-CunOnY9l.js.map → choice-card-BVNP1NB6.js.map} +1 -1
  15. package/dist/chunks/data-table--f6am1O3.js +279 -0
  16. package/dist/chunks/data-table--f6am1O3.js.map +1 -0
  17. package/dist/chunks/{dialog-8SRNi_rv.js → dialog-BxKACOj5.js} +4 -4
  18. package/dist/chunks/{dialog-8SRNi_rv.js.map → dialog-BxKACOj5.js.map} +1 -1
  19. package/dist/chunks/{error-Crs-wqMC.js → error-D0yoFxCv.js} +2 -2
  20. package/dist/chunks/{error-Crs-wqMC.js.map → error-D0yoFxCv.js.map} +1 -1
  21. package/dist/chunks/is-react-component-DSnjtBmo.js +10 -0
  22. package/dist/chunks/is-react-component-DSnjtBmo.js.map +1 -0
  23. package/dist/chunks/link-provider-DXzDQ08N.js +24 -0
  24. package/dist/chunks/link-provider-DXzDQ08N.js.map +1 -0
  25. package/dist/chunks/{scroll-area-Bu7egArU.js → scroll-area-BO-9PMM7.js} +27 -26
  26. package/dist/chunks/scroll-area-BO-9PMM7.js.map +1 -0
  27. package/dist/chunks/sidebar-DVzZsCts.js +223 -0
  28. package/dist/chunks/sidebar-DVzZsCts.js.map +1 -0
  29. package/dist/chunks/skeleton-DwjSl4vC.js +18 -0
  30. package/dist/chunks/skeleton-DwjSl4vC.js.map +1 -0
  31. package/dist/chunks/{toast-DoXaRPIZ.js → toast-iSf6NuAS.js} +43 -45
  32. package/dist/chunks/toast-iSf6NuAS.js.map +1 -0
  33. package/dist/chunks/use-press-pulse-IbNstUT0.js +58 -0
  34. package/dist/chunks/use-press-pulse-IbNstUT0.js.map +1 -0
  35. package/dist/components/button.js +1 -1
  36. package/dist/components/checkbox.js +4 -126
  37. package/dist/components/command.js +54 -57
  38. package/dist/components/command.js.map +1 -1
  39. package/dist/components/data-table.js +11 -0
  40. package/dist/components/dialog.js +1 -1
  41. package/dist/components/error.js +1 -1
  42. package/dist/components/phone-input.js +608 -615
  43. package/dist/components/phone-input.js.map +1 -1
  44. package/dist/components/radio-group.js +2 -2
  45. package/dist/components/scroll-area.js +1 -1
  46. package/dist/components/select.js +35 -39
  47. package/dist/components/select.js.map +1 -1
  48. package/dist/components/skeleton.js +2 -15
  49. package/dist/components/toast.js +1 -1
  50. package/dist/index.js +94 -73
  51. package/dist/motion.js +89 -0
  52. package/dist/motion.js.map +1 -0
  53. package/dist/primitives.js +2 -2
  54. package/dist/sections/basic-page.js +16 -14
  55. package/dist/sections/sidebar.js +1 -1
  56. package/dist/src/components/data-table/data-table.d.ts +227 -0
  57. package/dist/src/components/data-table/index.d.ts +2 -0
  58. package/dist/src/index.d.ts +1 -0
  59. package/dist/src/motion/index.d.ts +149 -0
  60. package/dist/src/sections/basic-page/basic-page.d.ts +47 -7
  61. package/dist/src/sections/basic-page/index.d.ts +1 -0
  62. package/dist/utils.js +13 -12
  63. package/package.json +10 -1
  64. package/dist/chunks/animated-height-C2eQstGy.js +0 -51
  65. package/dist/chunks/animated-height-C2eQstGy.js.map +0 -1
  66. package/dist/chunks/basic-page-DsI1xo6p.js +0 -1295
  67. package/dist/chunks/basic-page-DsI1xo6p.js.map +0 -1
  68. package/dist/chunks/button-DNNs5RsT.js.map +0 -1
  69. package/dist/chunks/link-provider-9TFNyDqO.js +0 -28
  70. package/dist/chunks/link-provider-9TFNyDqO.js.map +0 -1
  71. package/dist/chunks/scroll-area-Bu7egArU.js.map +0 -1
  72. package/dist/chunks/sidebar-DvB-G0BP.js +0 -231
  73. package/dist/chunks/sidebar-DvB-G0BP.js.map +0 -1
  74. package/dist/chunks/toast-DoXaRPIZ.js.map +0 -1
  75. package/dist/chunks/use-press-pulse-8iorQPsh.js +0 -62
  76. package/dist/chunks/use-press-pulse-8iorQPsh.js.map +0 -1
  77. package/dist/components/checkbox.js.map +0 -1
  78. package/dist/components/skeleton.js.map +0 -1
@@ -0,0 +1,227 @@
1
+ import { ComponentProps, ReactNode, Ref } from 'react';
2
+ import { ColumnDef, RowData, SortingState } from '@tanstack/react-table';
3
+ /**
4
+ * DataTable — a table of records with column semantics: sortable headers,
5
+ * aligned cells, rows that are links into a subpage. TanStack Table (v9)
6
+ * does the thinking — column defs, sorting, row identity — and renders
7
+ * nothing; every pixel here is Pho's.
8
+ *
9
+ * Design notes:
10
+ * - The surface is `BasicPage.Card`'s: hairline border, `base` radius, the
11
+ * page's primary fill, hairline rules between rows. A table is a card of
12
+ * rows that happen to line up in columns, so it sits in a page's `Stack`
13
+ * as any card does.
14
+ * - Header cells are description ink at body size — never small caps, never
15
+ * bold. Hierarchy comes from ink alone; the header is a label row, not a
16
+ * banner.
17
+ * - A sortable header is a button inside the cell: the label and its
18
+ * indicator (`IconSelector` at rest, a chevron once sorted). `aria-sort`
19
+ * sits on the `th`.
20
+ * - Rows that navigate stretch one link across the row (the first cell's
21
+ * link, extended with a pseudo-element), so the whole row is one target
22
+ * for pointer and one stop for keyboard. Hover washes the row; the row
23
+ * whose page is open beside the table (`current`) keeps the wash — the
24
+ * same cue `BasicPage.RowLink` gives under `aria-current`.
25
+ * - Numbers align to the end with tabular figures (`meta.align: "end"`).
26
+ * - `search` is one box over every column — plain contains matching, and
27
+ * the body says when nothing matches. In a page, `BasicPage.Search` in
28
+ * the section header adopts the box; alone, the table keeps one in a
29
+ * toolbar row inside its frame. Server-side search keeps its own box
30
+ * outside.
31
+ * - A wide table scrolls sideways inside its own frame; the page never does.
32
+ * `contain: inline-size` keeps the table's intrinsic width out of the
33
+ * page's — a host that sizes to content (a scroll area's fit-content
34
+ * pane, a grid's auto column) would otherwise grow to the widest row.
35
+ *
36
+ * Reach for it when a list needs column semantics — sorting, aligned
37
+ * figures, several fields side by side. A list where each row is one
38
+ * label and a chevron stays `BasicPage.RowLink`.
39
+ */
40
+ /** Column metadata a `DataTable` understands. */
41
+ export interface DataTableColumnMeta {
42
+ /** Cell alignment — `end` for figures (right-aligned, tabular). */
43
+ align?: "start" | "end";
44
+ }
45
+ /**
46
+ * The feature set every `DataTable` runs on — sorting with the stock sort
47
+ * functions, the column meta above. Fixed, so column definitions written
48
+ * once fit every table.
49
+ */
50
+ declare const features: {
51
+ rowSortingFeature: import('@tanstack/react-table').TableFeature;
52
+ rowSelectionFeature: import('@tanstack/react-table').TableFeature;
53
+ columnFilteringFeature: import('@tanstack/react-table').TableFeature;
54
+ globalFilteringFeature: import('@tanstack/react-table').TableFeature;
55
+ sortedRowModel: (table: import('@tanstack/react-table').Table<any, any>) => () => import('@tanstack/react-table').RowModel<any, any>;
56
+ filteredRowModel: (table: import('@tanstack/react-table').Table<any, any>) => () => import('@tanstack/react-table').RowModel<any, any>;
57
+ filterFns: {
58
+ includesString: import('@tanstack/react-table').CreatedFilterFn<any, any>;
59
+ };
60
+ sortFns: {
61
+ alphanumeric: import('@tanstack/react-table').CreatedSortFn<any, any>;
62
+ basic: import('@tanstack/react-table').CreatedSortFn<any, any>;
63
+ datetime: import('@tanstack/react-table').CreatedSortFn<any, any>;
64
+ text: import('@tanstack/react-table').CreatedSortFn<any, any>;
65
+ };
66
+ columnMeta: DataTableColumnMeta;
67
+ };
68
+ export type DataTableFeatures = typeof features;
69
+ /** A column definition for a `DataTable` of `TData` rows. */
70
+ export type DataTableColumn<TData extends RowData, TValue = unknown> = ColumnDef<DataTableFeatures, TData, TValue>;
71
+ /**
72
+ * The column helper, bound to the table's feature set — `accessor`,
73
+ * `display`, `group`, and `columns` to collect them with their types
74
+ * intact.
75
+ *
76
+ * @example
77
+ * const col = createColumns<Request>();
78
+ * const columns = col.columns([
79
+ * col.accessor("actor", { header: "Actor" }),
80
+ * col.accessor("amount", { header: "Amount", meta: { align: "end" } }),
81
+ * ]);
82
+ */
83
+ export declare function createColumns<TData extends RowData>(): import('@tanstack/react-table').ColumnHelper<{
84
+ rowSortingFeature: import('@tanstack/react-table').TableFeature;
85
+ rowSelectionFeature: import('@tanstack/react-table').TableFeature;
86
+ columnFilteringFeature: import('@tanstack/react-table').TableFeature;
87
+ globalFilteringFeature: import('@tanstack/react-table').TableFeature;
88
+ sortedRowModel: (table: import('@tanstack/react-table').Table<any, any>) => () => import('@tanstack/react-table').RowModel<any, any>;
89
+ filteredRowModel: (table: import('@tanstack/react-table').Table<any, any>) => () => import('@tanstack/react-table').RowModel<any, any>;
90
+ filterFns: {
91
+ includesString: import('@tanstack/react-table').CreatedFilterFn<any, any>;
92
+ };
93
+ sortFns: {
94
+ alphanumeric: import('@tanstack/react-table').CreatedSortFn<any, any>;
95
+ basic: import('@tanstack/react-table').CreatedSortFn<any, any>;
96
+ datetime: import('@tanstack/react-table').CreatedSortFn<any, any>;
97
+ text: import('@tanstack/react-table').CreatedSortFn<any, any>;
98
+ };
99
+ columnMeta: DataTableColumnMeta;
100
+ }, TData>;
101
+ export interface DataTableProps<TData extends RowData> extends Omit<ComponentProps<"div">, "children"> {
102
+ columns: ReadonlyArray<DataTableColumn<TData, any>>;
103
+ data: ReadonlyArray<TData>;
104
+ /**
105
+ * A row's identity — what `current` is matched against, and what keeps a
106
+ * row itself across sorts. Defaults to the row's `id` field, else its
107
+ * index.
108
+ */
109
+ getRowId?: (row: TData) => string;
110
+ /**
111
+ * Rows that navigate: each row is one link into a subpage, through the
112
+ * host's `LinkProvider`. Beside a `BasicPage.Aside`, the subpage opens
113
+ * next to the table.
114
+ */
115
+ rowHref?: (row: TData) => string;
116
+ /** The row whose page is open (its id) — it keeps the wash. */
117
+ current?: string | null;
118
+ /**
119
+ * Sorting is on for every accessor column (`enableSorting: false` on a
120
+ * column turns it off there). Uncontrolled by default; `defaultSort`
121
+ * seeds it. Pass `sort` with `onSortChange` to own it — the way to sort
122
+ * on the server.
123
+ */
124
+ defaultSort?: SortingState;
125
+ sort?: SortingState;
126
+ onSortChange?: (sort: SortingState) => void;
127
+ /**
128
+ * A search box on the table — `true`, or the placeholder it should
129
+ * show. One box searches every column (a column opts out with
130
+ * `enableGlobalFilter: false`); matching is a plain case-insensitive
131
+ * contains, on the client. With no matches the body says so. When the
132
+ * list doesn't all come down, keep the box outside and ask the server.
133
+ */
134
+ search?: boolean | string;
135
+ /**
136
+ * Controls in the frame's top band — a `BasicPage.Search` first, then a
137
+ * filter or two. The band is part of the table (one surface, a hairline
138
+ * above the header row), so nothing floats over the page and the
139
+ * spacing above and below is the frame's own. A `Search` here adopts
140
+ * the table's box the way a header one does.
141
+ */
142
+ toolbar?: ReactNode;
143
+ /**
144
+ * Row selection: hand either of these and rows grow a leading checkbox
145
+ * column. The header checkbox works the visible set — everything the
146
+ * search still shows — so select-all after a search means what it says;
147
+ * rows selected earlier and then hidden stay selected. `selected` is
148
+ * the selected ids (ids come from `getRowId`; pair with
149
+ * `onSelectedChange` to own the state); `onSelectedChange` alone
150
+ * listens while the table keeps it. The page renders its own bulk
151
+ * actions from the ids — the table only keeps the score.
152
+ */
153
+ selected?: readonly string[];
154
+ onSelectedChange?: (ids: string[]) => void;
155
+ /**
156
+ * File rows into labeled groups: return a label and the row renders
157
+ * under it, `null` and it stays in the main body. Groups follow the
158
+ * main body, in the order their labels first appear in `data`, each
159
+ * under a quiet label row — and only while they have rows, so a group
160
+ * a search empties simply leaves. Sorting orders rows within their
161
+ * group.
162
+ */
163
+ group?: (row: TData) => string | null;
164
+ /**
165
+ * Glide the frame's height when the rows change — a search narrowing,
166
+ * a filter, bones becoming rows. The height follows the content on the
167
+ * page spring instead of jumping; reserve it for tables whose rows
168
+ * change while mounted (same contract as `BasicPage.Card`'s).
169
+ */
170
+ animateHeight?: boolean;
171
+ /** Bones while the rows are on their way — `true` for five, or a count. */
172
+ loading?: boolean | number;
173
+ /** What the body says when there are no rows. */
174
+ empty?: ReactNode;
175
+ ref?: Ref<HTMLDivElement>;
176
+ }
177
+ /**
178
+ * A host that sizes itself around tables provides this; each table
179
+ * announces itself while mounted. `BasicPage.Root` runs wide when the
180
+ * page carries one and `width` was left unsaid.
181
+ */
182
+ export declare const TableHostContext: import('react').Context<((present: boolean) => void) | null>;
183
+ /** A searchable table's search, published for a header to adopt. */
184
+ export interface TableSearchApi {
185
+ placeholder: string;
186
+ query: string;
187
+ setQuery: (query: string) => void;
188
+ /** Rows still showing after the current query. */
189
+ matched: number;
190
+ /** Rows in the list, before search. */
191
+ total: number;
192
+ }
193
+ interface TableSearchState {
194
+ api: TableSearchApi | null;
195
+ adopted: boolean;
196
+ }
197
+ /**
198
+ * The channel between a searchable table and a `BasicPage.Search` placed
199
+ * in a section header: the table publishes its search (placeholder,
200
+ * query, the setter); a mounted `Search` adopts it, and the table's own
201
+ * box yields. Provided by `BasicPage.Root`; a table outside any page
202
+ * keeps its built-in box. One searchable table per page — a later
203
+ * publisher takes the slot.
204
+ */
205
+ export interface TableSearchStore {
206
+ get: () => TableSearchState;
207
+ subscribe: (listener: () => void) => () => void;
208
+ publish: (api: TableSearchApi | null) => void;
209
+ adopt: (adopted: boolean) => void;
210
+ }
211
+ export declare function createTableSearchStore(): TableSearchStore;
212
+ export declare const TableSearchContext: import('react').Context<TableSearchStore | null>;
213
+ export interface GhostSearchProps extends Omit<ComponentProps<"input">, "value" | "onChange" | "type" | "children"> {
214
+ value: string;
215
+ onChange: (value: string) => void;
216
+ }
217
+ /**
218
+ * The search's ghost face — a magnifier and a bare field, quiet ink on a
219
+ * text line, no box around it. What a table wears in its own toolbar
220
+ * band; `BasicPage.Search variant="ghost"` is the same face wherever a
221
+ * page wants it. Presentational: hand it the value and the setter.
222
+ */
223
+ export declare function GhostSearch({ value, onChange, className, ...props }: GhostSearchProps): import("react").JSX.Element;
224
+ /** Same chrome as `BasicPage.Card`, minus its dividers — rows draw their own. */
225
+ declare const DATA_TABLE_SURFACE = "rounded-base border border-pho-secondary bg-pho-primary";
226
+ declare function DataTable<TData extends RowData>({ columns, data, getRowId, rowHref, current, selected, onSelectedChange, defaultSort, sort, onSortChange, search, toolbar, group, animateHeight, loading, empty, className, ...props }: DataTableProps<TData>): import("react").JSX.Element;
227
+ export { DataTable, DATA_TABLE_SURFACE };
@@ -0,0 +1,2 @@
1
+ export { DataTable, DATA_TABLE_SURFACE, GhostSearch, type GhostSearchProps, TableHostContext, TableSearchContext, createTableSearchStore, type TableSearchApi, type TableSearchStore, createColumns, type DataTableProps, type DataTableColumn, type DataTableColumnMeta, type DataTableFeatures, } from './data-table';
2
+ export type { SortingState } from '@tanstack/react-table';
@@ -6,6 +6,7 @@
6
6
  * This root barrel re-exports everything for convenience.
7
7
  */
8
8
  export * from './utils';
9
+ export * from './motion';
9
10
  export * from './components/button';
10
11
  export * from './components/input';
11
12
  export * from './components/input-group';
@@ -0,0 +1,149 @@
1
+ /**
2
+ * The motion vocabulary — every animation in Pho speaks it. Five springs,
3
+ * three timings, two CSS eases, and a handful of distances cover the
4
+ * system; components import from here and add nothing of their own.
5
+ *
6
+ * The grammar:
7
+ * - **Arrive** on `SPRING_ENTRANCE` with `FADE_ENTRANCE` — content
8
+ * surfaces, it doesn't slide in.
9
+ * - **Swap** on `FADE_SWAP`, displacement on `TRAVEL_LINEAR` — crossfades
10
+ * ease out, travel is linear.
11
+ * - **Press** on the press springs — deeper mass for bigger surfaces.
12
+ * - **Follow** on `SPRING_SETTLE` — heights and reflows chase content.
13
+ */
14
+ /**
15
+ * Pages arrive on this — the `Stack` entrance, `Reveal`, `Title` / `Header`
16
+ * enter, the Title ↔ Back morph, `Alert` / `Footer` height reveals.
17
+ * Measured off a 60fps iOS push: ~330ms, a long gentle glide, everything
18
+ * co-landing; essentially critically damped (overshoot would flash
19
+ * clipped content).
20
+ */
21
+ export declare const SPRING_ENTRANCE: {
22
+ readonly type: "spring";
23
+ readonly stiffness: 400;
24
+ readonly damping: 40;
25
+ };
26
+ /**
27
+ * Layout follows content on this — `AnimatedHeight`, a command palette's
28
+ * rows gliding into new places. Stiffer than the entrance: reflow should
29
+ * settle before it reads as motion.
30
+ */
31
+ export declare const SPRING_SETTLE: {
32
+ readonly type: "spring";
33
+ readonly stiffness: 640;
34
+ readonly damping: 50;
35
+ };
36
+ /**
37
+ * The press pulse for full-size surfaces — `Button`, a sidebar row.
38
+ * Framer source: `spring-physics 200 30 0.1`.
39
+ */
40
+ export declare const SPRING_PRESS: {
41
+ readonly type: "spring";
42
+ readonly stiffness: 200;
43
+ readonly damping: 30;
44
+ readonly mass: 0.1;
45
+ };
46
+ /** The press pulse for small controls — toggles, checks; quick and light. */
47
+ export declare const SPRING_PRESS_QUICK: {
48
+ readonly type: "spring";
49
+ readonly stiffness: 700;
50
+ readonly damping: 32;
51
+ readonly mass: 0.06;
52
+ };
53
+ /** The press pulse for inline triggers — a `Select` sitting in text. */
54
+ export declare const SPRING_PRESS_INLINE: {
55
+ readonly type: "spring";
56
+ readonly stiffness: 1100;
57
+ readonly damping: 40;
58
+ readonly mass: 0.04;
59
+ };
60
+ /** The fade that rides `SPRING_ENTRANCE` — spans most of the glide. */
61
+ export declare const FADE_ENTRANCE: {
62
+ readonly duration: 0.25;
63
+ readonly ease: "easeOut";
64
+ };
65
+ /**
66
+ * The swap — crossfading faces, a condensed bar arriving, a saved word,
67
+ * an aside changing pages. Fades ease out.
68
+ */
69
+ export declare const FADE_SWAP: {
70
+ readonly duration: 0.2;
71
+ readonly ease: "easeOut";
72
+ };
73
+ /**
74
+ * Displacement — a bar's rise, a drawer's scope swap, a search box's
75
+ * width. Travel is linear: the eye reads speed changes as personality,
76
+ * and furniture has none.
77
+ */
78
+ export declare const TRAVEL_LINEAR: {
79
+ readonly duration: 0.2;
80
+ readonly ease: "linear";
81
+ };
82
+ /** CSS pop-in for floating layers (menus, select popups) — slight overshoot. */
83
+ export declare const EASE_POP = "cubic-bezier(0.34, 1.3, 0.64, 1)";
84
+ /** CSS stack glide — toasts trading places (Kumo's curve). */
85
+ export declare const EASE_STACK = "cubic-bezier(0.22, 1, 0.36, 1)";
86
+ /** Rise distance for entrances — content surfaces from just below. */
87
+ export declare const RISE_PX = 12;
88
+ /** Rise distance for the condensed bar — it comes from nearer still. */
89
+ export declare const BAR_RISE_PX = -8;
90
+ /** Press depth for small controls. */
91
+ export declare const PRESS_SCALE = 0.96;
92
+ /** Press depth for rows and full-size surfaces — shallower. */
93
+ export declare const PRESS_SCALE_ROW = 0.97;
94
+ /** The blur budget — one breath of blur, everywhere it is used. */
95
+ export declare const BLUR_PX = 1;
96
+ /**
97
+ * The crossfade recipe — fade with a breath of blur, both ways. Spread
98
+ * onto a motion element inside `AnimatePresence`:
99
+ *
100
+ * <motion.div {...fadeThroughBlur(reduceMotion)} />
101
+ *
102
+ * Under reduced motion the swap is instant and never blurs.
103
+ */
104
+ export declare function fadeThroughBlur(reduceMotion?: boolean | null): {
105
+ readonly initial: {
106
+ readonly opacity: 0;
107
+ readonly filter: "blur(0px)" | "blur(1px)";
108
+ };
109
+ readonly animate: {
110
+ readonly opacity: 1;
111
+ readonly filter: "blur(0px)";
112
+ };
113
+ readonly exit: {
114
+ readonly opacity: 0;
115
+ readonly filter: "blur(0px)" | "blur(1px)";
116
+ };
117
+ readonly transition: {
118
+ readonly duration: 0.2;
119
+ readonly ease: "easeOut";
120
+ } | {
121
+ readonly duration: 0;
122
+ };
123
+ };
124
+ /**
125
+ * The entrance recipe — rise `RISE_PX` on `SPRING_ENTRANCE` while
126
+ * `FADE_ENTRANCE` brings the ink. What `Stack` and `Reveal` do; spread it
127
+ * onto a motion element for content that should arrive the same way.
128
+ */
129
+ export declare function riseIn(reduceMotion?: boolean | null): {
130
+ readonly initial: false | {
131
+ readonly opacity: 0;
132
+ readonly y: 12;
133
+ };
134
+ readonly animate: {
135
+ readonly opacity: 1;
136
+ readonly y: 0;
137
+ };
138
+ readonly transition: {
139
+ readonly y: {
140
+ readonly type: "spring";
141
+ readonly stiffness: 400;
142
+ readonly damping: 40;
143
+ };
144
+ readonly opacity: {
145
+ readonly duration: 0.25;
146
+ readonly ease: "easeOut";
147
+ };
148
+ };
149
+ };
@@ -1,6 +1,7 @@
1
1
  import { AnchorHTMLAttributes, ComponentProps, ReactNode, Ref } from 'react';
2
2
  import { ButtonLinkProps, ButtonProps } from '../../components/button';
3
3
  import { Form as FormElement } from '../../components/form';
4
+ import { DataTable } from '../../components/data-table';
4
5
  /** Bordered card that stacks rows with hairline dividers. */
5
6
  export declare const BASIC_PAGE_CARD = "rounded-base border border-pho-secondary bg-pho-primary divide-y divide-pho-secondary [&>:has(+[data-basic-page-alert])]:border-b-0";
6
7
  /**
@@ -71,12 +72,14 @@ export interface BasicPageRootProps extends ComponentProps<"div"> {
71
72
  */
72
73
  condense?: boolean;
73
74
  /**
74
- * The content column's cap. `base` (default) is the 42rem reading
75
- * column every settings page uses; `wide` opens it to 80rem for a page
76
- * that is a list first — rows with several columns. Forms stay on
77
- * `base`: across a wide row a label and its control drift out of one
78
- * glance. Either width can host an `Aside` — the column keeps its cap
79
- * beside the panel, and leftover space is margin, not content width.
75
+ * The content column's cap. `base` is the 42rem reading column every
76
+ * settings page uses; `wide` opens it to 80rem for a page that is a
77
+ * list first — rows with several columns. Left unsaid, the page sizes
78
+ * itself: `base`, until it carries a `Table` — records in columns run
79
+ * wide on their own. Forms stay on `base`: across a wide row a label
80
+ * and its control drift out of one glance. Either width can host an
81
+ * `Aside` — the column keeps its cap beside the panel, and leftover
82
+ * space is margin, not content width.
80
83
  */
81
84
  width?: "base" | "wide";
82
85
  }
@@ -192,7 +195,7 @@ export interface BasicPageStackProps extends Omit<ComponentProps<"div">, RevealM
192
195
  *
193
196
  * Static under reduced motion.
194
197
  */
195
- declare function Stack({ className, children, ...props }: BasicPageStackProps): import("react").JSX.Element;
198
+ declare function Stack({ className, children, ref: forwardedRef, ...props }: BasicPageStackProps): import("react").JSX.Element;
196
199
  /**
197
200
  * Title + optional description pair. No gap between them — the block's
198
201
  * `gap-3` only applies between this header and the card.
@@ -222,6 +225,19 @@ declare function SectionTitle({ id, className, children, ...props }: ComponentPr
222
225
  declare function Description({ className, ...props }: ComponentProps<"p"> & {
223
226
  ref?: Ref<HTMLParagraphElement>;
224
227
  }): import("react").JSX.Element;
228
+ /**
229
+ * How many rows the searchable table is showing — a `Description` that
230
+ * reads the table's search. Idle: "12 people." A query: "3 of 12."
231
+ * Renders nothing until a table with `search` is on the page. Put it in
232
+ * the `Header` under the section title.
233
+ */
234
+ declare function TableCount({ one, other, ...props }: ComponentProps<"p"> & {
235
+ /** Singular noun — "1 person." */
236
+ one: string;
237
+ /** Plural noun — "12 people." */
238
+ other: string;
239
+ ref?: Ref<HTMLParagraphElement>;
240
+ }): import("react").JSX.Element | null;
225
241
  /**
226
242
  * Optional caption below a section's `Card` (outside the card). One step
227
243
  * smaller than `Description` (`text-xs`). Not the card-internal dirty-state
@@ -687,9 +703,32 @@ export interface BasicPageAsideProps {
687
703
  * column cap beside the panel, and leftover space is margin.
688
704
  */
689
705
  declare function Aside({ open, placeholder, onClose, children }: BasicPageAsideProps): import('react').ReactPortal | null;
706
+ /**
707
+ * The table's search, standing at the end of a section header — put it in
708
+ * the `Header` beside the title and it adopts the searchable table's box
709
+ * (the table then renders none of its own). On a header at least 36rem
710
+ * wide the box simply stands open: the header is a container, CSS
711
+ * decides, nothing is measured. On a narrower one a round button holds
712
+ * the place; clicking it opens the box — the width glides linearly, the
713
+ * page's travel — with focus landing inside. Emptied and blurred it
714
+ * glides shut and the button returns; Escape clears, blurs, and shuts.
715
+ * Renders nothing until a table with `search` is on the page.
716
+ */
717
+ declare function Search({ variant, className, ...props }: ComponentProps<"div"> & {
718
+ /**
719
+ * The face. `box` (default) is the bordered field beside a section
720
+ * title — open on a wide header, a round button that glides it open on
721
+ * a narrow one. `ghost` is the table-chrome face: a magnifier and a
722
+ * bare field on the text line, no box — for a toolbar band or anywhere
723
+ * a boxed control would be noise; it flexes instead of folding.
724
+ */
725
+ variant?: "box" | "ghost";
726
+ }): import("react").JSX.Element;
690
727
  export declare const BasicPage: {
691
728
  Root: typeof Root;
692
729
  Aside: typeof Aside;
730
+ Search: typeof Search;
731
+ Table: typeof DataTable;
693
732
  CondenseScope: typeof CondenseScope;
694
733
  Breadcrumbs: typeof Breadcrumbs;
695
734
  Crumb: typeof Crumb;
@@ -699,6 +738,7 @@ export declare const BasicPage: {
699
738
  Header: typeof Header;
700
739
  SectionTitle: typeof SectionTitle;
701
740
  Description: typeof Description;
741
+ TableCount: typeof TableCount;
702
742
  SectionFooter: typeof SectionFooter;
703
743
  Card: typeof Card;
704
744
  Item: typeof Item;
@@ -1 +1,2 @@
1
1
  export { BasicPage, formToasts, AUTO_SAVE_DELAY_MS, useCondense, useAutoSave, type BasicPageCondense, BASIC_PAGE_CARD, CONDENSE_BAR_ENTER, CONDENSE_BAR_TRAVEL, CONDENSE_BAR_RISE_PX, BASIC_PAGE_CALLOUT, BASIC_PAGE_ROW, BASIC_PAGE_ITEM, BASIC_PAGE_ITEM_PANEL, BASIC_PAGE_ITEM_PANEL_DIVIDED, type BasicPageRootProps, type BasicPageAsideProps, type BasicPageRowProps, type BasicPageRowLinkProps, type BasicPageBackProps, type BasicPageBreadcrumbsProps, type BasicPageCrumbProps, type BasicPageFooterProps, type BasicPageFormProps, type BasicPageFormErrors, type BasicPageItemPanelProps, } from './basic-page';
2
+ export { createColumns, type DataTableColumn, type DataTableColumnMeta, type DataTableProps, type SortingState, } from '../../components/data-table';
package/dist/utils.js CHANGED
@@ -1,15 +1,16 @@
1
1
  "use client";
2
- import { t as s } from "./chunks/cn-ChIgwEl3.js";
3
- import { n as t, r as e, t as a } from "./chunks/link-provider-9TFNyDqO.js";
4
- import { t as i } from "./chunks/resolve-variant-CcD8iG5l.js";
5
- import { n as p, r as P, t as R } from "./chunks/use-press-pulse-8iorQPsh.js";
2
+ import { t as r } from "./chunks/cn-ChIgwEl3.js";
3
+ import { t } from "./chunks/is-react-component-DSnjtBmo.js";
4
+ import { t as m } from "./chunks/resolve-variant-CcD8iG5l.js";
5
+ import { n as i, t as n } from "./chunks/link-provider-DXzDQ08N.js";
6
+ import { n as P, r as R, t as S } from "./chunks/use-press-pulse-IbNstUT0.js";
6
7
  export {
7
- R as CONTROL_PRESS_SCALE,
8
- p as CONTROL_PRESS_SPRING,
9
- a as LinkProvider,
10
- s as cn,
11
- e as isReactComponent,
12
- i as resolveVariant,
13
- t as useLinkComponent,
14
- P as usePressPulse
8
+ S as CONTROL_PRESS_SCALE,
9
+ P as CONTROL_PRESS_SPRING,
10
+ n as LinkProvider,
11
+ r as cn,
12
+ t as isReactComponent,
13
+ m as resolveVariant,
14
+ i as useLinkComponent,
15
+ R as usePressPulse
15
16
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@photon-ai/pho-ui",
3
- "version": "2.6.0",
3
+ "version": "2.7.1",
4
4
  "description": "Pho Design System — Photon's React component library, built on Base UI",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -33,6 +33,10 @@
33
33
  "types": "./dist/src/components/button/index.d.ts",
34
34
  "import": "./dist/components/button.js"
35
35
  },
36
+ "./components/data-table": {
37
+ "types": "./dist/src/components/data-table/index.d.ts",
38
+ "import": "./dist/components/data-table.js"
39
+ },
36
40
  "./components/input": {
37
41
  "types": "./dist/src/components/input/index.d.ts",
38
42
  "import": "./dist/components/input.js"
@@ -233,6 +237,10 @@
233
237
  "types": "./dist/src/utils/index.d.ts",
234
238
  "import": "./dist/utils.js"
235
239
  },
240
+ "./motion": {
241
+ "types": "./dist/src/motion/index.d.ts",
242
+ "import": "./dist/motion.js"
243
+ },
236
244
  "./primitives": {
237
245
  "types": "./dist/src/primitives/index.d.ts",
238
246
  "import": "./dist/primitives.js"
@@ -430,6 +438,7 @@
430
438
  },
431
439
  "dependencies": {
432
440
  "@base-ui/react": "^1.7.0",
441
+ "@tanstack/react-table": "^9.2.3",
433
442
  "clsx": "^2.1.1",
434
443
  "cmdk": "^1.1.1",
435
444
  "country-flag-icons": "^1.6.20",
@@ -1,51 +0,0 @@
1
- "use client";
2
- import { useEffect as v, useRef as a } from "react";
3
- import { jsx as m } from "react/jsx-runtime";
4
- import { animate as g, motion as R, useMotionValue as x, useReducedMotion as H } from "motion/react";
5
- var M = {
6
- type: "spring",
7
- stiffness: 640,
8
- damping: 50
9
- };
10
- function y({ className: d, transition: r, children: p }) {
11
- const s = a(null), i = a(null), t = x("auto"), c = H();
12
- return v(() => {
13
- const h = s.current, n = i.current;
14
- if (!h || !n) return;
15
- let e = null;
16
- const u = new ResizeObserver((b) => {
17
- const f = b[0]?.borderBoxSize?.[0], o = f ? f.blockSize : n.offsetHeight, l = t.get();
18
- if (l === "auto" || c) {
19
- e?.stop(), e = null, t.set(o);
20
- return;
21
- }
22
- !e && Math.abs(l - o) < 0.5 || (e?.stop(), e = g(t, o, {
23
- ...r ?? M,
24
- onComplete: () => {
25
- e = null;
26
- }
27
- }));
28
- });
29
- return u.observe(n), () => {
30
- u.disconnect(), e?.stop();
31
- };
32
- }, [
33
- t,
34
- c,
35
- r
36
- ]), /* @__PURE__ */ m(R.div, {
37
- ref: s,
38
- className: "min-h-0 overflow-hidden",
39
- style: { height: t },
40
- children: /* @__PURE__ */ m("div", {
41
- ref: i,
42
- className: d,
43
- children: p
44
- })
45
- });
46
- }
47
- export {
48
- y as t
49
- };
50
-
51
- //# sourceMappingURL=animated-height-C2eQstGy.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"animated-height-C2eQstGy.js","names":[],"sources":["../../src/utils/animated-height.tsx"],"sourcesContent":["import { useEffect, useRef, type ReactNode } from \"react\";\nimport {\n animate,\n motion,\n useMotionValue,\n useReducedMotion,\n type ValueAnimationTransition,\n} from \"motion/react\";\n\n/**\n * Height spring for container-size transitions — same feel as the onboarding /\n * invite card and BasicPage footer reveal: quick, critically damped (no\n * overshoot that would flash clipped content).\n */\nconst HEIGHT_SPRING = { type: \"spring\", stiffness: 640, damping: 50 } as const;\n\n/**\n * Animates its own height to follow the measured height of `children`, so\n * content swaps (search filtering, skeleton → data) glide instead of jumping.\n * Honors `prefers-reduced-motion` by snapping height with no spring.\n */\nexport function AnimatedHeight({\n className,\n transition,\n children,\n}: {\n className?: string;\n /** Spring override for the height follow. @default the Command spring */\n transition?: ValueAnimationTransition<number | \"auto\">;\n children: ReactNode;\n}) {\n const containerRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n const height = useMotionValue<number | \"auto\">(\"auto\");\n const reduceMotion = useReducedMotion();\n\n useEffect(() => {\n const container = containerRef.current;\n const content = contentRef.current;\n if (!container || !content) return;\n\n let animation: ReturnType<typeof animate> | null = null;\n\n const observer = new ResizeObserver((entries) => {\n const box = entries[0]?.borderBoxSize?.[0];\n const next = box ? box.blockSize : content.offsetHeight;\n const current = height.get();\n if (current === \"auto\" || reduceMotion) {\n animation?.stop();\n animation = null;\n height.set(next);\n return;\n }\n if (!animation && Math.abs(current - next) < 0.5) return;\n animation?.stop();\n animation = animate(height, next, {\n ...(transition ?? HEIGHT_SPRING),\n onComplete: () => {\n animation = null;\n },\n });\n });\n observer.observe(content);\n\n return () => {\n observer.disconnect();\n animation?.stop();\n };\n }, [height, reduceMotion, transition]);\n\n return (\n <motion.div\n ref={containerRef}\n // Stack (and other flex parents) set `min-height: auto` on children,\n // which sizes to content and swallows the height spring. Clip and\n // allow shrinking so the measured height can actually animate.\n className=\"min-h-0 overflow-hidden\"\n style={{ height }}\n >\n <div ref={contentRef} className={className}>\n {children}\n </div>\n </motion.div>\n );\n}\n"],"mappings":";;;;AAcA,IAAM,IAAgB;AAAA,EAAE,MAAM;AAAA,EAAU,WAAW;AAAA,EAAK,SAAS;AAAG;AAOpE,SAAgB,EAAe,EAC7B,WAAA,GACA,YAAA,GACA,UAAA,EAAA,GAMC;AACD,QAAM,IAAe,EAAuB,IAAI,GAC1C,IAAa,EAAuB,IAAI,GACxC,IAAS,EAAgC,MAAM,GAC/C,IAAe,EAAiB;AAEtC,SAAA,EAAA,MAAgB;AACd,UAAM,IAAY,EAAa,SACzB,IAAU,EAAW;AAC3B,QAAI,CAAC,KAAa,CAAC,EAAS;AAE5B,QAAI,IAA+C;AAEnD,UAAM,IAAW,IAAI,eAAA,CAAgB,MAAY;AAC/C,YAAM,IAAM,EAAQ,CAAA,GAAI,gBAAgB,CAAA,GAClC,IAAO,IAAM,EAAI,YAAY,EAAQ,cACrC,IAAU,EAAO,IAAI;AAC3B,UAAI,MAAY,UAAU,GAAc;AACtC,QAAA,GAAW,KAAK,GAChB,IAAY,MACZ,EAAO,IAAI,CAAI;AACf;AAAA,MACF;AACA,MAAI,CAAC,KAAa,KAAK,IAAI,IAAU,CAAI,IAAI,QAC7C,GAAW,KAAK,GAChB,IAAY,EAAQ,GAAQ,GAAM;AAAA,QAChC,GAAI,KAAc;AAAA,QAClB,YAAA,MAAkB;AAChB,UAAA,IAAY;AAAA,QACd;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AACD,WAAA,EAAS,QAAQ,CAAO,GAExB,MAAa;AACX,MAAA,EAAS,WAAW,GACpB,GAAW,KAAK;AAAA,IAClB;AAAA,EACF,GAAG;AAAA,IAAC;AAAA,IAAQ;AAAA,IAAc;AAAA,EAAU,CAAC,GAGnC,gBAAA,EAAC,EAAO,KAAR;AAAA,IACE,KAAK;AAAA,IAIL,WAAU;AAAA,IACV,OAAO,EAAE,QAAA,EAAO;AAAA,IAEhB,UAAA,gBAAA,EAAC,OAAD;AAAA,MAAK,KAAK;AAAA,MAAuB,WAAA;AAAA,MAC9B,UAAA;AAAA,IACE,CAAA;AAAA,EACK,CAAA;AAEhB"}