panelui-native 0.38.0 → 0.40.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.
Files changed (37) hide show
  1. package/README.md +2 -1
  2. package/lib/module/components/heatmap-chart/index.js +113 -6
  3. package/lib/module/components/heatmap-chart/index.js.map +1 -1
  4. package/lib/module/components/pagination/index.js +519 -0
  5. package/lib/module/components/pagination/index.js.map +1 -0
  6. package/lib/module/components/ring-chart/index.js +183 -45
  7. package/lib/module/components/ring-chart/index.js.map +1 -1
  8. package/lib/module/components/steps/index.js +8 -6
  9. package/lib/module/components/steps/index.js.map +1 -1
  10. package/lib/module/components/table/index.js +100 -26
  11. package/lib/module/components/table/index.js.map +1 -1
  12. package/lib/module/components/timeline/index.js +8 -7
  13. package/lib/module/components/timeline/index.js.map +1 -1
  14. package/lib/module/index.js +1 -0
  15. package/lib/module/index.js.map +1 -1
  16. package/lib/typescript/src/components/heatmap-chart/index.d.ts +51 -3
  17. package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
  18. package/lib/typescript/src/components/pagination/index.d.ts +285 -0
  19. package/lib/typescript/src/components/pagination/index.d.ts.map +1 -0
  20. package/lib/typescript/src/components/ring-chart/index.d.ts +75 -8
  21. package/lib/typescript/src/components/ring-chart/index.d.ts.map +1 -1
  22. package/lib/typescript/src/components/steps/index.d.ts +8 -6
  23. package/lib/typescript/src/components/steps/index.d.ts.map +1 -1
  24. package/lib/typescript/src/components/table/index.d.ts +59 -17
  25. package/lib/typescript/src/components/table/index.d.ts.map +1 -1
  26. package/lib/typescript/src/components/timeline/index.d.ts +8 -7
  27. package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
  28. package/lib/typescript/src/index.d.ts +2 -1
  29. package/lib/typescript/src/index.d.ts.map +1 -1
  30. package/package.json +1 -1
  31. package/src/components/heatmap-chart/index.tsx +147 -5
  32. package/src/components/pagination/index.tsx +591 -0
  33. package/src/components/ring-chart/index.tsx +269 -47
  34. package/src/components/steps/index.tsx +8 -6
  35. package/src/components/table/index.tsx +146 -30
  36. package/src/components/timeline/index.tsx +8 -7
  37. package/src/index.ts +15 -0
@@ -4,28 +4,37 @@
4
4
  * React Native has no table layout: no `<table>`, no column model, nothing that
5
5
  * makes the third cell of one row the same width as the third cell of the next.
6
6
  * A table here is therefore a stack of flex rows, and the columns exist only
7
- * because every row divides its width the same way. That is the one rule the
8
- * component cannot enforce for you — `flex` and `width` on a `Table.Head` and
9
- * on the `Table.Cell` beneath it have to agree, or the column drifts.
7
+ * because every row divides its width the same way.
8
+ *
9
+ * `columns` is the column model that closes that gap. Give the root one entry
10
+ * per column and every `Table.Head` and `Table.Cell` takes its `flex`, `width`
11
+ * and `align` from the entry at its own position in the row — so a column is
12
+ * described once, at the top, and a row is just its contents. Without it the
13
+ * sizing has to be repeated on every head and every cell, and the column drifts
14
+ * the moment two of them disagree.
15
+ *
16
+ * ```tsx
17
+ * <Table columns={[{ flex: 2 }, { align: 'end' }]}>
18
+ * ```
19
+ *
20
+ * Props set on a head or a cell still win, for the one row that has to differ.
10
21
  *
11
22
  * Everything else it does own: the hairlines between rows and the missing one
12
23
  * under the last, the muted header and footer, the striping, the alignment of a
13
24
  * numeric column, and the sort arrow that turns over rather than swapping.
14
25
  *
