ionbase-ui 0.46.0 → 0.48.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "ionbase-ui",
3
- "version": "0.46.0",
3
+ "version": "0.48.0",
4
4
  "usage": "Pick a component here, then read dist/meta/<Name>.json for its full contract.",
5
5
  "hooks": [
6
6
  "useToast"
@@ -416,6 +416,23 @@
416
416
  "detail": "dist/meta/NavItem.json",
417
417
  "hasIntent": true
418
418
  },
419
+ "Pagination": {
420
+ "summary": "Page navigation for a table or list. Renders prev/next arrows, a truncated run of page numbers, and an optional rows-per-page Select.",
421
+ "status": "stable",
422
+ "variants": {
423
+ "type": [
424
+ "numbered",
425
+ "simple"
426
+ ],
427
+ "size": [
428
+ "sm",
429
+ "md",
430
+ "lg"
431
+ ]
432
+ },
433
+ "detail": "dist/meta/Pagination.json",
434
+ "hasIntent": true
435
+ },
419
436
  "PhoneInput": {
420
437
  "summary": "A dial-code block butted against an Input, sharing one outline. It does not pick countries.",
421
438
  "status": "stable",
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "pageItems",
3
+ "source": "src/components/Pagination.tsx",
4
+ "propsType": null,
5
+ "description": "Which page numbers to draw, and where the runs are broken.\n\nExported for tests: truncation is the only real logic in this component, and\nthe failure mode is a pager that renders a gap marker with nothing behind it\n— an ellipsis standing in for exactly one page, which is both a lie and a\ncell the user cannot click to reach a page they can see the number of.\n\nThe rule that prevents it: a gap is only drawn where it replaces TWO or more\npages. `left > 2` rather than `left > 1`, and the mirror on the right.",
6
+ "import": "import { pageItems } from 'ionbase-ui';",
7
+ "stylesheet": null,
8
+ "tokens": [],
9
+ "props": {},
10
+ "propCounts": {
11
+ "own": 0,
12
+ "aria": 0,
13
+ "dom": 0,
14
+ "other": 0
15
+ }
16
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "ionbase-ui",
3
- "version": "0.46.0",
3
+ "version": "0.48.0",
4
4
  "usage": "A pattern is a documented composition of components, not a component. Read one here, then read the contracts of what it composes.",
5
5
  "patterns": {
6
6
  "AgentRun": {
@@ -67,26 +67,30 @@
67
67
  * a userSpaceOnUse radial scaled per size, and the ratios come out the same
68
68
  * to three decimals, so the geometry is written once as percentages.
69
69
  *
70
- * THE ALPHA IS A TOKEN NOW, AND IT HAS TO BE. It was a raw `18%` in both
71
- * themes, and the initials sit exactly where the gloss peaks: over the dark
72
- * disc it lightened the ground under light text and dropped five of seven
73
- * hues under AA — green 3.76, red 3.92, pink 4.08, orange 4.15, blue 4.23.
74
- * `surface/sheen` is 20% in Light and 5% in Dark, which brings the worst hue
75
- * back to 4.94. 10% would land exactly on 4.50, which is not a margin.
70
+ * THE ALPHA IS A TOKEN NOW. It was a raw `18%` in both themes, hardcoded
71
+ * twice, which is a place dark mode has to be fixed by hand — the same reason
72
+ * the bevel below is a local. `surface/sheen` is 20% in Light and 5% in Dark,
73
+ * following `surface/scrim` and `surface/hover`: a translucent wash whose
74
+ * alpha themes.
76
75
  *
77
- * THE GATE ENFORCES THAT NUMBER NOW. It used to composite a translucent
78
- * ground over the component's flat `background-color` — the `to` stop — so it
79
- * measured the gloss over the palest part of the disc and never over `from`,
80
- * where the failure is; 4.94 was a hand measurement in this comment and
81
- * nothing checked it. `verify-contrast` pairs each layer against every opaque
82
- * ground beneath it as of 5 Sep 2026, so the sheen is measured over all three
83
- * disc stops and reports 4.94 on red by itself. Putting 18% back fails five
84
- * hues at the values above.
76
+ * THE ACCESSIBILITY ARGUMENT THAT PUT IT AT 5% WAS WRONG, AND THIS COMMENT
77
+ * USED TO MAKE IT. It claimed the gloss dropped five of seven hues under AA in
78
+ * Dark — green 3.76, red 3.92, pink 4.08, orange 4.15, blue 4.23. Those
79
+ * numbers came from compositing the sheen at FULL STRENGTH over the `from`
80
+ * stop, and that pixel does not exist. `from` is the top of the disc; the
81
+ * radial is centred at 70.513% of the height with a vertical radius of
82
+ * 64.103%, so at y=0 it is 1.10 radii out — past its own transparent stop,
83
+ * contributing nothing. The gloss and the darkest stop never meet.
85
84
  *
86
- * The cross product is a bound, not a rendering: the gloss peaks low and
87
- * centre, where the linear gradient is already near `to`, so full-strength
88
- * sheen over `from` is not a pixel that exists. It is the strictest the
89
- * composite could be, which is the right side to be wrong on for a floor.
85
+ * The gate evaluates the gradient geometrically as of 5 Sep 2026, and at the
86
+ * authored 18% the worst REAL point in Dark is 5.07 (green, at 50%,69%, where
87
+ * the gloss actually peaks and the disc beneath it is already near `to`).
88
+ * Nothing was ever failing. 5% is an aesthetic choice, not a fix, and it is
89
+ * only still here because it was reviewed in Figma and preferred.
90
+ *
91
+ * The worst point on this component is the bare top of the disc — 5.29 in
92
+ * Light (pink) and 5.31 in Dark (red), the same two numbers the stop comment
93
+ * below records, because the sheen never touches it.
90
94
  */
91
95
 
92
96
  /*
@@ -134,12 +138,14 @@
134
138
  * in the component that was not mirrored, which is precisely why Light failed
135
139
  * and Dark did not.
136
140
  *
137
- * THE GATE MEASURES ALL THREE STOPS NOW. It used to read only a flat
138
- * `background-color`, which is what let this hide; `verify-contrast` parses
139
- * `background-image` gradients as of 4 Sep 2026, so the worst stop is enforced
140
- * rather than trusted — 5.29 in Light (pink) and 5.31 in Dark (red) as it
141
- * stands. Negative-tested by putting the initials back on `/600`: ten
142
- * pairings fail that the old gate passed.
141
+ * THE GATE DOES NOT READ STOPS ANY MORE, IT EVALUATES THE GRADIENT. It began
142
+ * by reading only a flat `background-color`, which is what let this hide; then
143
+ * every stop; and as of 5 Sep 2026 it samples the disc, composites the layers
144
+ * at each point and reports the worst pixel the element actually paints —
145
+ * 5.29 in Light (pink) and 5.31 in Dark (red), both at 50%,0%, the bare top.
146
+ * Colours BETWEEN two stops are real pixels and none of them were measured
147
+ * before. Negative-tested by putting the initials back on `/600`: pink fails
148
+ * at 3.27 where the old gate passed it.
143
149
  */
144
150
  background-color: var(--ion-avatar-gradient-to);
145
151
  background-image:
@@ -43,6 +43,7 @@
43
43
  @import url('./header.css');
44
44
  @import url('./logo.css');
45
45
  @import url('./table.css');
46
+ @import url('./pagination.css');
46
47
  @import url('./divider.css');
47
48
  @import url('./link.css');
48
49
  @import url('./modal.css');
@@ -0,0 +1,170 @@
1
+ /*
2
+ * Pagination
3
+ *
4
+ * Geometry measured from Figma `Pagination Item` (1283:289) and `Pagination`
5
+ * (1291:503), designed 5 Sep 2026. Two Figma sets, one exported component: the
6
+ * item is an internal part, not something a caller composes, so it is not in
7
+ * the public API. See the `unmapped` note in figma/mapping.json.
8
+ *
9
+ * Size is a set of slots defaulting to medium — the same shape tabs.css uses.
10
+ * A size modifier reassigns five variables rather than restating five rules,
11
+ * which is why the item, the icon and the summary all resize from one class.
12
+ *
13
+ * sm 32 type/body-sm icon 14
14
+ * md 40 type/body icon 20
15
+ * lg 48 type/body-lg icon 24
16
+ *
17
+ * THE CELL IS NOT A FIXED SQUARE, AND FIGMA'S IS.
18
+ *
19
+ * Figma binds width AND height to the same spacing token, because a Figma
20
+ * variant has to pick a number and `1` is what fits in the frame. A real pager
21
+ * reaches three and four digits, and a fixed 40px box either clips `1024` or
22
+ * pads every single-digit cell to fit the widest one that might occur. So the
23
+ * height is the token and the width is a MINIMUM — the cell is square until the
24
+ * number outgrows it, then it grows. Do not "fix" this back to a square to
25
+ * match the Figma frame; the frame is the constraint, not the intent.
26
+ */
27
+
28
+ .ion-pagination {
29
+ --ion-pagination-box: var(--spacing-40);
30
+ --ion-pagination-gap: var(--spacing-8);
31
+ --ion-pagination-font-size: var(--type-body);
32
+ --ion-pagination-line-height: var(--type-body-line-height);
33
+ --ion-pagination-icon: var(--icon-size-md);
34
+
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: space-between;
38
+ gap: var(--spacing-16);
39
+ }
40
+
41
+ .ion-pagination--sm {
42
+ --ion-pagination-box: var(--spacing-32);
43
+ --ion-pagination-gap: var(--spacing-6);
44
+ --ion-pagination-font-size: var(--type-body-sm);
45
+ --ion-pagination-line-height: var(--type-body-sm-line-height);
46
+ --ion-pagination-icon: var(--icon-size-xs);
47
+ }
48
+
49
+ .ion-pagination--lg {
50
+ --ion-pagination-box: var(--spacing-48);
51
+ --ion-pagination-font-size: var(--type-body-lg);
52
+ --ion-pagination-line-height: var(--type-body-lg-line-height);
53
+ --ion-pagination-icon: var(--icon-size-lg);
54
+ }
55
+
56
+ /*
57
+ * With no page-size slot there is nothing to sit opposite, and
58
+ * `space-between` would pin a lone control group to the left edge of whatever
59
+ * contains it. Ending the row is what the component means in that case.
60
+ */
61
+ .ion-pagination--no-page-size {
62
+ justify-content: flex-end;
63
+ }
64
+
65
+ .ion-pagination__page-size {
66
+ display: flex;
67
+ align-items: center;
68
+ gap: var(--spacing-8);
69
+ }
70
+
71
+ .ion-pagination__controls {
72
+ display: flex;
73
+ align-items: center;
74
+ gap: var(--spacing-4);
75
+ margin: 0;
76
+ padding: 0;
77
+ list-style: none;
78
+ }
79
+
80
+ /* The cell. A button for pages and arrows; a span for the ellipsis. */
81
+ .ion-pagination__item {
82
+ display: inline-flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ box-sizing: border-box;
86
+ min-width: var(--ion-pagination-box);
87
+ height: var(--ion-pagination-box);
88
+ padding: 0 var(--spacing-4);
89
+ border: 0;
90
+ border-radius: var(--radius-md);
91
+ background-color: transparent;
92
+ color: var(--text-tertiary);
93
+ font-family: var(--font-family-sans);
94
+ font-size: var(--ion-pagination-font-size);
95
+ line-height: var(--ion-pagination-line-height);
96
+ font-weight: var(--font-weight-medium);
97
+ cursor: pointer;
98
+ transition:
99
+ background-color var(--ion-duration-base) var(--ion-ease-out),
100
+ color var(--ion-duration-base) var(--ion-ease-out);
101
+ }
102
+
103
+ .ion-pagination__item[data-hovered] {
104
+ background-color: var(--surface-hover);
105
+ color: var(--text-default);
106
+ }
107
+
108
+ /*
109
+ * SELECTED IS `surface/selected`, NOT THE `surface/default` TABS USES.
110
+ *
111
+ * A Tabs pill sits on a `surface/muted` track, so `surface/default` reads as a
112
+ * raised chip against it. Pagination has no track: on a `surface/default` card
113
+ * the current page would be the same colour as the thing behind it, and the one
114
+ * cell that must be unmistakable would be the only invisible one.
115
+ */
116
+ .ion-pagination__item[data-selected] {
117
+ background-color: var(--surface-selected);
118
+ color: var(--text-default);
119
+ }
120
+
121
+ .ion-pagination__item[data-selected][data-hovered] {
122
+ background-color: var(--surface-selected-hover);
123
+ }
124
+
125
+ .ion-pagination__item[data-disabled] {
126
+ color: var(--text-disabled);
127
+ cursor: not-allowed;
128
+ }
129
+
130
+ .ion-pagination__item[data-focused] {
131
+ outline: var(--border-width-thick) solid var(--border-focus);
132
+ outline-offset: var(--spacing-2);
133
+ }
134
+
135
+ .ion-pagination__icon {
136
+ width: var(--ion-pagination-icon);
137
+ height: var(--ion-pagination-icon);
138
+ flex-shrink: 0;
139
+ }
140
+
141
+ /*
142
+ * The truncation marker. Not a button and not focusable — it is the absence of
143
+ * pages, and giving it a tab stop would put a control in the sequence that does
144
+ * nothing when activated. `aria-hidden` in the TSX keeps it out of the
145
+ * announcement too; the surrounding numbers already say what was skipped.
146
+ */
147
+ .ion-pagination__ellipsis {
148
+ display: inline-flex;
149
+ align-items: center;
150
+ justify-content: center;
151
+ min-width: var(--ion-pagination-box);
152
+ height: var(--ion-pagination-box);
153
+ color: var(--text-tertiary);
154
+ }
155
+
156
+ .ion-pagination__summary {
157
+ padding: 0 var(--spacing-8);
158
+ color: var(--text-secondary);
159
+ font-family: var(--font-family-sans);
160
+ font-size: var(--ion-pagination-font-size);
161
+ line-height: var(--ion-pagination-line-height);
162
+ font-weight: var(--font-weight-regular);
163
+ white-space: nowrap;
164
+ }
165
+
166
+ @media (prefers-reduced-motion: reduce) {
167
+ .ion-pagination__item {
168
+ transition: none;
169
+ }
170
+ }
package/llms.txt CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > IonBase is a React design system for enterprise SaaS, built to be consumed by an agent rather than read by a developer. Every component ships a machine-readable contract carrying the judgement a type signature cannot: when to use it, what to use instead, the anti-patterns, and what it guarantees for accessibility versus what it requires of you.
4
4
 
5
- Version 0.46.0. 46 components.
5
+ Version 0.48.0. 47 components.
6
6
 
7
7
  ## Read these, in this order
8
8
 
@@ -13,7 +13,7 @@ Both are importable: `ionbase-ui/meta/index` and `ionbase-ui/meta/<Name>.json`.
13
13
 
14
14
  ## Components
15
15
 
16
- `AgentActivity`, `AgentActivityStep`, `AgentStop`, `Alert`, `ApprovalGate`, `Avatar`, `AvatarGradient`, `AvatarGroup`, `Badge`, `Button`, `Checkbox`, `Citation`, `CitationList`, `CitationListItem`, `ConfidenceIndicator`, `Divider`, `EmptyState`, `FullCard`, `Header`, `Icon`, `Input`, `Link`, `Logo`, `LogoMark`, `Menu`, `MenuItem`, `Modal`, `NavItem`, `PhoneInput`, `Popover`, `Radio`, `RadioGroup`, `ScrollProgress`, `Select`, `StreamingText`, `TabItem`, `Table`, `TableBody`, `TableCell`, `TableHead`, `TableRow`, `Tabs`, `Toast`, `ToastProvider`, `Toggle`, `Tooltip`
16
+ `AgentActivity`, `AgentActivityStep`, `AgentStop`, `Alert`, `ApprovalGate`, `Avatar`, `AvatarGradient`, `AvatarGroup`, `Badge`, `Button`, `Checkbox`, `Citation`, `CitationList`, `CitationListItem`, `ConfidenceIndicator`, `Divider`, `EmptyState`, `FullCard`, `Header`, `Icon`, `Input`, `Link`, `Logo`, `LogoMark`, `Menu`, `MenuItem`, `Modal`, `NavItem`, `Pagination`, `PhoneInput`, `Popover`, `Radio`, `RadioGroup`, `ScrollProgress`, `Select`, `StreamingText`, `TabItem`, `Table`, `TableBody`, `TableCell`, `TableHead`, `TableRow`, `Tabs`, `Toast`, `ToastProvider`, `Toggle`, `Tooltip`
17
17
 
18
18
  ## Patterns
19
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ionbase-ui",
3
- "version": "0.46.0",
3
+ "version": "0.48.0",
4
4
  "description": "IonBase Design System — accessible React components, styles and design tokens in one package",
5
5
  "license": "MIT",
6
6
  "type": "module",