@plexui/mcp 0.1.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 (2) hide show
  1. package/dist/index.js +4586 -0
  2. package/package.json +40 -0
package/dist/index.js ADDED
@@ -0,0 +1,4586 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { z } from "zod";
7
+
8
+ // src/data/manifest.json
9
+ var manifest_default = {
10
+ name: "@plexui/ui",
11
+ version: "latest",
12
+ description: "The most flexible UI kit for React. Production-grade components with pixel-perfect Figma parity.",
13
+ homepage: "https://plexui.com",
14
+ docsUrl: "https://plexui.com/docs/components",
15
+ install: "npm install @plexui/ui",
16
+ setup: {
17
+ css: '@import "tailwindcss";\n@import "@plexui/ui/css";',
18
+ provider: 'Wrap your app with <PlexUIProvider> from "@plexui/ui/components/PlexUIProvider"'
19
+ },
20
+ sizeScale: [
21
+ "3xs",
22
+ "2xs",
23
+ "xs",
24
+ "sm",
25
+ "md",
26
+ "lg",
27
+ "xl",
28
+ "2xl",
29
+ "3xl"
30
+ ],
31
+ colors: [
32
+ "primary",
33
+ "secondary",
34
+ "danger",
35
+ "success",
36
+ "warning",
37
+ "caution",
38
+ "discovery",
39
+ "info"
40
+ ],
41
+ variants: [
42
+ "solid",
43
+ "soft",
44
+ "outline",
45
+ "ghost"
46
+ ],
47
+ componentCount: 37,
48
+ components: [
49
+ {
50
+ slug: "alert",
51
+ name: "Alert",
52
+ description: "Call attention to a specific message or warning",
53
+ importPath: "@plexui/ui/components/Alert",
54
+ exports: [
55
+ "Alert"
56
+ ],
57
+ props: [
58
+ {
59
+ name: "title",
60
+ type: "ReactNode",
61
+ description: "Title displayed in the alert"
62
+ },
63
+ {
64
+ name: "description",
65
+ type: "ReactNode",
66
+ description: "Description text"
67
+ },
68
+ {
69
+ name: "actions",
70
+ type: "ReactNode",
71
+ description: "Actions (for example buttons)"
72
+ },
73
+ {
74
+ name: "actionsPlacement",
75
+ type: '"end" | "bottom"',
76
+ description: "Placement of actions"
77
+ },
78
+ {
79
+ name: "color",
80
+ type: "SemanticColors",
81
+ default: '"primary"',
82
+ description: "Color scheme"
83
+ },
84
+ {
85
+ name: "variant",
86
+ type: '"solid" | "soft" | "outline"',
87
+ default: '"outline"',
88
+ description: "Style variant"
89
+ },
90
+ {
91
+ name: "indicator",
92
+ type: "ReactNode | false",
93
+ description: "Custom indicator or false to hide"
94
+ },
95
+ {
96
+ name: "onDismiss",
97
+ type: "() => void",
98
+ description: "Shows dismiss button when provided"
99
+ }
100
+ ],
101
+ examples: [
102
+ {
103
+ name: "Overview",
104
+ code: `<Alert
105
+ title="Scheduled maintenance"
106
+ description="We'll be offline 2 - 4 AM UTC on July 14 while we upgrade our database."
107
+ actions={<Button color="primary" variant="soft" pill>Dismiss</Button>}
108
+ />`
109
+ },
110
+ {
111
+ name: "Colors & variants",
112
+ code: '<Alert\n color="primary"\n variant="outline"\n title="Scheduled maintenance on Saturday, 2-4 AM PT"\n description={<>Track status updates on <TextLink color="currentcolor" href="#">status page</TextLink></>}\n/>',
113
+ details: "You can stylize the display of the alert using the color and variant props."
114
+ },
115
+ {
116
+ name: "Title",
117
+ code: '<Alert title="Your current access level limits what you can view or modify" />',
118
+ details: "You can optionally include a title for your alert with the title prop."
119
+ },
120
+ {
121
+ name: "Description",
122
+ code: `<Alert
123
+ description={
124
+ <>
125
+ We're working on centralizing SCIM and invite settings. For now, setup is handled within individual product settings. <TextLink href="#">Learn more</TextLink>
126
+ </>
127
+ }
128
+ />`,
129
+ details: "You can optionally include description text for your alert with the description prop."
130
+ },
131
+ {
132
+ name: "Actions",
133
+ code: '<Alert\n variant="soft"\n color="warning"\n title="Password expires in 3 days"\n description="Update it now to avoid losing access to your account."\n actions={<Button color="primary" pill>Update password</Button>}\n/>',
134
+ details: "You can add actions to the alert with the actions prop, which accepts ReactNode. Combine with onDismiss to add a dismiss button."
135
+ },
136
+ {
137
+ name: "Action placement",
138
+ code: `<Alert
139
+ title="Our terms of service has been updated"
140
+ description="We've updated our terms to clarify how we handle data, billing, and user permissions. Please review and accept the latest terms to avoid impacting your service."
141
+ actions={(
142
+ <>
143
+ <Button color="primary" variant="soft" pill>Set reminder</Button>
144
+ <Button color="primary" variant="solid" pill>Review terms</Button>
145
+ </>
146
+ )}
147
+ actionsPlacement="bottom"
148
+ />`,
149
+ details: "Actions are automatically positioned in the alert by default. You can force a specific position with the actionsPlacement prop by passing either 'end' or 'bottom'."
150
+ },
151
+ {
152
+ name: "Indicator",
153
+ code: `<Alert
154
+ indicator={<Lightbulb />}
155
+ description={
156
+ <>
157
+ We're working on centralizing SCIM and invite settings. For now, setup is handled within individual product settings. <TextLink href="#">Learn more</TextLink>
158
+ </>
159
+ }
160
+ />`,
161
+ details: "Alert provides an indicator icon by default. You can override it by passing a custom icon to indicator, or pass indicator={'{false}'} to hide it entirely."
162
+ },
163
+ {
164
+ name: "Dismissible",
165
+ code: `<Alert
166
+ className="items-start"
167
+ title="Try our new dashboard layout"
168
+ description="We've introduced a streamlined layout that makes using the dashboard even easier. You can switch back any time."
169
+ actions={(
170
+ <>
171
+ <Button color="primary" variant="soft" pill>Dismiss</Button>
172
+ <Button color="primary" variant="solid" pill>Try it</Button>
173
+ </>
174
+ )}
175
+ />`,
176
+ details: "The suggested approach for dismissible notices is to use labeled buttons via actions. You can also provide onDismiss to render a dismiss button in the alert chrome."
177
+ }
178
+ ],
179
+ docsUrl: "https://plexui.com/docs/components/alert"
180
+ },
181
+ {
182
+ slug: "avatar-group",
183
+ name: "Avatar Group",
184
+ description: "Display avatars as a single stack",
185
+ importPath: "@plexui/ui/components/Avatar",
186
+ exports: [
187
+ "AvatarGroup"
188
+ ],
189
+ props: [
190
+ {
191
+ name: "size",
192
+ type: "number",
193
+ description: "Size of all avatars in the group"
194
+ },
195
+ {
196
+ name: "stack",
197
+ type: '"start" | "end"',
198
+ default: '"start"',
199
+ description: "Stacking direction"
200
+ }
201
+ ],
202
+ examples: [
203
+ {
204
+ name: "Overview",
205
+ code: '<AvatarGroup size={42}>\n <Avatar name="Tyler" imageUrl="https://gravatar.com/avatar/xyz" />\n <Avatar name="Jane" color="primary" variant="solid" />\n <Avatar name="Tech support" Icon={Robot} variant="solid" />\n <Avatar overflowCount={5} />\n</AvatarGroup>'
206
+ },
207
+ {
208
+ name: "Stacking direction",
209
+ code: '<AvatarGroup stack="end" size={42}>...</AvatarGroup>',
210
+ details: 'You can control the stacking direction using the stack prop, as either "start" or "end".'
211
+ },
212
+ {
213
+ name: "Group sizing",
214
+ code: '<AvatarGroup size={48}>\n <Avatar name="Tyler" color="info" />\n <Avatar name="Jane" color="discovery" />\n <Avatar name="Will" color="danger" />\n <Avatar overflowCount={5} />\n</AvatarGroup>',
215
+ details: "You can control the size of all avatars in the group by passing size directly to the &lt;AvatarGroup&gt; component."
216
+ }
217
+ ],
218
+ docsUrl: "https://plexui.com/docs/components/avatar-group"
219
+ },
220
+ {
221
+ slug: "avatar",
222
+ name: "Avatar",
223
+ description: "Display user identities with either text, photo, or an icon",
224
+ importPath: "@plexui/ui/components/Avatar",
225
+ exports: [
226
+ "Avatar"
227
+ ],
228
+ props: [
229
+ {
230
+ name: "name",
231
+ type: "string",
232
+ description: "Display name; used for text initial when no image/icon"
233
+ },
234
+ {
235
+ name: "imageUrl",
236
+ type: "string",
237
+ description: "Absolute URL for image avatar"
238
+ },
239
+ {
240
+ name: "Icon",
241
+ type: "React.ComponentType",
242
+ description: "Icon component for icon avatar"
243
+ },
244
+ {
245
+ name: "size",
246
+ type: "number",
247
+ description: "Width and height in pixels"
248
+ },
249
+ {
250
+ name: "color",
251
+ type: "string",
252
+ description: "Semantic color for text/icon avatar"
253
+ },
254
+ {
255
+ name: "variant",
256
+ type: '"soft" | "solid"',
257
+ description: "Style variant"
258
+ },
259
+ {
260
+ name: "overflowCount",
261
+ type: "number",
262
+ description: "Show overflow count (e.g. +9)"
263
+ },
264
+ {
265
+ name: "onClick",
266
+ type: "function",
267
+ description: "Makes avatar interactive"
268
+ }
269
+ ],
270
+ examples: [
271
+ {
272
+ name: "Overview",
273
+ code: '<div className="flex gap-4">\n <Avatar name="Tyler" imageUrl="https://gravatar.com/avatar/9531b260b9693f3394bea8646c6ea141ce58fe5a138b7db7729d60a4c5dde552" size={48} />\n <Avatar name="Will" size={48} />\n <Avatar name="Tech support" Icon={Robot} size={48} color="primary" variant="solid" />\n</div>',
274
+ details: "A variety of avatar types: text, image, icon, and overflow."
275
+ },
276
+ {
277
+ name: "Text",
278
+ code: '<Avatar name="David" size={48} />',
279
+ details: "For text-based avatars, a single initial is displayed based on the name prop passed to the component."
280
+ },
281
+ {
282
+ name: "Image",
283
+ code: '<Avatar\n name="Tyler"\n imageUrl="https://gravatar.com/avatar/9531b260b9693f3394bea8646c6ea141ce58fe5a138b7db7729d60a4c5dde552"\n size={48}\n/>',
284
+ details: "To display an image avatar, provide an absolute URL to the image with imageUrl. Images will have a soft fade entrance once loaded, or fallback to a text display if the image fail to load."
285
+ },
286
+ {
287
+ name: "Icon",
288
+ code: "<Avatar\n Icon={Robot}\n size={48}\n/>",
289
+ details: "To display an icon avatar, provide an icon to render to the Icon prop. The name prop is not used in this case, and can be omitted."
290
+ },
291
+ {
292
+ name: "Overflow count",
293
+ code: "<Avatar overflowCount={9} size={48} />",
294
+ details: "To display an overflow avatar, provide a number to the overflowCount prop. The number will be automatically formatted and scaled to fit."
295
+ },
296
+ {
297
+ name: "Sizing",
298
+ code: '<Avatar name="David" size={48} />',
299
+ details: "To size the avatar, pass a number to the size property, which will set the width and height in pixels. Text and icon avatars will scale their sizes dynamically based on this value."
300
+ },
301
+ {
302
+ name: "Colors & variants",
303
+ code: '<div className="flex gap-4 flex-wrap">\n <Avatar name="A" color="primary" variant="soft" size={40} />\n <Avatar name="B" color="primary" variant="solid" size={40} />\n <Avatar name="C" color="success" variant="soft" size={40} />\n <Avatar name="D" color="danger" variant="solid" size={40} />\n</div>',
304
+ details: "You can stylize the display of the avatar using the color and variant props."
305
+ },
306
+ {
307
+ name: "Roundness",
308
+ code: '<Avatar name="Acme, co." color="primary" variant="solid" size={48} className="rounded-lg" />',
309
+ details: "By default, avatars are fully rounded. You can override this by passing a Tailwind rounding class to className."
310
+ },
311
+ {
312
+ name: "Interactive",
313
+ code: '<Avatar\n name="Will"\n size={48}\n onClick={saySup}\n/>',
314
+ details: "Avatars can be used as interactive elements by passing an onClick handler. The avatar will automatically apply semantic and visual interaction styles."
315
+ }
316
+ ],
317
+ docsUrl: "https://plexui.com/docs/components/avatar"
318
+ },
319
+ {
320
+ slug: "badge",
321
+ name: "Badge",
322
+ description: "Emphasize details with a status indicator",
323
+ importPath: "@plexui/ui/components/Badge",
324
+ exports: [
325
+ "Badge"
326
+ ],
327
+ props: [
328
+ {
329
+ name: "children",
330
+ type: "ReactNode",
331
+ description: "Content inside the badge"
332
+ },
333
+ {
334
+ name: "color",
335
+ type: '"secondary" | "success" | "danger" | "warning" | "info" | "discovery" | "caution"',
336
+ default: '"secondary"',
337
+ description: "Semantic color"
338
+ },
339
+ {
340
+ name: "variant",
341
+ type: '"solid" | "soft" | "outline"',
342
+ default: '"soft"',
343
+ description: "Style variant"
344
+ },
345
+ {
346
+ name: "size",
347
+ type: '"sm" | "md" | "lg"',
348
+ default: '"sm"',
349
+ description: "Badge size"
350
+ },
351
+ {
352
+ name: "pill",
353
+ type: "boolean",
354
+ default: "false",
355
+ description: "Fully rounded"
356
+ }
357
+ ],
358
+ examples: [
359
+ {
360
+ name: "Overview",
361
+ code: '<div className="flex gap-4 flex-wrap">\n <Badge color="success" size="lg">Successful</Badge>\n <Badge size="lg" className="gap-1.5"><LoadingIndicator /> In progress</Badge>\n <Badge color="danger" size="lg">Failed</Badge>\n <Badge color="warning" size="lg" pill><Flask /> Beta</Badge>\n <Badge color="caution" size="lg">Caution</Badge>\n</div>',
362
+ details: "Status badges in different colors and compositions."
363
+ },
364
+ {
365
+ name: "Colors & variants",
366
+ code: 'const variants = ["soft", "solid", "outline"] as const\nconst colors = ["secondary", "success", "warning", "danger", "info", "discovery", "caution"] as const\n\n<div className="pt-1 pb-6 px-8 w-fit min-w-full">\n <div\n className="grid gap-6 items-center justify-center"\n style={{ gridTemplateColumns: \\`auto repeat(\\${colors.length}, min-content)\\` }}\n >\n <div />\n {colors.map((color) => (\n <div key={color} className="text-center mb-1 text-sm" style={{ color: "var(--color-text-tertiary)" }}>\n {color}\n </div>\n ))}\n\n {variants.map((variant) => (\n <React.Fragment key={variant}>\n <div className="text-right mr-3 text-sm" style={{ color: "var(--color-text-tertiary)" }}>\n {variant}\n </div>\n {colors.map((color) => (\n <div key={\\`\\${variant}-\\${color}\\',
367
+ details: "You can stylize the display of the badge using the color and variant props."
368
+ },
369
+ {
370
+ name: "Size & roundness",
371
+ code: 'const [size, setSize] = useState("md")\nconst [pill, setPill] = useState(false)\n\n<>\n <div style={{ background: "var(--color-surface-secondary)", width: "100%" }}>\n <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "6px 16px 6px 8px", borderTop: "1px solid var(--color-fd-border)" }}>\n <span style={{ fontFamily: "var(--font-mono, ui-monospace, monospace)", fontSize: "0.8125rem", padding: "2px 8px", borderRadius: 6, background: "var(--color-surface-secondary)" }}>\n size\n </span>\n <SegmentedControl value={size} onChange={setSize} size="xs" aria-label="size">\n <SegmentedControl.Option value="sm">sm</SegmentedControl.Option>\n <SegmentedControl.Option value="md">md</SegmentedControl.Option>\n <SegmentedControl.Option value="lg">lg</SegmentedControl.Option>\n </SegmentedControl>\n </div>\n <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "6px 16px 6px 8px", borderTop: "1px solid var(--color-fd-border)" }}>\n <span style={{ fontFamily: "var(--font-mono, ui-monospace, monospace)", fontSize: "0.8125rem", padding: "2px 8px", borderRadius: 6, background: "var(--color-surface-secondary)" }}>\n pill\n </span>\n <Switch checked={pill} onCheckedChange={setPill} aria-label="pill" />\n </div>\n </div>\n <div className="py-10 flex justify-center">\n <Badge color="info" size={size} pill={pill}>New</Badge>\n </div>\n</>',
372
+ details: "You can control the size and roundness of the badge with size and pill props. When pill is set, extra horizontal gutter is dynamically applied to the badge."
373
+ },
374
+ {
375
+ name: "Icon",
376
+ code: '<Badge color="warning" size="lg"><Flask /> Beta</Badge>',
377
+ details: "Content within the badge can be composed, like showing icons. Color, sizing, and spacing adapt by default."
378
+ },
379
+ {
380
+ name: "Loading",
381
+ code: '<Badge size="lg" color="secondary" className="gap-1.5">\n <LoadingIndicator /> In progress\n</Badge>',
382
+ details: "In this example, additional gap is added with a Tailwind gap-1.5 class to provide the appropriate visual spacing."
383
+ }
384
+ ],
385
+ docsUrl: "https://plexui.com/docs/components/badge"
386
+ },
387
+ {
388
+ slug: "button-link",
389
+ name: "Button Link",
390
+ description: '"Button as a semantic anchor element"',
391
+ importPath: "@plexui/ui/components/Button",
392
+ exports: [
393
+ "ButtonLink"
394
+ ],
395
+ props: [
396
+ {
397
+ name: "children",
398
+ type: "ReactNode",
399
+ description: "Content inside the link button"
400
+ },
401
+ {
402
+ name: "color",
403
+ type: '"primary" | "secondary" | "danger" | "success" | "info" | "discovery" | "caution" | "warning"',
404
+ default: '"primary"',
405
+ description: "Visual color"
406
+ },
407
+ {
408
+ name: "variant",
409
+ type: '"solid" | "soft" | "outline" | "ghost"',
410
+ default: '"solid"',
411
+ description: "Style variant"
412
+ },
413
+ {
414
+ name: "size",
415
+ type: '"3xs" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl"',
416
+ default: '"md"',
417
+ description: "Control height and typography scale"
418
+ },
419
+ {
420
+ name: "gutterSize",
421
+ type: '"3xs" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl"',
422
+ description: "Override horizontal padding"
423
+ },
424
+ {
425
+ name: "iconSize",
426
+ type: '"sm" | "md" | "lg" | "xl" | "2xl"',
427
+ description: "Override icon size"
428
+ },
429
+ {
430
+ name: "href",
431
+ type: "string",
432
+ description: "Link URL"
433
+ },
434
+ {
435
+ name: "to",
436
+ type: "string | object",
437
+ description: "Router link destination (internal)"
438
+ },
439
+ {
440
+ name: "as",
441
+ type: "ComponentType",
442
+ description: "Link component override (e.g. Next Link)"
443
+ },
444
+ {
445
+ name: "external",
446
+ type: "boolean",
447
+ description: "Force external-link behavior"
448
+ },
449
+ {
450
+ name: "opticallyAlign",
451
+ type: '"start" | "end"',
452
+ description: "Apply optical negative margin alignment"
453
+ },
454
+ {
455
+ name: "block",
456
+ type: "boolean",
457
+ default: "false",
458
+ description: "Full width"
459
+ },
460
+ {
461
+ name: "pill",
462
+ type: "boolean",
463
+ default: "true",
464
+ description: "Fully rounded"
465
+ },
466
+ {
467
+ name: "disabled",
468
+ type: "boolean",
469
+ default: "false",
470
+ description: "Disable link"
471
+ },
472
+ {
473
+ name: "disabledTone",
474
+ type: '"relaxed"',
475
+ description: "Use relaxed visual tone for disabled state"
476
+ },
477
+ {
478
+ name: "className",
479
+ type: "string",
480
+ description: "Custom classes for the root element"
481
+ }
482
+ ],
483
+ examples: [
484
+ {
485
+ name: "Overview",
486
+ code: '<ButtonLink color="primary" pill={false} href="#">View API Keys <ArrowRight /></ButtonLink>'
487
+ },
488
+ {
489
+ name: "Internal link",
490
+ code: '<ButtonLink href="/some-path" color="primary">\n Internal link <ArrowRight />\n</ButtonLink>',
491
+ details: "When a relative href or to is provided, ButtonLink automatically uses the configured LinkComponent from PlexUIProvider so your router handles navigation and prefetching."
492
+ },
493
+ {
494
+ name: "External link",
495
+ code: '<ButtonLink href="#" color="primary">\n External link <ArrowUpRight />\n</ButtonLink>',
496
+ details: 'When linking to an external URL (or when external is set), &lt;a&gt; is used with target="_blank" and rel="noopener noreferrer" added automatically. If your link is not being automatically identified, you can force external link behavior with the external prop.'
497
+ },
498
+ {
499
+ name: "Colors & variants",
500
+ code: '<div className="pt-1 pb-6 px-8 w-fit min-w-full">\n {/* Matrix of variant x color */}\n</div>',
501
+ details: "Stylize the link with color and variant props."
502
+ },
503
+ {
504
+ name: "Sizing & roundness",
505
+ code: '<ButtonLink size="lg" pill={false}>\n View account <ArrowRight />\n</ButtonLink>\n<ButtonLink size="lg" pill={false}>\n View account\n</ButtonLink>',
506
+ details: "Sizing of the link is controlled with size and gutterSize props. size sets the control height, and gutterSize sets the horizontal padding. Create fully rounded links with the pill prop. When pill is set, extra horizontal gutter is dynamically applied to the link."
507
+ },
508
+ {
509
+ name: "Icons",
510
+ code: '<ButtonLink color="info" size="lg" iconSize="lg" variant="soft" href="#">\n <Globe /> View website\n</ButtonLink>',
511
+ details: "Icon sizing is controlled by the iconSize prop, which makes it easy to add icons as children without additional props. size will provide a default value for iconSize, but setting the prop will force the specified size."
512
+ },
513
+ {
514
+ name: "Block",
515
+ code: '<ButtonLink block size="lg" href="#">Continue to dashboard</ButtonLink>',
516
+ details: "Create full-width links with the block prop."
517
+ },
518
+ {
519
+ name: "Disabled",
520
+ code: '<ButtonLink href="#" color="primary" disabled>\n View API Keys\n</ButtonLink>',
521
+ details: 'Use disabled to both visually and accessibly disable a link. The component renders as a span[role="link"], and onClick handlers are not bound.'
522
+ }
523
+ ],
524
+ docsUrl: "https://plexui.com/docs/components/button-link"
525
+ },
526
+ {
527
+ slug: "button",
528
+ name: "Button",
529
+ description: "Create actions in many different styles",
530
+ importPath: "@plexui/ui/components/Button",
531
+ exports: [
532
+ "Button"
533
+ ],
534
+ props: [
535
+ {
536
+ name: "color",
537
+ type: '"primary" | "secondary" | "danger" | "success" | "info" | "discovery" | "caution" | "warning"',
538
+ default: '"primary"',
539
+ description: "Visual color scheme"
540
+ },
541
+ {
542
+ name: "variant",
543
+ type: '"solid" | "soft" | "outline" | "ghost"',
544
+ default: '"solid"',
545
+ description: "Button style variant"
546
+ },
547
+ {
548
+ name: "size",
549
+ type: '"3xs" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl"',
550
+ default: '"md"',
551
+ description: "Control height and default gutter/icon size"
552
+ },
553
+ {
554
+ name: "pill",
555
+ type: "boolean",
556
+ default: "true",
557
+ description: "Fully rounded corners"
558
+ },
559
+ {
560
+ name: "block",
561
+ type: "boolean",
562
+ default: "false",
563
+ description: "Full width"
564
+ },
565
+ {
566
+ name: "uniform",
567
+ type: "boolean",
568
+ default: "false",
569
+ description: "Square icon button (width equals height)"
570
+ },
571
+ {
572
+ name: "disabled",
573
+ type: "boolean",
574
+ default: "false",
575
+ description: "Visually and accessibly disabled"
576
+ },
577
+ {
578
+ name: "loading",
579
+ type: "boolean",
580
+ default: "false",
581
+ description: "Shows loading indicator"
582
+ },
583
+ {
584
+ name: "inert",
585
+ type: "boolean",
586
+ default: "false",
587
+ description: "Accessibly disabled without visual change"
588
+ },
589
+ {
590
+ name: "selected",
591
+ type: "boolean",
592
+ default: "false",
593
+ description: "Selected state styling"
594
+ },
595
+ {
596
+ name: "opticallyAlign",
597
+ type: '"start" | "end"',
598
+ description: "Offset gutter for optical alignment"
599
+ },
600
+ {
601
+ name: "gutterSize",
602
+ type: "string",
603
+ description: "Horizontal padding size"
604
+ },
605
+ {
606
+ name: "iconSize",
607
+ type: "string",
608
+ description: "Size of icons in children"
609
+ }
610
+ ],
611
+ examples: [
612
+ {
613
+ name: "Overview",
614
+ code: '<div className="flex gap-3">\n <Button color="primary">Submit</Button>\n <Button variant="soft"><CompareArrows /> Compare</Button>\n <Button color="danger" gutterSize="sm" pill={false}><Delete /></Button>\n <Button variant="outline">Next <ArrowRight /></Button>\n <Button variant="ghost" color="secondary" uniform><Clear /></Button>\n</div>',
615
+ details: "A showcase of Button's key styles: solid, soft, outline, ghost, and icon-only."
616
+ },
617
+ {
618
+ name: "Colors & variants",
619
+ code: '<div className="w-[calc(100%+48px)] -mx-6 overflow-x-auto [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden">\n <div className="pt-1 pb-6 px-8 w-max min-w-full">\n <Matrix\n rowLabels={["soft", "solid", "outline", "ghost"]}\n columnLabels={["primary", "secondary", "danger", "info", "discovery", "success", "caution", "warning"]}\n renderCell={(row, col) => (\n <Button\n color={["primary", "secondary", "danger", "info", "discovery", "success", "caution", "warning"][col]}\n variant={["soft", "solid", "outline", "ghost"][row]}\n size="md"\n >\n Submit\n </Button>\n )}\n />\n </div>\n</div>',
620
+ details: "You can stylize the display of the button using the color and variant props."
621
+ },
622
+ {
623
+ name: "Sizing & roundness",
624
+ code: '<div className="flex flex-col gap-2 justify-start items-start">\n <Button color="primary" size="xl" pill><Mail /> Button <ArrowRight /></Button>\n <Button color="primary" size="xl" pill>Button <ArrowRight /></Button>\n <Button color="primary" size="xl" pill><Mail /> Button</Button>\n <Button color="primary" size="xl" pill>Button</Button>\n</div>',
625
+ details: "Control height with size and horizontal padding with gutterSize. Use pill for fully rounded corners. size sets the button to a specific control height, and gutterSize sets the horizontal padding. Each size provides default values for gutterSize and iconSize props, as well as font-size. Each of these values can be overridden independently. When pill is set, extra horizontal gutter is dynamically applied to the button."
626
+ },
627
+ {
628
+ name: "Icons",
629
+ code: '<Button color="secondary" size="lg" uniform variant="ghost">\n <PlusLg />\n</Button>',
630
+ details: "Add icons as children \u2014 sizing is automatic via iconSize prop. Default values for iconSize are provided through the size prop, but you can pass the prop directly to change the defaults. For icon-only buttons, there are two common approaches: Square \u2014 create perfect square icon buttons with the uniform prop. When uniform is passed, gutterSize is ignored. Combine with pill to make circular icons. Tighter gutters \u2014 applying a smaller gutterSize than size can create well-sized, but not exactly square, icon buttons."
631
+ },
632
+ {
633
+ name: "Block",
634
+ code: '<div className="w-[290px] text-center p-2 border border-dashed border-alpha/20 rounded-md">\n <Button block size="lg">Continue</Button>\n</div>',
635
+ details: "Create full-width buttons with the block prop."
636
+ },
637
+ {
638
+ name: "Disabled",
639
+ code: "<Button disabled>Submit</Button>",
640
+ details: "Use disabled to both visually and accessibly disable a button."
641
+ },
642
+ {
643
+ name: "Inert",
644
+ code: "<Button inert>Submit</Button>",
645
+ details: "Use inert to accessibly disable a button without any visual changes. Note: For accessibility reasons, disabled is used instead of applying inert directly to the button. Visual disablement in the disabled prop is applied via [data-disable]."
646
+ },
647
+ {
648
+ name: "Loading",
649
+ code: '<Button loading size="xl" pill><ArrowUp /> Click to load</Button>',
650
+ details: "Use loading to display a loading indicator in the button, fading out the current content. Interactions will be prevented by default during this state, by providing a default value for inert to true. You can override this behavior directly with inert, or use disabled to add visual disablement."
651
+ },
652
+ {
653
+ name: "Selected",
654
+ code: '<Popover>\n <Popover.Trigger>\n <Button selected variant="solid">Click to open</Button>\n </Popover.Trigger>\n <Popover.Content minWidth="auto" className="p-4 text-sm">\n <ShimmerText className="font-medium text-secondary">Button should look selected</ShimmerText>\n </Popover.Content>\n</Popover>',
655
+ details: 'Use selected to indicate that a given button is correctly in an open or selected state. The exact visual behavior differs across each variant. This behavior is automatically configured with components like Menu that apply [aria-expanded="true"] or [data-state="open"] attributes.'
656
+ },
657
+ {
658
+ name: "Optical alignment",
659
+ code: '<div className="flex flex-col gap-3">\n <div className="border border-dashed border-alpha/20 rounded-md py-4 px-6">\n <div className="mb-2 text-secondary text-sm">Default gutters</div>\n <Button variant="ghost">Ghost button</Button>\n </div>\n <div className="border border-dashed border-alpha/20 rounded-md py-4 px-6">\n <div className="mb-2 text-secondary text-sm">opticallyAlign="start"</div>\n <Button variant="ghost" opticallyAlign="start">Ghost button</Button>\n </div>\n</div>',
660
+ details: 'Use opticallyAlign with either "start" or "end" to offset the gutter of the button.'
661
+ }
662
+ ],
663
+ docsUrl: "https://plexui.com/docs/components/button"
664
+ },
665
+ {
666
+ slug: "checkbox",
667
+ name: "Checkbox",
668
+ description: "Toggle control for on and off states",
669
+ importPath: "@plexui/ui/components/Checkbox",
670
+ exports: [
671
+ "Checkbox"
672
+ ],
673
+ props: [
674
+ {
675
+ name: "variant",
676
+ type: '"solid" | "ghost"',
677
+ default: '"solid"',
678
+ description: "Visual style \u2014 solid (filled background) or ghost (checkmark only)"
679
+ },
680
+ {
681
+ name: "pill",
682
+ type: "boolean",
683
+ default: "false",
684
+ description: "Fully rounded indicator corners"
685
+ },
686
+ {
687
+ name: "checked",
688
+ type: 'boolean | "indeterminate"',
689
+ description: "Controlled state"
690
+ },
691
+ {
692
+ name: "defaultChecked",
693
+ type: 'boolean | "indeterminate"',
694
+ description: "Initial state (uncontrolled)"
695
+ },
696
+ {
697
+ name: "onCheckedChange",
698
+ type: "(next: boolean) => void",
699
+ description: "Called when state changes"
700
+ },
701
+ {
702
+ name: "label",
703
+ type: "ReactNode",
704
+ description: "Accessible label \u2014 accepts ReactNode for rich content"
705
+ },
706
+ {
707
+ name: "disabled",
708
+ type: "boolean",
709
+ default: "false",
710
+ description: "Prevent interaction"
711
+ },
712
+ {
713
+ name: "required",
714
+ type: "boolean",
715
+ description: "Required for form submission"
716
+ },
717
+ {
718
+ name: "name",
719
+ type: "string",
720
+ description: "Form field name"
721
+ },
722
+ {
723
+ name: "value",
724
+ type: "string",
725
+ description: "Value when submitted"
726
+ },
727
+ {
728
+ name: "orientation",
729
+ type: '"left" | "right"',
730
+ default: '"left"',
731
+ description: "Label position relative to checkbox"
732
+ },
733
+ {
734
+ name: "className",
735
+ type: "string",
736
+ description: "CSS classes applied to wrapper node"
737
+ },
738
+ {
739
+ name: "id",
740
+ type: "string",
741
+ description: "The id of the checkbox element"
742
+ }
743
+ ],
744
+ examples: [
745
+ {
746
+ name: "Overview",
747
+ code: '<div className="flex flex-col gap-3">\n <Checkbox label="Same as billing address" />\n <Checkbox label="Subscribe to newsletter" defaultChecked />\n</div>'
748
+ },
749
+ {
750
+ name: "Variants",
751
+ code: '{/* solid (default) */}\n<Checkbox label="Checked" defaultChecked />\n\n{/* solid pill */}\n<Checkbox label="Checked" pill defaultChecked />\n\n{/* ghost */}\n<Checkbox label="Checked" variant="ghost" defaultChecked />',
752
+ details: "The checkbox supports three visual styles: solid (default) with a filled background and standard radius, solid pill with fully rounded corners, and ghost which renders only the checkmark without a border or background fill."
753
+ },
754
+ {
755
+ name: "Pill",
756
+ code: '<Checkbox label="Checked" pill defaultChecked />',
757
+ details: "Set pill to render the checkbox indicator with fully rounded corners (border-radius: full) instead of the default square radius."
758
+ },
759
+ {
760
+ name: "Ghost",
761
+ code: '<Checkbox label="Checked" variant="ghost" defaultChecked />',
762
+ details: 'Use variant="ghost" to render the checkbox without a border or background. The checkmark uses the text color instead of white, making it suitable for subtle inline selections or task lists.'
763
+ },
764
+ {
765
+ name: "With description",
766
+ code: '<div className="flex flex-col gap-4">\n <Checkbox\n label={\n <div className="flex flex-col">\n <span>Accept terms and conditions</span>\n <span className="text-xs text-secondary mt-0.5">\n By checking this box, you agree to our Terms of Service\n and Privacy Policy.\n </span>\n </div>\n }\n defaultChecked\n />\n <Checkbox\n label={\n <div className="flex flex-col">\n <span>Enable notifications</span>\n <span className="text-xs text-secondary mt-0.5">\n You can enable or disable notifications at any time\n from settings.\n </span>\n </div>\n }\n />\n</div>',
767
+ details: "The label prop accepts ReactNode, so you can compose a label with secondary description text. This is useful for options that need additional context or explanation."
768
+ },
769
+ {
770
+ name: "Indeterminate",
771
+ code: 'const [checked, setChecked] = useState<boolean | "indeterminate">("indeterminate");\n\n<Checkbox\n checked={checked}\n onCheckedChange={() =>\n setChecked(c => c === "indeterminate" ? true : c ? false : "indeterminate")\n }\n label="Select all"\n/>',
772
+ details: 'Use checked="indeterminate" to indicate a partially selected state. This is typically used for "select all" controls where only some child items are checked.'
773
+ },
774
+ {
775
+ name: "Disabled",
776
+ code: '{/* solid */}\n<Checkbox label="Unchecked" disabled />\n<Checkbox label="Checked" disabled defaultChecked />\n<Checkbox label="Indeterminate" disabled checked="indeterminate" />\n\n{/* solid pill */}\n<Checkbox label="Unchecked" pill disabled />\n<Checkbox label="Checked" pill disabled defaultChecked />\n\n{/* ghost */}\n<Checkbox label="Unchecked" variant="ghost" disabled />\n<Checkbox label="Checked" variant="ghost" disabled defaultChecked />',
777
+ details: "When disabled is set, the checkbox is dimmed to 50% opacity and prevents interaction. Works consistently across all variants \u2014 solid, pill, and ghost."
778
+ },
779
+ {
780
+ name: "Label orientation",
781
+ code: '<Checkbox label="Accept terms and conditions" orientation="right" />',
782
+ details: 'By default the checkbox renders to the left of the label. Set orientation="right" to place the checkbox after the label.'
783
+ },
784
+ {
785
+ name: "Without label",
786
+ code: '<div className="flex gap-4">\n <Checkbox />\n <Checkbox defaultChecked />\n <Checkbox checked="indeterminate" />\n</div>',
787
+ details: "When no label is provided, the checkbox renders as a standalone control. All three visual states are shown: unchecked, checked, and indeterminate."
788
+ },
789
+ {
790
+ name: "Group with select all",
791
+ code: 'const [items, setItems] = useState([\n { id: "read", label: "Read", checked: true },\n { id: "write", label: "Write", checked: false },\n { id: "execute", label: "Execute", checked: false },\n]);\n\nconst allChecked = items.every(i => i.checked);\nconst noneChecked = items.every(i => !i.checked);\nconst parentChecked = allChecked ? true : noneChecked ? false : "indeterminate";\n\n<>\n <Checkbox\n label="Permissions"\n checked={parentChecked}\n onCheckedChange={() => {\n const next = !allChecked;\n setItems(prev => prev.map(i => ({ ...i, checked: next })));\n }}\n />\n <div className="pl-6 flex flex-col gap-3">\n {items.map(item => (\n <Checkbox\n key={item.id}\n label={item.label}\n checked={item.checked}\n onCheckedChange={next => {\n setItems(prev =>\n prev.map(i => i.id === item.id ? { ...i, checked: Boolean(next) } : i)\n );\n }}\n />\n ))}\n </div>\n</>',
792
+ details: 'Combine a parent checkbox with child checkboxes to create a "select all" pattern. The parent shows an indeterminate state when only some children are checked.'
793
+ },
794
+ {
795
+ name: "Group with description",
796
+ code: '<Field label="Show on desktop" description="Select the items you want to display on the desktop.">\n {() => (\n <div className="flex flex-col gap-3">\n <Checkbox label="Hard disks" defaultChecked />\n <Checkbox label="External disks" />\n <Checkbox label="CDs, DVDs, and iPods" />\n <Checkbox label="Connected servers" />\n </div>\n )}\n</Field>',
797
+ details: "Wrap a group of checkboxes in a Field for a shared label and description. This is a common pattern for preference lists and settings panels."
798
+ },
799
+ {
800
+ name: "With Field",
801
+ code: '<Field\n label="Terms"\n description="Please review and accept the terms before continuing."\n errorMessage={showError ? "You must accept the terms to continue." : undefined}\n>\n {() => (\n <div className="flex flex-col gap-3 mt-1">\n <Checkbox label="I agree to the Terms of Service" />\n <Checkbox label="Subscribe to product updates" />\n </div>\n )}\n</Field>',
802
+ details: "Wrap checkboxes in a Field component for form layout with label, description, and error message support. The Field provides aria-describedby and aria-invalid linking automatically."
803
+ },
804
+ {
805
+ name: "Grouped sections",
806
+ code: '<div className="divide-y">\n <div className="pb-5">\n <Field label="Email notifications" description="Configure which emails you receive.">\n {() => (\n <div className="flex flex-col gap-4">\n <Checkbox\n label={\n <div className="flex flex-col">\n <span>Marketing</span>\n <span className="text-xs text-secondary mt-0.5">\n Receive emails about new products and features.\n </span>\n </div>\n }\n defaultChecked\n />\n <Checkbox\n label={\n <div className="flex flex-col">\n <span>Security</span>\n <span className="text-xs text-secondary mt-0.5">\n Receive emails about account activity and security.\n </span>\n </div>\n }\n defaultChecked\n />\n </div>\n )}\n </Field>\n </div>\n <div className="pt-5">\n <Field label="Push notifications" description="Configure which push notifications you receive.">\n {() => (\n <div className="flex flex-col gap-3">\n <Checkbox label="Everything" />\n <Checkbox label="Direct messages and mentions" defaultChecked />\n <Checkbox label="Nothing" />\n </div>\n )}\n </Field>\n </div>\n</div>',
807
+ details: "Use dividers to separate checkbox groups into logical sections. Each section can be wrapped in a Field with its own label and description."
808
+ },
809
+ {
810
+ name: "With Radio Group",
811
+ code: '<div className="flex flex-row gap-12 items-start">\n <div>\n <span className="text-xs text-tertiary font-mono mb-1">RadioGroup</span>\n <RadioGroup direction="row" defaultValue="email" aria-label="Channel">\n <RadioGroup.Item value="email">Email</RadioGroup.Item>\n <RadioGroup.Item value="sms">SMS</RadioGroup.Item>\n <RadioGroup.Item value="push">Push</RadioGroup.Item>\n </RadioGroup>\n </div>\n <div>\n <span className="text-xs text-tertiary font-mono mb-1">Checkbox</span>\n <div className="flex flex-row gap-5">\n <Checkbox label="Email" defaultChecked />\n <Checkbox label="SMS" />\n <Checkbox label="Push" />\n </div>\n </div>\n</div>',
812
+ details: "Place Checkbox alongside RadioGroup controls to verify that indicator sizes, label baselines, and spacing align consistently in both horizontal and vertical layouts."
813
+ }
814
+ ],
815
+ docsUrl: "https://plexui.com/docs/components/checkbox"
816
+ },
817
+ {
818
+ slug: "code-block",
819
+ name: "Code Block",
820
+ description: "Display syntax-highlighted code snippets",
821
+ importPath: "@plexui/ui/components/CodeBlock",
822
+ exports: [
823
+ "CodeBlock"
824
+ ],
825
+ examples: [
826
+ {
827
+ name: "Overview",
828
+ code: '<CodeBlock language="javascript">{codeSnippet}</CodeBlock>'
829
+ },
830
+ {
831
+ name: "Collapsible",
832
+ code: '<CollapsibleCodeBlock language="tsx">{code}</CollapsibleCodeBlock>',
833
+ details: "Use CollapsibleCodeBlock to display code in a compact view with gradient overlay and an expand action."
834
+ },
835
+ {
836
+ name: "Collapsible \u2014 expanded by default",
837
+ code: '<CollapsibleCodeBlock language="tsx" defaultExpanded>{code}</CollapsibleCodeBlock>'
838
+ },
839
+ {
840
+ name: "Collapsible \u2014 custom height",
841
+ code: '<CollapsibleCodeBlock language="tsx" collapsedHeight={60}>{code}</CollapsibleCodeBlock>'
842
+ },
843
+ {
844
+ name: "Collapsible \u2014 max expanded height",
845
+ code: '<CollapsibleCodeBlock language="tsx" defaultExpanded maxExpandedHeight={200}>{code}</CollapsibleCodeBlock>'
846
+ },
847
+ {
848
+ name: "Collapsible \u2014 without copy button",
849
+ code: '<CollapsibleCodeBlock language="tsx" defaultExpanded copyable={false}>{code}</CollapsibleCodeBlock>'
850
+ },
851
+ {
852
+ name: "Supported languages",
853
+ code: '<CodeBlock language="javascript">{code}</CodeBlock>',
854
+ details: "A pragmatic set of Prism grammars are pre-registered out of the box."
855
+ }
856
+ ],
857
+ subComponents: [
858
+ {
859
+ name: "CodeBlock",
860
+ props: [
861
+ {
862
+ name: "children",
863
+ type: "string",
864
+ description: "Code content"
865
+ },
866
+ {
867
+ name: "language",
868
+ type: "string",
869
+ description: "Prism language name"
870
+ },
871
+ {
872
+ name: "className",
873
+ type: "string",
874
+ description: "Additional wrapper class"
875
+ }
876
+ ]
877
+ },
878
+ {
879
+ name: "CollapsibleCodeBlock",
880
+ props: [
881
+ {
882
+ name: "children",
883
+ type: "string",
884
+ description: "Code content"
885
+ },
886
+ {
887
+ name: "language",
888
+ type: "string",
889
+ description: "Prism language name"
890
+ },
891
+ {
892
+ name: "defaultExpanded",
893
+ type: "boolean",
894
+ default: "false",
895
+ description: "Start expanded"
896
+ },
897
+ {
898
+ name: "collapsedHeight",
899
+ type: "number",
900
+ default: "80",
901
+ description: "Height when collapsed (px)"
902
+ },
903
+ {
904
+ name: "maxExpandedHeight",
905
+ type: "number",
906
+ description: "Max height when expanded (px)"
907
+ },
908
+ {
909
+ name: "copyable",
910
+ type: "boolean",
911
+ default: "true",
912
+ description: "Show copy button"
913
+ }
914
+ ]
915
+ }
916
+ ],
917
+ docsUrl: "https://plexui.com/docs/components/code-block"
918
+ },
919
+ {
920
+ slug: "copy-tooltip",
921
+ name: "Copy Tooltip",
922
+ description: "Allow users to easily copy to clipboard",
923
+ importPath: "@plexui/ui/components/Tooltip",
924
+ exports: [
925
+ "CopyTooltip"
926
+ ],
927
+ props: [
928
+ {
929
+ name: "children",
930
+ type: "ReactNode",
931
+ description: "Trigger element (for example Tooltip.TriggerDecorator)"
932
+ },
933
+ {
934
+ name: "copyValue",
935
+ type: "string",
936
+ default: '"Very cool content to copy"',
937
+ description: "Content to copy to clipboard"
938
+ },
939
+ {
940
+ name: "openDelay",
941
+ type: "number",
942
+ default: "150",
943
+ description: "Tooltip open delay in milliseconds"
944
+ },
945
+ {
946
+ name: "align",
947
+ type: '"start" | "center" | "end"',
948
+ default: '"center"',
949
+ description: "Preferred tooltip alignment against the trigger"
950
+ },
951
+ {
952
+ name: "alignOffset",
953
+ type: "number",
954
+ default: "0",
955
+ description: "Offset from start/end alignment in pixels"
956
+ },
957
+ {
958
+ name: "side",
959
+ type: '"top" | "right" | "bottom" | "left"',
960
+ default: '"top"',
961
+ description: "Preferred side of the trigger"
962
+ },
963
+ {
964
+ name: "sideOffset",
965
+ type: "number",
966
+ default: "5",
967
+ description: "Distance from trigger in pixels"
968
+ }
969
+ ],
970
+ examples: [
971
+ {
972
+ name: "Overview",
973
+ code: '<CopyTooltip copyValue="Very cool content to copy">\n <Tooltip.TriggerDecorator>sess_12345abcdefg</Tooltip.TriggerDecorator>\n</CopyTooltip>'
974
+ }
975
+ ],
976
+ docsUrl: "https://plexui.com/docs/components/copy-tooltip"
977
+ },
978
+ {
979
+ slug: "date-picker",
980
+ name: "Date Picker",
981
+ description: "Select a single date with an interactive calendar",
982
+ importPath: "@plexui/ui/components/DatePicker",
983
+ exports: [
984
+ "DatePicker"
985
+ ],
986
+ props: [
987
+ {
988
+ name: "value",
989
+ type: "DateTime | null",
990
+ description: "Selected date (luxon)"
991
+ },
992
+ {
993
+ name: "onChange",
994
+ type: "(date: DateTime | null) => void",
995
+ description: "Called when date changes"
996
+ },
997
+ {
998
+ name: "min",
999
+ type: "DateTime",
1000
+ description: "Minimum selectable date"
1001
+ },
1002
+ {
1003
+ name: "max",
1004
+ type: "DateTime",
1005
+ description: "Maximum selectable date"
1006
+ },
1007
+ {
1008
+ name: "captionLayout",
1009
+ type: '"buttons" | "dropdown"',
1010
+ default: '"buttons"',
1011
+ description: "Calendar header mode. Use dropdown for date of birth"
1012
+ },
1013
+ {
1014
+ name: "showTime",
1015
+ type: "boolean",
1016
+ default: "false",
1017
+ description: "Show time input below calendar"
1018
+ },
1019
+ {
1020
+ name: "yearRange",
1021
+ type: "[number, number]",
1022
+ description: "Min/max years for dropdown caption"
1023
+ },
1024
+ {
1025
+ name: "size",
1026
+ type: "string",
1027
+ description: "Trigger size"
1028
+ },
1029
+ {
1030
+ name: "pill",
1031
+ type: "boolean",
1032
+ description: "Rounded trigger"
1033
+ },
1034
+ {
1035
+ name: "clearable",
1036
+ type: "boolean",
1037
+ description: "Show clear button"
1038
+ },
1039
+ {
1040
+ name: "triggerShowIcon",
1041
+ type: "boolean",
1042
+ default: "true",
1043
+ description: "Show calendar icon"
1044
+ },
1045
+ {
1046
+ name: "triggerDateFormat",
1047
+ type: "string",
1048
+ default: '"MM/dd/yy"',
1049
+ description: "Date format in trigger. Auto-includes time when showTime is enabled"
1050
+ },
1051
+ {
1052
+ name: "placeholder",
1053
+ type: "string",
1054
+ description: "Trigger placeholder"
1055
+ },
1056
+ {
1057
+ name: "disabled",
1058
+ type: "boolean",
1059
+ description: "Disable picker"
1060
+ }
1061
+ ],
1062
+ examples: [
1063
+ {
1064
+ name: "Overview",
1065
+ code: "const [selectedDate, setSelectedDate] = useState<DateTime | null>(null)\n<DatePicker value={selectedDate} onChange={setSelectedDate} clearable pill />"
1066
+ },
1067
+ {
1068
+ name: "Limits",
1069
+ code: "const minDate = today.minus({ days: 30 }).startOf('day')\nconst maxDate = today.plus({ days: 30 }).endOf('day')\n<DatePicker value={...} onChange={...} min={minDate} max={maxDate} />",
1070
+ details: "Use min and max to limit selectable dates to a specific timeframe."
1071
+ },
1072
+ {
1073
+ name: "Date of birth",
1074
+ code: '<DatePicker\n captionLayout="dropdown"\n triggerDateFormat="MMMM d, yyyy"\n max={DateTime.local()}\n placeholder="Select your birthday"\n clearable pill\n/>',
1075
+ details: 'Set captionLayout="dropdown" to add month and year dropdowns inline with the arrow navigation. Ideal for selecting a date of birth where navigating month-by-month would be impractical.'
1076
+ },
1077
+ {
1078
+ name: "Time picker",
1079
+ code: '<DatePicker\n showTime\n triggerDateFormat="MM/dd/yy HH:mm"\n placeholder="Select date & time"\n clearable pill\n/>',
1080
+ details: "Enable showTime to add hour and minute inputs below the calendar. The popover stays open after selecting a date so the user can adjust the time. Use arrow keys to increment or decrement values."
1081
+ },
1082
+ {
1083
+ name: "Dropdown + time",
1084
+ code: '<DatePicker\n captionLayout="dropdown"\n showTime\n yearRange={[2020, 2030]}\n triggerDateFormat="MMM d, yyyy HH:mm"\n placeholder="Schedule event"\n clearable pill\n/>',
1085
+ details: 'Combine captionLayout="dropdown" with showTime for quick month/year jumps and time selection in a single picker \u2014 useful for scheduling events at a specific date and time.'
1086
+ },
1087
+ {
1088
+ name: "Two fields",
1089
+ code: `<div style={{ display: 'flex', gap: '12px' }}>
1090
+ <div>
1091
+ <label>Date</label>
1092
+ <DatePicker value={...} onChange={...} />
1093
+ </div>
1094
+ <div>
1095
+ <label>Time</label>
1096
+ <Input type="time" value={...} onChange={...} />
1097
+ </div>
1098
+ </div>`,
1099
+ details: 'Compose DatePicker and Input type="time" side by side for maximum flexibility in form layouts. Each field operates independently while sharing the same DateTime value.'
1100
+ },
1101
+ {
1102
+ name: "Date input",
1103
+ code: "<MaskedDateInput value={date} onChange={setDate} />",
1104
+ details: "A masked input for typing a date directly. Auto-formats as MM / DD / YYYY with segment highlighting and separator auto-advance."
1105
+ },
1106
+ {
1107
+ name: "DateTime input",
1108
+ code: "<MaskedDateTimeInput value={dateTime} onChange={setDateTime} />",
1109
+ details: "Extended masked input for date and time. Format: MM / DD / YYYY HH : MM. Same auto-formatting behavior with time segments appended."
1110
+ },
1111
+ {
1112
+ name: "Without Icon",
1113
+ code: '<DatePicker triggerShowIcon={false} placeholder="Choose a date" ... />',
1114
+ details: "Remove the calendar icon from the trigger by setting triggerShowIcon to false."
1115
+ },
1116
+ {
1117
+ name: "Disabled",
1118
+ code: "<DatePicker disabled value={...} onChange={...} />",
1119
+ details: "Disable the picker to prevent user interaction."
1120
+ }
1121
+ ],
1122
+ docsUrl: "https://plexui.com/docs/components/date-picker"
1123
+ },
1124
+ {
1125
+ slug: "date-range-picker",
1126
+ name: "Date Range Picker",
1127
+ description: "Select a date range with an interactive calendar",
1128
+ importPath: "@plexui/ui/components/DateRangePicker",
1129
+ exports: [
1130
+ "DateRangePicker"
1131
+ ],
1132
+ props: [
1133
+ {
1134
+ name: "value",
1135
+ type: "DateRange | null",
1136
+ description: "Selected range ([start, end], inclusive)"
1137
+ },
1138
+ {
1139
+ name: "onChange",
1140
+ type: "(next: DateRange | null, shortcut?: DateRangeShortcut) => void",
1141
+ description: "Called when range changes"
1142
+ },
1143
+ {
1144
+ name: "min",
1145
+ type: "DateTime",
1146
+ description: "Earliest selectable date"
1147
+ },
1148
+ {
1149
+ name: "max",
1150
+ type: "DateTime",
1151
+ description: "Latest selectable date"
1152
+ },
1153
+ {
1154
+ name: "maxRangeDays",
1155
+ type: "number",
1156
+ description: "Max length of range in days (inclusive)"
1157
+ },
1158
+ {
1159
+ name: "shortcuts",
1160
+ type: "DateRangeShortcut[]",
1161
+ description: "Quick selection shortcuts"
1162
+ },
1163
+ {
1164
+ name: "side",
1165
+ type: '"top" | "right" | "bottom" | "left"',
1166
+ default: '"bottom"',
1167
+ description: "Preferred side for popover"
1168
+ },
1169
+ {
1170
+ name: "sideOffset",
1171
+ type: "number",
1172
+ default: "8",
1173
+ description: "Distance from trigger in pixels"
1174
+ },
1175
+ {
1176
+ name: "align",
1177
+ type: '"start" | "center" | "end"',
1178
+ default: '"center"',
1179
+ description: "Preferred popover alignment"
1180
+ },
1181
+ {
1182
+ name: "alignOffset",
1183
+ type: "number",
1184
+ default: "0",
1185
+ description: "Offset from start/end alignment in pixels"
1186
+ },
1187
+ {
1188
+ name: "disabled",
1189
+ type: "boolean",
1190
+ default: "false",
1191
+ description: "Disable picker interactions"
1192
+ },
1193
+ {
1194
+ name: "placeholder",
1195
+ type: "string",
1196
+ default: '"Select date range..."',
1197
+ description: "Placeholder in trigger when value is empty"
1198
+ },
1199
+ {
1200
+ name: "variant",
1201
+ type: '"soft" | "outline" | "ghost"',
1202
+ default: '"outline"',
1203
+ description: "Trigger style variant"
1204
+ },
1205
+ {
1206
+ name: "pill",
1207
+ type: "boolean",
1208
+ default: "true",
1209
+ description: "Rounded trigger shape"
1210
+ },
1211
+ {
1212
+ name: "size",
1213
+ type: '"3xs" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl"',
1214
+ default: '"md"',
1215
+ description: "Trigger size scale"
1216
+ },
1217
+ {
1218
+ name: "dropdownIconType",
1219
+ type: '"chevronDown" | "dropdown" | "none"',
1220
+ default: '"dropdown"',
1221
+ description: "Icon shown at trigger end"
1222
+ },
1223
+ {
1224
+ name: "triggerClassName",
1225
+ type: "string",
1226
+ description: "Custom class for trigger"
1227
+ },
1228
+ {
1229
+ name: "triggerShowIcon",
1230
+ type: "boolean",
1231
+ default: "true",
1232
+ description: "Show calendar icon at trigger start"
1233
+ },
1234
+ {
1235
+ name: "triggerStepperUnit",
1236
+ type: '"month"',
1237
+ description: "Enable month stepper mode in trigger"
1238
+ },
1239
+ {
1240
+ name: "triggerDateFormat",
1241
+ type: "string",
1242
+ default: '"MM/dd/yy"',
1243
+ description: "Date formatting in trigger text"
1244
+ },
1245
+ {
1246
+ name: "clearable",
1247
+ type: "boolean",
1248
+ default: "false",
1249
+ description: "Show clear button in trigger"
1250
+ },
1251
+ {
1252
+ name: "block",
1253
+ type: "boolean",
1254
+ default: "false",
1255
+ description: "Stretch trigger to full available width"
1256
+ }
1257
+ ],
1258
+ examples: [
1259
+ {
1260
+ name: "Overview",
1261
+ code: "const [value, setValue] = useState<DateRange | null>(null)\n<DateRangePicker value={value} onChange={setValue} shortcuts={shortcuts} clearable pill />"
1262
+ },
1263
+ {
1264
+ name: "Limits",
1265
+ code: "<DateRangePicker value={...} onChange={...} min={minDate} max={maxDate} />",
1266
+ details: "Use min and max to limit the selectable range. Use maxRangeDays to limit the range length."
1267
+ },
1268
+ {
1269
+ name: "Shortcuts",
1270
+ code: "<DateRangePicker shortcuts={dayShortcuts} ... />",
1271
+ details: "Use the shortcuts array to provide quick date range options. When a shortcut is selected, its label is shown in the trigger."
1272
+ },
1273
+ {
1274
+ name: "Month stepper",
1275
+ code: '<DateRangePicker\n triggerStepperUnit="month"\n value={value}\n onChange={setValue}\n clearable\n pill\n max={maxDate}\n/>',
1276
+ details: 'Enable a stepper in the trigger with triggerStepperUnit="month". When the selected range matches the unit, the trigger shows the stepper.'
1277
+ }
1278
+ ],
1279
+ docsUrl: "https://plexui.com/docs/components/date-range-picker"
1280
+ },
1281
+ {
1282
+ slug: "dialog",
1283
+ name: "Dialog",
1284
+ description: "Modal dialog overlay for confirmations, forms, and alerts",
1285
+ importPath: "@plexui/ui/components/Dialog",
1286
+ exports: [
1287
+ "Dialog"
1288
+ ],
1289
+ props: [
1290
+ {
1291
+ name: "open",
1292
+ type: "boolean",
1293
+ description: "Controlled open state"
1294
+ },
1295
+ {
1296
+ name: "onOpenChange",
1297
+ type: "(open: boolean) => void",
1298
+ description: "Called when the dialog opens or closes"
1299
+ },
1300
+ {
1301
+ name: "width",
1302
+ type: "number | string",
1303
+ default: "480",
1304
+ description: "Panel width"
1305
+ },
1306
+ {
1307
+ name: "showClose",
1308
+ type: "boolean",
1309
+ default: "true",
1310
+ description: "Show close button in top-right corner"
1311
+ },
1312
+ {
1313
+ name: "className",
1314
+ type: "string",
1315
+ description: "Additional CSS class on the panel"
1316
+ }
1317
+ ],
1318
+ examples: [
1319
+ {
1320
+ name: "Overview",
1321
+ code: '<Dialog>\n <Dialog.Trigger>\n <Button color="secondary" variant="outline">Open dialog</Button>\n </Dialog.Trigger>\n <Dialog.Content>\n <Dialog.Header>\n <Dialog.Title>Unsaved changes</Dialog.Title>\n </Dialog.Header>\n <p>You have unsaved changes that will be lost...</p>\n <Dialog.Footer>\n <Dialog.Close><Button color="primary" variant="soft">Cancel</Button></Dialog.Close>\n <Button color="primary">Save changes</Button>\n </Dialog.Footer>\n </Dialog.Content>\n</Dialog>'
1322
+ },
1323
+ {
1324
+ name: "Destructive confirmation",
1325
+ code: '<Dialog>\n <Dialog.Trigger>\n <Button color="danger" variant="outline">Delete</Button>\n </Dialog.Trigger>\n <Dialog.Content>\n <Dialog.Header>\n <Dialog.Title>Delete assistant?</Dialog.Title>\n </Dialog.Header>\n <p>This change will affect all API integrations. It cannot be undone.</p>\n <Dialog.Footer>\n <Dialog.Close><Button color="secondary" variant="soft">Cancel</Button></Dialog.Close>\n <Button color="danger">Delete</Button>\n </Dialog.Footer>\n </Dialog.Content>\n</Dialog>',
1326
+ details: 'Use color="danger" on the action button for destructive operations.'
1327
+ }
1328
+ ],
1329
+ docsUrl: "https://plexui.com/docs/components/dialog"
1330
+ },
1331
+ {
1332
+ slug: "empty-message",
1333
+ name: "Empty Message",
1334
+ description: "Gracefully inform users when there's nothing to see",
1335
+ importPath: "@plexui/ui/components/EmptyMessage",
1336
+ exports: [
1337
+ "EmptyMessage"
1338
+ ],
1339
+ props: [
1340
+ {
1341
+ name: "color",
1342
+ type: "string",
1343
+ description: "Semantic color (e.g. danger, warning)"
1344
+ },
1345
+ {
1346
+ name: "size",
1347
+ type: '"sm" | "md" | "lg"',
1348
+ description: "Icon size"
1349
+ },
1350
+ {
1351
+ name: "className",
1352
+ type: "string",
1353
+ description: "Additional classes for the action row container"
1354
+ }
1355
+ ],
1356
+ examples: [
1357
+ {
1358
+ name: "Composition",
1359
+ code: `<EmptyMessage>
1360
+ <EmptyMessage.Icon><Explore /></EmptyMessage.Icon>
1361
+ <EmptyMessage.Title>Your evaluations will appear here</EmptyMessage.Title>
1362
+ <EmptyMessage.Description>Create an evaluation to assess your model's responses</EmptyMessage.Description>
1363
+ <EmptyMessage.ActionRow><Button color="primary">Create</Button></EmptyMessage.ActionRow>
1364
+ </EmptyMessage>`
1365
+ },
1366
+ {
1367
+ name: "Error message",
1368
+ code: '<EmptyMessage>\n <EmptyMessage.Icon color="danger"><Mic /></EmptyMessage.Icon>\n <EmptyMessage.Title color="danger">Enable microphone access...</EmptyMessage.Title>\n</EmptyMessage>',
1369
+ details: "The color property enables you to display clearer intent with the message, such as showing an error state."
1370
+ },
1371
+ {
1372
+ name: "Warning message",
1373
+ code: '<EmptyMessage>\n <EmptyMessage.Icon color="warning"><Sparkle /></EmptyMessage.Icon>\n <EmptyMessage.Title>o1-preview is in beta</EmptyMessage.Title>\n <EmptyMessage.Description>System instructions and model configuration are not available yet.</EmptyMessage.Description>\n <EmptyMessage.ActionRow className="text-sm">\n <TextLink href="/">Learn more<ArrowUpRight /></TextLink>\n </EmptyMessage.ActionRow>\n</EmptyMessage>'
1374
+ },
1375
+ {
1376
+ name: "No results",
1377
+ code: '<EmptyMessage fill="none">\n <EmptyMessage.Icon size="sm"><Search /></EmptyMessage.Icon>\n <EmptyMessage.Description>No icons found matching "pizza"</EmptyMessage.Description>\n</EmptyMessage>',
1378
+ details: "The components of EmptyMessage are both composable and optional. This example omits the EmptyMessage.Title component, using only an icon and description for a simpler message style."
1379
+ }
1380
+ ],
1381
+ docsUrl: "https://plexui.com/docs/components/empty-message"
1382
+ },
1383
+ {
1384
+ slug: "field",
1385
+ name: "Field",
1386
+ description: "Form field wrapper with label, description, error, and accessibility linking",
1387
+ importPath: "@plexui/ui/components/Field",
1388
+ exports: [
1389
+ "Field"
1390
+ ],
1391
+ props: [
1392
+ {
1393
+ name: "label",
1394
+ type: "ReactNode",
1395
+ description: "Label text for the field"
1396
+ },
1397
+ {
1398
+ name: "description",
1399
+ type: "ReactNode",
1400
+ description: "Helper text below the label; linked via aria-describedby"
1401
+ },
1402
+ {
1403
+ name: "errorMessage",
1404
+ type: "ReactNode",
1405
+ description: "Error message below the control; auto-sets aria-invalid on child"
1406
+ },
1407
+ {
1408
+ name: "size",
1409
+ type: '"3xs" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl"',
1410
+ default: '"md"',
1411
+ description: "Controls the label font size to match the child control"
1412
+ },
1413
+ {
1414
+ name: "required",
1415
+ type: "boolean",
1416
+ default: "false",
1417
+ description: "Shows a required asterisk after the label (visual only)"
1418
+ },
1419
+ {
1420
+ name: "orientation",
1421
+ type: '"vertical" | "horizontal"',
1422
+ default: '"vertical"',
1423
+ description: "Layout direction of label and control"
1424
+ },
1425
+ {
1426
+ name: "opticallyAlign",
1427
+ type: '"start" | "end"',
1428
+ description: "Applies optical alignment to the child control via negative margin using its gutter"
1429
+ },
1430
+ {
1431
+ name: "id",
1432
+ type: "string",
1433
+ description: "Override the auto-generated id used for htmlFor/aria linking"
1434
+ },
1435
+ {
1436
+ name: "children",
1437
+ type: "ReactElement | (fieldProps) => ReactNode",
1438
+ description: "Form control; receives id, aria-describedby, and aria-invalid"
1439
+ }
1440
+ ],
1441
+ examples: [
1442
+ {
1443
+ name: "Overview",
1444
+ code: '<Field label="Email">\n <Input placeholder="Enter email..." />\n</Field>',
1445
+ details: "Field wraps a form control with a label, optional description, and error message. It automatically generates htmlFor, aria-describedby, and aria-invalid linking between the label, description, error, and child control."
1446
+ },
1447
+ {
1448
+ name: "With description",
1449
+ code: `<Field label="Email" description="We'll never share your email with anyone else.">
1450
+ <Input placeholder="Enter email..." />
1451
+ </Field>`,
1452
+ details: "Use description to display helper text below the label. The description is automatically linked to the control via aria-describedby."
1453
+ },
1454
+ {
1455
+ name: "With error message",
1456
+ code: '<Field label="Email" errorMessage="Please enter a valid email address">\n <Input placeholder="Enter email..." />\n</Field>',
1457
+ details: 'Use errorMessage to display a validation error. When provided, the child control automatically receives aria-invalid="true". The error is rendered using the FieldError component with role="alert" for accessibility.'
1458
+ },
1459
+ {
1460
+ name: "Required",
1461
+ code: '<Field label="Email" required>\n <Input placeholder="Enter email..." />\n</Field>',
1462
+ details: "Use required to show a visual asterisk after the label. This is purely visual and does not add the required HTML attribute \u2014 pass required directly to the child control for form validation."
1463
+ },
1464
+ {
1465
+ name: "Sizing",
1466
+ code: '<Field label="Email" size="lg">\n <Input placeholder="Enter email..." size="lg" />\n</Field>',
1467
+ details: "Field accepts a size prop that scales the label and description font size to match the child control. Pass the same size to both Field and the child control."
1468
+ },
1469
+ {
1470
+ name: "Horizontal layout",
1471
+ code: '<div className="flex flex-col gap-4">\n <Field label="First name" orientation="horizontal">\n <Input placeholder="Jane" />\n </Field>\n <Field label="Last name" orientation="horizontal">\n <Input placeholder="Doe" />\n </Field>\n</div>',
1472
+ details: 'Use orientation="horizontal" to render the label beside the control instead of above it.'
1473
+ },
1474
+ {
1475
+ name: "Textarea",
1476
+ code: '<Field label="Bio" description="Write a short bio for your profile.">\n <Textarea placeholder="Tell us about yourself..." rows={3} />\n</Field>'
1477
+ },
1478
+ {
1479
+ name: "Select",
1480
+ code: '<Field label="Role" description="Select your primary role.">\n {(fieldProps) => (\n <Select\n {...fieldProps}\n placeholder="Select a role..."\n options={[\n { label: "Admin", value: "admin" },\n { label: "Editor", value: "editor" },\n { label: "Viewer", value: "viewer" },\n ]}\n />\n )}\n</Field>',
1481
+ details: "For components like Select that may not forward all props via cloneElement, use the render prop pattern to explicitly pass field props."
1482
+ },
1483
+ {
1484
+ name: "Slider",
1485
+ code: 'const [price, setPrice] = useState([200, 800])\n<Field label="Price Range" description={\\`Set your budget range ($\\${price[0]} \u2013 $\\${price[1]}).\\',
1486
+ details: "Field works with any form control. Use the description to display the current slider value."
1487
+ },
1488
+ {
1489
+ name: "Fieldset",
1490
+ code: '<div>\n <div className="mb-3">\n <h3 className="text-base font-semibold">Address Information</h3>\n <p className="text-sm text-secondary mt-1">We need your address to deliver your order.</p>\n </div>\n <div className="flex flex-col gap-4">\n <Field label="Street Address">\n <Input placeholder="123 Main St" />\n </Field>\n <div className="flex gap-3">\n <Field label="City" className="flex-1">\n <Input placeholder="New York" />\n </Field>\n <Field label="Postal Code" className="flex-1">\n <Input placeholder="90502" />\n </Field>\n </div>\n </div>\n</div>',
1491
+ details: "Group related fields under a heading and description to create a logical fieldset."
1492
+ },
1493
+ {
1494
+ name: "Checkbox",
1495
+ code: `<Field label="Notifications" description="Choose which notifications you'd like to receive.">
1496
+ {() => (
1497
+ <div className="flex flex-col gap-3 mt-1">
1498
+ <Checkbox label="Email notifications" />
1499
+ <Checkbox label="Push notifications" />
1500
+ <Checkbox label="SMS notifications" />
1501
+ </div>
1502
+ )}
1503
+ </Field>`,
1504
+ details: "Wrap a group of checkboxes in a Field for a shared label and description."
1505
+ },
1506
+ {
1507
+ name: "Radio",
1508
+ code: '<Field label="Subscription plan" description="Select your preferred plan.">\n {(fieldProps) => (\n <RadioGroup aria-label="Subscription plan" value={plan} onChange={setPlan} direction="col">\n <RadioGroup.Item value="free">Free</RadioGroup.Item>\n <RadioGroup.Item value="pro">Pro \u2014 $9/mo</RadioGroup.Item>\n <RadioGroup.Item value="enterprise">Enterprise \u2014 $29/mo</RadioGroup.Item>\n </RadioGroup>\n )}\n</Field>',
1509
+ details: "Use Field with RadioGroup for labeled radio selections."
1510
+ },
1511
+ {
1512
+ name: "Switch",
1513
+ code: '<Field label="Marketing emails" orientation="horizontal">\n {(fieldProps) => (\n <Switch id={fieldProps.id} checked={enabled} onCheckedChange={setEnabled} />\n )}\n</Field>',
1514
+ details: 'Pair a Field with a Switch using orientation="horizontal" for inline toggle settings.'
1515
+ },
1516
+ {
1517
+ name: "Field group",
1518
+ code: `<div className="divide-y divide-alpha/15">
1519
+ <div className="pb-6">
1520
+ <Field label="Responses" description="Get notified when the assistant responds to requests that take time.">
1521
+ {() => (
1522
+ <div className="flex flex-col gap-3 mt-1">
1523
+ <Checkbox label="Push notifications" defaultChecked />
1524
+ </div>
1525
+ )}
1526
+ </Field>
1527
+ </div>
1528
+ <div className="py-6">
1529
+ <Field label="Tasks" description="Get notified when tasks you've created have updates.">
1530
+ {() => (
1531
+ <div className="flex flex-col gap-3 mt-1">
1532
+ <Checkbox label="Push notifications" />
1533
+ <Checkbox label="Email notifications" />
1534
+ </div>
1535
+ )}
1536
+ </Field>
1537
+ </div>
1538
+ </div>`,
1539
+ details: "Stack Field components with a divider to group related notification settings."
1540
+ },
1541
+ {
1542
+ name: "Validation and Errors",
1543
+ code: '<div className="flex flex-col gap-4">\n <Field label="Name" required errorMessage={nameError}>\n <Input placeholder="Enter your name" value={name} onChange={(evt) => setName(evt.target.value)} />\n </Field>\n <Field label="Email" required errorMessage={emailError}>\n <Input placeholder="Enter your email" value={email} onChange={(evt) => setEmail(evt.target.value)} />\n </Field>\n</div>',
1544
+ details: "Combine required and errorMessage for client-side validation feedback. Toggle errors on and off to see the visual transition."
1545
+ },
1546
+ {
1547
+ name: "Optical alignment",
1548
+ code: `<div className="border border-dashed border-alpha/20 rounded-md py-6 px-8">
1549
+ <div className="flex flex-col gap-5">
1550
+ <Field label="Full name" opticallyAlign="start">
1551
+ <Input placeholder="Jane Doe" pill />
1552
+ </Field>
1553
+ <Field label="Email" description="We'll never share your email." errorMessage="Please enter a valid email address." required opticallyAlign="start">
1554
+ <Input placeholder="jane@example.com" pill />
1555
+ </Field>
1556
+ </div>
1557
+ <Button className="mt-6" color="primary" variant="soft" pill>Submit</Button>
1558
+ </div>`,
1559
+ details: 'Use opticallyAlign="start" to apply a negative margin on the child control, aligning its text with the surrounding content. Works with pill inputs. The label, description, and error message stay at the container edge while the control extends past it.'
1560
+ },
1561
+ {
1562
+ name: "Form example",
1563
+ code: 'const [pill, setPill] = useState(false);\n\n<fieldset className="flex flex-col gap-5">\n <h3 className="text-base font-semibold">Payment Method</h3>\n <Field label="Name on Card">\n <Input placeholder="Evil Rabbit" pill={pill} />\n </Field>\n <Field label="Card Number">\n <Input placeholder="1234 5678 9012 3456" pill={pill} />\n </Field>\n <div className="grid grid-cols-3 gap-3">\n <Field label="Month">\n {(fp) => <Select id={fp.id} placeholder="MM" pill={pill} options={months} />}\n </Field>\n <Field label="Year">\n {(fp) => <Select id={fp.id} placeholder="YYYY" pill={pill} options={years} />}\n </Field>\n <Field label="CVV">\n <Input placeholder="123" pill={pill} />\n </Field>\n </div>\n</fieldset>\n\n<Checkbox label="Same as shipping address" defaultChecked />\n\n<Field label="Comments">\n <Textarea placeholder="Add any additional comments" rows={3} />\n</Field>\n\n<div className="flex gap-2">\n <Button color="primary" pill={pill}>Submit</Button>\n <Button color="secondary" variant="outline" pill={pill}>Cancel</Button>\n</div>',
1564
+ details: "A payment form built with Field, showing two fieldsets, a 3-column grid row, and a pill toggle that applies rounded corners to all controls."
1565
+ },
1566
+ {
1567
+ name: "Birthday segmented input",
1568
+ code: '<Field label="Birthday">\n <div className="flex items-center gap-2">\n <Input value={month} placeholder="MM" maxLength={2} className="w-[56px]" />\n <span>/</span>\n <Input value={day} placeholder="DD" maxLength={2} className="w-[56px]" />\n <span>/</span>\n <Input value={year} placeholder="YYYY" maxLength={4} className="w-[80px]" />\n </div>\n</Field>',
1569
+ details: "Three compact Input controls composed as month/day/year with auto-advance behavior, wrapped in a Field for shared label and accessibility."
1570
+ },
1571
+ {
1572
+ name: "Birthday with selects",
1573
+ code: '<Field label="Birthday">\n <div className="flex gap-2">\n <Select placeholder="Month" options={monthOptions} />\n <Select placeholder="Day" options={dayOptions} />\n <Select placeholder="Year" options={yearOptions} />\n </div>\n</Field>',
1574
+ details: "Dropdown birthday picker with Month/Day/Year selects inside a labeled Field group."
1575
+ }
1576
+ ],
1577
+ docsUrl: "https://plexui.com/docs/components/field"
1578
+ },
1579
+ {
1580
+ slug: "floating-label-input",
1581
+ name: "Float Input",
1582
+ description: "Input with floating label that animates on focus",
1583
+ importPath: "@plexui/ui/components/FloatingLabelInput",
1584
+ exports: [
1585
+ "FloatingLabelInput"
1586
+ ],
1587
+ props: [
1588
+ {
1589
+ name: "label",
1590
+ type: "string",
1591
+ description: "Floating label text"
1592
+ },
1593
+ {
1594
+ name: "errorMessage",
1595
+ type: "string",
1596
+ description: "Validation message below input; sets invalid state"
1597
+ },
1598
+ {
1599
+ name: "disabled",
1600
+ type: "boolean",
1601
+ default: "false",
1602
+ description: "Disable input interactions"
1603
+ },
1604
+ {
1605
+ name: "invalid",
1606
+ type: "boolean",
1607
+ default: "false",
1608
+ description: "Invalid visual state"
1609
+ },
1610
+ {
1611
+ name: "readOnly",
1612
+ type: "boolean",
1613
+ default: "false",
1614
+ description: "Read-only input state"
1615
+ },
1616
+ {
1617
+ name: "onClear",
1618
+ type: "() => void",
1619
+ description: "Shows a clear button when input has value"
1620
+ },
1621
+ {
1622
+ name: "startAdornment",
1623
+ type: "ReactNode",
1624
+ description: "Content rendered before the input, inside the field. Visible when focused or has value."
1625
+ },
1626
+ {
1627
+ name: "endAdornment",
1628
+ type: "ReactNode",
1629
+ description: "Content rendered after the input, before the clear button"
1630
+ }
1631
+ ],
1632
+ examples: [
1633
+ {
1634
+ name: "Overview",
1635
+ code: '<FloatingLabelInput label="Email address" />'
1636
+ },
1637
+ {
1638
+ name: "Filled state",
1639
+ code: '<FloatingLabelInput label="Email address" defaultValue="jane.doe@example.com" />',
1640
+ details: "When the input has a value, the label stays floated above the input."
1641
+ },
1642
+ {
1643
+ name: "With error message",
1644
+ code: '<FloatingLabelInput\n label="Email address"\n defaultValue="jane.doe@example"\n invalid\n errorMessage="Email is not valid."\n/>',
1645
+ details: "Use the errorMessage prop to display a validation error below the input. When errorMessage is provided, the input automatically enters the invalid state."
1646
+ },
1647
+ {
1648
+ name: "Clear button",
1649
+ code: 'const [value, setValue] = useState("clearable@example.com")\n\n<FloatingLabelInput\n label="Email address"\n value={value}\n onChange={(evt) => setValue(evt.target.value)}\n onClear={() => setValue("")}\n/>',
1650
+ details: "Use the onClear prop to display a built-in clear button. The button appears when the input has a value and is not disabled or read-only."
1651
+ },
1652
+ {
1653
+ name: "Disabled",
1654
+ code: '<FloatingLabelInput label="Email address" defaultValue="jane.doe@example.com" disabled />',
1655
+ details: "Use the disabled prop to disable the input, greying it out and preventing user interactions."
1656
+ },
1657
+ {
1658
+ name: "Read only",
1659
+ code: '<FloatingLabelInput label="Email address" defaultValue="jane.doe@example.com" readOnly />',
1660
+ details: "Use the readOnly prop to make the input read-only. The user can still select and copy the text."
1661
+ },
1662
+ {
1663
+ name: "All states",
1664
+ code: '<div className="flex flex-col items-center gap-8 py-8">\n <div className="flex items-center">\n <div className="text-right text-secondary text-sm mr-8 min-w-[5rem]">Empty</div>\n <FloatingLabelInput label="Email address" className="w-[320px]" />\n </div>\n <div className="flex items-center">\n <div className="text-right text-secondary text-sm mr-8 min-w-[5rem]">Filled</div>\n <FloatingLabelInput label="Email address" defaultValue="jane.doe@gmail.com" className="w-[320px]" />\n </div>\n <div className="flex items-center">\n <div className="text-right text-secondary text-sm mr-8 min-w-[5rem]">Invalid</div>\n <FloatingLabelInput invalid label="Email address" defaultValue="invalid@email" className="w-[320px]" />\n </div>\n <div className="flex items-center">\n <div className="text-right text-secondary text-sm mr-8 min-w-[5rem]">Disabled</div>\n <FloatingLabelInput disabled label="Email address" defaultValue="jane.doe@gmail.com" className="w-[320px]" />\n </div>\n <div className="flex items-center">\n <div className="text-right text-secondary text-sm mr-8 min-w-[5rem]">Read only</div>\n <FloatingLabelInput readOnly label="Email address" defaultValue="readonly@gmail.com" className="w-[320px]" />\n </div>\n</div>',
1665
+ details: "Overview of all input states."
1666
+ },
1667
+ {
1668
+ name: "Password input",
1669
+ code: '<FloatingLabelInput label="Password" type="password" />',
1670
+ details: "The component works with different input types."
1671
+ },
1672
+ {
1673
+ name: "Password with visibility toggle",
1674
+ code: 'const [visible, setVisible] = useState(false)\n\n<FloatingLabelInput\n label="Password"\n type={visible ? "text" : "password"}\n endAdornment={\n <button onClick={() => setVisible(v => !v)} aria-label={visible ? "Hide" : "Show"}>\n {visible ? <EyeOff /> : <Eye />}\n </button>\n }\n/>',
1675
+ details: 'Use the endAdornment prop to render a visibility toggle button inside the input. Clicking the eye icon switches between type="password" and type="text".'
1676
+ },
1677
+ {
1678
+ name: "SSN input",
1679
+ code: `const [rawDigits, setRawDigits] = useState("")
1680
+ const [visible, setVisible] = useState(false)
1681
+ const formatted = formatSSN(rawDigits)
1682
+
1683
+ <FloatingLabelInput
1684
+ label="Social Security Number"
1685
+ value={formatted}
1686
+ inputMode="numeric"
1687
+ maxLength={11}
1688
+ onChange={(evt) => setRawDigits(extractDigits(evt.target.value))}
1689
+ style={!visible && rawDigits ? { color: 'transparent', caretColor: 'var(--color-text)' } : undefined}
1690
+ endAdornment={
1691
+ <button onClick={() => setVisible(v => !v)}>
1692
+ {visible ? <EyeOff /> : <Eye />}
1693
+ </button>
1694
+ }
1695
+ />`,
1696
+ details: "A Social Security Number input combining three techniques: an input mask that auto-inserts dashes (XXX-XX-XXXX) with highlighted segment, value masking that hides digits behind bullets (\u2022\u2022\u2022-\u2022\u2022-\u2022\u2022\u2022\u2022), and an endAdornment visibility toggle to reveal the real value."
1697
+ },
1698
+ {
1699
+ name: "Birthday input",
1700
+ code: 'const [birthday, setBirthday] = useState("")\n\n<FloatingLabelInput\n label="Birthday"\n value={birthday}\n maxLength={14}\n onChange={(evt) => setBirthday(formatBirthdayValue(evt.target.value))}\n/>',
1701
+ details: "A date-of-birth input with inline formatting that inserts separators as you type (MM / DD / YYYY)."
1702
+ },
1703
+ {
1704
+ name: "Birthday with validation",
1705
+ code: '<FloatingLabelInput\n label="Birthday"\n defaultValue="02 / 30 / 2001"\n invalid\n errorMessage="Please enter a valid date."\n/>',
1706
+ details: "Use invalid and errorMessage to display the OpenAI-style birthday validation state."
1707
+ },
1708
+ {
1709
+ name: "Birthday states",
1710
+ code: '<div className="flex flex-col items-center gap-8 py-8">\n <div className="flex items-center">\n <div className="text-right text-secondary text-sm mr-8 min-w-[5rem]">Empty</div>\n <FloatingLabelInput label="Birthday" className="w-[320px]" />\n </div>\n <div className="flex items-center">\n <div className="text-right text-secondary text-sm mr-8 min-w-[5rem]">Filled</div>\n <FloatingLabelInput label="Birthday" defaultValue="01 / 15 / 1995" className="w-[320px]" />\n </div>\n <div className="flex items-center">\n <div className="text-right text-secondary text-sm mr-8 min-w-[5rem]">Invalid</div>\n <FloatingLabelInput invalid label="Birthday" defaultValue="02 / 30 / 2001" className="w-[320px]" />\n </div>\n <div className="flex items-center">\n <div className="text-right text-secondary text-sm mr-8 min-w-[5rem]">Disabled</div>\n <FloatingLabelInput disabled label="Birthday" defaultValue="01 / 15 / 1995" className="w-[320px]" />\n </div>\n</div>',
1711
+ details: "Overview of birthday input states."
1712
+ },
1713
+ {
1714
+ name: "Country select",
1715
+ code: 'import { FloatingLabelSelect } from "@plexui/ui/components/FloatingLabelSelect"\nimport { Menu } from "@plexui/ui/components/Menu"\n\nconst [country, setCountry] = useState(null)\nconst selected = COUNTRIES.find((c) => c.value === country)\n\n<Menu>\n <Menu.Trigger>\n <FloatingLabelSelect\n label="Country"\n selected={!!selected}\n onClearClick={() => setCountry(null)}\n >\n {selected?.flag} {selected?.label}\n </FloatingLabelSelect>\n </Menu.Trigger>\n <Menu.Content>\n <Menu.RadioGroup value={country} onChange={setCountry}>\n {COUNTRIES.map((c) => (\n <Menu.RadioItem key={c.value} value={c.value}>\n {c.flag} {c.label}\n </Menu.RadioItem>\n ))}\n </Menu.RadioGroup>\n </Menu.Content>\n</Menu>',
1716
+ details: "Use FloatingLabelSelect alongside FloatingLabelInput for consistent form styling. The select trigger shares the same floating-label visual shell \u2014 the label floats up when a value is selected."
1717
+ },
1718
+ {
1719
+ name: "Phone input",
1720
+ code: '<Select trigger={countryTrigger} ... />\n<FloatingLabelInput\n label="Phone number"\n type="tel"\n startAdornment={<span className="text-tertiary">+34</span>}\n/>',
1721
+ details: "A country code selector paired with a FloatingLabelInput that uses startAdornment to show the dial code as a non-editable prefix. The prefix appears when the field is focused or has a value."
1722
+ }
1723
+ ],
1724
+ docsUrl: "https://plexui.com/docs/components/floating-label-input"
1725
+ },
1726
+ {
1727
+ slug: "indicators",
1728
+ name: "Indicators",
1729
+ description: "Display loading and progress states to users",
1730
+ importPath: "@plexui/ui/components/Indicator",
1731
+ exports: [
1732
+ "LoadingIndicator",
1733
+ "CircularProgress",
1734
+ "LoadingDots"
1735
+ ],
1736
+ examples: [
1737
+ {
1738
+ name: "Overview",
1739
+ code: '<div className="flex items-center gap-6">\n <LoadingIndicator size={20} />\n <CircularProgress size={24} />\n <LoadingDots />\n</div>'
1740
+ },
1741
+ {
1742
+ name: "LoadingIndicator",
1743
+ code: "<LoadingIndicator />",
1744
+ details: "Simple spinning indicator for general loading states."
1745
+ },
1746
+ {
1747
+ name: "Sizing & stroke",
1748
+ code: "<LoadingIndicator size={30} strokeWidth={2} />",
1749
+ details: "By default, the indicator adapts to the surrounding font-size and color of text. You can control the size of the LoadingIndicator using the --indicator-size CSS custom property."
1750
+ },
1751
+ {
1752
+ name: "Color",
1753
+ code: '<div className="flex items-center gap-2 text-danger">\n <LoadingIndicator size={16} /> <span>Destroying forever...</span>\n</div>',
1754
+ details: "The indicator defaults to currentcolor, inheriting the surrounding text color. This can also be customized by altering the --indicator-color variable around the DOM scope of the component."
1755
+ },
1756
+ {
1757
+ name: "Duration",
1758
+ code: "<div style={{ '--indicator-rotate-duration': '3s' } as React.CSSProperties}>\n <LoadingIndicator />\n</div>",
1759
+ details: "You can control the rotation speed of the spinner with --indicator-rotate-duration."
1760
+ },
1761
+ {
1762
+ name: "CircularProgress",
1763
+ code: "<CircularProgress />",
1764
+ details: "Circular progress ring with simulated progress animation."
1765
+ },
1766
+ {
1767
+ name: "Progress",
1768
+ code: "<CircularProgress progress={25} />",
1769
+ details: "Use progress to show static progress and prevent the animation."
1770
+ },
1771
+ {
1772
+ name: "Sizing & stroke",
1773
+ code: "<CircularProgress size={30} strokeWidth={2} />",
1774
+ details: "Use size and strokeWidth. Or use --circular-progress-size and --circular-progress-stroke CSS variables."
1775
+ },
1776
+ {
1777
+ name: "Color",
1778
+ code: '<CircularProgress trackColor="var(--alpha-15)" trackActiveColor="var(--gray-1000)" />',
1779
+ details: "Customize the color of the indicator with the --circular-progress-track-color and --circular-progress-track-active-color variables."
1780
+ },
1781
+ {
1782
+ name: "LoadingDots",
1783
+ code: "<LoadingDots />",
1784
+ details: "Indicate typing progress with staggered pulsing dot animation."
1785
+ },
1786
+ {
1787
+ name: "Color",
1788
+ code: '<div className="text-danger">\n <LoadingDots />\n</div>',
1789
+ details: "The indicator defaults to currentcolor, inheriting the surrounding text color. You can customize the color by providing a different color value or context."
1790
+ }
1791
+ ],
1792
+ subComponents: [
1793
+ {
1794
+ name: "LoadingIndicator",
1795
+ props: [
1796
+ {
1797
+ name: "size",
1798
+ type: "number",
1799
+ description: "Size in px (or use --indicator-size CSS variable)"
1800
+ },
1801
+ {
1802
+ name: "strokeWidth",
1803
+ type: "number",
1804
+ description: "Stroke width"
1805
+ }
1806
+ ]
1807
+ },
1808
+ {
1809
+ name: "CircularProgress",
1810
+ props: [
1811
+ {
1812
+ name: "progress",
1813
+ type: "number",
1814
+ description: "Static progress 0\u2013100 (disables animation)"
1815
+ },
1816
+ {
1817
+ name: "size",
1818
+ type: "number",
1819
+ description: "Diameter in px"
1820
+ },
1821
+ {
1822
+ name: "strokeWidth",
1823
+ type: "number",
1824
+ description: "Stroke thickness"
1825
+ },
1826
+ {
1827
+ name: "done",
1828
+ type: "boolean",
1829
+ description: "Completion state for animation"
1830
+ },
1831
+ {
1832
+ name: "trackColor",
1833
+ type: "string",
1834
+ description: "Track color (CSS variable or value)"
1835
+ },
1836
+ {
1837
+ name: "trackActiveColor",
1838
+ type: "string",
1839
+ description: "Active segment color"
1840
+ }
1841
+ ]
1842
+ }
1843
+ ],
1844
+ docsUrl: "https://plexui.com/docs/components/indicators"
1845
+ },
1846
+ {
1847
+ slug: "input",
1848
+ name: "Input",
1849
+ description: "Semantic input text collection",
1850
+ importPath: "@plexui/ui/components/Input",
1851
+ exports: [
1852
+ "Input"
1853
+ ],
1854
+ props: [
1855
+ {
1856
+ name: "variant",
1857
+ type: '"outline" | "soft"',
1858
+ default: '"outline"',
1859
+ description: "Visual style of the input"
1860
+ },
1861
+ {
1862
+ name: "size",
1863
+ type: '"3xs" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl"',
1864
+ default: '"md"',
1865
+ description: "Controls the size of the input"
1866
+ },
1867
+ {
1868
+ name: "gutterSize",
1869
+ type: "string",
1870
+ description: "Controls gutter on the edges; defaults to value from size"
1871
+ },
1872
+ {
1873
+ name: "disabled",
1874
+ type: "boolean",
1875
+ default: "false",
1876
+ description: "Disables the input visually and from interactions"
1877
+ },
1878
+ {
1879
+ name: "invalid",
1880
+ type: "boolean",
1881
+ default: "false",
1882
+ description: "Mark the input as invalid"
1883
+ },
1884
+ {
1885
+ name: "allowAutofillExtensions",
1886
+ type: "boolean",
1887
+ default: "false",
1888
+ description: "Allow autofill extensions to appear in the input"
1889
+ },
1890
+ {
1891
+ name: "autoSelect",
1892
+ type: "boolean",
1893
+ default: "false",
1894
+ description: "Select all contents of the input when mounted"
1895
+ },
1896
+ {
1897
+ name: "startAdornment",
1898
+ type: "ReactNode",
1899
+ description: "Content rendered at the start of the input"
1900
+ },
1901
+ {
1902
+ name: "endAdornment",
1903
+ type: "ReactNode",
1904
+ description: "Content rendered at the end of the input"
1905
+ },
1906
+ {
1907
+ name: "onClear",
1908
+ type: "() => void",
1909
+ description: "When provided, displays a clear button that appears when the input has a value"
1910
+ },
1911
+ {
1912
+ name: "pill",
1913
+ type: "boolean",
1914
+ default: "false",
1915
+ description: "Fully rounded pill shape"
1916
+ },
1917
+ {
1918
+ name: "opticallyAlign",
1919
+ type: '"start" | "end"',
1920
+ description: "Applies a negative margin using the current gutter to optically align with surrounding content"
1921
+ }
1922
+ ],
1923
+ examples: [
1924
+ {
1925
+ name: "Overview",
1926
+ code: '<Input placeholder="Enter text..." />'
1927
+ },
1928
+ {
1929
+ name: "Variants",
1930
+ code: '<div className="flex flex-col gap-2 w-[320px]">\n <Input placeholder="Enter text..." variant="outline" />\n <Input placeholder="Enter text..." variant="soft" />\n</div>',
1931
+ details: "Use variant to stylize the input."
1932
+ },
1933
+ {
1934
+ name: "Sizing & roundness",
1935
+ code: '<div className="flex flex-col gap-2.5 justify-start items-start w-[320px]">\n <Input placeholder="Enter text..." size="lg" />\n <Input placeholder="Search..." startAdornment={<Search className="fill-tertiary" />} size="lg" />\n <Input value={username} placeholder="Username" onChange={(evt) => setUsername(evt.target.value)} maxLength={16} endAdornment={<span className="mt-[1.5px] tabular-nums text-tertiary text-[.875em]">{username.length}/16</span>} />\n</div>',
1936
+ details: "Sizing of the input is controlled with size and gutterSize props. size sets the input to a specific control height, and gutterSize sets the horizontal padding. Each size provides default values for gutterSize, as well as font-size. Each of these values can be overridden independently."
1937
+ },
1938
+ {
1939
+ name: "Start adornment",
1940
+ code: '<Input\n placeholder="Start adornment"\n startAdornment={<Search className="fill-tertiary" />}\n/>',
1941
+ details: "You can add arbitrary content to the beginning of an input with startAdornment &ndash; like a search icon. Ideally, the spacing will look pretty good by default. If you need to customize the spacing: gutterSize determines the amount of spacing on the edges of the container, which you can fine-tune as needed (if you need precise spacing not supported by gutterSize, use negative left margin on your adornment directly, e.g. -ml-2). The spacing between the adornment and the input is controlled with gap on the container, which you can customize as needed with Tailwind classes."
1942
+ },
1943
+ {
1944
+ name: "Clear button",
1945
+ code: '<Input\n placeholder="Enter text..."\n value={value}\n onChange={(evt) => setValue(evt.target.value)}\n onClear={() => setValue("")}\n pill\n/>',
1946
+ details: "Use the onClear prop to display a built-in clear button. The button appears when the input has a value and is not disabled or read-only."
1947
+ },
1948
+ {
1949
+ name: "End adornment",
1950
+ code: '<Input\n placeholder="Username"\n value={username}\n onChange={(evt) => setUsername(evt.target.value)}\n maxLength={16}\n endAdornment={\n <span className="mt-[1.5px] tabular-nums text-tertiary text-[.875em]">\n {username.length}/16\n </span>\n }\n/>',
1951
+ details: "You can add arbitrary content to the end of an input with endAdornment &ndash; like a character counter or custom icon. The same spacing guidance from startAdornment applies here."
1952
+ },
1953
+ {
1954
+ name: "Password with visibility toggle",
1955
+ code: `<Input
1956
+ placeholder="Password"
1957
+ type={visible ? 'text' : 'password'}
1958
+ endAdornment={<VisibilityToggle visible={visible} onToggle={toggle} />}
1959
+ />`,
1960
+ details: 'Combine endAdornment with a toggle button to switch between type="password" and type="text". Works across all sizes, variants, and with pill.'
1961
+ },
1962
+ {
1963
+ name: "SSN input",
1964
+ code: `<Input
1965
+ placeholder="Social Security Number"
1966
+ value={formatted}
1967
+ inputMode="numeric"
1968
+ endAdornment={<VisibilityToggle visible={visible} onToggle={toggle} />}
1969
+ style={!visible && rawDigits ? { color: 'transparent', caretColor: 'var(--color-text)' } : undefined}
1970
+ />`,
1971
+ details: "A masked SSN field that formats input as XXX-XX-XXXX, hides digits behind bullets when masked, and reveals the value via a visibility toggle in endAdornment. An inline mask suffix highlights the next expected segment. Adapts to all sizes automatically."
1972
+ },
1973
+ {
1974
+ name: "Birthday input",
1975
+ code: '<Input\n placeholder="Birthday (MM / DD / YYYY)"\n value={birthday}\n inputMode="numeric"\n maxLength={14}\n/>',
1976
+ details: "A date-of-birth input with inline formatting that inserts separators as you type (MM / DD / YYYY). An inline mask suffix highlights the next expected segment. Works across all sizes, variants, and with pill."
1977
+ },
1978
+ {
1979
+ name: "Birthday with validation",
1980
+ code: '<Input\n placeholder="Birthday (MM / DD / YYYY)"\n value={birthday}\n inputMode="numeric"\n invalid={!!errorMessage}\n/>',
1981
+ details: "Use invalid and FieldError to display a validation state when the entered date is not valid."
1982
+ },
1983
+ {
1984
+ name: "Disabled",
1985
+ code: '<Input disabled defaultValue="Jane Doe" />',
1986
+ details: "Use the disabled prop to disable the input, greying it out and preventing user interactions."
1987
+ },
1988
+ {
1989
+ name: "Invalid",
1990
+ code: '<Input invalid placeholder="Invalid input" />',
1991
+ details: "Use the invalid prop to mark the input as invalid. You decide how this state is removed, whether that's through onFocus, onChange, an onSubmit handler, etc. Default visual styles will be applied, based on the variant. Additionally, [data-invalid] is applied to the container for custom styling."
1992
+ },
1993
+ {
1994
+ name: "Auto select text",
1995
+ code: '<Input autoSelect placeholder="Add text..." defaultValue="Toggle to auto select" />',
1996
+ details: "Pass autoSelect to select all text in the input &ndash; useful when pairing with other copy actions. This prop behaves like an imperative action when toggled and allows selection to change."
1997
+ },
1998
+ {
1999
+ name: "Autofill extensions",
2000
+ code: '<Input allowAutofillExtensions name="email" placeholder="Allowed" />',
2001
+ details: 'By default, autofill extensions are prevented by Input. For the occasions where you want browser extensions to be present, pass allowAutofillExtensions to remove the disabling. When name is passed, or type is set to "password", this value will default to true.'
2002
+ },
2003
+ {
2004
+ name: "With button",
2005
+ code: '<div className="flex gap-2">\n <Input placeholder="jane.doe@gmail.com" size="lg" variant="outline" />\n <Button color="primary" size="lg" variant="outline" pill={false}>Subscribe</Button>\n</div>',
2006
+ details: "You can pair Input and Button components together by using the same size value."
2007
+ },
2008
+ {
2009
+ name: "Optical alignment",
2010
+ code: `<Field label="Full name" opticallyAlign="start">
2011
+ <Input placeholder="Jane Doe" pill />
2012
+ </Field>
2013
+ <Field
2014
+ label="Email"
2015
+ description="We'll never share your email."
2016
+ errorMessage="Please enter a valid email address."
2017
+ required
2018
+ opticallyAlign="start"
2019
+ >
2020
+ <Input placeholder="jane@example.com" pill />
2021
+ </Field>
2022
+ <Button className="mt-6" color="primary" variant="soft" pill>
2023
+ Submit
2024
+ </Button>`
2025
+ }
2026
+ ],
2027
+ docsUrl: "https://plexui.com/docs/components/input"
2028
+ },
2029
+ {
2030
+ slug: "markdown",
2031
+ name: "Markdown",
2032
+ description: "Render rich formatted content",
2033
+ importPath: "@plexui/ui/components/Markdown",
2034
+ exports: [
2035
+ "Markdown"
2036
+ ],
2037
+ props: [
2038
+ {
2039
+ name: "children",
2040
+ type: "string",
2041
+ description: "Markdown source"
2042
+ },
2043
+ {
2044
+ name: "includeMath",
2045
+ type: "boolean",
2046
+ default: "false",
2047
+ description: "Enable LaTeX math rendering"
2048
+ }
2049
+ ],
2050
+ examples: [
2051
+ {
2052
+ name: "Overview",
2053
+ code: "<Markdown>{markdownContent}</Markdown>",
2054
+ details: "Use Markdown to render rich markdown text."
2055
+ },
2056
+ {
2057
+ name: "Math",
2058
+ code: "<Markdown includeMath>{markdownWithLatex}</Markdown>",
2059
+ details: "Use includeMath to enable LaTeX rendering."
2060
+ }
2061
+ ],
2062
+ docsUrl: "https://plexui.com/docs/components/markdown"
2063
+ },
2064
+ {
2065
+ slug: "menu",
2066
+ name: "Menu",
2067
+ description: "Structured actions in a dropdown list",
2068
+ importPath: "@plexui/ui/components/Menu",
2069
+ exports: [
2070
+ "Menu"
2071
+ ],
2072
+ props: [
2073
+ {
2074
+ name: "forceOpen",
2075
+ type: "boolean",
2076
+ description: "Keep menu open (controlled)"
2077
+ },
2078
+ {
2079
+ name: "onOpen",
2080
+ type: "() => void",
2081
+ description: "Called when menu opens"
2082
+ },
2083
+ {
2084
+ name: "onClose",
2085
+ type: "() => void",
2086
+ description: "Called when menu closes"
2087
+ },
2088
+ {
2089
+ name: "modal",
2090
+ type: "boolean",
2091
+ description: "Whether menu is modal (focus trap)"
2092
+ },
2093
+ {
2094
+ name: "width",
2095
+ type: "number | string",
2096
+ description: "Panel width (px or CSS value)"
2097
+ },
2098
+ {
2099
+ name: "minWidth",
2100
+ type: "number | string",
2101
+ default: "auto",
2102
+ description: "Minimum panel width"
2103
+ },
2104
+ {
2105
+ name: "side",
2106
+ type: '"top" | "right" | "bottom" | "left"',
2107
+ description: "Preferred placement"
2108
+ },
2109
+ {
2110
+ name: "align",
2111
+ type: '"start" | "center" | "end"',
2112
+ description: "Alignment relative to trigger"
2113
+ },
2114
+ {
2115
+ name: "onSelect",
2116
+ type: "(event: Event) => void",
2117
+ description: "Called when item is selected"
2118
+ },
2119
+ {
2120
+ name: "disabled",
2121
+ type: "boolean",
2122
+ description: "Disable the item"
2123
+ },
2124
+ {
2125
+ name: "checked",
2126
+ type: "boolean",
2127
+ description: "Checked state"
2128
+ },
2129
+ {
2130
+ name: "onCheckedChange",
2131
+ type: "(checked: boolean) => void",
2132
+ description: "When checkbox toggles"
2133
+ },
2134
+ {
2135
+ name: "indicatorPosition",
2136
+ type: '"start" | "end"',
2137
+ description: "Checkbox position"
2138
+ },
2139
+ {
2140
+ name: "indicatorVariant",
2141
+ type: "string",
2142
+ description: "Badge/indicator style (e.g. solid)"
2143
+ }
2144
+ ],
2145
+ examples: [
2146
+ {
2147
+ name: "Overview",
2148
+ code: '<Menu>\n <Menu.Trigger>\n <Button color="primary" size="lg" variant="ghost">Sample menu <ChevronDownMd /></Button>\n </Menu.Trigger>\n <Menu.Content width={210} minWidth={210}>\n <Menu.Item>\n <p className="font-semibold">Sam Smith</p>\n <p className="text-secondary">sam.smith@gmail.com</p>\n </Menu.Item>\n <Menu.Separator />\n <Menu.Link href="/settings">Your profile</Menu.Link>\n <Menu.Link href="#">Terms & policies</Menu.Link>\n <Menu.Item disabled onSelect={() => {}}>Feature flags</Menu.Item>\n <Menu.Separator />\n <Menu.Item onSelect={() => {}}>Logout</Menu.Item>\n </Menu.Content>\n</Menu>'
2149
+ },
2150
+ {
2151
+ name: "Natural sizing",
2152
+ code: '<Menu>\n <Menu.Trigger>\n <Button color="primary" size="lg" variant="ghost" className="font-semibold gap-1.5"><Tools /> Tools</Button>\n </Menu.Trigger>\n <Menu.Content minWidth="auto">\n <Menu.Item onSelect={() => {}}><Functions height={18} width={18} /> Function</Menu.Item>\n <Menu.Item onSelect={() => {}}><Search height={18} width={18} /> File Search</Menu.Item>\n <Menu.Item onSelect={() => {}}><Globe height={18} width={18} /> Web Search</Menu.Item>\n <Menu.Item onSelect={() => {}}><Code height={18} width={18} /> Code Interpreter</Menu.Item>\n <Menu.Item onSelect={() => {}}><ImageSquare height={18} width={18} /> Image Generation</Menu.Item>\n </Menu.Content>\n</Menu>',
2153
+ details: `Menu supports several sizing strategies:Natural sizing &ndash; use minWidth="auto" to have the menu fit naturally to the size of the content.Fixed size &ndash; use width and minWidth to set the menu to a fixed size, regardless of content size.Flexible sizing &ndash; minWidth can be used by itself to describe a menu that must be a given size, but can grow larger depending on its content.Max sizing &ndash; apply max-width to the children of Menu.Content. maxWidth isn't exposed as a prop because it's reserved for Radix variables used in collision detection.`
2154
+ },
2155
+ {
2156
+ name: "Grouped items",
2157
+ code: '<Menu>\n <Menu.Trigger>\n <Button color="primary" size="lg" variant="ghost" className="font-semibold gap-1.5"><FileDocument /> Templates <ChevronDownMd /></Button>\n </Menu.Trigger>\n <Menu.Content minWidth={220}>\n <Menu.Group>\n <Menu.Label>Active</Menu.Label>\n <Menu.Link href="#">Welcome email</Menu.Link>\n <Menu.Link href="#">Order confirmation</Menu.Link>\n <Menu.Link href="#">Password reset</Menu.Link>\n </Menu.Group>\n <Menu.Separator />\n <Menu.Group>\n <Menu.Label>Draft</Menu.Label>\n <Menu.Link href="#">New promo campaign</Menu.Link>\n <Menu.Link href="#">Account deactivation</Menu.Link>\n </Menu.Group>\n </Menu.Content>\n</Menu>',
2158
+ details: "Use Menu.Group and Menu.Label to organize related items into labeled sections."
2159
+ },
2160
+ {
2161
+ name: "Submenus",
2162
+ code: '<Menu>\n <Menu.Trigger>\n <Button color="primary" size="lg" variant="ghost">Options <ChevronDownMd /></Button>\n </Menu.Trigger>\n <Menu.Content minWidth={180}>\n <Menu.Item onSelect={() => {}}>Edit</Menu.Item>\n <Menu.Item onSelect={() => {}}>Duplicate</Menu.Item>\n <Menu.Separator />\n <Menu.Sub>\n <Menu.SubTrigger>More</Menu.SubTrigger>\n <Menu.SubContent>\n <Menu.Item onSelect={() => {}}>Move to project\u2026</Menu.Item>\n <Menu.Item onSelect={() => {}}>Move to folder\u2026</Menu.Item>\n <Menu.Separator />\n <Menu.Item onSelect={() => {}}>Advanced options\u2026</Menu.Item>\n </Menu.SubContent>\n </Menu.Sub>\n <Menu.Separator />\n <Menu.Item onSelect={() => {}}>Share</Menu.Item>\n <Menu.Item onSelect={() => {}}>Add to favorites</Menu.Item>\n <Menu.Separator />\n <Menu.Item onSelect={() => {}}>Delete</Menu.Item>\n </Menu.Content>\n</Menu>',
2163
+ details: "Use Menu.Sub, Menu.SubTrigger, and Menu.SubContent for nested menus."
2164
+ },
2165
+ {
2166
+ name: "Checkbox items",
2167
+ code: 'const [settings, setSettings] = useState({\n showGrid: true,\n showLabels: false,\n enableShadows: false,\n})\n\nreturn (\n <Menu>\n <Menu.Trigger>\n <Button variant="ghost" color="primary">\n Checkbox menu <ChevronDownMd />\n </Button>\n </Menu.Trigger>\n <Menu.Content minWidth={200}>\n <Menu.CheckboxItem\n checked={settings.showGrid}\n onCheckedChange={(checked) => setSettings((s) => ({ ...s, showGrid: checked }))}\n onSelect={(evt) => evt.preventDefault()}\n indicatorPosition="end"\n indicatorVariant="solid"\n >\n Show grid lines\n </Menu.CheckboxItem>\n <Menu.CheckboxItem\n checked={settings.showLabels}\n onCheckedChange={(checked) => setSettings((s) => ({ ...s, showLabels: checked }))}\n onSelect={(evt) => evt.preventDefault()}\n indicatorPosition="end"\n indicatorVariant="solid"\n >\n Display labels\n </Menu.CheckboxItem>\n <Menu.CheckboxItem\n checked={settings.enableShadows}\n onCheckedChange={(checked) => setSettings((s) => ({ ...s, enableShadows: checked }))}\n onSelect={(evt) => evt.preventDefault()}\n indicatorPosition="end"\n indicatorVariant="solid"\n >\n Enable shadows\n </Menu.CheckboxItem>\n </Menu.Content>\n </Menu>\n)',
2168
+ details: 'Use Menu.CheckboxItem with checked, onCheckedChange, and {"onSelect={(evt) => evt.preventDefault()}"} to keep the menu open.'
2169
+ },
2170
+ {
2171
+ name: "Radio items",
2172
+ code: 'const [value, setValue] = useState("any")\n\n<Menu>\n <Menu.Trigger>\n <Button color="primary" variant="ghost">\n Radio menu <ChevronDownMd />\n </Button>\n </Menu.Trigger>\n <Menu.Content align="start" minWidth="auto" width="auto">\n <Menu.RadioGroup\n indicatorPosition="end"\n value={value}\n onChange={setValue}\n >\n <Menu.RadioItem value="any">Any time</Menu.RadioItem>\n <Menu.RadioItem value="today" disabled>Today</Menu.RadioItem>\n <Menu.RadioItem value="last7d">Last 7 days</Menu.RadioItem>\n <Menu.RadioItem value="last30d">Last 30 days</Menu.RadioItem>\n <Menu.RadioItem value="last3m">Last 3 months</Menu.RadioItem>\n </Menu.RadioGroup>\n </Menu.Content>\n</Menu>',
2173
+ details: "Use Menu.RadioGroup and Menu.RadioItem for single-selection options."
2174
+ },
2175
+ {
2176
+ name: "Item actions",
2177
+ code: '<Menu>\n <Menu.Trigger>\n <Button color="primary" size="lg" variant="ghost" className="font-semibold gap-1.5"><History /> Conversations</Button>\n </Menu.Trigger>\n <Menu.Content width={270}>\n <Menu.Item onSelect={() => {}}>\n <span className="flex-1 truncate">Sample thread title</span>\n <Menu.ItemActions>\n <Menu.ItemAction onClick={() => {}}><Trash /></Menu.ItemAction>\n </Menu.ItemActions>\n </Menu.Item>\n <Menu.Item onSelect={() => {}}>\n <span className="flex-1 truncate">Another sample thread title</span>\n <Menu.ItemActions>\n <Menu.ItemAction onClick={() => {}}><Trash /></Menu.ItemAction>\n </Menu.ItemActions>\n </Menu.Item>\n <Menu.Item onSelect={() => {}}>\n <span className="flex-1 truncate">Long thread title that is going to be clipped off</span>\n <Menu.ItemActions>\n <Menu.ItemAction onClick={() => {}}><Trash /></Menu.ItemAction>\n </Menu.ItemActions>\n </Menu.Item>\n </Menu.Content>\n</Menu>',
2178
+ details: "Use Menu.ItemActions and Menu.ItemAction for hover actions (e.g. delete) on an item.Accessibility note: these actions are not keyboard accessible due to how Radix Menu works, so they should not be critical actions with no other parity."
2179
+ }
2180
+ ],
2181
+ docsUrl: "https://plexui.com/docs/components/menu"
2182
+ },
2183
+ {
2184
+ slug: "otp-input",
2185
+ name: "OTP Input",
2186
+ description: "Individual digit input cells for verification code entry",
2187
+ importPath: "@plexui/ui/components/OTPInput",
2188
+ exports: [
2189
+ "OTPInput"
2190
+ ],
2191
+ props: [
2192
+ {
2193
+ name: "length",
2194
+ type: "number",
2195
+ default: "6",
2196
+ description: "Number of digit cells"
2197
+ },
2198
+ {
2199
+ name: "value",
2200
+ type: "string",
2201
+ description: "Controlled value"
2202
+ },
2203
+ {
2204
+ name: "onChange",
2205
+ type: "(value: string) => void",
2206
+ description: "Called when value changes"
2207
+ },
2208
+ {
2209
+ name: "onComplete",
2210
+ type: "(value: string) => void",
2211
+ description: "Called when all digits filled"
2212
+ },
2213
+ {
2214
+ name: "grouping",
2215
+ type: "number[]",
2216
+ description: "Grouping pattern with extra gap (e.g. [3,3])"
2217
+ },
2218
+ {
2219
+ name: "invalid",
2220
+ type: "boolean",
2221
+ default: "false",
2222
+ description: "Invalid visual state"
2223
+ },
2224
+ {
2225
+ name: "errorMessage",
2226
+ type: "string",
2227
+ description: "Error message below the input"
2228
+ },
2229
+ {
2230
+ name: "disabled",
2231
+ type: "boolean",
2232
+ default: "false",
2233
+ description: "Disable all cells"
2234
+ },
2235
+ {
2236
+ name: "autoFocus",
2237
+ type: "boolean",
2238
+ default: "false",
2239
+ description: "Auto-focus first cell on mount"
2240
+ }
2241
+ ],
2242
+ examples: [
2243
+ {
2244
+ name: "Overview",
2245
+ code: "<OTPInput />"
2246
+ },
2247
+ {
2248
+ name: "Filled state",
2249
+ code: '<OTPInput value="488030" onChange={setValue} />'
2250
+ },
2251
+ {
2252
+ name: "Grouped (3+3)",
2253
+ code: "<OTPInput grouping={[3, 3]} />"
2254
+ },
2255
+ {
2256
+ name: "With error",
2257
+ code: '<OTPInput invalid errorMessage="Invalid verification code." />'
2258
+ },
2259
+ {
2260
+ name: "Disabled",
2261
+ code: '<OTPInput value="488030" disabled />'
2262
+ }
2263
+ ],
2264
+ docsUrl: "https://plexui.com/docs/components/otp-input"
2265
+ },
2266
+ {
2267
+ slug: "popover",
2268
+ name: "Popover",
2269
+ description: "Generic floating UI utility for contextual actions",
2270
+ importPath: "@plexui/ui/components/Popover",
2271
+ exports: [
2272
+ "Popover"
2273
+ ],
2274
+ props: [
2275
+ {
2276
+ name: "showOnHover",
2277
+ type: "boolean",
2278
+ description: "Open on hover and focus instead of click"
2279
+ },
2280
+ {
2281
+ name: "width",
2282
+ type: "number | string",
2283
+ description: "Panel width (px or CSS value)"
2284
+ },
2285
+ {
2286
+ name: "minWidth",
2287
+ type: "number | string",
2288
+ default: "300",
2289
+ description: "Minimum panel width in px"
2290
+ },
2291
+ {
2292
+ name: "maxWidth",
2293
+ type: "number | string",
2294
+ description: "Maximum panel width"
2295
+ },
2296
+ {
2297
+ name: "side",
2298
+ type: '"top" | "right" | "bottom" | "left"',
2299
+ description: "Preferred placement"
2300
+ },
2301
+ {
2302
+ name: "align",
2303
+ type: '"start" | "center" | "end"',
2304
+ description: "Alignment relative to trigger"
2305
+ },
2306
+ {
2307
+ name: "sideOffset",
2308
+ type: "number",
2309
+ description: "Offset from trigger (px)"
2310
+ },
2311
+ {
2312
+ name: "alignOffset",
2313
+ type: "number",
2314
+ description: "Alignment offset (px)"
2315
+ }
2316
+ ],
2317
+ examples: [
2318
+ {
2319
+ name: "Overview",
2320
+ code: '<Popover>\n <Popover.Trigger><Button color="primary">Generate</Button></Popover.Trigger>\n <Popover.Content side="right">...</Popover.Content>\n</Popover>'
2321
+ },
2322
+ {
2323
+ name: "Sizing the popover",
2324
+ code: '<Popover>\n <Popover.Trigger>...</Popover.Trigger>\n <Popover.Content minWidth="auto" side="top">...</Popover.Content>\n</Popover>',
2325
+ details: "The popover can be sized with any combination of width, minWidth, or maxWidth. All popovers default to a minWidth of 300px, and a maxWidth of Radix's viewport detection."
2326
+ },
2327
+ {
2328
+ name: "Open on hover",
2329
+ code: "<Popover showOnHover>\n <Popover.Trigger>...</Popover.Trigger>\n <Popover.Content>...</Popover.Content>\n</Popover>",
2330
+ details: "Use showOnHover to create popovers that appear on hover and focus events. This should be used sparingly and with clear intention.By default, hover popovers will not alter the focused element on the page, nor restore focus to the trigger on close. However, if interactive content is present in the popover, and tab is used to navigate into the popover, focus will be restored to the trigger on close."
2331
+ },
2332
+ {
2333
+ name: "Using the controller",
2334
+ code: "const { close, shake } = usePopoverController()\n// e.g. close after form submit, shake on validation error",
2335
+ details: "You can access usePopoverController() controller context from components within Popover.Content. The hook exposes two methods, close() and shake(). They do exactly what you think they do.Attempting to submit an empty form calls the shake() methodAfter async submission occurs (faked loading state), the close() method is called to close the popup imperatively."
2336
+ }
2337
+ ],
2338
+ docsUrl: "https://plexui.com/docs/components/popover"
2339
+ },
2340
+ {
2341
+ slug: "progress-steps",
2342
+ name: "Progress Steps",
2343
+ description: "Display progress through a multi-step process",
2344
+ importPath: "@plexui/ui/components/ProgressSteps",
2345
+ exports: [
2346
+ "ProgressSteps"
2347
+ ],
2348
+ props: [
2349
+ {
2350
+ name: "current",
2351
+ type: "number",
2352
+ description: "Current active step (1-based)"
2353
+ },
2354
+ {
2355
+ name: "orientation",
2356
+ type: '"horizontal" | "vertical"',
2357
+ default: '"horizontal"',
2358
+ description: "Layout direction"
2359
+ },
2360
+ {
2361
+ name: "size",
2362
+ type: '"sm" | "md" | "lg"',
2363
+ default: '"md"',
2364
+ description: "Step indicator size"
2365
+ },
2366
+ {
2367
+ name: "color",
2368
+ type: '"default" | "success"',
2369
+ default: '"default"',
2370
+ description: "Color scheme for completed/active steps"
2371
+ },
2372
+ {
2373
+ name: "connectorStyle",
2374
+ type: '"solid" | "dashed"',
2375
+ default: '"solid"',
2376
+ description: "Connector line style"
2377
+ },
2378
+ {
2379
+ name: "variant",
2380
+ type: '"minimal"',
2381
+ description: "Compact bar variant (requires total)"
2382
+ },
2383
+ {
2384
+ name: "total",
2385
+ type: "number",
2386
+ description: "Total steps for minimal variant"
2387
+ },
2388
+ {
2389
+ name: "icon",
2390
+ type: "ReactNode",
2391
+ description: "Custom icon instead of step number"
2392
+ }
2393
+ ],
2394
+ examples: [
2395
+ {
2396
+ name: "Overview",
2397
+ code: "<ProgressSteps current={2}>\n <ProgressSteps.Step>\n <ProgressSteps.Title>Your details</ProgressSteps.Title>\n <ProgressSteps.Description>Please provide your name and email</ProgressSteps.Description>\n </ProgressSteps.Step>\n <ProgressSteps.Step>\n <ProgressSteps.Title>Company details</ProgressSteps.Title>\n <ProgressSteps.Description>A few details about your company</ProgressSteps.Description>\n </ProgressSteps.Step>\n <ProgressSteps.Step>\n <ProgressSteps.Title>Invite your team</ProgressSteps.Title>\n <ProgressSteps.Description>Start collaborating with your team</ProgressSteps.Description>\n </ProgressSteps.Step>\n <ProgressSteps.Step>\n <ProgressSteps.Title>Add your socials</ProgressSteps.Title>\n <ProgressSteps.Description>Share posts to your social accounts</ProgressSteps.Description>\n </ProgressSteps.Step>\n</ProgressSteps>"
2398
+ },
2399
+ {
2400
+ name: "Sizes",
2401
+ code: '<ProgressSteps current={2} size="md">\n <ProgressSteps.Step><ProgressSteps.Title>Step 1</ProgressSteps.Title></ProgressSteps.Step>\n <ProgressSteps.Step><ProgressSteps.Title>Step 2</ProgressSteps.Title></ProgressSteps.Step>\n <ProgressSteps.Step><ProgressSteps.Title>Step 3</ProgressSteps.Title></ProgressSteps.Step>\n <ProgressSteps.Step><ProgressSteps.Title>Step 4</ProgressSteps.Title></ProgressSteps.Step>\n</ProgressSteps>',
2402
+ details: "Use size to control the scale of step indicators."
2403
+ },
2404
+ {
2405
+ name: "Vertical",
2406
+ code: '<ProgressSteps current={2} orientation="vertical">...</ProgressSteps>\n<ProgressSteps current={3} orientation="vertical" connectorStyle="dashed">...</ProgressSteps>',
2407
+ details: 'Use orientation="vertical" to display steps in a vertical layout, ideal for sidebar navigation or multi-step forms. The example also shows the dashed connector style.'
2408
+ },
2409
+ {
2410
+ name: "With Icons",
2411
+ code: "<ProgressSteps current={3}>\n <ProgressSteps.Step icon={<Email />}>...</ProgressSteps.Step>\n <ProgressSteps.Step icon={<Globe />}>...</ProgressSteps.Step>\n <ProgressSteps.Step icon={<Users />}>...</ProgressSteps.Step>\n <ProgressSteps.Step icon={<Bolt />}>...</ProgressSteps.Step>\n</ProgressSteps>",
2412
+ details: "Use icon prop on ProgressSteps.Step to display custom icons instead of step numbers."
2413
+ },
2414
+ {
2415
+ name: "With Navigation",
2416
+ code: '<ProgressSteps variant="minimal" current={step} total={4} />\n<Button onClick={() => setStep(s => Math.max(1, s - 1))}>Previous</Button>\n<Button onClick={() => setStep(s => Math.min(4, s + 1))}>Next</Button>',
2417
+ details: "Example of integrating with navigation controls."
2418
+ },
2419
+ {
2420
+ name: "Minimal",
2421
+ code: '<ProgressSteps variant="minimal" current={3} total={4} color="default" />',
2422
+ details: 'Use variant="minimal" for a compact horizontal bar indicator. Requires total prop.'
2423
+ },
2424
+ {
2425
+ name: "Icons Only",
2426
+ code: "<ProgressSteps current={2}>\n <ProgressSteps.Step />\n <ProgressSteps.Step />\n <ProgressSteps.Step />\n <ProgressSteps.Step />\n</ProgressSteps>",
2427
+ details: "Omit Title and Description for a minimal indicator when step details are shown elsewhere."
2428
+ },
2429
+ {
2430
+ name: "Colors",
2431
+ code: '<ProgressSteps current={3}>...</ProgressSteps>\n<ProgressSteps current={3} color="success">...</ProgressSteps>',
2432
+ details: "Use color to change the color scheme of completed and active steps."
2433
+ }
2434
+ ],
2435
+ docsUrl: "https://plexui.com/docs/components/progress-steps"
2436
+ },
2437
+ {
2438
+ slug: "radio-group",
2439
+ name: "Radio Group",
2440
+ description: "Semantic radio option selection",
2441
+ importPath: "@plexui/ui/components/RadioGroup",
2442
+ exports: [
2443
+ "RadioGroup"
2444
+ ],
2445
+ props: [
2446
+ {
2447
+ name: "value",
2448
+ type: "string",
2449
+ description: "Controlled selected value"
2450
+ },
2451
+ {
2452
+ name: "defaultValue",
2453
+ type: "string",
2454
+ description: "Initial selected value (uncontrolled)"
2455
+ },
2456
+ {
2457
+ name: "onChange",
2458
+ type: "(value: string) => void",
2459
+ description: "Called when selected option changes"
2460
+ },
2461
+ {
2462
+ name: "direction",
2463
+ type: '"col" | "row"',
2464
+ default: '"col"',
2465
+ description: "Item flow direction"
2466
+ },
2467
+ {
2468
+ name: "disabled",
2469
+ type: "boolean",
2470
+ default: "false",
2471
+ description: "Disable the whole group"
2472
+ },
2473
+ {
2474
+ name: "aria-label",
2475
+ type: "string",
2476
+ description: "Accessible group label"
2477
+ },
2478
+ {
2479
+ name: "block",
2480
+ type: "boolean",
2481
+ default: "false",
2482
+ description: "Stretch item to fill available width"
2483
+ },
2484
+ {
2485
+ name: "orientation",
2486
+ type: '"left" | "right"',
2487
+ default: '"left"',
2488
+ description: "Position of the radio indicator relative to the label"
2489
+ }
2490
+ ],
2491
+ examples: [
2492
+ {
2493
+ name: "Overview",
2494
+ code: 'const [frequency, setFrequency] = useState("daily")\n<RadioGroup direction="col" value={frequency} onChange={setFrequency} aria-label="Notification frequency">\n <RadioGroup.Item value="daily">Daily</RadioGroup.Item>\n <RadioGroup.Item value="weekly">Weekly</RadioGroup.Item>\n <RadioGroup.Item value="monthly">Monthly</RadioGroup.Item>\n <RadioGroup.Item value="never">Never</RadioGroup.Item>\n</RadioGroup>'
2495
+ },
2496
+ {
2497
+ name: "Direction",
2498
+ code: '<RadioGroup direction="col" defaultValue="light" aria-label="Theme">\n <RadioGroup.Item value="light">Light</RadioGroup.Item>\n <RadioGroup.Item value="dark">Dark</RadioGroup.Item>\n <RadioGroup.Item value="system">System</RadioGroup.Item>\n</RadioGroup>',
2499
+ details: "Use direction to control whether the items flow in a column or row. An optimal gap will be dynamically applied based on the orientation."
2500
+ },
2501
+ {
2502
+ name: "Orientation",
2503
+ code: '<RadioGroup className="w-[320px]" direction="col" defaultValue="email" aria-label="Default notification channel">\n <RadioGroup.Item value="email" orientation="right" block>Email</RadioGroup.Item>\n <RadioGroup.Item value="sms" orientation="right" block>SMS</RadioGroup.Item>\n <RadioGroup.Item value="push" orientation="right" block>Push notification</RadioGroup.Item>\n</RadioGroup>',
2504
+ details: 'Use orientation="right" on RadioGroup.Item to place the radio indicator after the label text. Useful for settings lists and card-style selections where the content should lead.'
2505
+ },
2506
+ {
2507
+ name: "Group disabled",
2508
+ code: '<RadioGroup disabled defaultValue="standard" aria-label="Shipping method">\n <RadioGroup.Item value="standard">Standard</RadioGroup.Item>\n <RadioGroup.Item value="express">Express</RadioGroup.Item>\n <RadioGroup.Item value="overnight">Overnight</RadioGroup.Item>\n</RadioGroup>',
2509
+ details: "Use disabled on RadioGroup to disable all radio options."
2510
+ },
2511
+ {
2512
+ name: "Item disabled",
2513
+ code: '<RadioGroup aria-label="Delivery method" direction="col">\n <RadioGroup.Item value="standard">Standard shipping (3\u20135 days)</RadioGroup.Item>\n <RadioGroup.Item value="express" disabled>Express shipping (1\u20132 days)</RadioGroup.Item>\n <RadioGroup.Item value="same-day">\n Same-day delivery \u2014 available in select metro areas with a minimum order of $50\n </RadioGroup.Item>\n</RadioGroup>',
2514
+ details: "Use disabled on single RadioGroup.Item to disable only that option."
2515
+ },
2516
+ {
2517
+ name: "With description",
2518
+ code: '<RadioGroup direction="col" value={value} onChange={setValue} aria-label="Spacing">\n <RadioGroup.Item value="default">\n <div className="flex flex-col">\n <span>Default</span>\n <span className="text-xs text-secondary mt-0.5">\n Standard spacing for most use cases.\n </span>\n </div>\n </RadioGroup.Item>\n <RadioGroup.Item value="comfortable">\n <div className="flex flex-col">\n <span>Comfortable</span>\n <span className="text-xs text-secondary mt-0.5">\n More space between elements.\n </span>\n </div>\n </RadioGroup.Item>\n <RadioGroup.Item value="compact">\n <div className="flex flex-col">\n <span>Compact</span>\n <span className="text-xs text-secondary mt-0.5">\n Minimal spacing for dense layouts.\n </span>\n </div>\n </RadioGroup.Item>\n</RadioGroup>',
2519
+ details: "Radio items accept ReactNode children, so you can compose a label with secondary description text \u2014 useful for options that need additional context."
2520
+ },
2521
+ {
2522
+ name: "With Field",
2523
+ code: '<Field label="Notification method" description="Choose how you want to receive notifications.">\n {() => (\n <RadioGroup direction="col" value={value} onChange={setValue} aria-label="Notification method">\n <RadioGroup.Item value="email">Email</RadioGroup.Item>\n <RadioGroup.Item value="sms">SMS</RadioGroup.Item>\n <RadioGroup.Item value="push">Push notification</RadioGroup.Item>\n </RadioGroup>\n )}\n</Field>',
2524
+ details: "Wrap a RadioGroup in a Field for a shared label and description. The Field provides aria-describedby linking automatically."
2525
+ },
2526
+ {
2527
+ name: "With Field error",
2528
+ code: '<Field\n label="Subscription plan"\n description="Select a plan to continue."\n errorMessage={showError ? "Please select a subscription plan." : undefined}\n>\n {(fieldProps) => (\n <RadioGroup direction="col" aria-label="Subscription plan" {...fieldProps}>\n <RadioGroup.Item value="monthly">Monthly ($9.99/month)</RadioGroup.Item>\n <RadioGroup.Item value="yearly">Yearly ($99.99/year)</RadioGroup.Item>\n <RadioGroup.Item value="lifetime">Lifetime ($299.99)</RadioGroup.Item>\n </RadioGroup>\n )}\n</Field>',
2529
+ details: "When a Field has an errorMessage, it sets aria-invalid on the child control for screen readers. The error message is displayed below the group."
2530
+ },
2531
+ {
2532
+ name: "Choice card",
2533
+ code: '<RadioGroup className="flex-col w-[320px] gap-3" value={value} onChange={setValue} aria-label="Workspace type">\n <div style={{\n border: "1px solid",\n borderColor: isSelected ? "var(--color-background-primary-solid)" : "var(--color-border-primary-outline)",\n borderRadius: "var(--radius-lg)",\n backgroundColor: isSelected ? "var(--color-background-primary-soft-alpha)" : undefined,\n }}>\n <RadioGroup.Item value="personal" block orientation={orientation} className="gap-3 p-3">\n <div className="flex flex-col flex-1">\n <span className="font-semibold">Personal</span>\n <span className="text-xs text-secondary mt-0.5">For hobby projects and experimentation.</span>\n </div>\n </RadioGroup.Item>\n </div>\n ...\n</RadioGroup>',
2534
+ details: 'Style each radio option as a bordered card with visual selection state. The selected card gets a highlighted border using design tokens. Use orientation="right" to place the indicator after the content. Wrap each RadioGroup.Item in a card container with border and borderRadius on the same element.'
2535
+ },
2536
+ {
2537
+ name: "Card selection",
2538
+ code: '<RadioGroup className="flex-col w-[390px] gap-4">\n <RadioGroup.Item className="gap-2.5" value="basic">\n <div><h4>Basic Plan</h4><p className="text-secondary text-sm">5 GB storage</p></div>\n </RadioGroup.Item>\n ...\n</RadioGroup>',
2539
+ details: "Leverage component composition and className overrides to build card-style radio selections with rich content per option."
2540
+ },
2541
+ {
2542
+ name: "Grouped sections",
2543
+ code: '<div className="flex flex-col max-w-sm w-full divide-y divide-alpha/15">\n <div className="pb-5">\n <Field label="Visibility" description="Control who can see project resources.">\n {() => (\n <RadioGroup direction="col" value={visibility} onChange={setVisibility} aria-label="Visibility">\n <RadioGroup.Item value="hidden">Hidden</RadioGroup.Item>\n <RadioGroup.Item value="owners">Visible to organization owners</RadioGroup.Item>\n <RadioGroup.Item value="everyone">Visible to everyone</RadioGroup.Item>\n </RadioGroup>\n )}\n </Field>\n </div>\n <div className="pt-5">\n <Field label="Digest frequency" description="How often you receive summary emails.">\n {() => (\n <RadioGroup direction="col" value={frequency} onChange={setFrequency} aria-label="Digest frequency">\n <RadioGroup.Item value="daily">Daily</RadioGroup.Item>\n <RadioGroup.Item value="weekly">Weekly</RadioGroup.Item>\n <RadioGroup.Item value="monthly">Monthly</RadioGroup.Item>\n <RadioGroup.Item value="never">Never</RadioGroup.Item>\n </RadioGroup>\n )}\n </Field>\n </div>\n</div>',
2544
+ details: "Use dividers to separate radio groups into logical sections. Each section can be wrapped in a Field with its own label and description."
2545
+ },
2546
+ {
2547
+ name: "With Checkbox",
2548
+ code: '<div className="flex flex-row gap-12 items-start">\n <div>\n <span className="text-xs text-tertiary font-mono mb-1">RadioGroup</span>\n <RadioGroup direction="row" defaultValue="email" aria-label="Channel">\n <RadioGroup.Item value="email">Email</RadioGroup.Item>\n <RadioGroup.Item value="sms">SMS</RadioGroup.Item>\n <RadioGroup.Item value="push">Push</RadioGroup.Item>\n </RadioGroup>\n </div>\n <div>\n <span className="text-xs text-tertiary font-mono mb-1">Checkbox</span>\n <div className="flex flex-row gap-5">\n <Checkbox label="Email" defaultChecked />\n <Checkbox label="SMS" />\n <Checkbox label="Push" />\n </div>\n </div>\n</div>',
2549
+ details: "Place RadioGroup alongside Checkbox controls to verify that indicator sizes, label baselines, and spacing align consistently in both horizontal and vertical layouts."
2550
+ }
2551
+ ],
2552
+ docsUrl: "https://plexui.com/docs/components/radio-group"
2553
+ },
2554
+ {
2555
+ slug: "select-control",
2556
+ name: "Select Control",
2557
+ description: "Customizable select control for use with any type of floating UI",
2558
+ importPath: "@plexui/ui/components/SelectControl",
2559
+ exports: [
2560
+ "SelectControl"
2561
+ ],
2562
+ props: [
2563
+ {
2564
+ name: "selected",
2565
+ type: "boolean",
2566
+ description: "Whether a value is selected (affects placeholder styling)"
2567
+ },
2568
+ {
2569
+ name: "variant",
2570
+ type: '"soft" | "outline" | "ghost"',
2571
+ default: '"outline"',
2572
+ description: "Visual variant"
2573
+ },
2574
+ {
2575
+ name: "size",
2576
+ type: "string",
2577
+ description: "Control size"
2578
+ },
2579
+ {
2580
+ name: "pill",
2581
+ type: "boolean",
2582
+ description: "Fully rounded trigger"
2583
+ },
2584
+ {
2585
+ name: "StartIcon",
2586
+ type: "Component",
2587
+ description: "Icon at start"
2588
+ },
2589
+ {
2590
+ name: "onClearClick",
2591
+ type: "() => void",
2592
+ description: "Enables clear button when set"
2593
+ },
2594
+ {
2595
+ name: "dropdownIconType",
2596
+ type: '"chevronDown" | "dropdown" | "none"',
2597
+ description: "Dropdown indicator style"
2598
+ },
2599
+ {
2600
+ name: "invalid",
2601
+ type: "boolean",
2602
+ description: "Invalid state"
2603
+ },
2604
+ {
2605
+ name: "disabled",
2606
+ type: "boolean",
2607
+ description: "Disable control"
2608
+ },
2609
+ {
2610
+ name: "block",
2611
+ type: "boolean",
2612
+ description: "Full width"
2613
+ },
2614
+ {
2615
+ name: "loading",
2616
+ type: "boolean",
2617
+ description: "Loading state"
2618
+ },
2619
+ {
2620
+ name: "opticallyAlign",
2621
+ type: '"start" | "end"',
2622
+ description: "Optical alignment"
2623
+ }
2624
+ ],
2625
+ examples: [
2626
+ {
2627
+ name: "Overview",
2628
+ code: 'const [value, setValue] = useState("")\nconst placeholder = "Select date..."\n\nreturn (\n <Menu>\n <Menu.Trigger>\n <SelectControl selected={!!value} onClearClick={() => setValue("")} StartIcon={CalendarAlt}>\n {value || placeholder}\n </SelectControl>\n </Menu.Trigger>\n <Menu.Content>\n <Menu.RadioGroup value={value} onChange={setValue}>\n ...\n </Menu.RadioGroup>\n </Menu.Content>\n </Menu>\n)'
2629
+ },
2630
+ {
2631
+ name: "Variants",
2632
+ code: '<SelectControl variant="soft" selected StartIcon={Cabinet} onClearClick={() => {}}>Sample value</SelectControl>\n<SelectControl variant="outline" selected StartIcon={Cabinet} onClearClick={() => {}}>Sample value</SelectControl>\n<SelectControl variant="ghost" selected StartIcon={Cabinet} onClearClick={() => {}}>Sample value</SelectControl>',
2633
+ details: 'Use the variant prop to stylize the select control. Available variant styles are similar to {""}, but aligned with subtle differences from {""} variant treatments.'
2634
+ },
2635
+ {
2636
+ name: "Sizing & roundness",
2637
+ code: '<SelectControl size="md" pill={false} dropdownIconType="none">Select...</SelectControl>\n<SelectControl size="md" loading>Select...</SelectControl>\n<SelectControl size="md">Select...</SelectControl>\n<SelectControl size="md" StartIcon={Tools} dropdownIconType="none">Tool calls</SelectControl>\n...',
2638
+ details: "Sizing of the control is determined with the size prop. Each size provides dynamic values for various aspects of the control, including icon and indicator placement, as well font-size and gutter values.Create fully rounded controls with the pill prop. When pill is set, extra horizontal gutter is dynamically applied, and the clear action is also set to pill."
2639
+ },
2640
+ {
2641
+ name: "Selected",
2642
+ code: "<SelectControl selected={false}>Select...</SelectControl>",
2643
+ details: "Use selected to indicate if the control has a selected value, which toggles visual placeholder styling.As you'll notice here, this doesn't do anything to the content of the control &ndash; that's up to you."
2644
+ },
2645
+ {
2646
+ name: "Start icon",
2647
+ code: "<SelectControl StartIcon={CalendarAlt}>Select date...</SelectControl>",
2648
+ details: "Use StartIcon to display an icon in the control. Sizing, spacing, and coloring are handled automatically."
2649
+ },
2650
+ {
2651
+ name: "Dropdown icon",
2652
+ code: '<SelectControl selected dropdownIconType="dropdown" StartIcon={CalendarAlt}>Select...</SelectControl>',
2653
+ details: "Use dropdownIconType to control the presentation of the dropdown indicator."
2654
+ },
2655
+ {
2656
+ name: "Clearable",
2657
+ code: '<SelectControl selected onClearClick={() => setValue("")} StartIcon={CalendarAlt}>\n {value || "Select..."}\n</SelectControl>',
2658
+ details: "Pass a callback onClearClick to enable the clearable action."
2659
+ },
2660
+ {
2661
+ name: "Loading",
2662
+ code: "<SelectControl loading>Loading models...</SelectControl>",
2663
+ details: "During loading, dropdown icon and clear actions are automatically hidden.Interactions will be prevented by default during this state, by providing a default value for inert to true. You can override this behavior directly with inert, or use disabled to add visual disablement."
2664
+ },
2665
+ {
2666
+ name: "Invalid",
2667
+ code: "<SelectControl invalid selected>Pineapple on pizza</SelectControl>",
2668
+ details: "Use invalid to visually indicate the control is in an invalid state."
2669
+ },
2670
+ {
2671
+ name: "Disabled",
2672
+ code: "<SelectControl disabled selected StartIcon={User}>Reader</SelectControl>",
2673
+ details: "Use disabled to both visually and accessibly disable the control."
2674
+ },
2675
+ {
2676
+ name: "Block",
2677
+ code: '<div className="w-[290px] text-center p-2 border border-dashed border-alpha/20 rounded-md">\n <SelectControl block size="lg">Select...</SelectControl>\n</div>',
2678
+ details: "Create full-width controls with the block prop."
2679
+ },
2680
+ {
2681
+ name: "Optical alignment",
2682
+ code: '<SelectControl selected variant="ghost" opticallyAlign="start">Ghost control</SelectControl>',
2683
+ details: 'Use opticallyAlign with either "start" or "end" to offset the gutter of the control.'
2684
+ },
2685
+ {
2686
+ name: "Country selector",
2687
+ code: `const countries = [
2688
+ { value: "us", label: "United States" },
2689
+ { value: "gb", label: "United Kingdom" },
2690
+ // ...60 countries
2691
+ ];
2692
+
2693
+ const CountryView = ({ value }) => (
2694
+ <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
2695
+ <img src={\\\`https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.5.0/flags/4x3/\\\${value}.svg\\`,
2696
+ details: "A plain country selector built with Select. Shows flag icons with country names \u2014 no dial codes. Useful for profile forms, shipping addresses, and other country-only fields."
2697
+ },
2698
+ {
2699
+ name: "Country code selector",
2700
+ code: `const countries = [
2701
+ { value: "us", label: "United States (+1)", dialCode: "+1" },
2702
+ { value: "gb", label: "United Kingdom (+44)", dialCode: "+44" },
2703
+ // ...60 countries
2704
+ ];
2705
+
2706
+ const CountryTriggerView = ({ value }) => {
2707
+ const country = lookup.get(value);
2708
+ return (
2709
+ <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
2710
+ <img src={\\\`https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.5.0/flags/4x3/\\\${value}.svg\\`,
2711
+ details: "A searchable country selector built with Select. Custom TriggerView and OptionView render flag icons and dial codes. Search matches both country name and dial code."
2712
+ },
2713
+ {
2714
+ name: "Phone form",
2715
+ code: "import { useState, useCallback, useRef } from 'react';\nimport { Input } from '@plexui/ui/components/Input';\nimport { Select, type Option } from '@plexui/ui/components/Select';\nimport { Button } from '@plexui/ui/components/Button';\nimport { FieldError } from '@plexui/ui/components/FieldError';\n\n// Country data \u2014 include all 245 countries in production\ntype Country = { value: string; label: string; dialCode: string; flag: string };\n\nconst COUNTRIES: Country[] = [\n { value: 'us', label: 'United States', dialCode: '+1', flag: '\u{1F1FA}\u{1F1F8}' },\n { value: 'gb', label: 'United Kingdom', dialCode: '+44', flag: '\u{1F1EC}\u{1F1E7}' },\n { value: 'de', label: 'Germany', dialCode: '+49', flag: '\u{1F1E9}\u{1F1EA}' },\n { value: 'fr', label: 'France', dialCode: '+33', flag: '\u{1F1EB}\u{1F1F7}' },\n { value: 'es', label: 'Spain', dialCode: '+34', flag: '\u{1F1EA}\u{1F1F8}' },\n { value: 'it', label: 'Italy', dialCode: '+39', flag: '\u{1F1EE}\u{1F1F9}' },\n { value: 'jp', label: 'Japan', dialCode: '+81', flag: '\u{1F1EF}\u{1F1F5}' },\n { value: 'au', label: 'Australia', dialCode: '+61', flag: '\u{1F1E6}\u{1F1FA}' },\n { value: 'br', label: 'Brazil', dialCode: '+55', flag: '\u{1F1E7}\u{1F1F7}' },\n { value: 'in', label: 'India', dialCode: '+91', flag: '\u{1F1EE}\u{1F1F3}' },\n // ... full list of 245 countries\n];\n\nconst COUNTRY_LOOKUP = new Map(COUNTRIES.map((c) => [c.value, c]));\nconst COUNTRY_OPTIONS = COUNTRIES.map((c) => ({\n ...c,\n label: \\`\\${c.label} (\\${c.dialCode})\\`,\n}));\n\n// Per-country phone number format patterns\nconst PHONE_FORMATS: Record<string, number[]> = {\n us: [3, 3, 4], ca: [3, 3, 4], // 123 456 7890\n gb: [4, 3, 3], de: [3, 4, 4], // 1234 567 890 | 123 4567 8901\n fr: [1, 2, 2, 2, 2], // 1 23 45 67 89\n es: [3, 2, 2, 2], // 123 45 67 89\n it: [3, 3, 4], jp: [2, 4, 4],\n au: [3, 3, 3], br: [2, 5, 4],\n in: [5, 5], // 12345 67890\n // ... add more countries as needed\n};\n\nfunction formatPhoneNumber(raw: string, countryCode?: string) {\n const digits = raw.replace(/\\\\D/g, '');\n if (!digits) return '';\n const format = (countryCode && PHONE_FORMATS[countryCode]) || [3, 3, 4];\n const groups: string[] = [];\n let i = 0;\n for (const size of format) {\n if (i >= digits.length) break;\n groups.push(digits.slice(i, i + size));\n i += size;\n }\n if (i < digits.length) groups.push(digits.slice(i));\n return groups.join(' ');\n}\n\n// Flag icon \u2014 SVG flags from lipis/flag-icons, all 4:3\nfunction FlagIcon({ code, trigger }: { code: string; trigger?: boolean }) {\n return (\n <span\n style={{\n display: 'block',\n height: trigger ? 'var(--start-icon-size, 18px)' : 16,\n aspectRatio: '4 / 3',\n borderRadius: 2,\n overflow: 'hidden',\n flexShrink: 0,\n }}\n >\n <img\n src={\\`https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.5.0/flags/4x3/\\${code}.svg\\",
2716
+ details: "Country selector with flag icons and phone input composed into a form. Selecting a country updates the dial code prefix and clears the phone field. Phone numbers are formatted as you type using per-country patterns (e.g. 123 456 7890 for US, 1234 567 890 for UK). Search works by both country name and dial code. Validation runs on submit &mdash; the phone field shows an error state with FieldError when the number is too short or too long."
2717
+ }
2718
+ ],
2719
+ docsUrl: "https://plexui.com/docs/components/select-control"
2720
+ },
2721
+ {
2722
+ slug: "select",
2723
+ name: "Select",
2724
+ description: "Choose from a dropdown of options",
2725
+ importPath: "@plexui/ui/components/Select",
2726
+ exports: [
2727
+ "Select"
2728
+ ],
2729
+ props: [
2730
+ {
2731
+ name: "options",
2732
+ type: "Options<T>",
2733
+ description: "Array of options or grouped options"
2734
+ },
2735
+ {
2736
+ name: "value",
2737
+ type: "string | string[]",
2738
+ description: "Selected value (single) or values (multiple)"
2739
+ },
2740
+ {
2741
+ name: "onChange",
2742
+ type: "function",
2743
+ description: "Called when selection changes"
2744
+ },
2745
+ {
2746
+ name: "multiple",
2747
+ type: "boolean",
2748
+ default: "false",
2749
+ description: "Allow multiple selection"
2750
+ },
2751
+ {
2752
+ name: "placeholder",
2753
+ type: "string",
2754
+ default: '"Select..."',
2755
+ description: "Trigger placeholder"
2756
+ },
2757
+ {
2758
+ name: "variant",
2759
+ type: "string",
2760
+ default: '"outline"',
2761
+ description: "Trigger variant"
2762
+ },
2763
+ {
2764
+ name: "size",
2765
+ type: "string",
2766
+ default: '"md"',
2767
+ description: "Trigger size"
2768
+ },
2769
+ {
2770
+ name: "pill",
2771
+ type: "boolean",
2772
+ default: "false",
2773
+ description: "Rounded trigger"
2774
+ },
2775
+ {
2776
+ name: "clearable",
2777
+ type: "boolean",
2778
+ default: "false",
2779
+ description: "Show clear button"
2780
+ },
2781
+ {
2782
+ name: "block",
2783
+ type: "boolean",
2784
+ default: "true",
2785
+ description: "Full width trigger"
2786
+ },
2787
+ {
2788
+ name: "disabled",
2789
+ type: "boolean",
2790
+ default: "false",
2791
+ description: "Disable select"
2792
+ },
2793
+ {
2794
+ name: "actions",
2795
+ type: "array",
2796
+ description: "Actions below options list"
2797
+ },
2798
+ {
2799
+ name: "OptionView",
2800
+ type: "React.FC",
2801
+ description: "Custom option render"
2802
+ },
2803
+ {
2804
+ name: "TriggerView",
2805
+ type: "React.FC",
2806
+ description: "Custom trigger render (multi)"
2807
+ },
2808
+ {
2809
+ name: "searchPlaceholder",
2810
+ type: "string",
2811
+ description: "Search input placeholder"
2812
+ }
2813
+ ],
2814
+ examples: [
2815
+ {
2816
+ name: "Overview",
2817
+ code: '<Select\n value={fruit}\n options={fruits}\n onChange={handleChange}\n placeholder="Select a fruit..."\n variant="solid"\n/>'
2818
+ },
2819
+ {
2820
+ name: "Custom views",
2821
+ code: 'const RoleOptionDescription = ({ children }: { children: ReactNode }) => (\n <div className="font-normal text-secondary text-[0.935em] leading-[1.45]">\n {children}\n </div>\n);\n\nconst roles = [\n {\n value: "owner",\n label: "Owner",\n description: (\n <RoleOptionDescription>\n Can modify project information and manage project members\n </RoleOptionDescription>\n ),\n },\n {\n value: "reader",\n label: "Reader",\n description: (\n <RoleOptionDescription>\n Can make API requests that read or modify data\n </RoleOptionDescription>\n ),\n },\n];\n\n<Select\n value={role}\n options={roles}\n placeholder="Select role..."\n align="start"\n listMinWidth={260}\n variant="ghost"\n size="lg"\n onChange={({ value }) => setRole(value)}\n TriggerStartIcon={role === "owner" ? UserLock : User}\n triggerClassName="font-semibold"\n optionClassName="font-semibold"\n/>',
2822
+ details: "You can fully customize the styling and rendering of select's trigger and options:Trigger &ndash; triggerClassName, TriggerStartIcon, and TriggerViewOptions &ndash; optionClassName, OptionView, as well as description and tooltip fields in options"
2823
+ },
2824
+ {
2825
+ name: "Actions",
2826
+ code: '<Select\n value={value}\n onChange={handleChange}\n options={items}\n placeholder="Select..."\n align="start"\n listMinWidth={240}\n triggerClassName="font-semibold"\n actions={[\n { id: "create", label: "Create project", Icon: Plus, onSelect: () => {} },\n { id: "overview", label: "Organization overview", Icon: Workspace, onSelect: () => {} },\n ]}\n/>',
2827
+ details: "Custom, arbitrary actions can be displayed below the options list via the actions prop."
2828
+ },
2829
+ {
2830
+ name: "Grouped options",
2831
+ code: '<Select\n value={value}\n options={groupedItems}\n onChange={(v) => setValue(v.value)}\n variant="outline"\n size="lg"\n side="bottom"\n listMinWidth={300}\n searchPlaceholder="Select an option..."\n clearable\n/>',
2832
+ details: "Select supports option groups, similar to the native &lt;optgroup&gt; element. All selects are searchable by default, but will only present a searchbox when it makes sense. Currently, this means when there are 15 or more options."
2833
+ },
2834
+ {
2835
+ name: "Multiple selection",
2836
+ code: '<Select\n variant="soft"\n placeholder="Select fruits..."\n options={fruitsOptions}\n name="fruits"\n multiple\n clearable\n value={fruits}\n onChange={(values) => setFruits(values.map(({ value }) => value))}\n TriggerView={MultiFruitTriggerView}\n/>',
2837
+ details: "Multiple selection is supported through the multiple property. This changes how value, onChange, and TriggerView are typed, accepting and returning arrays of values."
2838
+ },
2839
+ {
2840
+ name: "Column configuration",
2841
+ code: `const [shown, setShown] = useState(['name', 'email', 'role', 'status'])
2842
+ const toggle = (id) => {
2843
+ setShown((prev) =>
2844
+ prev.includes(id) ? prev.filter((c) => c !== id) : [...prev, id]
2845
+ )
2846
+ }
2847
+ const shownColumns = allColumns.filter((c) => shown.includes(c.id))
2848
+ const hiddenColumns = allColumns.filter((c) => !shown.includes(c.id))
2849
+ <Popover.Trigger>
2850
+ <Button variant="outline" size="sm"><SettingsSlider /> Columns</Button>
2851
+ </Popover.Trigger>
2852
+ <Popover.Content minWidth={240} side="bottom" align="start">
2853
+ {shownColumns.map((col) => (
2854
+ <Row key={col.id} onClick={() => toggle(col.id)}>
2855
+ <span>{col.label}</span>
2856
+ <Switch checked />
2857
+ </Row>
2858
+ ))}
2859
+ {hiddenColumns.map((col) => (
2860
+ <Row key={col.id} onClick={() => toggle(col.id)}>
2861
+ <span className="text-tertiary">{col.label}</span>
2862
+ <Switch checked={false} />
2863
+ </Row>
2864
+ ))}
2865
+ </Popover.Content>
2866
+ </Popover>`,
2867
+ details: "A column-visibility panel built with Popover and Switch. Toggling a switch moves the column between shown and hidden groups."
2868
+ }
2869
+ ],
2870
+ docsUrl: "https://plexui.com/docs/components/select"
2871
+ },
2872
+ {
2873
+ slug: "shimmer-text",
2874
+ name: "Shimmer Text",
2875
+ description: "Animated shimmer effect for text content",
2876
+ importPath: "@plexui/ui/components/ShimmerText",
2877
+ exports: [
2878
+ "ShimmerText",
2879
+ "ShimmerableText"
2880
+ ],
2881
+ props: [
2882
+ {
2883
+ name: "as",
2884
+ type: '"p" | "span" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "div"',
2885
+ default: '"div"',
2886
+ description: "HTML element to render"
2887
+ },
2888
+ {
2889
+ name: "children",
2890
+ type: "ReactNode",
2891
+ description: "Text content to shimmer"
2892
+ },
2893
+ {
2894
+ name: "className",
2895
+ type: "string",
2896
+ description: "Additional CSS classes"
2897
+ },
2898
+ {
2899
+ name: "shimmer",
2900
+ type: "boolean",
2901
+ default: "false",
2902
+ description: "Whether the shimmer animation is active"
2903
+ }
2904
+ ],
2905
+ examples: [
2906
+ {
2907
+ name: "Overview",
2908
+ code: '<ShimmerText className="text-lg font-semibold">\n Thinking...\n</ShimmerText>',
2909
+ details: "ShimmerText applies a continuous shimmer animation to text content. Useful for loading states, AI-generated content indicators, or visual emphasis."
2910
+ },
2911
+ {
2912
+ name: "Semantic elements",
2913
+ code: '<div className="flex flex-col gap-4">\n <ShimmerText as="h2" className="text-2xl font-bold">Heading shimmer</ShimmerText>\n <ShimmerText as="p" className="text-base">Paragraph shimmer effect</ShimmerText>\n <ShimmerText as="span" className="text-sm text-secondary">Inline shimmer</ShimmerText>\n</div>',
2914
+ details: "Use the as prop to render as any text element: p, span, h1\u2013h6, or div."
2915
+ },
2916
+ {
2917
+ name: "Controllable shimmer",
2918
+ code: 'const [shimmer, setShimmer] = useState(true)\n\n<ShimmerableText shimmer={shimmer} className="text-lg font-semibold">\n Generating response...\n</ShimmerableText>',
2919
+ details: "ShimmerableText allows toggling the shimmer on and off. When shimmer is set to false, the animation finishes its current iteration and then pauses gracefully \u2014 no abrupt cut. Use this variant when text transitions between a loading and idle state."
2920
+ }
2921
+ ],
2922
+ docsUrl: "https://plexui.com/docs/components/shimmer-text"
2923
+ },
2924
+ {
2925
+ slug: "sidebar",
2926
+ name: "Sidebar",
2927
+ description: "Composable navigation sidebar for application layouts",
2928
+ importPath: "@plexui/ui/components/Sidebar",
2929
+ exports: [
2930
+ "SidebarProvider",
2931
+ "SidebarLayout",
2932
+ "Sidebar",
2933
+ "SidebarHeader",
2934
+ "SidebarContent",
2935
+ "SidebarFooter",
2936
+ "SidebarGroup",
2937
+ "SidebarGroupLabel",
2938
+ "SidebarMenu",
2939
+ "SidebarMenuItem",
2940
+ "SidebarMenuButton",
2941
+ "SidebarInset",
2942
+ "SidebarTrigger"
2943
+ ],
2944
+ examples: [
2945
+ {
2946
+ name: "Overview",
2947
+ code: '<SidebarProvider>\n <SidebarLayout style={{ height: 500 }}>\n <Sidebar>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupLabel size="sm">Project</SidebarGroupLabel>\n <SidebarGroupContent>\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton isActive tooltip="Overview">\n <SidebarMenuButtonIcon><Home /></SidebarMenuButtonIcon>\n <SidebarMenuButtonLabel>Overview</SidebarMenuButtonLabel>\n </SidebarMenuButton>\n </SidebarMenuItem>\n ...\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n <SidebarFooter><SidebarTrigger /></SidebarFooter>\n </Sidebar>\n <SidebarInset>...</SidebarInset>\n </SidebarLayout>\n</SidebarProvider>'
2948
+ },
2949
+ {
2950
+ name: "Scrollable Content",
2951
+ code: '<SidebarProvider>\n <SidebarLayout style={{ height: 400 }}>\n <Sidebar>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupLabel size="sm">Project</SidebarGroupLabel>\n <SidebarGroupContent><SidebarMenu>...</SidebarMenu></SidebarGroupContent>\n </SidebarGroup>\n <SidebarGroup>\n <SidebarGroupLabel size="sm">System</SidebarGroupLabel>\n <SidebarGroupContent><SidebarMenu>...</SidebarMenu></SidebarGroupContent>\n </SidebarGroup>\n <SidebarGroup>\n <SidebarGroupLabel size="sm">Resources</SidebarGroupLabel>\n <SidebarGroupContent><SidebarMenu>...</SidebarMenu></SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n <SidebarFooter><SidebarTrigger /></SidebarFooter>\n </Sidebar>\n <SidebarInset>...</SidebarInset>\n </SidebarLayout>\n</SidebarProvider>',
2952
+ details: "When the content is taller than the sidebar, it will scroll vertically."
2953
+ },
2954
+ {
2955
+ name: "Text-Only Navigation",
2956
+ code: '<SidebarProvider collapsible="none">\n <SidebarLayout style={{ height: 500 }}>\n <Sidebar variant="docs" style={{ width: "180px" }}>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupLabel size="lg">Settings</SidebarGroupLabel>\n <SidebarGroupContent>\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton isActive>\n <SidebarMenuButtonLabel>Your profile</SidebarMenuButtonLabel>\n </SidebarMenuButton>\n </SidebarMenuItem>\n ...\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n </Sidebar>\n <SidebarInset>...</SidebarInset>\n </SidebarLayout>\n</SidebarProvider>',
2957
+ details: 'A text-only sidebar without icons. Use collapsible="none" since there are no icons to collapse to.'
2958
+ },
2959
+ {
2960
+ name: "Collapse Modes",
2961
+ code: '<SidebarProvider collapsible="icon">\n <SidebarLayout style={{ height: 500 }}>\n <Sidebar>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupLabel size="sm">Project</SidebarGroupLabel>\n <SidebarGroupContent>\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton isActive tooltip="Overview">\n <SidebarMenuButtonIcon><Home /></SidebarMenuButtonIcon>\n <SidebarMenuButtonLabel>Overview</SidebarMenuButtonLabel>\n </SidebarMenuButton>\n </SidebarMenuItem>\n ...\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n <SidebarFooter><SidebarTrigger /></SidebarFooter>\n </Sidebar>\n <SidebarInset>...</SidebarInset>\n </SidebarLayout>\n</SidebarProvider>',
2962
+ details: "The sidebar supports different collapse behaviors via the collapsible prop on SidebarProvider:offcanvas (default) &ndash; On mobile, works as a drawer. On desktop, collapses completely.icon &ndash; Collapses to an icon-only rail.none &ndash; Static sidebar that cannot be collapsed.When using icon mode, add the tooltip prop to SidebarMenuButton to show labels on hover. Tooltips appear only in collapsed state and are hidden on mobile.You can control the open state externally with open and onOpenChange (controlled mode). Use the control below to toggle the sidebar."
2963
+ },
2964
+ {
2965
+ name: "Nested Navigation",
2966
+ code: '<SidebarProvider collapsible="none">\n <SidebarLayout style={{ height: 500 }}>\n <Sidebar variant="docs" style={{ width: "260px" }}>\n <SidebarContent>\n <SidebarGroup>\n <SidebarMenuItem expanded={isExpanded}>\n <SidebarMenuButton onClick={() => toggle(id)}>\n <SidebarMenuButtonLabel>Getting Started</SidebarMenuButtonLabel>\n <ChevronRightSm className="ml-auto transition-transform" />\n </SidebarMenuButton>\n </SidebarMenuItem>\n <SidebarMenuSub>\n <SidebarMenuSubItem>\n <SidebarMenuSubButton isActive>Introduction</SidebarMenuSubButton>\n </SidebarMenuSubItem>\n ...\n </SidebarMenuSub>\n </SidebarGroup>\n </SidebarContent>\n </Sidebar>\n <SidebarInset>...</SidebarInset>\n </SidebarLayout>\n</SidebarProvider>',
2967
+ details: "Build collapsible nested menus with chevron toggles for documentation and API reference sidebars."
2968
+ },
2969
+ {
2970
+ name: "Mobile Menu",
2971
+ code: '// Desktop: standard sidebar with icon collapse\n<SidebarProvider collapsible="icon">\n <SidebarLayout style={{ height: 500 }}>\n <Sidebar>...</Sidebar>\n <SidebarInset>...</SidebarInset>\n </SidebarLayout>\n</SidebarProvider>\n\n// Mobile: phone-sized container with hamburger menu\n<SidebarProvider collapsible="offcanvas">\n <header>\n <SidebarMobileMenuButton />\n </header>\n {openMobile ? <SidebarContent>...</SidebarContent> : <div>Page content</div>}\n</SidebarProvider>',
2972
+ details: "Mobile behavior is handled automatically when using SidebarProvider. The SidebarMobileMenuButton provides a standard animated hamburger trigger."
2973
+ },
2974
+ {
2975
+ name: "Search",
2976
+ code: '<SidebarProvider collapsible="none">\n <SidebarLayout style={{ height: 500 }}>\n <Sidebar variant="docs" style={{ width: "220px" }}>\n <SidebarHeader>\n <SidebarInput\n placeholder="Search"\n value={searchValue}\n onChange={(e) => setSearchValue(e.target.value)}\n onClear={() => setSearchValue("")}\n />\n </SidebarHeader>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupLabel>Get started</SidebarGroupLabel>\n <SidebarGroupContent>\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuSubButton indent={0} isActive>Overview</SidebarMenuSubButton>\n </SidebarMenuItem>\n ...\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n </Sidebar>\n <SidebarInset>...</SidebarInset>\n </SidebarLayout>\n</SidebarProvider>',
2977
+ details: "The sidebar provides a dedicated SidebarInput component for search functionality."
2978
+ },
2979
+ {
2980
+ name: "Skeleton",
2981
+ code: '<SidebarProvider>\n <SidebarLayout style={{ height: 300 }}>\n <Sidebar>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupContent>\n <SidebarMenu>\n <SidebarMenuItem><SidebarMenuSkeleton labelWidth="70%" /></SidebarMenuItem>\n <SidebarMenuItem><SidebarMenuSkeleton labelWidth="55%" /></SidebarMenuItem>\n <SidebarMenuItem><SidebarMenuSkeleton labelWidth="80%" /></SidebarMenuItem>\n <SidebarMenuItem><SidebarMenuSkeleton labelWidth="45%" /></SidebarMenuItem>\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n </Sidebar>\n <SidebarInset>...</SidebarInset>\n </SidebarLayout>\n</SidebarProvider>',
2982
+ details: "Loading skeleton states while navigation data is loading."
2983
+ },
2984
+ {
2985
+ name: "Group Label Sizes",
2986
+ code: '<SidebarProvider collapsible="none">\n <SidebarLayout style={{ height: 350 }}>\n <Sidebar>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupLabel size="sm">Project</SidebarGroupLabel>\n <SidebarGroupContent>\n <SidebarMenu>...</SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n </Sidebar>\n <SidebarInset>...</SidebarInset>\n </SidebarLayout>\n</SidebarProvider>',
2987
+ details: "SidebarGroupLabel supports two size variants:sm &ndash; Compact, tertiary color. For dashboard navigation, settings panels, sidebars with fewer items.lg (default) &ndash; Larger, primary color, more spacing. For documentation, API reference, content-heavy sections."
2988
+ },
2989
+ {
2990
+ name: "Badges",
2991
+ code: '<SidebarProvider collapsible="none">\n <SidebarLayout style={{ height: 400 }}>\n <Sidebar>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupLabel>Navigation</SidebarGroupLabel>\n <SidebarGroupContent>\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton isActive>\n <SidebarMenuButtonIcon><Home /></SidebarMenuButtonIcon>\n <SidebarMenuButtonLabel>Overview</SidebarMenuButtonLabel>\n </SidebarMenuButton>\n </SidebarMenuItem>\n <SidebarMenuItem>\n <SidebarMenuButton>\n <SidebarMenuButtonIcon><Code /></SidebarMenuButtonIcon>\n <SidebarMenuButtonLabel>API Reference</SidebarMenuButtonLabel>\n <SidebarMenuBadge>\n <Badge size="sm" color="info">New</Badge>\n </SidebarMenuBadge>\n </SidebarMenuButton>\n </SidebarMenuItem>\n ...\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n </Sidebar>\n <SidebarInset>...</SidebarInset>\n </SidebarLayout>\n</SidebarProvider>',
2992
+ details: "Use SidebarMenuBadge with the Badge component to display status indicators, counts, or labels alongside menu items."
2993
+ },
2994
+ {
2995
+ name: "Footer Cards",
2996
+ code: '<SidebarProvider collapsible="icon">\n <SidebarLayout style={{ height: 500 }}>\n <Sidebar>\n <SidebarContent>...</SidebarContent>\n <SidebarFooter>\n <SidebarCard dismissible onDismiss={() => {}}>\n <SidebarCardTitleLink href="#">Upgrade to Pro</SidebarCardTitleLink>\n <SidebarCardContent>\n Unlock higher rate limits, priority support, and advanced features.\n </SidebarCardContent>\n <SidebarCardFooter>\n <Button size="sm" pill color="primary">View Plans</Button>\n </SidebarCardFooter>\n </SidebarCard>\n <SidebarTrigger />\n </SidebarFooter>\n </Sidebar>\n <SidebarInset>...</SidebarInset>\n </SidebarLayout>\n</SidebarProvider>',
2997
+ details: "Use SidebarCard to display promotional content, upgrade CTAs, or announcements in the sidebar footer."
2998
+ }
2999
+ ],
3000
+ subComponents: [
3001
+ {
3002
+ name: "SidebarProvider",
3003
+ description: "Handles sidebar state and provides context to all child components. Wrap your application layout in this provider.",
3004
+ props: [
3005
+ {
3006
+ name: "collapsible",
3007
+ type: '"offcanvas" | "icon" | "none"',
3008
+ default: '"offcanvas"',
3009
+ description: "Collapse behavior: offcanvas (drawer on mobile, full collapse on desktop), icon (icon rail), or none"
3010
+ },
3011
+ {
3012
+ name: "defaultOpen",
3013
+ type: "boolean",
3014
+ default: "true",
3015
+ description: "Initial open state (uncontrolled)"
3016
+ },
3017
+ {
3018
+ name: "open",
3019
+ type: "boolean",
3020
+ description: "Controlled open state"
3021
+ },
3022
+ {
3023
+ name: "onOpenChange",
3024
+ type: "(open: boolean) => void",
3025
+ description: "Called when open state changes"
3026
+ }
3027
+ ]
3028
+ },
3029
+ {
3030
+ name: "Sidebar",
3031
+ description: "The main sidebar container. Renders as an `` element.",
3032
+ props: [
3033
+ {
3034
+ name: "variant",
3035
+ type: '"sidebar" | "docs"',
3036
+ default: '"sidebar"',
3037
+ description: "Visual variant"
3038
+ },
3039
+ {
3040
+ name: "side",
3041
+ type: '"left" | "right"',
3042
+ default: '"left"',
3043
+ description: "Which side the sidebar is on"
3044
+ }
3045
+ ]
3046
+ },
3047
+ {
3048
+ name: "SidebarMenuButton",
3049
+ description: "Renders a menu item button. Use `isActive` to highlight the current page.",
3050
+ props: [
3051
+ {
3052
+ name: "isActive",
3053
+ type: "boolean",
3054
+ default: "false",
3055
+ description: "Highlight the current page"
3056
+ },
3057
+ {
3058
+ name: "tooltip",
3059
+ type: "string | ReactNode",
3060
+ description: "Label shown on hover when collapsed (icon mode). Hidden on mobile."
3061
+ }
3062
+ ]
3063
+ },
3064
+ {
3065
+ name: "SidebarGroupLabel",
3066
+ description: "Renders a section label within a `SidebarGroup`. Supports two size variants.",
3067
+ props: [
3068
+ {
3069
+ name: "size",
3070
+ type: '"sm" | "lg"',
3071
+ default: '"lg"',
3072
+ description: "sm \u2014 compact, tertiary; lg \u2014 larger, primary, more spacing"
3073
+ }
3074
+ ]
3075
+ },
3076
+ {
3077
+ name: "SidebarMenuSub",
3078
+ description: "Renders a collapsible submenu container for nested navigation.",
3079
+ props: [
3080
+ {
3081
+ name: "open",
3082
+ type: "boolean",
3083
+ default: "true",
3084
+ description: "Whether submenu is expanded"
3085
+ },
3086
+ {
3087
+ name: "hasIcons",
3088
+ type: "boolean",
3089
+ default: "false",
3090
+ description: "Align child items with parent text that has icons"
3091
+ }
3092
+ ]
3093
+ },
3094
+ {
3095
+ name: "SidebarCard",
3096
+ description: "Renders a promotional card in the sidebar footer for CTAs, upgrades, or announcements.",
3097
+ props: [
3098
+ {
3099
+ name: "dismissible",
3100
+ type: "boolean",
3101
+ description: "Shows dismiss button"
3102
+ },
3103
+ {
3104
+ name: "onDismiss",
3105
+ type: "() => void",
3106
+ description: "Callback when dismissed"
3107
+ }
3108
+ ]
3109
+ }
3110
+ ],
3111
+ hooks: [
3112
+ {
3113
+ name: "useSidebar",
3114
+ description: "Hook to access sidebar state and controls from any component within `SidebarProvider`.",
3115
+ properties: [
3116
+ {
3117
+ name: "state",
3118
+ description: 'ndash; Current state: `"expanded"` or `"collapsed"`'
3119
+ },
3120
+ {
3121
+ name: "open",
3122
+ description: "/ `setOpen` - Desktop sidebar open state"
3123
+ },
3124
+ {
3125
+ name: "openMobile",
3126
+ description: "/ `setOpenMobile` - Mobile menu open state"
3127
+ },
3128
+ {
3129
+ name: "isMobile",
3130
+ description: "ndash; Whether the viewport is mobile"
3131
+ },
3132
+ {
3133
+ name: "collapsible",
3134
+ description: "ndash; Current collapsible mode"
3135
+ },
3136
+ {
3137
+ name: "toggleSidebar",
3138
+ description: "ndash; Toggle function for both desktop and mobile"
3139
+ }
3140
+ ]
3141
+ }
3142
+ ],
3143
+ docsUrl: "https://plexui.com/docs/components/sidebar"
3144
+ },
3145
+ {
3146
+ slug: "skeleton",
3147
+ name: "Skeleton",
3148
+ description: "Use to show a placeholder while content is loading",
3149
+ importPath: "@plexui/ui/components/Skeleton",
3150
+ exports: [
3151
+ "Skeleton"
3152
+ ],
3153
+ props: [
3154
+ {
3155
+ name: "circle",
3156
+ type: "boolean",
3157
+ default: "false",
3158
+ description: "Render a circular skeleton"
3159
+ },
3160
+ {
3161
+ name: "className",
3162
+ type: "string",
3163
+ description: "Set dimensions and custom styling"
3164
+ }
3165
+ ],
3166
+ examples: [
3167
+ {
3168
+ name: "Overview",
3169
+ code: '<div className="flex items-center gap-4">\n <Skeleton circle className="size-12" />\n <div className="flex flex-col gap-2">\n <Skeleton className="h-4 w-[200px]" />\n <Skeleton className="h-4 w-[150px]" />\n </div>\n</div>',
3170
+ details: "Use a circular skeleton to represent loading avatars, combined with text placeholders."
3171
+ },
3172
+ {
3173
+ name: "Card",
3174
+ code: '<div className="flex flex-col gap-3 w-[300px] p-4 border border-default rounded-xl">\n <Skeleton className="h-[150px] w-full rounded-lg" />\n <Skeleton className="h-4 w-3/4" />\n <Skeleton className="h-4 w-1/2" />\n</div>',
3175
+ details: "Compose multiple skeletons to represent loading cards with images and text content."
3176
+ },
3177
+ {
3178
+ name: "Text",
3179
+ code: '<div className="flex flex-col gap-2 w-[300px]">\n <Skeleton className="h-4 w-full" />\n <Skeleton className="h-4 w-full" />\n <Skeleton className="h-4 w-4/5" />\n</div>',
3180
+ details: "Multiple text lines with varying widths create natural-looking paragraph placeholders."
3181
+ },
3182
+ {
3183
+ name: "Form",
3184
+ code: '<div className="flex flex-col gap-4 w-[300px]">\n <div className="flex flex-col gap-2">\n <Skeleton className="h-4 w-[80px]" />\n <Skeleton className="h-10 w-full rounded-lg" />\n </div>\n <div className="flex flex-col gap-2">\n <Skeleton className="h-4 w-[100px]" />\n <Skeleton className="h-10 w-full rounded-lg" />\n </div>\n <Skeleton className="h-10 w-[120px] rounded-lg" />\n</div>',
3185
+ details: "Form skeletons use Plex UI control sizing (32px height for inputs and buttons)."
3186
+ },
3187
+ {
3188
+ name: "Table",
3189
+ code: '<div className="flex flex-col gap-2 w-[400px]">\n <div className="flex gap-4 pb-2 border-b border-default">\n <Skeleton className="h-4 w-[100px]" />\n <Skeleton className="h-4 w-[150px]" />\n <Skeleton className="h-4 w-[80px]" />\n </div>\n {Array.from({ length: 4 }).map((_, i) => (\n <div key={i} className="flex gap-4 py-2">\n <Skeleton className="h-4 w-[100px]" />\n <Skeleton className="h-4 w-[150px]" />\n <Skeleton className="h-4 w-[80px]" />\n </div>\n ))}\n</div>',
3190
+ details: "Table skeletons show header and row structure while data loads."
3191
+ }
3192
+ ],
3193
+ docsUrl: "https://plexui.com/docs/components/skeleton"
3194
+ },
3195
+ {
3196
+ slug: "slider",
3197
+ name: "Slider",
3198
+ description: "Fine-tune values within a set range",
3199
+ importPath: "@plexui/ui/components/Slider",
3200
+ exports: [
3201
+ "Slider"
3202
+ ],
3203
+ props: [
3204
+ {
3205
+ name: "value",
3206
+ type: "number | number[]",
3207
+ description: "Current value; array when range is true"
3208
+ },
3209
+ {
3210
+ name: "min",
3211
+ type: "number",
3212
+ description: "Minimum value"
3213
+ },
3214
+ {
3215
+ name: "max",
3216
+ type: "number",
3217
+ description: "Maximum value"
3218
+ },
3219
+ {
3220
+ name: "step",
3221
+ type: "number",
3222
+ description: "Step increment"
3223
+ },
3224
+ {
3225
+ name: "range",
3226
+ type: "boolean",
3227
+ default: "false",
3228
+ description: "Enable multi-thumb range mode"
3229
+ },
3230
+ {
3231
+ name: "orientation",
3232
+ type: '"horizontal" | "vertical"',
3233
+ default: '"horizontal"',
3234
+ description: "Layout direction of the slider"
3235
+ },
3236
+ {
3237
+ name: "resetValue",
3238
+ type: "number",
3239
+ description: "Value for reset option (single mode only)"
3240
+ },
3241
+ {
3242
+ name: "unit",
3243
+ type: "string",
3244
+ description: "Unit displayed (e.g. ms, px)"
3245
+ },
3246
+ {
3247
+ name: "prefixUnit",
3248
+ type: "string",
3249
+ description: "Unit displayed before the value (e.g. $)"
3250
+ },
3251
+ {
3252
+ name: "label",
3253
+ type: "ReactNode",
3254
+ description: "Label (string or JSX)"
3255
+ },
3256
+ {
3257
+ name: "marks",
3258
+ type: "Array<{ value: number, label: string }>",
3259
+ description: "Marks below track (horizontal only)"
3260
+ },
3261
+ {
3262
+ name: "onChange",
3263
+ type: "(value: number | number[]) => void",
3264
+ description: "Change handler; receives array when range is true"
3265
+ },
3266
+ {
3267
+ name: "minStepsBetweenThumbs",
3268
+ type: "number",
3269
+ description: "Minimum steps between thumbs (range mode only)"
3270
+ },
3271
+ {
3272
+ name: "disabled",
3273
+ type: "boolean",
3274
+ default: "false",
3275
+ description: "Disable slider"
3276
+ }
3277
+ ],
3278
+ examples: [
3279
+ {
3280
+ name: "Overview",
3281
+ code: 'const [value, setValue] = useState(1000)\n<Slider\n label="Example field"\n min={0}\n max={2000}\n resetValue={1000}\n step={10}\n unit="ms"\n value={value}\n onChange={setValue}\n/>'
3282
+ },
3283
+ {
3284
+ name: "Adding a tooltip",
3285
+ code: '<Slider\n label={\n <Tooltip content="More details about this field">\n <Tooltip.TriggerDecorator>Example field</Tooltip.TriggerDecorator>\n </Tooltip>\n }\n value={value}\n onChange={setValue}\n ...\n/>',
3286
+ details: "A tooltip can easily be added to the Slider label by providing JSX instead of a simple string."
3287
+ },
3288
+ {
3289
+ name: "Marks",
3290
+ code: '<Slider\n value={value}\n onChange={setValue}\n label="Passing grade"\n min={1}\n max={5}\n step={1}\n marks={[\n { value: 1, label: "1" },\n { value: 2, label: "2" },\n ...\n ]}\n trackColor="#61C454"\n rangeColor="#EF4146"\n/>',
3291
+ details: "Slider marks can be easily added by passing an array of values to the marks prop. Responsive behaviors are handled automatically."
3292
+ },
3293
+ {
3294
+ name: "Range",
3295
+ code: 'const [value, setValue] = useState([25, 75])\n<Slider\n range\n label="Price range"\n min={0}\n max={100}\n step={1}\n value={value}\n onChange={setValue}\n prefixUnit="$"\n/>',
3296
+ details: "Set range to enable a dual-thumb slider for selecting a value range. The value and onChange props accept arrays instead of single numbers."
3297
+ },
3298
+ {
3299
+ name: "Multiple thumbs",
3300
+ code: 'const [value, setValue] = useState([10, 40, 70])\n<Slider\n range\n label="Breakpoints"\n min={0}\n max={100}\n step={1}\n value={value}\n onChange={setValue}\n minStepsBetweenThumbs={5}\n/>',
3301
+ details: "Pass three or more values to create a multi-thumb slider. Use minStepsBetweenThumbs to enforce minimum spacing between thumbs."
3302
+ },
3303
+ {
3304
+ name: "Vertical",
3305
+ code: '<Slider\n label="Volume"\n min={0}\n max={100}\n step={1}\n value={value}\n onChange={setValue}\n orientation="vertical"\n/>',
3306
+ details: 'Set orientation="vertical" to render a vertical slider. Marks are not supported in vertical orientation.'
3307
+ },
3308
+ {
3309
+ name: "Disabled",
3310
+ code: '<Slider\n label="Disabled"\n min={0}\n max={100}\n step={1}\n value={50}\n onChange={() => {}}\n disabled\n/>',
3311
+ details: "Set disabled to prevent interaction with the slider."
3312
+ }
3313
+ ],
3314
+ docsUrl: "https://plexui.com/docs/components/slider"
3315
+ },
3316
+ {
3317
+ slug: "stat-card",
3318
+ name: "StatCard",
3319
+ description: "Display key metrics and KPIs with trend indicators",
3320
+ importPath: "@plexui/ui/components/StatCard",
3321
+ exports: [
3322
+ "StatCard"
3323
+ ],
3324
+ props: [
3325
+ {
3326
+ name: "description",
3327
+ type: "string",
3328
+ description: "Subtitle below the value"
3329
+ },
3330
+ {
3331
+ name: "trend",
3332
+ type: "{ value: number; label?: string }",
3333
+ description: "Trend indicator with percentage"
3334
+ },
3335
+ {
3336
+ name: "invertTrend",
3337
+ type: "boolean",
3338
+ default: "false",
3339
+ description: "Invert trend colors (positive = bad)"
3340
+ },
3341
+ {
3342
+ name: "trendPosition",
3343
+ type: '"value" | "header"',
3344
+ default: '"value"',
3345
+ description: "Where the trend indicator is rendered"
3346
+ },
3347
+ {
3348
+ name: "trendVariant",
3349
+ type: '"text" | "badge"',
3350
+ default: '"text"',
3351
+ description: "Render trend as text or badge"
3352
+ },
3353
+ {
3354
+ name: "size",
3355
+ type: '"default" | "sm"',
3356
+ default: '"default"',
3357
+ description: "Card size \u2014 sm for compact analytics layouts"
3358
+ },
3359
+ {
3360
+ name: "icon",
3361
+ type: "ReactNode",
3362
+ description: "Icon displayed on the left with a soft background"
3363
+ },
3364
+ {
3365
+ name: "sparkline",
3366
+ type: "ReactNode",
3367
+ description: "Chart rendered at the bottom"
3368
+ },
3369
+ {
3370
+ name: "progress",
3371
+ type: "number",
3372
+ description: "Progress bar value (0-100)"
3373
+ },
3374
+ {
3375
+ name: "progressLabel",
3376
+ type: "string",
3377
+ description: "Label next to progress bar"
3378
+ },
3379
+ {
3380
+ name: "tooltip",
3381
+ type: "string",
3382
+ description: "Shows an info icon next to the label with tooltip text"
3383
+ },
3384
+ {
3385
+ name: "variant",
3386
+ type: '"default" | "accent"',
3387
+ default: '"default"',
3388
+ description: "Visual variant"
3389
+ },
3390
+ {
3391
+ name: "accentColor",
3392
+ type: '"primary" | "secondary" | "danger" | "info" | "discovery" | "success" | "caution" | "warning"',
3393
+ default: '"primary"',
3394
+ description: "Left border color (accent variant)"
3395
+ }
3396
+ ],
3397
+ examples: [
3398
+ {
3399
+ name: "Overview",
3400
+ code: 'const [size, setSize] = useState("default")\nconst [variant, setVariant] = useState("default")\nconst [trendPosition, setTrendPosition] = useState("value")\nconst [trendVariant, setTrendVariant] = useState("text")\nconst [showTrend, setShowTrend] = useState(true)\nconst [showTooltip, setShowTooltip] = useState(false)\nconst [invertTrend, setInvertTrend] = useState(false)\n\n<StatCard\n label="Total Revenue"\n value="$45,231"\n size={size}\n variant={variant}\n trendPosition={trendPosition}\n trendVariant={trendVariant}\n trend={showTrend ? { value: 12.5, label: "vs last month" } : undefined}\n tooltip={showTooltip ? "Sum of all revenue streams" : undefined}\n invertTrend={invertTrend}\n/>',
3401
+ details: "Dashboard-ready stat cards with trends, icons, and progress bars. Use the controls below to explore all available props."
3402
+ },
3403
+ {
3404
+ name: "Sizes",
3405
+ code: '<div className="flex flex-col gap-6">\n {/* Default size */}\n <div className="grid grid-cols-3 gap-4">\n <StatCard label="Total Revenue" value="$45,231" trend={{ value: 12.5 }} />\n <StatCard label="Active Users" value="2,847" trend={{ value: 8.2 }} />\n <StatCard label="Conversion Rate" value="3.24%" trend={{ value: -1.8 }} />\n </div>\n {/* Small size */}\n <div className="grid grid-cols-3 gap-3">\n <StatCard size="sm" label="Total Inquiries" value="3,544" trend={{ value: -2.6 }} tooltip="Total inquiries in period" />\n <StatCard size="sm" label="Completion Rate" value="89.2%" trend={{ value: 0.5 }} tooltip="Average across templates" />\n <StatCard size="sm" label="Approval Rate" value="84.6%" trend={{ value: 1.8 }} tooltip="Average across templates" />\n </div>\n</div>',
3406
+ details: 'Use size="sm" for compact analytics rows with denser spacing and smaller typography. The small size also pairs well with the tooltip prop for info icons.'
3407
+ },
3408
+ {
3409
+ name: "Trend position",
3410
+ code: '<div className="grid grid-cols-2 gap-4" style={{ maxWidth: 480 }}>\n <StatCard label="Revenue" value="$45,231" trend={{ value: 12.5 }} trendPosition="value" />\n <StatCard label="Revenue" value="$45,231" trend={{ value: 12.5 }} trendPosition="header" />\n</div>',
3411
+ details: "Control where the trend appears: next to the value (default) or right-aligned in the header row."
3412
+ },
3413
+ {
3414
+ name: "Trend variant",
3415
+ code: '<div className="grid grid-cols-2 gap-4" style={{ maxWidth: 480 }}>\n <StatCard label="Users" value="2,847" trend={{ value: 8.2 }} trendVariant="text" />\n <StatCard label="Users" value="2,847" trend={{ value: 8.2 }} trendVariant="badge" />\n</div>',
3416
+ details: "Display the trend as plain text or wrapped in a badge."
3417
+ },
3418
+ {
3419
+ name: "Info tooltip",
3420
+ code: '<StatCard label="Avg Completion Rate" value="89.2%" trend={{ value: 0.5 }} tooltip="Percentage of inquiries reaching final state" />',
3421
+ details: "Pass a tooltip string to render an info icon next to the label with a tooltip on hover."
3422
+ },
3423
+ {
3424
+ name: "With icon",
3425
+ code: 'import { DollarCircle, Users, ShoppingBag } from "@plexui/ui/components/Icon"\n\n{/* Default size */}\n<div className="grid grid-cols-2 gap-4">\n <StatCard icon={<DollarCircle />} label="Total Revenue" value="$45,231" trend={{ value: 12.5 }} />\n <StatCard icon={<Users />} label="Active Users" value="2,847" trend={{ value: 8.2 }} />\n</div>\n\n{/* Small size */}\n<div className="grid grid-cols-3 gap-3">\n <StatCard size="sm" icon={<DollarCircle />} label="Revenue" value="$45,231" trend={{ value: 12.5 }} />\n <StatCard size="sm" icon={<Users />} label="Users" value="2,847" trend={{ value: 8.2 }} />\n <StatCard size="sm" icon={<ShoppingBag />} label="Orders" value="1,234" trend={{ value: -3.1 }} />\n</div>',
3426
+ details: "Pass an icon to display it on the left side with a soft background. The layout automatically switches to a horizontal icon + content arrangement. Works with both default and sm sizes."
3427
+ },
3428
+ {
3429
+ name: "With progress",
3430
+ code: '<StatCard label="Storage Used" value="7.2 GB" progress={72} progressLabel="72%" description="of 10 GB" />',
3431
+ details: "Progress bars show a visual representation of completion or usage percentage."
3432
+ },
3433
+ {
3434
+ name: "Accent border",
3435
+ code: 'const [accentColor, setAccentColor] = useState("primary")\n\n<StatCard\n label="Total Revenue"\n value="$45,231"\n variant="accent"\n accentColor={accentColor}\n trend={{ value: 12.5, label: "vs last month" }}\n/>',
3436
+ details: "The accent variant adds a colored left border. Choose from six semantic colors."
3437
+ },
3438
+ {
3439
+ name: "Inverted trend",
3440
+ code: 'const [invertTrend, setInvertTrend] = useState(true)\n\n<StatCard\n label="Avg Response Time"\n value="234ms"\n trend={{ value: 15.3, label: "vs last hour" }}\n invertTrend={invertTrend}\n/>',
3441
+ details: "Use invertTrend for metrics where positive values are bad (e.g., error rate, response time). Toggle the switch to see the color flip."
3442
+ }
3443
+ ],
3444
+ docsUrl: "https://plexui.com/docs/components/stat-card"
3445
+ },
3446
+ {
3447
+ slug: "switch",
3448
+ name: "Switch",
3449
+ description: "Toggle control for on and off states",
3450
+ importPath: "@plexui/ui/components/Switch",
3451
+ exports: [
3452
+ "Switch"
3453
+ ],
3454
+ props: [
3455
+ {
3456
+ name: "checked",
3457
+ type: "boolean",
3458
+ description: "Controlled state"
3459
+ },
3460
+ {
3461
+ name: "defaultChecked",
3462
+ type: "boolean",
3463
+ description: "Initial state (uncontrolled)"
3464
+ },
3465
+ {
3466
+ name: "onCheckedChange",
3467
+ type: "(next: boolean) => void",
3468
+ description: "Called when toggled"
3469
+ },
3470
+ {
3471
+ name: "label",
3472
+ type: "ReactNode",
3473
+ description: "Accessible label rendered next to the switch"
3474
+ },
3475
+ {
3476
+ name: "description",
3477
+ type: "ReactNode",
3478
+ description: "Secondary text below the label, linked via aria-describedby"
3479
+ },
3480
+ {
3481
+ name: "labelPosition",
3482
+ type: '"start" | "end"',
3483
+ default: '"end"',
3484
+ description: "Label on left (start) or right (end)"
3485
+ },
3486
+ {
3487
+ name: "disabled",
3488
+ type: "boolean",
3489
+ default: "false",
3490
+ description: "Prevent interaction"
3491
+ },
3492
+ {
3493
+ name: "required",
3494
+ type: "boolean",
3495
+ description: "Required for form submission"
3496
+ },
3497
+ {
3498
+ name: "name",
3499
+ type: "string",
3500
+ description: "Form field name"
3501
+ },
3502
+ {
3503
+ name: "value",
3504
+ type: "string",
3505
+ description: "Value when submitted"
3506
+ },
3507
+ {
3508
+ name: "className",
3509
+ type: "string",
3510
+ description: "CSS classes applied to wrapper node"
3511
+ },
3512
+ {
3513
+ name: "id",
3514
+ type: "string",
3515
+ description: "The id of the switch element"
3516
+ }
3517
+ ],
3518
+ examples: [
3519
+ {
3520
+ name: "Base",
3521
+ code: "<Switch />"
3522
+ },
3523
+ {
3524
+ name: "With label",
3525
+ code: '<Switch label="Notifications" />',
3526
+ details: "Add a label using the label prop. The label is automatically connected to the switch via id."
3527
+ },
3528
+ {
3529
+ name: "With description",
3530
+ code: '<Switch\n label="Disable user API keys"\n description="Disable user-based API keys across your entire organization."\n/>',
3531
+ details: "Add a description below the label for additional context. Linked to the switch via aria-describedby."
3532
+ },
3533
+ {
3534
+ name: "Label position",
3535
+ code: '<Switch label="Right aligned" labelPosition="start" />',
3536
+ details: `Set {'labelPosition="start"'} to place the label on the left side of the switch control.`
3537
+ },
3538
+ {
3539
+ name: "Label position with description",
3540
+ code: '<Switch\n label="Dark mode"\n description="Use dark theme across the entire application."\n labelPosition="start"\n/>',
3541
+ details: `The description follows the label position \u2014 both move together when {'labelPosition="start"'} is set.`
3542
+ },
3543
+ {
3544
+ name: "Default checked",
3545
+ code: '<Switch label="Enable notifications" name="notifications" defaultChecked />',
3546
+ details: "Initialize the switch with defaultChecked when you don't need to control its state. Use the name prop to read the value on form submission."
3547
+ },
3548
+ {
3549
+ name: "Controlled",
3550
+ code: 'const [checked, setChecked] = useState(false)\n<Switch label="Airplane mode" checked={checked} onCheckedChange={setChecked} />',
3551
+ details: "Pass checked and onCheckedChange to manage the state in a controlled manner."
3552
+ },
3553
+ {
3554
+ name: "Disabled",
3555
+ code: '<div className="flex gap-6">\n <Switch disabled />\n <Switch disabled checked />\n</div>',
3556
+ details: "Use disabled to prevent the user from toggling the control."
3557
+ },
3558
+ {
3559
+ name: "Disabled with label",
3560
+ code: '<div className="flex flex-col gap-4">\n <Switch label="Notifications" disabled />\n <Switch label="Notifications" disabled defaultChecked />\n</div>',
3561
+ details: "Disabled state works with labels \u2014 both the toggle and the label show a not-allowed cursor."
3562
+ },
3563
+ {
3564
+ name: "Disabled with description",
3565
+ code: '<div className="flex flex-col gap-4">\n <Switch\n label="Enable SSO"\n description="Requires an Enterprise plan to configure."\n disabled\n />\n <Switch\n label="Two-factor authentication"\n description="Enforced by organization policy."\n disabled\n defaultChecked\n />\n</div>',
3566
+ details: "Combine disabled with label and description for read-only settings that need explanation."
3567
+ },
3568
+ {
3569
+ name: "With Field",
3570
+ code: '<Field label="Marketing emails" orientation="horizontal">\n {(fieldProps) => (\n <Switch id={fieldProps.id} checked={enabled} onCheckedChange={setEnabled} />\n )}\n</Field>',
3571
+ details: 'Pair a Switch with Field using orientation="horizontal" for inline toggle settings.'
3572
+ },
3573
+ {
3574
+ name: "With Field and description",
3575
+ code: '<Field\n label="Access Control"\n description="Disable user-based API keys across your entire organization. You can also choose to disable them on a project-by-project basis if needed."\n>\n {(fieldProps) => (\n <Switch\n id={fieldProps.id}\n label="Disable user API keys"\n checked={enabled}\n onCheckedChange={setEnabled}\n />\n )}\n</Field>',
3576
+ details: "Use Field with description for the settings pattern with a section heading, description, and a labeled switch underneath."
3577
+ },
3578
+ {
3579
+ name: "Settings panel",
3580
+ code: '<div className="flex w-full max-w-lg flex-col gap-8">\n <Switch\n label="Enable custom providers for evals"\n description="Allow eval runs to use third-party models you configure under Custom model providers."\n />\n <Switch\n label="Enable OpenRouter models"\n description="Make OpenRouter models available for evaluation runs."\n />\n <Switch\n label="Disable user API keys"\n description="Disable user-based API keys across your entire organization."\n defaultChecked\n />\n</div>',
3581
+ details: "Combine label and description to build settings-style toggle panels."
3582
+ },
3583
+ {
3584
+ name: "Grouped sections",
3585
+ code: '<div className="w-full max-w-lg divide-y divide-alpha/15">\n <div className="pb-5">\n <Field label="Notifications" description="Configure how you receive notifications.">\n {() => (\n <div className="flex flex-col gap-4">\n <Switch\n label="Push notifications"\n description="Receive push notifications on your devices."\n defaultChecked\n />\n <Switch\n label="Email notifications"\n description="Get notified via email for important updates."\n />\n </div>\n )}\n </Field>\n </div>\n <div className="pt-5">\n <Field label="Privacy" description="Control your data sharing preferences.">\n {() => (\n <div className="flex flex-col gap-4">\n <Switch\n label="Share usage data"\n description="Help us improve by sharing anonymous usage statistics."\n />\n <Switch\n label="Personalized content"\n description="Allow personalized recommendations based on your activity."\n defaultChecked\n />\n </div>\n )}\n </Field>\n </div>\n</div>',
3586
+ details: "Use dividers to separate switch groups into logical sections. Each section can be wrapped in a Field with its own label and description."
3587
+ }
3588
+ ],
3589
+ docsUrl: "https://plexui.com/docs/components/switch"
3590
+ },
3591
+ {
3592
+ slug: "table",
3593
+ name: "Table",
3594
+ description: "Styled HTML table primitives and a full-featured data table powered by TanStack Table v8",
3595
+ importPath: "@plexui/ui/components/Table",
3596
+ exports: [
3597
+ "Table",
3598
+ "TableHeader",
3599
+ "TableBody",
3600
+ "TableRow",
3601
+ "TableHead",
3602
+ "TableCell"
3603
+ ],
3604
+ examples: [
3605
+ {
3606
+ name: "Overview",
3607
+ code: 'const invoices = [\n { invoice: "INV001", status: "Paid", method: "Credit Card", amount: "$250.00" },\n { invoice: "INV002", status: "Pending", method: "PayPal", amount: "$150.00" },\n { invoice: "INV003", status: "Unpaid", method: "Bank Transfer", amount: "$350.00" },\n { invoice: "INV004", status: "Paid", method: "Credit Card", amount: "$450.00" },\n { invoice: "INV005", status: "Paid", method: "PayPal", amount: "$550.00" },\n]\n\n<Table>\n <TableCaption>A list of your recent invoices.</TableCaption>\n <TableHeader>\n <TableRow>\n <TableHead>Invoice</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Method</TableHead>\n <TableHead className="text-right">Amount</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {invoices.map((invoice) => (\n <TableRow key={invoice.invoice}>\n <TableCell className="font-medium">{invoice.invoice}</TableCell>\n <TableCell>{invoice.status}</TableCell>\n <TableCell>{invoice.method}</TableCell>\n <TableCell className="text-right">{invoice.amount}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n <TableFooter>\n <TableRow>\n <TableCell colSpan={3}>Total</TableCell>\n <TableCell className="text-right">$1,750.00</TableCell>\n </TableRow>\n </TableFooter>\n</Table>',
3608
+ details: "A complete table with header, body, footer, and caption showing invoice data."
3609
+ },
3610
+ {
3611
+ name: "With footer",
3612
+ code: '<Table>\n <TableHeader>\n <TableRow>\n <TableHead>Product</TableHead>\n <TableHead className="text-right">Qty</TableHead>\n <TableHead className="text-right">Price</TableHead>\n <TableHead className="text-right">Total</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n <TableRow>\n <TableCell className="font-medium">Pro Plan</TableCell>\n <TableCell className="text-right">3</TableCell>\n <TableCell className="text-right">$29.00</TableCell>\n <TableCell className="text-right">$87.00</TableCell>\n </TableRow>\n <TableRow>\n <TableCell className="font-medium">Storage Add-on</TableCell>\n <TableCell className="text-right">1</TableCell>\n <TableCell className="text-right">$9.00</TableCell>\n <TableCell className="text-right">$9.00</TableCell>\n </TableRow>\n </TableBody>\n <TableFooter>\n <TableRow>\n <TableCell colSpan={3}>Subtotal</TableCell>\n <TableCell className="text-right font-semibold">$96.00</TableCell>\n </TableRow>\n </TableFooter>\n</Table>',
3613
+ details: "A table with a footer row displaying subtotal information."
3614
+ },
3615
+ {
3616
+ name: "Minimal",
3617
+ code: 'const people = [\n { name: "Lindsay Walton", title: "Front-end Developer", email: "lindsay@example.com" },\n { name: "Courtney Henry", title: "Designer", email: "courtney@example.com" },\n { name: "Tom Cook", title: "Director of Product", email: "tom@example.com" },\n]\n\n<Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Title</TableHead>\n <TableHead>Email</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {people.map((person) => (\n <TableRow key={person.email}>\n <TableCell className="font-medium">{person.name}</TableCell>\n <TableCell>{person.title}</TableCell>\n <TableCell>{person.email}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n</Table>',
3618
+ details: "A simple table without caption or footer, showing a list of people."
3619
+ },
3620
+ {
3621
+ name: "Variant",
3622
+ code: '<DataTable\n columns={columns}\n data={users}\n variant="bordered"\n/>',
3623
+ details: 'Use variant="bordered" for a card-like table with a rounded border and tinted header background.'
3624
+ },
3625
+ {
3626
+ name: "Sorting",
3627
+ code: 'const columns: ColumnDef<User>[] = [\n {\n accessorKey: "name",\n header: ({ column }) => <DataTableColumnHeader column={column} title="Name" />,\n },\n {\n accessorKey: "role",\n header: ({ column }) => <DataTableColumnHeader column={column} title="Role" />,\n },\n]\n\n<DataTable columns={columns} data={users} initialSorting={[{ id: "name", desc: false }]} />',
3628
+ details: "Click column headers to toggle sort direction. Columns use DataTableColumnHeader for sort controls."
3629
+ },
3630
+ {
3631
+ name: "Pagination",
3632
+ code: 'const columns: ColumnDef<User>[] = [\n { accessorKey: "id", header: "ID" },\n { accessorKey: "name", header: "Name" },\n { accessorKey: "department", header: "Department" },\n]\n\n<DataTable columns={columns} data={users} pageSize={5} />',
3633
+ details: "Automatic pagination with rows-per-page selector and navigation controls. Set pageSize to control initial page size."
3634
+ },
3635
+ {
3636
+ name: "Row click",
3637
+ code: 'const [selected, setSelected] = useState<User | null>(null)\n\n<>\n <DataTable columns={columns} data={users} onRowClick={setSelected} />\n <p>{selected ? "Clicked: " + selected.name : "Click a row to select it."}</p>\n</>',
3638
+ details: "Pass onRowClick to make rows clickable and interactive."
3639
+ },
3640
+ {
3641
+ name: "Cell variants",
3642
+ code: 'import { idCell, statusCell, dateTimeCell } from "@plexui/ui/components/DataTable"\n\nconst columns: ColumnDef<User>[] = [\n { accessorKey: "id", header: "ID", cell: idCell<User>((row) => row.id) },\n { accessorKey: "status", header: "Status", cell: statusCell<User>((row) => row.status) },\n { accessorKey: "joined", header: "Joined", cell: dateTimeCell<User>((row) => row.joined) },\n]',
3643
+ details: "Use column helper utilities for rich cell types: monospace IDs with copy-on-click, status badges, formatted dates, and currency values."
3644
+ },
3645
+ {
3646
+ name: "Filtering",
3647
+ code: 'const [globalFilter, setGlobalFilter] = useState("")\n\n<>\n <Input value={globalFilter} onChange={(event) => setGlobalFilter(event.target.value)} />\n <DataTable\n columns={columns}\n data={users}\n globalFilter={globalFilter}\n onGlobalFilterChange={setGlobalFilter}\n />\n</>',
3648
+ details: "Control search with globalFilter and onGlobalFilterChange. The filter searches across all columns."
3649
+ },
3650
+ {
3651
+ name: "Column visibility",
3652
+ code: "const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({\n email: true,\n role: true,\n})\n\n<DataTable\n columns={columns}\n data={users}\n columnVisibility={columnVisibility}\n onColumnVisibilityChange={setColumnVisibility}\n/>",
3653
+ details: "Toggle column visibility with the columnVisibility and onColumnVisibilityChange props."
3654
+ },
3655
+ {
3656
+ name: "Empty state",
3657
+ code: '<DataTable columns={columns} data={[]} emptyMessage="No users found." />',
3658
+ details: "Customize the empty state message with the emptyMessage prop."
3659
+ }
3660
+ ],
3661
+ subComponents: [
3662
+ {
3663
+ name: "Table Primitives",
3664
+ props: [
3665
+ {
3666
+ name: "Table",
3667
+ type: "table",
3668
+ description: "Root table container with horizontal scroll wrapper"
3669
+ },
3670
+ {
3671
+ name: "TableHeader",
3672
+ type: "thead",
3673
+ description: "Table header group"
3674
+ },
3675
+ {
3676
+ name: "TableBody",
3677
+ type: "tbody",
3678
+ description: "Table body group"
3679
+ },
3680
+ {
3681
+ name: "TableFooter",
3682
+ type: "tfoot",
3683
+ description: "Table footer group"
3684
+ },
3685
+ {
3686
+ name: "TableRow",
3687
+ type: "tr",
3688
+ description: "Table row"
3689
+ },
3690
+ {
3691
+ name: "TableHead",
3692
+ type: "th",
3693
+ description: "Table header cell"
3694
+ },
3695
+ {
3696
+ name: "TableCell",
3697
+ type: "td",
3698
+ description: "Table data cell"
3699
+ },
3700
+ {
3701
+ name: "TableCaption",
3702
+ type: "caption",
3703
+ description: "Table caption"
3704
+ }
3705
+ ]
3706
+ },
3707
+ {
3708
+ name: "DataTable",
3709
+ props: [
3710
+ {
3711
+ name: "variant",
3712
+ type: '"default" | "bordered"',
3713
+ default: '"default"',
3714
+ description: "Visual variant \u2014 bordered adds a rounded border and tinted header"
3715
+ },
3716
+ {
3717
+ name: "pageSize",
3718
+ type: "number",
3719
+ default: "10",
3720
+ description: "Rows per page. Set 0 or Infinity to disable pagination"
3721
+ },
3722
+ {
3723
+ name: "initialSorting",
3724
+ type: "SortingState",
3725
+ description: "Initial column sorting state"
3726
+ },
3727
+ {
3728
+ name: "globalFilter",
3729
+ type: "string",
3730
+ description: "Controlled search/filter string"
3731
+ },
3732
+ {
3733
+ name: "onGlobalFilterChange",
3734
+ type: "(value: string) => void",
3735
+ description: "Callback when filter changes"
3736
+ },
3737
+ {
3738
+ name: "onRowClick",
3739
+ type: "(row: TData) => void",
3740
+ description: "Click handler for rows, makes rows interactive"
3741
+ },
3742
+ {
3743
+ name: "columnVisibility",
3744
+ type: "VisibilityState",
3745
+ description: "Controlled column visibility state"
3746
+ },
3747
+ {
3748
+ name: "onColumnVisibilityChange",
3749
+ type: "(visibility: VisibilityState) => void",
3750
+ description: "Callback when visibility changes"
3751
+ },
3752
+ {
3753
+ name: "enableRowSelection",
3754
+ type: "boolean",
3755
+ default: "false",
3756
+ description: "Enable row selection checkboxes"
3757
+ },
3758
+ {
3759
+ name: "rowSelection",
3760
+ type: "RowSelectionState",
3761
+ description: "Controlled row selection state"
3762
+ },
3763
+ {
3764
+ name: "onRowSelectionChange",
3765
+ type: "(selection: RowSelectionState) => void",
3766
+ description: "Callback when selection changes"
3767
+ },
3768
+ {
3769
+ name: "getRowId",
3770
+ type: "(row: TData) => string",
3771
+ description: "Custom row ID accessor"
3772
+ },
3773
+ {
3774
+ name: "emptyMessage",
3775
+ type: "ReactNode",
3776
+ default: '"No results."',
3777
+ description: "Shown when no rows"
3778
+ },
3779
+ {
3780
+ name: "showPagination",
3781
+ type: "boolean",
3782
+ default: "true",
3783
+ description: "Whether to show pagination controls"
3784
+ },
3785
+ {
3786
+ name: "className",
3787
+ type: "string",
3788
+ description: "Class applied to outermost wrapper"
3789
+ }
3790
+ ]
3791
+ },
3792
+ {
3793
+ name: "DataTableColumnHeader",
3794
+ props: []
3795
+ },
3796
+ {
3797
+ name: "Column helpers",
3798
+ props: [
3799
+ {
3800
+ name: "idCell(accessor)",
3801
+ type: "CellComponent",
3802
+ description: "Monospace ID with copy-on-click tooltip"
3803
+ },
3804
+ {
3805
+ name: "dateTimeCell(accessor, options?)",
3806
+ type: "CellComponent",
3807
+ description: "Formatted datetime with Intl.DateTimeFormat"
3808
+ },
3809
+ {
3810
+ name: "statusCell(accessor, colorMap?)",
3811
+ type: "CellComponent",
3812
+ description: "Badge cell with automatic status-to-color mapping"
3813
+ },
3814
+ {
3815
+ name: "tooltipHeader(label, description)",
3816
+ type: "HeaderComponent",
3817
+ description: "Header with dashed underline and info tooltip"
3818
+ }
3819
+ ]
3820
+ }
3821
+ ],
3822
+ docsUrl: "https://plexui.com/docs/components/table"
3823
+ },
3824
+ {
3825
+ slug: "tabs",
3826
+ name: "Tabs",
3827
+ description: "Toggle through grouped options with segmented or underline styles",
3828
+ importPath: "@plexui/ui/components/Tabs",
3829
+ exports: [
3830
+ "Tabs"
3831
+ ],
3832
+ props: [
3833
+ {
3834
+ name: "value",
3835
+ type: "string",
3836
+ description: "Selected tab value (controlled)"
3837
+ },
3838
+ {
3839
+ name: "onChange",
3840
+ type: "(value: string) => void",
3841
+ description: "Called when selection changes"
3842
+ },
3843
+ {
3844
+ name: "variant",
3845
+ type: '"segmented" | "underline"',
3846
+ default: '"segmented"',
3847
+ description: "Visual style \u2014 segmented (background highlight) or underline (line indicator)"
3848
+ },
3849
+ {
3850
+ name: "orientation",
3851
+ type: '"horizontal" | "vertical"',
3852
+ default: '"horizontal"',
3853
+ description: "Layout direction of the tab group"
3854
+ },
3855
+ {
3856
+ name: "size",
3857
+ type: "ControlSize",
3858
+ default: '"md"',
3859
+ description: "Control height (3xs through 3xl)"
3860
+ },
3861
+ {
3862
+ name: "gutterSize",
3863
+ type: "string",
3864
+ description: "Horizontal padding of tabs"
3865
+ },
3866
+ {
3867
+ name: "pill",
3868
+ type: "boolean",
3869
+ default: "true",
3870
+ description: "Fully rounded control (segmented variant only)"
3871
+ },
3872
+ {
3873
+ name: "flush",
3874
+ type: "boolean",
3875
+ default: "false",
3876
+ description: "Text-width indicator, no border, gap spacing (underline variant only)"
3877
+ },
3878
+ {
3879
+ name: "block",
3880
+ type: "boolean",
3881
+ default: "false",
3882
+ description: "Full-width control with equal-width tabs"
3883
+ },
3884
+ {
3885
+ name: "disabled",
3886
+ type: "boolean",
3887
+ default: "false",
3888
+ description: "Disable all tabs"
3889
+ },
3890
+ {
3891
+ name: "aria-label",
3892
+ type: "string",
3893
+ description: "Accessible name (required)"
3894
+ },
3895
+ {
3896
+ name: "icon",
3897
+ type: "ReactNode",
3898
+ description: "Icon element (sized automatically)"
3899
+ },
3900
+ {
3901
+ name: "badge",
3902
+ type: "number | { content: ReactNode; color?: string; variant?: string }",
3903
+ description: "Badge/count or custom badge config"
3904
+ }
3905
+ ],
3906
+ examples: [
3907
+ {
3908
+ name: "Overview",
3909
+ code: 'const [tab, setTab] = useState("overview")\n<Tabs value={tab} onChange={setTab} variant="underline" aria-label="Sections">\n <Tabs.Tab value="overview">Overview</Tabs.Tab>\n <Tabs.Tab value="analytics">Analytics</Tabs.Tab>\n <Tabs.Tab value="reports">Reports</Tabs.Tab>\n</Tabs>',
3910
+ details: "Tabs provides two visual variants: segmented (default) with a sliding background highlight, and underline with an animated line indicator. Both support horizontal and vertical orientations."
3911
+ },
3912
+ {
3913
+ name: "Segmented with icons",
3914
+ code: '<Tabs size="lg" value={tab} onChange={setTab} aria-label="Navigation">\n <Tabs.Tab value="home" icon={<Home />}>Home</Tabs.Tab>\n <Tabs.Tab value="settings" icon={<SettingsCog />}>Settings</Tabs.Tab>\n <Tabs.Tab value="notifications" icon={<Bell />}>Notifications</Tabs.Tab>\n</Tabs>',
3915
+ details: "Icons work with the segmented variant \u2014 use the icon prop on individual tabs. Icons are automatically sized to match the current size."
3916
+ },
3917
+ {
3918
+ name: "Segmented with badge",
3919
+ code: '<Tabs size="lg" value={tab} onChange={setTab} aria-label="Filter">\n <Tabs.Tab value="all" badge={128}>All</Tabs.Tab>\n <Tabs.Tab value="unread" badge={{ content: 12, color: "info" }}>Unread</Tabs.Tab>\n <Tabs.Tab value="flagged" badge={{ content: 3, color: "danger" }}>Flagged</Tabs.Tab>\n</Tabs>',
3920
+ details: "Pass a number or an object with content, color, and variant to the badge prop. Badges render inline after the tab label inside the segmented container."
3921
+ },
3922
+ {
3923
+ name: "Underline",
3924
+ code: '<Tabs variant="underline" value={tab} onChange={setTab} aria-label="Sections">\n <Tabs.Tab value="overview">Overview</Tabs.Tab>\n <Tabs.Tab value="analytics">Analytics</Tabs.Tab>\n <Tabs.Tab value="reports">Reports</Tabs.Tab>\n <Tabs.Tab value="settings">Settings</Tabs.Tab>\n</Tabs>',
3925
+ details: 'Set variant="underline" for a minimal tab style with an animated line indicator under the active tab. No background container is rendered.'
3926
+ },
3927
+ {
3928
+ name: "Underline flush",
3929
+ code: '<Tabs variant="underline" flush value={tab} onChange={setTab} aria-label="Sections">\n <Tabs.Tab value="overview">Overview</Tabs.Tab>\n <Tabs.Tab value="analytics">Analytics</Tabs.Tab>\n <Tabs.Tab value="reports">Reports</Tabs.Tab>\n <Tabs.Tab value="settings">Settings</Tabs.Tab>\n</Tabs>',
3930
+ details: 'Add flush with variant="underline" for minimal tabs where the indicator matches the text width exactly. No container border is rendered, and spacing between tabs uses gap instead of padding.'
3931
+ },
3932
+ {
3933
+ name: "Underline block",
3934
+ code: '<Tabs variant="underline" block value={tab} onChange={setTab} aria-label="Sections">\n <Tabs.Tab value="overview">Overview</Tabs.Tab>\n <Tabs.Tab value="analytics">Analytics</Tabs.Tab>\n <Tabs.Tab value="reports">Reports</Tabs.Tab>\n</Tabs>',
3935
+ details: "Use block with the underline variant for full-width tabs that stretch evenly across the container."
3936
+ },
3937
+ {
3938
+ name: "Underline with icons",
3939
+ code: '<Tabs variant="underline" size="lg" value={tab} onChange={setTab} aria-label="Navigation">\n <Tabs.Tab value="home" icon={<Home />}>Home</Tabs.Tab>\n <Tabs.Tab value="settings" icon={<SettingsCog />}>Settings</Tabs.Tab>\n <Tabs.Tab value="notifications" icon={<Bell />}>Notifications</Tabs.Tab>\n</Tabs>',
3940
+ details: "Icons work with the underline variant the same way as with segmented. Use the icon prop on individual tabs."
3941
+ },
3942
+ {
3943
+ name: "Underline with badge",
3944
+ code: '<Tabs variant="underline" size="lg" value={tab} onChange={setTab} aria-label="Filter">\n <Tabs.Tab value="all" badge={128}>All</Tabs.Tab>\n <Tabs.Tab value="unread" badge={{ content: 12, color: "info" }}>Unread</Tabs.Tab>\n <Tabs.Tab value="flagged" badge={{ content: 3, color: "danger" }}>Flagged</Tabs.Tab>\n</Tabs>',
3945
+ details: "Badges are supported with the underline variant. Pass a simple value or an object with content, color, and variant."
3946
+ },
3947
+ {
3948
+ name: "Vertical segmented",
3949
+ code: '<Tabs orientation="vertical" value={tab} onChange={setTab} aria-label="Settings">\n <Tabs.Tab value="account">Account</Tabs.Tab>\n <Tabs.Tab value="password">Password</Tabs.Tab>\n <Tabs.Tab value="notifications">Notifications</Tabs.Tab>\n <Tabs.Tab value="display">Display</Tabs.Tab>\n</Tabs>',
3950
+ details: 'Set orientation="vertical" to stack tabs vertically. The sliding thumb animates up and down.'
3951
+ },
3952
+ {
3953
+ name: "Vertical underline",
3954
+ code: '<Tabs variant="underline" orientation="vertical" value={tab} onChange={setTab} aria-label="Settings">\n <Tabs.Tab value="account">Account</Tabs.Tab>\n <Tabs.Tab value="password">Password</Tabs.Tab>\n <Tabs.Tab value="notifications">Notifications</Tabs.Tab>\n <Tabs.Tab value="display">Display</Tabs.Tab>\n</Tabs>',
3955
+ details: 'Combine variant="underline" with orientation="vertical" for a sidebar-style navigation pattern. The line indicator appears on the left side.'
3956
+ },
3957
+ {
3958
+ name: "Vertical underline with icons",
3959
+ code: '<Tabs variant="underline" orientation="vertical" size="lg" value={tab} onChange={setTab} aria-label="Navigation">\n <Tabs.Tab value="home" icon={<Home />}>Home</Tabs.Tab>\n <Tabs.Tab value="settings" icon={<SettingsCog />}>Settings</Tabs.Tab>\n <Tabs.Tab value="notifications" icon={<Bell />}>Notifications</Tabs.Tab>\n</Tabs>',
3960
+ details: "Vertical underline tabs with icons provide a rich navigation experience commonly used in settings or admin panels."
3961
+ },
3962
+ {
3963
+ name: "Disabled tab",
3964
+ code: '<Tabs variant="underline" value={tab} onChange={setTab} aria-label="Sections">\n <Tabs.Tab value="overview">Overview</Tabs.Tab>\n <Tabs.Tab value="analytics">Analytics</Tabs.Tab>\n <Tabs.Tab value="reports" disabled>Reports</Tabs.Tab>\n <Tabs.Tab value="settings">Settings</Tabs.Tab>\n</Tabs>',
3965
+ details: "Use disabled on individual Tabs.Tab elements to prevent interaction. Disabled tabs appear dimmed and cannot be selected."
3966
+ },
3967
+ {
3968
+ name: "Sizing",
3969
+ code: '<Tabs variant="underline" size="lg" value={tab} onChange={setTab} aria-label="Demo">\n <Tabs.Tab value="overview">Overview</Tabs.Tab>\n <Tabs.Tab value="analytics">Analytics</Tabs.Tab>\n</Tabs>',
3970
+ details: "Both variants support the full 9-step size scale. The size prop controls the height, font size, icon size, and spacing of tabs."
3971
+ },
3972
+ {
3973
+ name: "Scrollable",
3974
+ code: '<div className="max-w-[400px] flex">\n <Tabs value={tab} onChange={setTab} aria-label="Scrollable tabs">\n <Tabs.Tab value="1">Overview</Tabs.Tab>\n <Tabs.Tab value="2">Analytics</Tabs.Tab>\n <Tabs.Tab value="3">Reports</Tabs.Tab>\n <Tabs.Tab value="4">Settings</Tabs.Tab>\n <Tabs.Tab value="5">Members</Tabs.Tab>\n <Tabs.Tab value="6">Billing</Tabs.Tab>\n <Tabs.Tab value="7">Security</Tabs.Tab>\n <Tabs.Tab value="8">Integrations</Tabs.Tab>\n </Tabs>\n</div>',
3975
+ details: "When tabs overflow their container, they automatically become horizontally scrollable. The active tab scrolls into view smoothly. No extra prop is needed \u2014 just constrain the container width."
3976
+ }
3977
+ ],
3978
+ docsUrl: "https://plexui.com/docs/components/tabs"
3979
+ },
3980
+ {
3981
+ slug: "tag-input",
3982
+ name: "Tag Input",
3983
+ description: "Enter multiple unique tags",
3984
+ importPath: "@plexui/ui/components/TagInput",
3985
+ exports: [
3986
+ "TagInput"
3987
+ ],
3988
+ props: [
3989
+ {
3990
+ name: "value",
3991
+ type: "Tag[]",
3992
+ description: "Controlled value"
3993
+ },
3994
+ {
3995
+ name: "defaultValue",
3996
+ type: "Tag[]",
3997
+ description: "Uncontrolled initial tags"
3998
+ },
3999
+ {
4000
+ name: "onChange",
4001
+ type: "(tags: Tag[]) => void",
4002
+ description: "Change handler"
4003
+ },
4004
+ {
4005
+ name: "placeholder",
4006
+ type: "string",
4007
+ description: "Input placeholder"
4008
+ },
4009
+ {
4010
+ name: "validator",
4011
+ type: "(value: string) => boolean",
4012
+ description: "Validates each tag; invalid tags shown in red"
4013
+ },
4014
+ {
4015
+ name: "maxTags",
4016
+ type: "number",
4017
+ description: "Max tags before input is disabled"
4018
+ },
4019
+ {
4020
+ name: "rows",
4021
+ type: "number",
4022
+ default: "1",
4023
+ description: "Min rows"
4024
+ },
4025
+ {
4026
+ name: "size",
4027
+ type: "string",
4028
+ default: "xl",
4029
+ description: "Control height when rows=1"
4030
+ },
4031
+ {
4032
+ name: "autoFocus",
4033
+ type: "boolean",
4034
+ default: "false",
4035
+ description: "Focus on mount"
4036
+ }
4037
+ ],
4038
+ examples: [
4039
+ {
4040
+ name: "Overview",
4041
+ code: '<TagInput\n placeholder="example@example.com"\n validator={(email) => /^[^\\\\s@]+@[^\\\\s@]+\\\\.[^\\\\s@]+$/.test(email)}\n rows={3}\n/>'
4042
+ }
4043
+ ],
4044
+ docsUrl: "https://plexui.com/docs/components/tag-input"
4045
+ },
4046
+ {
4047
+ slug: "text-link",
4048
+ name: "Text Link",
4049
+ description: "Semantic link for internal and external links",
4050
+ importPath: "@plexui/ui/components/TextLink",
4051
+ exports: [
4052
+ "TextLink"
4053
+ ],
4054
+ props: [
4055
+ {
4056
+ name: "children",
4057
+ type: "ReactNode",
4058
+ description: "Link content"
4059
+ },
4060
+ {
4061
+ name: "href",
4062
+ type: "string",
4063
+ description: "URL"
4064
+ },
4065
+ {
4066
+ name: "to",
4067
+ type: "object",
4068
+ description: "Router link destination (internal)"
4069
+ },
4070
+ {
4071
+ name: "primary",
4072
+ type: "boolean",
4073
+ default: "false",
4074
+ description: "Primary color, no default underline"
4075
+ },
4076
+ {
4077
+ name: "underline",
4078
+ type: "boolean",
4079
+ default: "true",
4080
+ description: "Underline (default true unless primary)"
4081
+ },
4082
+ {
4083
+ name: "forceExternal",
4084
+ type: "boolean",
4085
+ description: "Force external link behavior"
4086
+ },
4087
+ {
4088
+ name: "as",
4089
+ type: "Component",
4090
+ description: "Link component (e.g. Next Link)"
4091
+ }
4092
+ ],
4093
+ examples: [
4094
+ {
4095
+ name: "Overview",
4096
+ code: '<div className="flex gap-4">\n <TextLink href="#">Read more</TextLink>\n <TextLink href="#" primary>External link <ArrowUpRight /></TextLink>\n</div>'
4097
+ },
4098
+ {
4099
+ name: "Inline usage",
4100
+ code: '<p>\n You can use the <TextLink href="#">responses endpoint</TextLink> to generate text.\n Or use one of the <TextLink href="#">official SDKs</TextLink> for your language.\n</p>'
4101
+ },
4102
+ {
4103
+ name: "Default inheritance",
4104
+ code: '<p className="text-secondary">\n <TextLink href="#">responses endpoint</TextLink> ... <TextLink href="#">official SDKs</TextLink>\n</p>',
4105
+ details: "By default, TextLink uses currentcolor inheritance and text-decoration: underline to create subtle visual differentiation from surrounding text. The hover effect is a dynamic deemphasis of the currentcolor, with some special cases for text-secondary and text-tertiary contexts.You can see this behavior by changing the text color context of the paragraph the links are in below:"
4106
+ },
4107
+ {
4108
+ name: "Primary color",
4109
+ code: '<TextLink href="#" primary>primary link</TextLink>',
4110
+ details: "You can create a colorful link style with the primary prop, which uses color for visual differentiation.Primary links also remove the default underline treatment - pass underline re-apply that style."
4111
+ },
4112
+ {
4113
+ name: "Underline",
4114
+ code: '<TextLink href="#" underline={false}>subtle link</TextLink>',
4115
+ details: "Links have an underlined aesthetic by default.Pass {'underline={false}'} to remove the underline from the link."
4116
+ },
4117
+ {
4118
+ name: "External links",
4119
+ code: '<TextLink href="#" primary underline>\n External link <ArrowUpRight />\n</TextLink>',
4120
+ details: `When external paths are provided, {''} will be used with {'target="_blank"'} and {'rel="noopener noreferrer"'} included by default. You can also compose icons along with the text to add visual indication.`
4121
+ },
4122
+ {
4123
+ name: "Font weight",
4124
+ code: '<TextLink href="#" className="font-semibold" underline={false}>\n Contact support <ArrowRight />\n</TextLink>',
4125
+ details: "Use the className prop to customize font weight."
4126
+ }
4127
+ ],
4128
+ docsUrl: "https://plexui.com/docs/components/text-link"
4129
+ },
4130
+ {
4131
+ slug: "textarea",
4132
+ name: "Textarea",
4133
+ description: "Semantic multi-line text input collection",
4134
+ importPath: "@plexui/ui/components/Textarea",
4135
+ exports: [
4136
+ "Textarea"
4137
+ ],
4138
+ props: [
4139
+ {
4140
+ name: "variant",
4141
+ type: '"outline" | "soft"',
4142
+ default: '"outline"',
4143
+ description: "Visual style of the textarea"
4144
+ },
4145
+ {
4146
+ name: "size",
4147
+ type: '"3xs" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl"',
4148
+ default: '"md"',
4149
+ description: "Control height for single-line equivalent"
4150
+ },
4151
+ {
4152
+ name: "gutterSize",
4153
+ type: "string",
4154
+ description: "Horizontal padding; defaults from size"
4155
+ },
4156
+ {
4157
+ name: "disabled",
4158
+ type: "boolean",
4159
+ default: "false",
4160
+ description: "Disable the textarea"
4161
+ },
4162
+ {
4163
+ name: "invalid",
4164
+ type: "boolean",
4165
+ default: "false",
4166
+ description: "Mark as invalid"
4167
+ },
4168
+ {
4169
+ name: "allowAutofillExtensions",
4170
+ type: "boolean",
4171
+ default: "false",
4172
+ description: "Allow autofill extensions"
4173
+ },
4174
+ {
4175
+ name: "autoSelect",
4176
+ type: "boolean",
4177
+ default: "false",
4178
+ description: "Select all contents when mounted"
4179
+ },
4180
+ {
4181
+ name: "rows",
4182
+ type: "number",
4183
+ default: "3",
4184
+ description: "Initial row count"
4185
+ },
4186
+ {
4187
+ name: "autoResize",
4188
+ type: "boolean",
4189
+ default: "false",
4190
+ description: "Grow/shrink with content"
4191
+ },
4192
+ {
4193
+ name: "maxRows",
4194
+ type: "number",
4195
+ description: "Max rows when autoResize"
4196
+ }
4197
+ ],
4198
+ examples: [
4199
+ {
4200
+ name: "Base",
4201
+ code: '<div className="w-[400px]">\n <Textarea placeholder="Enter text..." autoResize rows={3} />\n</div>'
4202
+ },
4203
+ {
4204
+ name: "Variants",
4205
+ code: '<Textarea placeholder="Enter text..." variant="outline" className="w-[400px]" /> ... <Textarea placeholder="Enter text..." variant="soft" className="w-[400px]" />',
4206
+ details: "Use variant to stylize the textarea."
4207
+ },
4208
+ {
4209
+ name: "Auto resize",
4210
+ code: 'const [value, setValue] = useState("Line 1\\\\nLine 2\\\\nLine 3")\n<div className="w-[400px]">\n <Textarea value={value} onChange={(e) => setValue(e.target.value)} placeholder="Type to grow..." autoResize rows={3} maxRows={8} />\n</div>',
4211
+ details: "Enable autoResize to automatically grow/shrink the textarea as the user types. Control the bounds with rows (initial height) and maxRows."
4212
+ },
4213
+ {
4214
+ name: "Sizing",
4215
+ code: '<div className="flex flex-col gap-2.5 justify-start items-start w-[400px]">\n <Textarea placeholder="Write a message..." size="lg" />\n</div>',
4216
+ details: "Sizing of the textarea is controlled with size and gutterSize props. size sets the textarea to a specific control height for single-line equivalent, and gutterSize sets the horizontal padding.Each size provides default values for gutterSize, as well as font-size and border-radius. Each of these values can be overridden independently."
4217
+ },
4218
+ {
4219
+ name: "Disabled",
4220
+ code: '<div className="w-[400px]">\n <Textarea disabled defaultValue="Jane Doe" />\n</div>',
4221
+ details: "Use the disabled prop to disable the textarea, greying it out and preventing user interactions."
4222
+ },
4223
+ {
4224
+ name: "Invalid",
4225
+ code: '<div className="w-[400px]">\n <Textarea invalid placeholder="Invalid textarea" />\n</div>',
4226
+ details: "Use the invalid prop to mark the textarea as invalid. You decide how this state is removed, whether that's through onFocus, onChange, an onSubmit handler, etc.Default visual styles will be applied, based on the variant. Additionally, [data-invalid] is applied to the container for custom styling."
4227
+ },
4228
+ {
4229
+ name: "Auto select text",
4230
+ code: '<div className="w-[400px]">\n <Textarea defaultValue="Toggle to auto select" autoSelect={false} />\n</div>',
4231
+ details: "Pass autoSelect to select all text in the textarea &ndash; useful when pairing with other copy actions. This prop behaves like an imperative action when toggled and allows selection to change."
4232
+ },
4233
+ {
4234
+ name: "Autofill extensions",
4235
+ code: '<div className="w-[400px]">\n <Textarea key={String(allowAutofillExtensions)} name="email" placeholder={allowAutofillExtensions ? "Allowed" : "Not allowed"} allowAutofillExtensions={allowAutofillExtensions} />\n</div>',
4236
+ details: "By default, autofill extensions are prevented by Textarea. For the occasions where you want browser extensions to be present, pass allowAutofillExtensions to remove the disabling.When name is passed this value will default to true."
4237
+ }
4238
+ ],
4239
+ docsUrl: "https://plexui.com/docs/components/textarea"
4240
+ },
4241
+ {
4242
+ slug: "tooltip",
4243
+ name: "Tooltip",
4244
+ description: "Brief and informative hover text",
4245
+ importPath: "@plexui/ui/components/Tooltip",
4246
+ exports: [
4247
+ "Tooltip"
4248
+ ],
4249
+ props: [
4250
+ {
4251
+ name: "content",
4252
+ type: "ReactNode",
4253
+ description: "Tooltip content (null = no tooltip)"
4254
+ },
4255
+ {
4256
+ name: "compact",
4257
+ type: "boolean",
4258
+ default: "false",
4259
+ description: "Short 1\u20133 word tooltips, inverse styling"
4260
+ },
4261
+ {
4262
+ name: "interactive",
4263
+ type: "boolean",
4264
+ default: "false",
4265
+ description: "Content is interactive, stays open when hovered"
4266
+ },
4267
+ {
4268
+ name: "openDelay",
4269
+ type: "number",
4270
+ default: "150",
4271
+ description: "Delay before showing (ms)"
4272
+ },
4273
+ {
4274
+ name: "side",
4275
+ type: "string",
4276
+ default: '"top"',
4277
+ description: "Preferred side"
4278
+ },
4279
+ {
4280
+ name: "sideOffset",
4281
+ type: "number",
4282
+ default: "5",
4283
+ description: "Distance from trigger"
4284
+ },
4285
+ {
4286
+ name: "align",
4287
+ type: "string",
4288
+ default: '"center"',
4289
+ description: "Alignment"
4290
+ },
4291
+ {
4292
+ name: "alignOffset",
4293
+ type: "number",
4294
+ default: "0",
4295
+ description: "Alignment offset"
4296
+ },
4297
+ {
4298
+ name: "gutterSize",
4299
+ type: '"sm" | "md" | "lg"',
4300
+ default: '"md"',
4301
+ description: "Content padding"
4302
+ },
4303
+ {
4304
+ name: "contentClassName",
4305
+ type: "string",
4306
+ description: "Class for tooltip content"
4307
+ },
4308
+ {
4309
+ name: "forceOpen",
4310
+ type: "boolean",
4311
+ description: "Force open or closed"
4312
+ }
4313
+ ],
4314
+ examples: [
4315
+ {
4316
+ name: "Base",
4317
+ code: '<Tooltip content="This is additional context that appears when the trigger is hovered or focused">\n <Tooltip.TriggerDecorator>Simple text with tooltip</Tooltip.TriggerDecorator>\n</Tooltip>'
4318
+ },
4319
+ {
4320
+ name: "Different triggers",
4321
+ code: '<div className="flex items-center gap-4">\n <Tooltip content="Tooltip on button">\n <Button color="primary" size="lg">Sample button</Button>\n </Tooltip>\n <Tooltip content="Tooltip on span">\n <span>Inaccessible trigger</span>\n </Tooltip>\n</div>',
4322
+ details: "Tooltip provides an optional component, Tooltip.TriggerDecorator, to help create accessible tooltip triggers. However, any component can be used as a trigger."
4323
+ },
4324
+ {
4325
+ name: "Conditionally enabled",
4326
+ code: '<Tooltip\n content={disabled ? "This action is disabled for reasons" : null}\n>\n <Button color="primary" size="lg" disabled={disabled}>\n Sample button\n </Button>\n</Tooltip>',
4327
+ details: "Sometimes a tooltip should only be disabled under certain conditions, such as when a component is disabled, providing more context as to why that is. Below is an example of how to do this by conditionally passing in null content."
4328
+ },
4329
+ {
4330
+ name: "Compact tooltips",
4331
+ code: '<Tooltip content="Copy to clipboard" compact>\n <Button color="secondary" variant="ghost" size="md"><Copy /></Button>\n</Tooltip>',
4332
+ details: "Compact tooltips are designed for short, 1-3 word tooltips with inverted styling. They feature minimal padding, a dark background with white text, and no box shadows. Use {'compact={true}'} for brief status messages."
4333
+ }
4334
+ ],
4335
+ docsUrl: "https://plexui.com/docs/components/tooltip"
4336
+ }
4337
+ ],
4338
+ generatedAt: "2026-03-21T15:31:38.907Z"
4339
+ };
4340
+
4341
+ // src/index.ts
4342
+ var components = manifest_default.components;
4343
+ function formatProps(props) {
4344
+ if (props.length === 0) return "No props documented.";
4345
+ return props.map((p) => {
4346
+ const parts = [`- \`${p.name}\``];
4347
+ if (p.type) parts.push(`(${p.type})`);
4348
+ if (p.default) parts.push(`default: ${p.default}`);
4349
+ if (p.description) parts.push(`\u2014 ${p.description}`);
4350
+ return parts.join(" ");
4351
+ }).join("\n");
4352
+ }
4353
+ function formatComponent(comp, options) {
4354
+ const lines = [];
4355
+ lines.push(`# ${comp.name}`);
4356
+ lines.push("");
4357
+ lines.push(comp.description);
4358
+ lines.push("");
4359
+ if (comp.importPath) {
4360
+ lines.push("## Import");
4361
+ lines.push("");
4362
+ lines.push("```tsx");
4363
+ const names = comp.exports?.join(", ") || comp.name;
4364
+ lines.push(`import { ${names} } from "${comp.importPath}";`);
4365
+ lines.push("```");
4366
+ lines.push("");
4367
+ }
4368
+ if (comp.props && comp.props.length > 0) {
4369
+ lines.push("## Props");
4370
+ lines.push("");
4371
+ lines.push(formatProps(comp.props));
4372
+ lines.push("");
4373
+ }
4374
+ if (comp.subComponents && comp.subComponents.length > 0) {
4375
+ lines.push("## Sub-Components");
4376
+ lines.push("");
4377
+ for (const sub of comp.subComponents) {
4378
+ lines.push(`### ${sub.name}`);
4379
+ if (sub.description) lines.push(sub.description);
4380
+ lines.push("");
4381
+ lines.push(formatProps(sub.props));
4382
+ lines.push("");
4383
+ }
4384
+ }
4385
+ if (comp.hooks && comp.hooks.length > 0) {
4386
+ lines.push("## Hooks");
4387
+ lines.push("");
4388
+ for (const hook of comp.hooks) {
4389
+ lines.push(`### ${hook.name}`);
4390
+ lines.push(hook.description);
4391
+ if (hook.properties) {
4392
+ lines.push("");
4393
+ for (const prop of hook.properties) {
4394
+ lines.push(`- \`${prop.name}\` \u2014 ${prop.description}`);
4395
+ }
4396
+ }
4397
+ lines.push("");
4398
+ }
4399
+ }
4400
+ if (!options?.brief && comp.examples && comp.examples.length > 0) {
4401
+ lines.push("## Examples");
4402
+ lines.push("");
4403
+ for (const ex of comp.examples) {
4404
+ lines.push(`### ${ex.name}`);
4405
+ if (ex.details) {
4406
+ lines.push(ex.details);
4407
+ }
4408
+ lines.push("");
4409
+ lines.push("```tsx");
4410
+ lines.push(ex.code);
4411
+ lines.push("```");
4412
+ lines.push("");
4413
+ }
4414
+ }
4415
+ lines.push(`Docs: ${comp.docsUrl}`);
4416
+ return lines.join("\n");
4417
+ }
4418
+ function fuzzyMatch(query, text) {
4419
+ const q = query.toLowerCase();
4420
+ const t = text.toLowerCase();
4421
+ return t.includes(q);
4422
+ }
4423
+ var server = new McpServer({
4424
+ name: "plexui",
4425
+ version: manifest_default.version || "1.0.0"
4426
+ });
4427
+ server.tool(
4428
+ "list_components",
4429
+ "List all available PlexUI components with their descriptions and import paths",
4430
+ {},
4431
+ async () => {
4432
+ const list = components.map((c) => {
4433
+ const parts = [`- **${c.name}** \u2014 ${c.description}`];
4434
+ if (c.importPath) parts.push(` Import: \`${c.importPath}\``);
4435
+ const propCount = c.props?.length || 0;
4436
+ const exampleCount = c.examples?.length || 0;
4437
+ parts.push(` ${propCount} props, ${exampleCount} examples`);
4438
+ return parts.join("\n");
4439
+ }).join("\n\n");
4440
+ const text = [
4441
+ `# PlexUI Components (${components.length})`,
4442
+ "",
4443
+ manifest_default.description,
4444
+ "",
4445
+ `Install: \`${manifest_default.install}\``,
4446
+ `Docs: ${manifest_default.docsUrl}`,
4447
+ "",
4448
+ list
4449
+ ].join("\n");
4450
+ return { content: [{ type: "text", text }] };
4451
+ }
4452
+ );
4453
+ server.tool(
4454
+ "get_component",
4455
+ "Get detailed documentation for a PlexUI component including props, examples, and usage",
4456
+ { name: z.string().describe("Component name (e.g. 'Button', 'Select', 'Sidebar')") },
4457
+ async ({ name }) => {
4458
+ const query = name.toLowerCase().replace(/[-_ ]/g, "");
4459
+ const comp = components.find(
4460
+ (c) => c.name.toLowerCase().replace(/[-_ ]/g, "") === query || c.slug.replace(/-/g, "") === query
4461
+ );
4462
+ if (!comp) {
4463
+ const suggestions = components.filter((c) => fuzzyMatch(name, c.name) || fuzzyMatch(name, c.description)).slice(0, 5);
4464
+ let text = `Component "${name}" not found.`;
4465
+ if (suggestions.length > 0) {
4466
+ text += `
4467
+
4468
+ Did you mean:
4469
+ ${suggestions.map((s) => `- ${s.name}`).join("\n")}`;
4470
+ }
4471
+ return { content: [{ type: "text", text }], isError: true };
4472
+ }
4473
+ return { content: [{ type: "text", text: formatComponent(comp) }] };
4474
+ }
4475
+ );
4476
+ server.tool(
4477
+ "search_components",
4478
+ "Search PlexUI components by name, description, or functionality",
4479
+ {
4480
+ query: z.string().describe("Search query (e.g. 'dropdown', 'form input', 'navigation')"),
4481
+ limit: z.number().optional().default(10).describe("Max results to return")
4482
+ },
4483
+ async ({ query, limit }) => {
4484
+ const matches = components.filter(
4485
+ (c) => fuzzyMatch(query, c.name) || fuzzyMatch(query, c.description) || fuzzyMatch(query, c.slug) || c.exports && c.exports.some((e) => fuzzyMatch(query, e)) || c.props && c.props.some((p) => fuzzyMatch(query, p.name))
4486
+ );
4487
+ if (matches.length === 0) {
4488
+ return {
4489
+ content: [
4490
+ {
4491
+ type: "text",
4492
+ text: `No components found matching "${query}". Use list_components to see all available components.`
4493
+ }
4494
+ ]
4495
+ };
4496
+ }
4497
+ const results = matches.slice(0, limit);
4498
+ const text = results.map((c) => formatComponent(c, { brief: true })).join("\n\n---\n\n");
4499
+ return {
4500
+ content: [
4501
+ {
4502
+ type: "text",
4503
+ text: `Found ${matches.length} component${matches.length === 1 ? "" : "s"} matching "${query}":
4504
+
4505
+ ${text}`
4506
+ }
4507
+ ]
4508
+ };
4509
+ }
4510
+ );
4511
+ server.tool(
4512
+ "get_setup_guide",
4513
+ "Get the PlexUI installation and setup guide",
4514
+ {},
4515
+ async () => {
4516
+ const text = `# PlexUI Setup Guide
4517
+
4518
+ ## Installation
4519
+
4520
+ \`\`\`bash
4521
+ ${manifest_default.install}
4522
+ \`\`\`
4523
+
4524
+ ## CSS Setup
4525
+
4526
+ Add to your global stylesheet (e.g. \`app/globals.css\`):
4527
+
4528
+ \`\`\`css
4529
+ ${manifest_default.setup.css}
4530
+ \`\`\`
4531
+
4532
+ ## Provider
4533
+
4534
+ ${manifest_default.setup.provider}
4535
+
4536
+ \`\`\`tsx
4537
+ import { PlexUIProvider } from "@plexui/ui/components/PlexUIProvider";
4538
+
4539
+ export default function RootLayout({ children }) {
4540
+ return (
4541
+ <PlexUIProvider>
4542
+ {children}
4543
+ </PlexUIProvider>
4544
+ );
4545
+ }
4546
+ \`\`\`
4547
+
4548
+ ## Design System
4549
+
4550
+ - **Size scale**: ${manifest_default.sizeScale.join(", ")} (unified across Button, Input, Select, etc.)
4551
+ - **Colors**: ${manifest_default.colors.join(", ")}
4552
+ - **Variants**: ${manifest_default.variants.join(", ")}
4553
+ - **${manifest_default.componentCount} components** available
4554
+
4555
+ ## Quick Example
4556
+
4557
+ \`\`\`tsx
4558
+ import { Button } from "@plexui/ui/components/Button";
4559
+ import { Input } from "@plexui/ui/components/Input";
4560
+ import { Select } from "@plexui/ui/components/Select";
4561
+
4562
+ export function MyForm() {
4563
+ return (
4564
+ <div className="flex flex-col gap-4">
4565
+ <Input placeholder="Email" size="md" />
4566
+ <Select options={[...]} placeholder="Role..." />
4567
+ <Button color="primary" size="md">Submit</Button>
4568
+ </div>
4569
+ );
4570
+ }
4571
+ \`\`\`
4572
+
4573
+ Docs: ${manifest_default.homepage}
4574
+ `;
4575
+ return { content: [{ type: "text", text }] };
4576
+ }
4577
+ );
4578
+ async function main() {
4579
+ const transport = new StdioServerTransport();
4580
+ await server.connect(transport);
4581
+ console.error("PlexUI MCP server running on stdio");
4582
+ }
4583
+ main().catch((err) => {
4584
+ console.error("Fatal error:", err);
4585
+ process.exit(1);
4586
+ });