15
26
  * ```tsx
16
- * <Table variant="outline">
27
+ * <Table variant="outline" columns={[{ flex: 2 }, { align: 'end' }]}>
17
28
  * <Table.Header>
18
29
  * <Table.Row>
19
- * <Table.Head flex={2}>Invoice</Table.Head>
20
- * <Table.Head align="end" sortDirection="desc" onPress={toggleSort}>
21
- * Amount
22
- * </Table.Head>
30
+ * <Table.Head>Invoice</Table.Head>
31
+ * <Table.Head sortDirection="desc" onPress={toggleSort}>Amount</Table.Head>
23
32
  * </Table.Row>
24
33
  * </Table.Header>
25
34
  * <Table.Body>
26
35
  * <Table.Row>
27
- * <Table.Cell flex={2}>INV-001</Table.Cell>
28
- * <Table.Cell align="end">$250.00</Table.Cell>
36
+ * <Table.Cell>INV-001</Table.Cell>
37
+ * <Table.Cell>$250.00</Table.Cell>
29
38
  * </Table.Row>
30
39
  * </Table.Body>
31
40
  * </Table>
@@ -125,15 +134,82 @@ const alignment: Record<CellAlign, string> = {
125
134
  end: 'justify-end',
126
135
  };
127
136
 
137
+ export interface TableColumn {
138
+ /**
139
+ * Share of the leftover width, relative to the other columns. Defaults to 1,
140
+ * so columns divide the row evenly.
141
+ */
142
+ flex?: number;
143
+ /**
144
+ * Fixed width in pixels, for a column that must not move — an icon, a state
145
+ * dot. Wins over `flex`.
146
+ */
147
+ width?: number;
148
+ /**
149
+ * Which edge the column's content sits against. Use `end` for numbers: a
150
+ * money column reads as a column only when the digits line up.
151
+ */
152
+ align?: CellAlign;
153
+ }
154
+
128
155
  /**
129
- * Density and striping are set once on the root; every part below reads them
130
- * from here rather than being told again per row and per cell.
156
+ * Density, striping and the column model are set once on the root; every part
157
+ * below reads them from here rather than being told again per row and per cell.
131
158
  */
132
- const TableContext = createContext<{ size: TableSize; striped: boolean }>({
159
+ const TableContext = createContext<{
160
+ size: TableSize;
161
+ striped: boolean;
162
+ columns?: TableColumn[];
163
+ }>({
133
164
  size: 'default',
134
165
  striped: false,
135
166
  });
136
167
 
168
+ /**
169
+ * Which column a head or a cell is in — its position among its row's children,
170
+ * counted by `Table.Row`. Only meaningful under a `columns` root, and the
171
+ * counting is skipped entirely without one, so a table that sizes its cells by
172
+ * hand pays nothing for a model it is not using.
173
+ */
174
+ const TableColumnContext = createContext<number>(-1);
175
+
176
+ /**
177
+ * Resolves a head's or a cell's sizing against the column it sits in. What the
178
+ * part was given always wins: the model is the default for the column, not a
179
+ * rule about it, so the one row that has to differ still can.
180
+ */
181
+ function useColumn(own: TableColumn): Required<Pick<TableColumn, 'align'>> & TableColumn {
182
+ const { columns } = useContext(TableContext);
183
+ const index = useContext(TableColumnContext);
184
+ const column = index >= 0 ? columns?.[index] : undefined;
185
+
186
+ return {
187
+ flex: own.flex ?? column?.flex,
188
+ width: own.width ?? column?.width,
189
+ align: own.align ?? column?.align ?? 'start',
190
+ };
191
+ }
192
+
193
+ /**
194
+ * Numbers a row's children so each one knows its column. Runs only when the
195
+ * root carries a `columns` model — otherwise the children are handed straight
196
+ * through, with no extra provider per cell on every row of the table.
197
+ */
198
+ function useRowCells(children: ReactNode, enabled: boolean): ReactNode {
199
+ return useMemo(() => {
200
+ const cells = textChildren(children);
201
+ if (!enabled) return cells;
202
+
203
+ return Children.map(cells, (child, index) =>
204
+ isValidElement(child) ? (
205
+ <TableColumnContext.Provider value={index}>{child}</TableColumnContext.Provider>
206
+ ) : (
207
+ child
208
+ )
209
+ );
210
+ }, [children, enabled]);
211
+ }
212
+
137
213
  /**
138
214
  * Which band of the table a row is in, and where in that band. A row needs all
139
215
  * three: the section decides its text and its borders, the index decides
@@ -162,13 +238,25 @@ export interface TableProps extends ViewProps, VariantProps<typeof tableVariants
162
238
  * for a short table, where the stripes are louder than the data.
163
239
  */
164
240
  striped?: boolean;
241
+ /**
242
+ * The column model: one entry per column, in order. Every `Table.Head` and
243
+ * `Table.Cell` takes its `flex`, `width` and `align` from the entry at its
244
+ * own position in the row, so a column is described once instead of on every
245
+ * row. Anything set on a head or a cell still wins.
246
+ *
247
+ * Declare it outside render — a new array each frame renumbers every cell.
248
+ */
249
+ columns?: TableColumn[];
165
250
  children?: ReactNode;
166
251
  }
167
252
 
168
253
  const TableRoot = forwardRef<View, TableProps>(
169
- ({ className, variant, size = 'default', striped = false, children, ...props }, ref) => {
254
+ (
255
+ { className, variant, size = 'default', striped = false, columns, children, ...props },
256
+ ref
257
+ ) => {
170
258
  const { root } = tableVariants({ variant, size });
171
- const context = useMemo(() => ({ size, striped }), [size, striped]);
259
+ const context = useMemo(() => ({ size, striped, columns }), [size, striped, columns]);
172
260
 
173
261
  return (
174
262
  <TableContext.Provider value={context}>
@@ -296,9 +384,10 @@ export interface TableRowProps
296
384
  */
297
385
  const TableRow = forwardRef<View, TableRowProps>(
298
386
  ({ className, selected, disabled, index, last, children, onPress, ...props }, ref) => {
299
- const { size, striped } = useContext(TableContext);
387
+ const { size, striped, columns } = useContext(TableContext);
300
388
  const row = useContext(TableRowContext);
301
389
  const { row: rowClass } = tableVariants({ size });
390
+ const cells = useRowCells(children, !!columns);
302
391
 
303
392
  const position = index ?? row.index;
304
393
  const isLast = last ?? row.last;
@@ -330,7 +419,7 @@ const TableRow = forwardRef<View, TableRowProps>(
330
419
  className={classes}
331
420
  {...(props as ViewProps)}
332
421
  >
333
- {textChildren(children)}
422
+ {cells}
334
423
  </View>
335
424
  );
336
425
  }
@@ -347,7 +436,7 @@ const TableRow = forwardRef<View, TableRowProps>(
347
436
  className={classes}
348
437
  {...props}
349
438
  >
350
- {textChildren(children)}
439
+ {cells}
351
440
  </AnimatedPressable>
352
441
  );
353
442
  }
@@ -368,13 +457,14 @@ export interface TableHeadProps extends Omit<ViewProps, 'children'> {
368
457
  className?: string;
369
458
  /**
370
459
  * Share of the leftover width, relative to the other cells in the row.
371
- * Defaults to 1, so columns divide the row evenly. Must match the `flex` on
372
- * the cells beneath it.
460
+ * Defaults to 1, so columns divide the row evenly. Without a `columns` model
461
+ * on the root it must match the `flex` on the cells beneath it.
373
462
  */
374
463
  flex?: number;
375
464
  /**
376
465
  * Fixed width in pixels, for a column that must not move — an icon, a state
377
- * dot. Must match the `width` on the cells beneath it.
466
+ * dot. Without a `columns` model on the root it must match the `width` on the
467
+ * cells beneath it.
378
468
  */
379
469
  width?: number;
380
470
  /**
@@ -415,12 +505,12 @@ const TableHead = forwardRef<View, TableHeadProps>(
415
505
  {
416
506
  className,
417
507
  labelClassName,
418
- align = 'start',
508
+ align: ownAlign,
419
509
  sortable,
420
510
  sortDirection,
421
511
  onPress,
422
- flex,
423
- width,
512
+ flex: ownFlex,
513
+ width: ownWidth,
424
514
  children,
425
515
  style,
426
516
  ...props
@@ -428,6 +518,11 @@ const TableHead = forwardRef<View, TableHeadProps>(
428
518
  ref
429
519
  ) => {
430
520
  const { size } = useContext(TableContext);
521
+ const { flex, width, align } = useColumn({
522
+ flex: ownFlex,
523
+ width: ownWidth,
524
+ align: ownAlign,
525
+ });
431
526
  const { head, headLabel } = tableVariants({ size });
432
527
  const arrowColor = useCSSVariable('--color-muted-foreground');
433
528
  const activeArrowColor = useCSSVariable('--color-foreground');
@@ -528,15 +623,19 @@ export interface TableCellProps extends ViewProps {
528
623
  className?: string;
529
624
  /**
530
625
  * Share of the leftover width, relative to the other cells in the row.
531
- * Defaults to 1. Must match the `flex` on the head above it.
626
+ * Defaults to 1. Without a `columns` model on the root it must match the
627
+ * `flex` on the head above it.
532
628
  */
533
629
  flex?: number;
534
630
  /**
535
- * Fixed width in pixels, for a column that must not move. Must match the
536
- * `width` on the head above it.
631
+ * Fixed width in pixels, for a column that must not move. Without a `columns`
632
+ * model on the root it must match the `width` on the head above it.
537
633
  */
538
634
  width?: number;
539
- /** Which edge the cell's content sits against. Match the head above it. */
635
+ /**
636
+ * Which edge the cell's content sits against. Without a `columns` model on
637
+ * the root, match the head above it.
638
+ */
540
639
  align?: CellAlign;
541
640
  /** Styles the cell's text. */
542
641
  labelClassName?: string;
@@ -550,10 +649,24 @@ export interface TableCellProps extends ViewProps {
550
649
  */
551
650
  const TableCell = forwardRef<View, TableCellProps>(
552
651
  (
553
- { className, labelClassName, align = 'start', flex, width, children, style, ...props },
652
+ {
653
+ className,
654
+ labelClassName,
655
+ align: ownAlign,
656
+ flex: ownFlex,
657
+ width: ownWidth,
658
+ children,
659
+ style,
660
+ ...props
661
+ },
554
662
  ref
555
663
  ) => {
556
664
  const { size } = useContext(TableContext);
665
+ const { flex, width, align } = useColumn({
666
+ flex: ownFlex,
667
+ width: ownWidth,
668
+ align: ownAlign,
669
+ });
557
670
  const { cell, cellLabel } = tableVariants({ size });
558
671
 
559
672
  return (
@@ -635,6 +748,8 @@ export interface TableFrameProps extends Omit<ViewProps, 'children'> {
635
748
  size?: TableSize;
636
749
  /** Tint every other body row, as on `Table`. */
637
750
  striped?: boolean;
751
+ /** The column model, as on `Table`. */
752
+ columns?: TableColumn[];
638
753
  children?: ReactNode;
639
754
  }
640
755
 
@@ -674,12 +789,13 @@ const TableFrame = forwardRef<View, TableFrameProps>(
674
789
  description,
675
790
  size = 'default',
676
791
  striped = false,
792
+ columns,
677
793
  children,
678
794
  ...props
679
795
  },
680
796
  ref
681
797
  ) => {
682
- const context = useMemo(() => ({ size, striped }), [size, striped]);
798
+ const context = useMemo(() => ({ size, striped, columns }), [size, striped, columns]);
683
799
 
684
800
  const { heading, body } = useMemo(() => {
685
801
  let found: ReactElement<TableHeaderProps> | null = null;
@@ -1,14 +1,15 @@
1
1
  /**
2
2
  * Timeline — a vertical sequence of events.
3
3
  *
4
- * Adapted from: origin-space/originui apps/origin/registry/default/ui/timeline.tsx
5
- * (the Item / Indicator / Separator / Header / Date / Title / Content anatomy
6
- * and the completed-up-to-active state rule), shaped to match this repo's
7
- * `Steps` component so the two read as siblings.
4
+ * One `value` on the root says how far the sequence has got, and every item
5
+ * resolves its own state from its `step` against it — completed below, active
6
+ * at, inactive above. Stating it once is the point: a timeline whose items each
7
+ * carried their own state would let you write two active items, or none.
8
8
  *
9
- * Upstream drives styling through `data-*` attributes, which React Native has
10
- * no equivalent for; state is resolved in JS and passed through context into
11
- * `tv()` variants instead.
9
+ * State is resolved in JS and handed down through context into `tv()` variants
10
+ * rather than being read off the element, because React Native has no
11
+ * attribute selectors for a stylesheet to key off. `Steps` resolves its states
12
+ * the same way, so the two read as siblings.
12
13
  *
13
14
  * Vertical only — a horizontal timeline gives each item roughly a fifth of a
14
15
  * phone's width, which is not enough for a date and a title.
package/src/index.ts CHANGED
@@ -267,6 +267,7 @@ export {
267
267
  type TableCellProps,
268
268
  type TableCaptionProps,
269
269
  type TableEmptyProps,
270
+ type TableColumn,
270
271
  type TableSortDirection,
271
272
  } from './components/table';
272
273
  export {
@@ -391,6 +392,20 @@ export {
391
392
  type PanelsideSwipeFrom,
392
393
  type UsePanelsideResult,
393
394
  } from './components/panelside';
395
+ export {
396
+ Pagination,
397
+ paginationRange,
398
+ type PaginationProps,
399
+ type PaginationItemProps,
400
+ type PaginationPreviousProps,
401
+ type PaginationNextProps,
402
+ type PaginationEllipsisProps,
403
+ type PaginationSummaryProps,
404
+ type PaginationStatusProps,
405
+ type PaginationVariant,
406
+ type PaginationSize,
407
+ type PaginationItemValue,
408
+ } from './components/pagination';
394
409
  export { Textarea, type TextareaProps } from './components/textarea';
395
410
  export {
396
411
  Item,