@cdx-ui/components 0.3.13 → 0.3.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/coverage.svg CHANGED
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="108" height="20" role="img" aria-label="coverage: 91.6%"><title>coverage: 91.6%</title><filter id="blur"><feGaussianBlur stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="108" height="20" rx="3"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="47" height="20" fill="#4b0"/><rect width="108" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="315" y="150" fill-opacity=".8" filter="url(#blur)" textLength="510">coverage</text><text x="315" y="150" fill-opacity=".3" textLength="510">coverage</text></g><text x="315" y="140" textLength="510">coverage</text></g><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="835" y="150" fill-opacity=".8" filter="url(#blur)" textLength="370">91.6%</text><text x="835" y="150" fill-opacity=".3" textLength="370">91.6%</text></g><text x="835" y="140" textLength="370">91.6%</text></g></g></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="108" height="20" role="img" aria-label="coverage: 91.7%"><title>coverage: 91.7%</title><filter id="blur"><feGaussianBlur stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="108" height="20" rx="3"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="47" height="20" fill="#4b0"/><rect width="108" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="315" y="150" fill-opacity=".8" filter="url(#blur)" textLength="510">coverage</text><text x="315" y="150" fill-opacity=".3" textLength="510">coverage</text></g><text x="315" y="140" textLength="510">coverage</text></g><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="835" y="150" fill-opacity=".8" filter="url(#blur)" textLength="370">91.7%</text><text x="835" y="150" fill-opacity=".3" textLength="370">91.7%</text></g><text x="835" y="140" textLength="370">91.7%</text></g></g></svg>
@@ -58,6 +58,365 @@ export default function AlertDialogBasicDemo() {
58
58
  }
59
59
  ```
60
60
 
61
+ ## Sizes
62
+
63
+ Set `size` on `AlertDialog` to cap the popup width. It accepts the same values as `Dialog`. Alert Dialogs are usually short, so `sm` and `md` fit most confirmations; reserve `lg` for decisions that need more explanation.
64
+
65
+ ```tsx
66
+ import { useState } from 'react';
67
+ import { AlertDialog, Button, HStack, type AlertDialogRootProps } from '@cdx-ui/components';
68
+
69
+ type AlertDialogSize = NonNullable<AlertDialogRootProps['size']>;
70
+
71
+ interface SizeScenario {
72
+ size: AlertDialogSize;
73
+ title: string;
74
+ description: string;
75
+ body: string;
76
+ cancel: string;
77
+ action: string;
78
+ }
79
+
80
+ const scenarios: SizeScenario[] = [
81
+ {
82
+ size: 'xs',
83
+ title: 'Sign out?',
84
+ description: 'You will need to sign in again.',
85
+ body: 'Unsaved transfer drafts will be discarded.',
86
+ cancel: 'Stay',
87
+ action: 'Sign out',
88
+ },
89
+ {
90
+ size: 'sm',
91
+ title: 'Remove this device?',
92
+ description: 'iPad Air · Last used 3 days ago',
93
+ body: 'This device will no longer receive sign-in codes or account alerts.',
94
+ cancel: 'Keep device',
95
+ action: 'Remove',
96
+ },
97
+ {
98
+ size: 'md',
99
+ title: 'Cancel scheduled payment?',
100
+ description: 'City Water & Power · Due November 18',
101
+ body: 'The $84.12 payment will not be sent. You can schedule a new payment at any time before the due date.',
102
+ cancel: 'Keep payment',
103
+ action: 'Cancel payment',
104
+ },
105
+ {
106
+ size: 'lg',
107
+ title: 'Transfer exceeds your daily limit',
108
+ description: 'Your remaining limit today is $2,500.00.',
109
+ body: 'Send $2,500.00 now and the remaining $1,250.00 tomorrow morning, or call us to request a one-time limit increase for this transfer.',
110
+ cancel: 'Edit amount',
111
+ action: 'Split transfer',
112
+ },
113
+ ];
114
+
115
+ function SizeAlertDialog({ scenario }: { scenario: SizeScenario }) {
116
+ const [open, setOpen] = useState(false);
117
+
118
+ return (
119
+ <AlertDialog isOpen={open} onOpenChange={setOpen} size={scenario.size}>
120
+ <AlertDialog.Trigger asChild>
121
+ <Button variant="outline">
122
+ <Button.Label>{scenario.size}</Button.Label>
123
+ </Button>
124
+ </AlertDialog.Trigger>
125
+ <AlertDialog.Popup>
126
+ <AlertDialog.Header>
127
+ <AlertDialog.Title>{scenario.title}</AlertDialog.Title>
128
+ <AlertDialog.Description>{scenario.description}</AlertDialog.Description>
129
+ </AlertDialog.Header>
130
+ <AlertDialog.Content>
131
+ <AlertDialog.Text>{scenario.body}</AlertDialog.Text>
132
+ </AlertDialog.Content>
133
+ <AlertDialog.Footer>
134
+ <AlertDialog.Cancel>{scenario.cancel}</AlertDialog.Cancel>
135
+ <AlertDialog.Action onPress={() => setOpen(false)}>{scenario.action}</AlertDialog.Action>
136
+ </AlertDialog.Footer>
137
+ </AlertDialog.Popup>
138
+ </AlertDialog>
139
+ );
140
+ }
141
+
142
+ export default function AlertDialogSizesDemo() {
143
+ return (
144
+ <HStack space="sm" className="flex-wrap justify-center">
145
+ {scenarios.map((scenario) => (
146
+ <SizeAlertDialog key={scenario.size} scenario={scenario} />
147
+ ))}
148
+ </HStack>
149
+ );
150
+ }
151
+ ```
152
+
153
+ ## Custom triggers
154
+
155
+ `AlertDialog.Trigger` with `asChild` turns any pressable element into a trigger, such as a row's `IconButton`. To confirm a change before applying it, such as a `Switch` that locks a card, control the dialog with `isOpen` and open it from the change handler.
156
+
157
+ ```tsx
158
+ import { useState } from 'react';
159
+ import {
160
+ AlertDialog,
161
+ Avatar,
162
+ Card,
163
+ Heading,
164
+ IconButton,
165
+ ListItem,
166
+ Switch,
167
+ } from '@cdx-ui/components';
168
+ import { CreditCard, Delete, Lock } from '@cdx-ui/icons';
169
+
170
+ export default function AlertDialogCustomTriggersDemo() {
171
+ const [locked, setLocked] = useState(false);
172
+ const [lockOpen, setLockOpen] = useState(false);
173
+ const [removeOpen, setRemoveOpen] = useState(false);
174
+ const [payeeRemoved, setPayeeRemoved] = useState(false);
175
+
176
+ const handleLockChange = (next: boolean) => {
177
+ if (next) {
178
+ setLockOpen(true);
179
+ } else {
180
+ setLocked(false);
181
+ }
182
+ };
183
+
184
+ return (
185
+ <Card fullBleed className="w-full max-w-sm">
186
+ <Card.Header>
187
+ <Heading size="sm">Card & payees</Heading>
188
+ </Card.Header>
189
+ <Card.Content>
190
+ <ListItem>
191
+ <ListItem.LeadingSlot>
192
+ <Avatar color={locked ? 'danger' : 'brand'}>
193
+ <Avatar.Icon as={locked ? Lock : CreditCard} />
194
+ </Avatar>
195
+ </ListItem.LeadingSlot>
196
+ <ListItem.Content>
197
+ <ListItem.Title>Lock debit card</ListItem.Title>
198
+ <ListItem.Description>
199
+ {locked ? 'Purchases are declined' : 'Debit •••• 4821'}
200
+ </ListItem.Description>
201
+ </ListItem.Content>
202
+ <ListItem.TrailingSlot>
203
+ <Switch
204
+ value={locked}
205
+ onValueChange={handleLockChange}
206
+ accessibilityLabel="Lock debit card"
207
+ />
208
+ </ListItem.TrailingSlot>
209
+ </ListItem>
210
+
211
+ <AlertDialog isOpen={lockOpen} onOpenChange={setLockOpen} size="sm">
212
+ <AlertDialog.Popup>
213
+ <AlertDialog.Header>
214
+ <AlertDialog.Title severity="warning">Lock debit card?</AlertDialog.Title>
215
+ <AlertDialog.Description>Debit card ending in 4821</AlertDialog.Description>
216
+ </AlertDialog.Header>
217
+ <AlertDialog.Content>
218
+ <AlertDialog.Text>
219
+ New purchases and ATM withdrawals will be declined. Recurring bills and direct
220
+ deposits continue as usual.
221
+ </AlertDialog.Text>
222
+ </AlertDialog.Content>
223
+ <AlertDialog.Footer>
224
+ <AlertDialog.Cancel>Keep unlocked</AlertDialog.Cancel>
225
+ <AlertDialog.Action
226
+ onPress={() => {
227
+ setLocked(true);
228
+ setLockOpen(false);
229
+ }}
230
+ >
231
+ Lock card
232
+ </AlertDialog.Action>
233
+ </AlertDialog.Footer>
234
+ </AlertDialog.Popup>
235
+ </AlertDialog>
236
+
237
+ {payeeRemoved ? null : (
238
+ <ListItem showSeparator={false}>
239
+ <ListItem.LeadingSlot>
240
+ <Avatar color="info">
241
+ <Avatar.Text>City Water</Avatar.Text>
242
+ </Avatar>
243
+ </ListItem.LeadingSlot>
244
+ <ListItem.Content>
245
+ <ListItem.Title>City Water & Power</ListItem.Title>
246
+ <ListItem.Description>Payee · Account •••• 9012</ListItem.Description>
247
+ </ListItem.Content>
248
+ <ListItem.TrailingSlot>
249
+ <AlertDialog isOpen={removeOpen} onOpenChange={setRemoveOpen} size="sm">
250
+ <AlertDialog.Trigger asChild>
251
+ <IconButton
252
+ as={Delete}
253
+ variant="ghost"
254
+ size="small"
255
+ accessibilityLabel="Remove City Water & Power"
256
+ />
257
+ </AlertDialog.Trigger>
258
+ <AlertDialog.Popup>
259
+ <AlertDialog.Header>
260
+ <AlertDialog.Title severity="danger">Remove this payee?</AlertDialog.Title>
261
+ <AlertDialog.Description>City Water & Power</AlertDialog.Description>
262
+ </AlertDialog.Header>
263
+ <AlertDialog.Content>
264
+ <AlertDialog.Text>
265
+ One scheduled payment of $84.12 on November 18 will also be canceled.
266
+ </AlertDialog.Text>
267
+ </AlertDialog.Content>
268
+ <AlertDialog.Footer>
269
+ <AlertDialog.Cancel>Keep payee</AlertDialog.Cancel>
270
+ <AlertDialog.Action
271
+ color="danger"
272
+ onPress={() => {
273
+ setPayeeRemoved(true);
274
+ setRemoveOpen(false);
275
+ }}
276
+ >
277
+ Remove
278
+ </AlertDialog.Action>
279
+ </AlertDialog.Footer>
280
+ </AlertDialog.Popup>
281
+ </AlertDialog>
282
+ </ListItem.TrailingSlot>
283
+ </ListItem>
284
+ )}
285
+ </Card.Content>
286
+ </Card>
287
+ );
288
+ }
289
+ ```
290
+
291
+ ## Stacked actions
292
+
293
+ When the user has more than two possible responses, stack the actions in a `Button.Group` with `flexDirection="column"` inside `AlertDialog.Footer`, and pass `fullWidth` to each `AlertDialog.Action` and `AlertDialog.Cancel`. Give the group `className="flex-1"` so it spans the footer. Put the cancel action last.
294
+
295
+ ```tsx
296
+ import { useState } from 'react';
297
+ import { AlertDialog, Avatar, Button, Card, ListItem, Text } from '@cdx-ui/components';
298
+ import { Security, ShoppingBag } from '@cdx-ui/icons';
299
+
300
+ export default function AlertDialogStackedActionsDemo() {
301
+ const [open, setOpen] = useState(false);
302
+
303
+ return (
304
+ <AlertDialog isOpen={open} onOpenChange={setOpen} size="sm">
305
+ <AlertDialog.Trigger asChild>
306
+ <Button variant="outline" color="danger">
307
+ <Button.Icon as={Security} position="leading" />
308
+ <Button.Label>Simulate fraud alert</Button.Label>
309
+ </Button>
310
+ </AlertDialog.Trigger>
311
+ <AlertDialog.Popup>
312
+ <AlertDialog.Header>
313
+ <AlertDialog.Title severity="warning">Was this you?</AlertDialog.Title>
314
+ <AlertDialog.Description>
315
+ We noticed an unusual purchase on Visa Signature •••• 4821.
316
+ </AlertDialog.Description>
317
+ </AlertDialog.Header>
318
+ <AlertDialog.Content>
319
+ <Card fullBleed>
320
+ <Card.Content>
321
+ <ListItem showSeparator={false}>
322
+ <ListItem.LeadingSlot>
323
+ <Avatar color="neutral">
324
+ <Avatar.Icon as={ShoppingBag} />
325
+ </Avatar>
326
+ </ListItem.LeadingSlot>
327
+ <ListItem.Content>
328
+ <ListItem.Title>Electronics Outlet Online</ListItem.Title>
329
+ <ListItem.Description>Today, 2:14 AM · Miami, FL</ListItem.Description>
330
+ </ListItem.Content>
331
+ <ListItem.TrailingSlot>
332
+ <Text weight="semibold">$842.19</Text>
333
+ </ListItem.TrailingSlot>
334
+ </ListItem>
335
+ </Card.Content>
336
+ </Card>
337
+ </AlertDialog.Content>
338
+ <AlertDialog.Footer>
339
+ <Button.Group flexDirection="column" className="flex-1">
340
+ <AlertDialog.Action fullWidth onPress={() => setOpen(false)}>
341
+ Yes, it was me
342
+ </AlertDialog.Action>
343
+ <AlertDialog.Action fullWidth color="danger" onPress={() => setOpen(false)}>
344
+ No, lock my card
345
+ </AlertDialog.Action>
346
+ <AlertDialog.Cancel fullWidth>Remind me later</AlertDialog.Cancel>
347
+ </Button.Group>
348
+ </AlertDialog.Footer>
349
+ </AlertDialog.Popup>
350
+ </AlertDialog>
351
+ );
352
+ }
353
+ ```
354
+
355
+ ## Form
356
+
357
+ Add a small input when the decision needs one more detail. Keep `AlertDialog.Action` disabled until the user has answered, and let its label describe the result of their choice.
358
+
359
+ ```tsx
360
+ import { useState } from 'react';
361
+ import { AlertDialog, Button, Field, Radio } from '@cdx-ui/components';
362
+ import { EventRepeat } from '@cdx-ui/icons';
363
+
364
+ export default function AlertDialogFormDemo() {
365
+ const [open, setOpen] = useState(false);
366
+ const [scope, setScope] = useState('');
367
+
368
+ const handleOpenChange = (next: boolean) => {
369
+ setOpen(next);
370
+ if (!next) {
371
+ setScope('');
372
+ }
373
+ };
374
+
375
+ return (
376
+ <AlertDialog isOpen={open} onOpenChange={handleOpenChange}>
377
+ <AlertDialog.Trigger asChild>
378
+ <Button variant="outline">
379
+ <Button.Icon as={EventRepeat} position="leading" />
380
+ <Button.Label>Stop recurring transfer</Button.Label>
381
+ </Button>
382
+ </AlertDialog.Trigger>
383
+ <AlertDialog.Popup>
384
+ <AlertDialog.Header>
385
+ <AlertDialog.Title severity="danger">Stop recurring transfer?</AlertDialog.Title>
386
+ <AlertDialog.Description>
387
+ $250.00 monthly from Everyday Checking to Vacation Fund
388
+ </AlertDialog.Description>
389
+ </AlertDialog.Header>
390
+ <AlertDialog.Content>
391
+ <Field isRequired>
392
+ <Field.Label>Which transfers should stop?</Field.Label>
393
+ <Radio.Group value={scope} onChange={setScope}>
394
+ <Radio value="next">
395
+ <Radio.Label>Only the next transfer on December 1</Radio.Label>
396
+ </Radio>
397
+ <Radio value="all">
398
+ <Radio.Label>This and all future transfers</Radio.Label>
399
+ </Radio>
400
+ </Radio.Group>
401
+ <Field.Helper>Transfers already processing cannot be stopped.</Field.Helper>
402
+ </Field>
403
+ </AlertDialog.Content>
404
+ <AlertDialog.Footer>
405
+ <AlertDialog.Cancel>Keep transfer</AlertDialog.Cancel>
406
+ <AlertDialog.Action
407
+ color="danger"
408
+ isDisabled={!scope}
409
+ onPress={() => handleOpenChange(false)}
410
+ >
411
+ {scope === 'next' ? 'Skip next transfer' : 'Stop transfers'}
412
+ </AlertDialog.Action>
413
+ </AlertDialog.Footer>
414
+ </AlertDialog.Popup>
415
+ </AlertDialog>
416
+ );
417
+ }
418
+ ```
419
+
61
420
  ## Destructive action
62
421
 
63
422
  Pair a danger title with a danger action when the decision has an irreversible consequence.
@@ -123,6 +123,110 @@ export default function BarChartHorizontalDemo() {
123
123
  }
124
124
  ```
125
125
 
126
+ ## Long labels
127
+
128
+ Category names never overlap. A name wider than its room wraps at word boundaries onto a second line, and anything still too long ends in `…`. In the vertical layout the room is the category's slot; in the horizontal layout it is the label gutter. The full name stays in the tooltip and on each bar's accessibility label.
129
+
130
+ A name that wraps onto two lines stays readable, and the chart gives up one line of plot height to fit it.
131
+
132
+ ```tsx
133
+ import { BarChart } from '@cdx-ui/components';
134
+ import { CATEGORY_AXIS, EXPENSES, SPEND_SERIES } from './_data';
135
+
136
+ export default function BarChartLongLabelsWrapDemo() {
137
+ return (
138
+ <BarChart
139
+ dataset={EXPENSES}
140
+ xAxis={CATEGORY_AXIS}
141
+ series={SPEND_SERIES}
142
+ hideLegend
143
+ scrollable={false}
144
+ accessibilityLabel="Spend by category"
145
+ />
146
+ );
147
+ }
148
+ ```
149
+
150
+ Truncation is a fallback, not a design. On a narrow chart with several categories, single long words such as "Transportation" lose their endings and the reader has to open the tooltip to compare bars.
151
+
152
+ ```tsx
153
+ import { BarChart, Box } from '@cdx-ui/components';
154
+ import { CATEGORY_AXIS, SPEND_SERIES, SPENDING_CATEGORIES } from './_data';
155
+
156
+ export default function BarChartLongLabelsTruncateDemo() {
157
+ return (
158
+ <Box className="w-80">
159
+ <BarChart
160
+ dataset={SPENDING_CATEGORIES}
161
+ xAxis={CATEGORY_AXIS}
162
+ series={SPEND_SERIES}
163
+ hideLegend
164
+ scrollable={false}
165
+ accessibilityLabel="Spend by category"
166
+ />
167
+ </Box>
168
+ );
169
+ }
170
+ ```
171
+
172
+ Switch to `layout="horizontal"` when names run longer than a word or two, or when there are more categories than fit side by side at phone width. The same data reads in full, and bars stay easy to compare. Keep names short at the source as well: the chart cannot shorten them for you.
173
+
174
+ ```tsx
175
+ import { BarChart, Box } from '@cdx-ui/components';
176
+ import { CATEGORY_AXIS, SPEND_SERIES, SPENDING_CATEGORIES } from './_data';
177
+
178
+ export default function BarChartLongLabelsHorizontalDemo() {
179
+ return (
180
+ <Box className="w-80">
181
+ <BarChart
182
+ dataset={SPENDING_CATEGORIES}
183
+ layout="horizontal"
184
+ yAxis={CATEGORY_AXIS}
185
+ series={SPEND_SERIES}
186
+ hideLegend
187
+ scrollable={false}
188
+ height={280}
189
+ accessibilityLabel="Spend by category"
190
+ />
191
+ </Box>
192
+ );
193
+ }
194
+ ```
195
+
196
+ Set `tickLabelMaxLines` on the category axis to change how far names wrap before the `…`. `1` truncates without wrapping, which keeps every chart in a row the same height; a higher value keeps more of each name and gives up one line of plot height per extra line. A horizontal chart also stops at the lines its rows are tall enough to hold.
197
+
198
+ ```tsx
199
+ import { BarChart, Box, VStack } from '@cdx-ui/components';
200
+ import { MULTI_WORD_CATEGORIES, SPEND_SERIES } from './_data';
201
+
202
+ export default function BarChartLongLabelsMaxLinesDemo() {
203
+ return (
204
+ <VStack space="2xl" className="w-full">
205
+ <Box className="w-80">
206
+ <BarChart
207
+ dataset={MULTI_WORD_CATEGORIES}
208
+ xAxis={[{ id: 'category', dataKey: 'category', scaleType: 'band', tickLabelMaxLines: 1 }]}
209
+ series={SPEND_SERIES}
210
+ hideLegend
211
+ scrollable={false}
212
+ accessibilityLabel="Spend by category, one line per name"
213
+ />
214
+ </Box>
215
+ <Box className="w-80">
216
+ <BarChart
217
+ dataset={MULTI_WORD_CATEGORIES}
218
+ xAxis={[{ id: 'category', dataKey: 'category', scaleType: 'band', tickLabelMaxLines: 3 }]}
219
+ series={SPEND_SERIES}
220
+ hideLegend
221
+ scrollable={false}
222
+ accessibilityLabel="Spend by category, up to three lines per name"
223
+ />
224
+ </Box>
225
+ </VStack>
226
+ );
227
+ }
228
+ ```
229
+
126
230
  ## Axis config
127
231
 
128
232
  The value axis takes `min`, `max`, and `tickNumber` to set the domain and the number of ticks, and `valueFormatter` for tick text. A series `valueFormatter` wins over the axis one everywhere except ticks, so ticks can read `$1k` while the tooltip reads `$860`. `position: 'none'` on the value axis hides its ticks. On the category axis, `position` moves the names (`'top'` / `'bottom'` when vertical, `'left'` / `'right'` when horizontal, `'none'` to hide them) and a `tickLabelInterval` function picks which names draw.
@@ -566,8 +670,8 @@ Generic over your row type, shown here as `TRow`: `series[].dataKey` and axis `d
566
670
  | `accessibilityLabel?` | `string` | — | Accessible summary. Defaults to `localeText.barChartSummary` over every category. |
567
671
  | `dataset` | `readonly TRow[]` | — | One object per category. Series and axes read it through `dataKey`. |
568
672
  | `series` | `readonly BarChartSeries<TRow>[]` | — | Bars to draw. Each reads one numeric column through `dataKey`. |
569
- | `xAxis?` | `readonly ChartAxis<TRow>[]` | — | Horizontal axis. In `vertical` layout it is the category (band) axis: `dataKey` names the category column, `position` (`'bottom'`, `'top'`, `'none'`) places its labels, and `tickLabelInterval` picks which labels draw. In `horizontal` layout it is the value axis. Only the first axis is used. |
570
- | `yAxis?` | `readonly ChartAxis<TRow>[]` | — | Vertical axis. In `vertical` layout it is the value axis (`min`, `max`, `tickNumber`, `valueFormatter`; `position: 'none'` hides its ticks). In `horizontal` layout it is the category axis (`position` `'left'`, `'right'`, or `'none'`). Only the first axis is used. |
673
+ | `xAxis?` | `readonly ChartAxis<TRow>[]` | — | Horizontal axis. In `vertical` layout it is the category (band) axis: `dataKey` names the category column, `position` (`'bottom'`, `'top'`, `'none'`) places its labels, and `tickLabelInterval` picks which labels draw, and `tickLabelMaxLines` caps how far a long name wraps before it ends in `…`. In `horizontal` layout it is the value axis. Only the first axis is used. |
674
+ | `yAxis?` | `readonly ChartAxis<TRow>[]` | — | Vertical axis. In `vertical` layout it is the value axis (`min`, `max`, `tickNumber`, `valueFormatter`; `position: 'none'` hides its ticks). In `horizontal` layout it is the category axis (`position` `'left'`, `'right'`, or `'none'`; `tickLabelMaxLines` as on `xAxis`). Only the first axis is used. |
571
675
  | `layout?` | `'vertical' \| 'horizontal'` | `'vertical'` | `vertical` grows bars up from the baseline. `horizontal` grows them to the right. |
572
676
  | `hideLegend?` | `boolean` | `false` | Hides the series legend under the chart. Waterfall charts never show one. |
573
677
  | `grid?` | `ChartGrid` | `{ horizontal: true } (vertical layout), { vertical: true } (horizontal layout)` | Grid lines along the value axis: `horizontal` in `vertical` layout, `vertical` in `horizontal` layout. The other direction is not drawn. Omit to draw the value-axis lines; pass an object without that key set to `true` to hide them and keep only the baseline. |
@@ -631,6 +735,7 @@ One entry in `xAxis` or `yAxis`. Only the first entry in each direction is read.
631
735
  | `valueFormatter?` | `ChartValueFormatter` | — | Formats tick text on this axis (`location: 'tick'`). On a value axis it also formats series values when the series has no `valueFormatter`. On a line chart's x-axis it formats numeric categories, both the tick (`location: 'tick'`) and the tooltip title (`location: 'tooltip'`). |
632
736
  | `position?` | `'top' \| 'bottom' \| 'left' \| 'right' \| 'none'` | — | Side the axis labels draw on. `'none'` hides them. Each chart accepts the sides it can draw and falls back to its default side for the others. |
633
737
  | `tickLabelInterval?` | `"auto" \| ((value: unknown, index: number) => boolean)` | `'auto'` | Which category labels to draw. A function keeps the label at `index` when it returns `true`. `'auto'` currently draws every label; it does not thin them yet. |
738
+ | `tickLabelMaxLines?` | `number` | `2` | Most lines a category name may wrap onto before it ends in `…`. `1` truncates without wrapping. Read by `BarChart` on its category axis; a horizontal chart also stops at the lines its rows are tall enough to hold. Forge only; MUI X has no equivalent. |
634
739
 
635
740
  Formatters resolve per value: the series `valueFormatter`, then the value axis `valueFormatter`, then a `locale` number. Ticks use the axis formatter with `location: 'tick'`.
636
741
 
@@ -673,6 +778,7 @@ Forge renders with `react-native-svg` on iOS, Android, and web, and does not dep
673
778
  | `grid={{ horizontal, vertical }}` | `grid` | Same shape, but only the lines along the value axis draw, and they are on by default. MUI draws no grid unless you pass `grid`. |
674
779
  | axis `position` | axis `position` | `'none'` hides an axis. The value axis draws only on its default side. |
675
780
  | axis `tickLabelInterval` | axis `tickLabelInterval` | The function form works on the category axis. `'auto'` draws every label for now. |
781
+ | — | axis `tickLabelMaxLines` | Forge only. MUI X has no wrap or truncation option; it breaks lines only where a `valueFormatter` returns `\n`. |
676
782
  | `<ChartsReferenceLine>` | `referenceLines` | Forge has no composition API yet, so reference lines are a prop. |
677
783
  | `highlightedItem` / `onItemClick` | `selectedItem` / `onSelectedItemChange` | Selection is shared by every Forge chart. Pressing the selected bar reports `null`. `defaultSelectedItem` is a Forge addition for uncontrolled use. |
678
784
  | `skipAnimation` | `animation="disable-all"` | Matches the `animation` prop on other Forge components. |