@plexui/mcp 0.2.2 → 0.2.3

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 +1006 -72
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -44,7 +44,7 @@ var manifest_default = {
44
44
  "outline",
45
45
  "ghost"
46
46
  ],
47
- componentCount: 58,
47
+ componentCount: 62,
48
48
  components: [
49
49
  {
50
50
  slug: "accordion",
@@ -683,6 +683,12 @@ actionsPlacement="bottom"
683
683
  default: '"horizontal"',
684
684
  description: "Direction of the joined items"
685
685
  },
686
+ {
687
+ name: "mobileMaterial",
688
+ type: '"glass" | "flat"',
689
+ default: "\u2014",
690
+ description: 'Phone surface with touch.css: "glass" makes the group one glass capsule outside a Toolbar, "flat" keeps the joined buttons in one'
691
+ },
686
692
  {
687
693
  name: "className",
688
694
  type: "string",
@@ -693,12 +699,13 @@ actionsPlacement="bottom"
693
699
  examples: [
694
700
  {
695
701
  name: "Overview",
696
- code: '<ButtonGroup>\n<ButtonGroup>\n <Button variant="outline" color="secondary" size="sm" uniform aria-label="Go back">\n <ArrowLeftSm />\n </Button>\n</ButtonGroup>\n<ButtonGroup>\n <Button variant="outline" color="secondary" size="sm">Pin</Button>\n <Button variant="outline" color="secondary" size="sm">Tag</Button>\n</ButtonGroup>\n<ButtonGroup>\n <Button variant="outline" color="secondary" size="sm">Bookmark</Button>\n <Button variant="outline" color="secondary" size="sm" uniform aria-label="More options">\n <DotsHorizontal />\n </Button>\n</ButtonGroup>\n</ButtonGroup>'
702
+ code: '// Desktop\n<ButtonGroup>\n<ButtonGroup>\n <Button variant="outline" color="secondary" size="sm" uniform aria-label="Go back">\n <ArrowLeftSm />\n </Button>\n</ButtonGroup>\n<ButtonGroup>\n <Button variant="outline" color="secondary" size="sm">Pin</Button>\n <Button variant="outline" color="secondary" size="sm">Tag</Button>\n</ButtonGroup>\n<ButtonGroup>\n <Button variant="outline" color="secondary" size="sm">Bookmark</Button>\n <Button variant="outline" color="secondary" size="sm" uniform aria-label="More options">\n <DotsHorizontal />\n </Button>\n</ButtonGroup>\n</ButtonGroup>\n\n// Phone: the bars of a note\n<Page>\n<Toolbar>\n <Toolbar.Leading>\n <NavigationButton action="back" />\n </Toolbar.Leading>\n <Toolbar.Trailing>\n <ButtonGroup aria-label="Note actions">\n <Button color="secondary" variant="soft" uniform pill aria-label="Pin"><Pin /></Button>\n <Button color="secondary" variant="soft" uniform pill aria-label="Tag"><Tag /></Button>\n <Button color="secondary" variant="soft" uniform pill aria-label="More"><DotsHorizontal /></Button>\n </ButtonGroup>\n </Toolbar.Trailing>\n</Toolbar>\n<Page.Content>\u2026</Page.Content>\n<Toolbar placement="bottom">\n <Toolbar.Leading>\n <ButtonGroup aria-label="Editing tools">\n <Button color="secondary" variant="soft" uniform pill aria-label="Checklist"><NotebookCheck /></Button>\n <Button color="secondary" variant="soft" uniform pill aria-label="Attach"><Clip /></Button>\n <Button color="secondary" variant="soft" uniform pill aria-label="Markup"><Pens /></Button>\n </ButtonGroup>\n </Toolbar.Leading>\n <Toolbar.Trailing>\n <Button color="secondary" variant="soft" uniform pill aria-label="New note"><PencilSquare /></Button>\n </Toolbar.Trailing>\n</Toolbar>\n</Page>',
703
+ details: "On a phone the example is the bars of a note, as in iOS Notes: Back alone, the note's actions in one glass capsule on top, the editing tools in one at the bottom beside New Note. A ButtonGroup in a Toolbar is that capsule, the Button Group of the iOS 27 kit: 44px buttons 12px apart in a 160px capsule on top, 48px ones 6px apart in a 164px capsule at the bottom, the symbols in the primary label color. Groups side by side stand apart as the bar's items do."
697
704
  },
698
705
  {
699
706
  name: "Orientation",
700
- code: '<ButtonGroup orientation="vertical" aria-label="Zoom controls">\n<Button variant="outline" color="secondary" size="sm" uniform aria-label="Zoom in">\n <PlusSm />\n</Button>\n<Button variant="outline" color="secondary" size="sm" uniform aria-label="Zoom out">\n <Minus />\n</Button>\n</ButtonGroup>',
701
- details: 'Pass orientation="vertical" to stack the buttons, useful for + / \u2212 steppers, zoom controls, or any vertical toolbar. On a phone with touch.css a group of icon-only buttons keeps their 44px at each size shown, so the phone view leaves out the size control.'
707
+ code: '// Desktop\n<ButtonGroup orientation="vertical" aria-label="Zoom controls">\n<Button variant="outline" color="secondary" size="sm" uniform aria-label="Zoom in">\n <PlusSm />\n</Button>\n<Button variant="outline" color="secondary" size="sm" uniform aria-label="Zoom out">\n <Minus />\n</Button>\n</ButtonGroup>\n\n// Phone: zoom controls over a canvas\n<ButtonGroup orientation="vertical" mobileMaterial="glass" aria-label="Zoom controls" className="absolute top-2 right-4">\n<Button color="secondary" variant="soft" uniform pill aria-label="Zoom in"><Plus /></Button>\n<Button color="secondary" variant="soft" uniform pill aria-label="Zoom out"><Minus /></Button>\n</ButtonGroup>',
708
+ details: 'Pass orientation="vertical" to stack the buttons, useful for + / \u2212 steppers, zoom controls, or any vertical toolbar. On a phone the example is zoom controls floating over a canvas, as map controls do: with mobileMaterial="glass" the group is the vertical Button Group of the iOS 27 kit, a 48px wide capsule of 48px buttons 6px apart. In the content a group of icon-only buttons keeps their 44px at each size, so the phone view leaves out the size control.'
702
709
  },
703
710
  {
704
711
  name: "Size",
@@ -713,7 +720,7 @@ actionsPlacement="bottom"
713
720
  {
714
721
  name: "Separator",
715
722
  code: '<ButtonGroup>\n<Button variant="soft" color="secondary" size="sm"><Undo /> Undo</Button>\n<ButtonGroup.Separator />\n<Button variant="soft" color="secondary" size="sm">Redo <Redo /></Button>\n</ButtonGroup>',
716
- details: 'Drop a ButtonGroup.Separator between items to add a visible divider, commonly used for Undo / Redo pairs or any "two related actions" toolbar.'
723
+ details: 'Drop a ButtonGroup.Separator between items to add a visible divider, commonly used for Undo / Redo pairs or any "two related actions" toolbar. A glass group on a phone draws no line, as the capsule of iOS has none.'
717
724
  },
718
725
  {
719
726
  name: "Split",
@@ -1790,7 +1797,7 @@ const [swatches, setSwatches] = useState(['#000000', '#007aff', '#34c759'])
1790
1797
  {
1791
1798
  name: "Overview",
1792
1799
  code: "<ContextMenu>\n<ContextMenu.Trigger>\n <div tabIndex={0}>Groceries</div>\n</ContextMenu.Trigger>\n<ContextMenu.Content>\n <ContextMenu.Item onSelect={copy}><Copy /> Copy</ContextMenu.Item>\n <ContextMenu.Item onSelect={share}><Share /> Share</ContextMenu.Item>\n <ContextMenu.Item onSelect={pin}><Pin /> Pin</ContextMenu.Item>\n <ContextMenu.Separator />\n <ContextMenu.Item danger onSelect={remove}><RemoveTrash /> Delete</ContextMenu.Item>\n</ContextMenu.Content>\n</ContextMenu>",
1793
- details: "Wrap the element in ContextMenu.Trigger: it keeps its own role and focus. On a touch screen, hold for half a second: the element stays clear above the dimmed, blurred page and the menu opens 18px below it, at the edge on the side of the touch. The press that opened the menu does not activate the element. On a phone the rows sit on the grouped background of an iOS list."
1800
+ details: "Wrap the element in ContextMenu.Trigger: it keeps its own role and focus. On a touch screen, hold for half a second: the element stays clear above the dimmed, blurred page and the menu opens 16px below it, as in the iOS 27 kit, at the edge on the side of the touch. The press that opened the menu does not activate the element. On a phone the rows sit on the grouped background of an iOS list."
1794
1801
  },
1795
1802
  {
1796
1803
  name: "Rounded elements",
@@ -2715,7 +2722,7 @@ export function DetailsDialogExample() {
2715
2722
  {
2716
2723
  name: "Scrolling body",
2717
2724
  code: 'const heading = useRef<HTMLDivElement>(null)\n\n<Dialog.Content mobileVariant="sheet" onOpenAutoFocus={(event) => { event.preventDefault(); heading.current?.focus({ preventScroll: true }); }}>\n<Dialog.Header>\n <div ref={heading} tabIndex={-1} className="outline-none"><Dialog.Title>Terms of service</Dialog.Title></div>\n</Dialog.Header>\n<Dialog.Body>\n <div className="flex flex-col gap-4 text-sm text-secondary max-md:text-[length:var(--dialog-mobile-type-size)] max-md:leading-[var(--dialog-mobile-type-leading)]">\n {terms.map((term) => <p key={term}>{term}</p>)}\n </div>\n</Dialog.Body>\n<Dialog.Footer>\n <Dialog.Close><Button color="secondary" variant="soft">Decline</Button></Dialog.Close>\n <Button color="primary">Accept</Button>\n</Dialog.Footer>\n</Dialog.Content>',
2718
- details: "Put long content in Dialog.Body: only the body scrolls, while the header and footer stay in view. Without it the whole panel scrolls. On a phone, background on Dialog.Header and Dialog.Footer sets how the Sheet's bars meet the body scrolled under them, as a Toolbar's do: soft by default, or hard, glass or solid."
2725
+ details: "Put long content in Dialog.Body: only the body scrolls, while the header and footer stay in view. Without it the whole panel scrolls. On a phone, background on Dialog.Header and Dialog.Footer sets how the Sheet's bars meet the body scrolled under them, as a Toolbar's do: by default glass on the header and soft on the footer, or hard or solid."
2719
2726
  },
2720
2727
  {
2721
2728
  name: "Placement",
@@ -2762,6 +2769,72 @@ export default function Example() {
2762
2769
  const [open, setOpen] = useState(false);
2763
2770
  return <SetupChoicesSheet open={open} onOpenChange={setOpen} layout="grouped" onAdd={(method) => console.log(method.id)} />;
2764
2771
  }`
2772
+ },
2773
+ {
2774
+ name: "Steps",
2775
+ code: `const [open, setOpen] = useState(false)
2776
+ const [step, setStep] = useState('email')
2777
+ const [direction, setDirection] = useState('forward')
2778
+ const go = (next, to) => { setDirection(to); setStep(next) }
2779
+
2780
+ <Dialog open={open} onOpenChange={setOpen}>
2781
+ <Dialog.Trigger>
2782
+ <Button color="secondary" variant="outline">Sign in</Button>
2783
+ </Dialog.Trigger>
2784
+ <Dialog.Content mobileVariant="sheet" width={420}>
2785
+ <Dialog.Header>
2786
+ <Dialog.Title>Sign in</Dialog.Title>
2787
+ </Dialog.Header>
2788
+ <Dialog.Body>
2789
+ <Dialog.Steps step={step} direction={direction}>
2790
+ {step === 'email' ? (
2791
+ <Field label="Email" description="We send a code to this address.">
2792
+ <Input type="email" placeholder="you@example.com" />
2793
+ </Field>
2794
+ ) : (
2795
+ <Field label="Code" description="Enter the code we sent to your email.">
2796
+ <Input inputMode="numeric" autoComplete="one-time-code" placeholder="6-digit code" />
2797
+ </Field>
2798
+ )}
2799
+ </Dialog.Steps>
2800
+ </Dialog.Body>
2801
+ <Dialog.Footer>
2802
+ {step === 'code' && <Button color="primary" variant="soft" onClick={() => go('email', 'back')}>Back</Button>}
2803
+ {step === 'email'
2804
+ ? <Button color="primary" onClick={() => go('code', 'forward')}>Continue</Button>
2805
+ : <Button color="primary" onClick={() => setOpen(false)}>Verify</Button>}
2806
+ </Dialog.Footer>
2807
+ </Dialog.Content>
2808
+ </Dialog>`,
2809
+ details: `Put the steps of a flow in Dialog.Steps: a new step comes in from the trailing edge over the one before, which moves a third of the way out under it, as a push on iOS. direction="back" reverses it for a return to a step before. The new step starts at its top, the steps reach the panel's edges, and focus in the step that leaves moves to the one that comes in. With reduced motion the step changes at once.`
2810
+ },
2811
+ {
2812
+ name: "Keep it open",
2813
+ code: `const [open, setOpen] = useState(false)
2814
+ const [name, setName] = useState('Mia')
2815
+ const dirty = name !== 'Mia'
2816
+ const hold = (event) => { if (dirty) event.preventDefault() }
2817
+
2818
+ <Dialog open={open} onOpenChange={setOpen}>
2819
+ <Dialog.Trigger>
2820
+ <Button color="secondary" variant="outline">Edit profile</Button>
2821
+ </Dialog.Trigger>
2822
+ <Dialog.Content width={420} onEscapeKeyDown={hold} onInteractOutside={hold}>
2823
+ <Dialog.Header>
2824
+ <Dialog.Title>Edit profile</Dialog.Title>
2825
+ </Dialog.Header>
2826
+ <Field label="Name">
2827
+ <Input value={name} onChange={(event) => setName(event.target.value)} />
2828
+ </Field>
2829
+ <Dialog.Footer>
2830
+ <Dialog.Close>
2831
+ <Button color="primary" variant="soft">Discard</Button>
2832
+ </Dialog.Close>
2833
+ <Button color="primary" onClick={() => setOpen(false)}>Save</Button>
2834
+ </Dialog.Footer>
2835
+ </Dialog.Content>
2836
+ </Dialog>`,
2837
+ details: "Escape and a press or a focus outside ask onEscapeKeyDown and onInteractOutside first: event.preventDefault() keeps the dialog open, such as for a form with unsaved changes, or for a press on a developer panel over the page. The Close button and Dialog.Close still close it."
2765
2838
  }
2766
2839
  ],
2767
2840
  subComponents: [
@@ -2822,6 +2895,38 @@ return <SetupChoicesSheet open={open} onOpenChange={setOpen} layout="grouped" on
2822
2895
  name: "onCloseAutoFocus",
2823
2896
  type: "(event: Event) => void",
2824
2897
  description: "Called when focus returns on close; call event.preventDefault() to keep it elsewhere"
2898
+ },
2899
+ {
2900
+ name: "onEscapeKeyDown",
2901
+ type: "(event: KeyboardEvent) => void",
2902
+ description: "Called on Escape before the dialog closes; call event.preventDefault() to keep it open"
2903
+ },
2904
+ {
2905
+ name: "onInteractOutside",
2906
+ type: "(event: DialogInteractOutsideEvent) => void",
2907
+ description: "Called on a press or a focus outside the dialog before it closes; call event.preventDefault() to keep it open. event.detail.originalEvent is the browser's event"
2908
+ },
2909
+ {
2910
+ name: "zIndex",
2911
+ type: "number",
2912
+ default: "50",
2913
+ description: "Stack order of the overlay and the dialog, for a page whose own layers sit above 50"
2914
+ },
2915
+ {
2916
+ name: "rootProps",
2917
+ type: "DialogLayerProps",
2918
+ description: "className, style and data attributes for the fixed layer that holds the panel. Data attributes on Dialog.Content land there too"
2919
+ },
2920
+ {
2921
+ name: "overlayProps",
2922
+ type: "DialogLayerProps",
2923
+ description: "className, style and data attributes for the overlay"
2924
+ },
2925
+ {
2926
+ name: "layout",
2927
+ type: '"auto" | "phone"',
2928
+ default: '"auto"',
2929
+ description: "phone lays the dialog out as on a phone at any window width, with the dialogs it opens. Inside a PhoneLayout it always is"
2825
2930
  }
2826
2931
  ]
2827
2932
  },
@@ -2841,6 +2946,28 @@ return <SetupChoicesSheet open={open} onOpenChange={setOpen} layout="grouped" on
2841
2946
  description: "Action layout at 767px and below. Defaults to inline for alert, stacked otherwise. Inline actions wrap when they do not fit"
2842
2947
  }
2843
2948
  ]
2949
+ },
2950
+ {
2951
+ name: "Dialog.Steps",
2952
+ description: "the steps of a flow, such as a sign-in that asks for an email and then a code. Place it in Dialog.Body.",
2953
+ props: [
2954
+ {
2955
+ name: "step",
2956
+ type: "string | number",
2957
+ description: "The step on screen. A new value brings its content in"
2958
+ },
2959
+ {
2960
+ name: "direction",
2961
+ type: '"forward" | "back"',
2962
+ default: '"forward"',
2963
+ description: "forward brings the new step in from the trailing edge over the one before; back returns to a step before it"
2964
+ },
2965
+ {
2966
+ name: "className",
2967
+ type: "string",
2968
+ description: "Additional CSS class on the steps"
2969
+ }
2970
+ ]
2844
2971
  }
2845
2972
  ],
2846
2973
  docsUrl: "https://plexui.com/components/dialog"
@@ -3619,6 +3746,66 @@ required
3619
3746
  ],
3620
3747
  docsUrl: "https://plexui.com/components/floating-label-input"
3621
3748
  },
3749
+ {
3750
+ slug: "index-bar",
3751
+ name: "Index Bar",
3752
+ description: '"The section index of a phone list: a column of letters at the trailing edge that jumps to a section under the finger, as the iOS section index does. Desktop lists have no index, so an index bar never renders from 768px up."',
3753
+ importPath: "@plexui/ui/components/IndexBar",
3754
+ exports: [
3755
+ "IndexBar"
3756
+ ],
3757
+ props: [
3758
+ {
3759
+ name: "items",
3760
+ type: "string[]",
3761
+ description: "The titles of the sections, in order: usually letters, such as A to Z and #"
3762
+ },
3763
+ {
3764
+ name: "onSelect",
3765
+ type: "(item: string, index: number) => void",
3766
+ description: "Called with the title and the index of the section to show: on a press, on each new letter while dragging, and from the keys"
3767
+ },
3768
+ {
3769
+ name: "id",
3770
+ type: "string",
3771
+ description: "The id of the index bar"
3772
+ },
3773
+ {
3774
+ name: "aria-label",
3775
+ type: "string",
3776
+ default: '"Section index"',
3777
+ description: "Name of the index bar"
3778
+ },
3779
+ {
3780
+ name: "aria-controls",
3781
+ type: "string",
3782
+ description: "The id of the list the index scrolls"
3783
+ },
3784
+ {
3785
+ name: "onFocus",
3786
+ type: "(event: FocusEvent) => void",
3787
+ description: "Called when the index bar gains focus"
3788
+ },
3789
+ {
3790
+ name: "onBlur",
3791
+ type: "(event: FocusEvent) => void",
3792
+ description: "Called when the index bar loses focus"
3793
+ },
3794
+ {
3795
+ name: "className",
3796
+ type: "string",
3797
+ description: "CSS classes applied to the strip of the index bar"
3798
+ }
3799
+ ],
3800
+ examples: [
3801
+ {
3802
+ name: "Overview",
3803
+ code: 'const content = useRef<HTMLDivElement>(null)\n\n<Page surface="grouped">\n<Toolbar>\n <Toolbar.Title>Countries</Toolbar.Title>\n</Toolbar>\n<Page.Content ref={content}>\n {sections.map(({ letter, names }) => (\n <FieldGroup key={letter} title={letter} data-section={letter}>...</FieldGroup>\n ))}\n</Page.Content>\n<IndexBar\n items={sections.map(({ letter }) => letter)}\n onSelect={(letter) => scrollToSection(content.current, letter)}\n/>\n</Page>',
3804
+ details: "Put the bar in a Page, or in any positioned container that holds the list: it runs down the trailing edge, centred between the bars of a Page (or the safe areas), as iOS centres its index, its letters 14px apart and centred 8px from the edge, in the info color, as native iOS draws its tint (Contacts and the iOS 27 kit). The rows keep the margins of the list, and the letters sit in the trailing one, as in Contacts. A press shows the section of the letter under the finger, and a drag moves through the sections letter by letter; onSelect receives the title and its index, and the list scrolls itself there. Where the letters do not fit, as in a short sheet, every other letter gives way to a dot, as UIKit&apos;s index does, and a drag over a dot still reaches the letters behind it."
3805
+ }
3806
+ ],
3807
+ docsUrl: "https://plexui.com/components/index-bar"
3808
+ },
3622
3809
  {
3623
3810
  slug: "indicators",
3624
3811
  name: "Indicators",
@@ -4531,12 +4718,12 @@ return (
4531
4718
  {
4532
4719
  name: "Overview",
4533
4720
  code: '// Desktop\nconst [icons] = useState(true)\nconst [checkmarks] = useState(true)\nconst [pill] = useState(false)\nconst [view, setView] = useState("list")\nconst [sort, setSort] = useState("date")\nconst [group, setGroup] = useState("date")\n\n<Menu>\n<Menu.Trigger>\n <Button color="primary" size="lg" variant="ghost">Downloads <ChevronDownMd /></Button>\n</Menu.Trigger>\n<Menu.Content minWidth={220} pill={pill}>\n <Menu.Item onSelect={() => {}}>{icons && <CheckCircle />} Select</Menu.Item>\n <Menu.Item onSelect={() => {}}>{icons && <FolderPlus />} New Folder</Menu.Item>\n <Menu.Item onSelect={() => {}}>{icons && <FileDocument />} Scan Documents</Menu.Item>\n <Menu.Item onSelect={() => {}}>{icons && <Desktop />} Connect to Server</Menu.Item>\n <Menu.Separator />\n {checkmarks && (\n <>\n <Menu.RadioGroup value={view} onChange={setView}>\n <Menu.RadioItem value="icons">{icons && <VideoGrid />} Icons</Menu.RadioItem>\n <Menu.RadioItem value="list">{icons && <List />} List</Menu.RadioItem>\n </Menu.RadioGroup>\n <Menu.Separator />\n <Menu.RadioGroup value={sort} onChange={setSort}>\n <Menu.RadioItem value="name">Name</Menu.RadioItem>\n <Menu.RadioItem value="kind">Kind</Menu.RadioItem>\n <Menu.RadioItem value="date" description={sort === "date" ? "Newest to Oldest" : undefined}>Date</Menu.RadioItem>\n <Menu.RadioItem value="size">Size</Menu.RadioItem>\n <Menu.RadioItem value="tags">Tags</Menu.RadioItem>\n </Menu.RadioGroup>\n <Menu.Separator />\n <Menu.Sub>\n <Menu.SubTrigger>Group By</Menu.SubTrigger>\n <Menu.SubContent>\n <Menu.RadioGroup value={group} onChange={setGroup}>\n <Menu.RadioItem value="none">None</Menu.RadioItem>\n <Menu.RadioItem value="kind">Kind</Menu.RadioItem>\n <Menu.RadioItem value="date">Date</Menu.RadioItem>\n <Menu.RadioItem value="size">Size</Menu.RadioItem>\n <Menu.RadioItem value="shared">Shared by</Menu.RadioItem>\n </Menu.RadioGroup>\n </Menu.SubContent>\n </Menu.Sub>\n </>\n )}\n <Menu.Item onSelect={() => {}}>View Options</Menu.Item>\n</Menu.Content>\n</Menu>\n\n// Phone: the More button of the top bar opens the menu\n<Toolbar titleStyle="large">\n <Toolbar.Title>Overview</Toolbar.Title>\n <Toolbar.Trailing>\n <Menu>\n <Menu.Trigger>\n <Button color="secondary" variant="soft" uniform pill aria-label="More"><DotsHorizontal /></Button>\n </Menu.Trigger>\n <Menu.Content align="end">\n <Menu.Item onSelect={() => {}}>{icons && <CheckCircle />} Select</Menu.Item>\n <Menu.Item onSelect={() => {}}>{icons && <FolderPlus />} New Folder</Menu.Item>\n <Menu.Item onSelect={() => {}}>{icons && <FileDocument />} Scan Documents</Menu.Item>\n <Menu.Item onSelect={() => {}}>{icons && <Desktop />} Connect to Server</Menu.Item>\n <Menu.Separator />\n {checkmarks && (\n <>\n <Menu.RadioGroup value={view} onChange={setView}>\n <Menu.RadioItem value="icons">{icons && <VideoGrid />} Icons</Menu.RadioItem>\n <Menu.RadioItem value="list">{icons && <List />} List</Menu.RadioItem>\n </Menu.RadioGroup>\n <Menu.Separator />\n <Menu.RadioGroup value={sort} onChange={setSort}>\n <Menu.RadioItem value="name">Name</Menu.RadioItem>\n <Menu.RadioItem value="kind">Kind</Menu.RadioItem>\n <Menu.RadioItem value="date" description={sort === "date" ? "Newest to Oldest" : undefined}>Date</Menu.RadioItem>\n <Menu.RadioItem value="size">Size</Menu.RadioItem>\n <Menu.RadioItem value="tags">Tags</Menu.RadioItem>\n </Menu.RadioGroup>\n <Menu.Separator />\n <Menu.Sub>\n <Menu.SubTrigger>Group By</Menu.SubTrigger>\n <Menu.SubContent>\n <Menu.RadioGroup value={group} onChange={setGroup}>\n <Menu.RadioItem value="none">None</Menu.RadioItem>\n <Menu.RadioItem value="kind">Kind</Menu.RadioItem>\n <Menu.RadioItem value="date">Date</Menu.RadioItem>\n <Menu.RadioItem value="size">Size</Menu.RadioItem>\n <Menu.RadioItem value="shared">Shared by</Menu.RadioItem>\n </Menu.RadioGroup>\n </Menu.SubContent>\n </Menu.Sub>\n </>\n )}\n <Menu.Item onSelect={() => {}}>View Options</Menu.Item>\n </Menu.Content>\n </Menu>\n </Toolbar.Trailing>\n</Toolbar>',
4534
- details: "A folder's menu, as Files opens it on iOS: actions with their icons, then the view and the sort order as radio items, Date with its order under it (description) while it is the chosen sort, and Group By in a submenu. Turn off icons for the same menu in plain text, and checkmarks for the folder's actions alone, a shorter menu with no checkmark column. pill on Menu.Content makes a rounder list with fully rounded rows, for menus beside pill controls. On a phone each example is a screen whose menu opens from the More button of its top bar, as iOS opens the menu of an album in Photos. There the panel covers the button, its top and end at the button's top and end, and is the 238px panel of the iOS kit, so the phone code leaves out width, minWidth and size. A pill menu is the same panel there, so the phone view leaves out pill."
4721
+ details: "A folder's menu, as Files opens it on iOS: actions with their icons, then the view and the sort order as radio items, Date with its order under it (description) while it is the chosen sort, and Group By in a submenu. Turn off icons for the same menu in plain text, and checkmarks for the folder's actions alone, a shorter menu with no checkmark column. pill on Menu.Content makes a rounder list with fully rounded rows, for menus beside pill controls. On a phone each example is a screen whose menu opens from the More button of its top bar, as iOS opens the menu of an album in Photos. There the panel covers the button, its top and end at the button's top and end, and is the 250px panel of iOS menus, so the phone code leaves out width, minWidth and size. A pill menu is the same panel there, so the phone view leaves out pill."
4535
4722
  },
4536
4723
  {
4537
4724
  name: "Natural sizing",
4538
4725
  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>',
4539
- details: `Menu supports several sizing strategies:Natural sizing: use minWidth="auto" to have the menu fit naturally to the size of the content.Fixed size: use width and minWidth to set the menu to a fixed size, regardless of content size.Flexible sizing: 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: 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.Desktop only: on a phone a menu is the iOS panel, at least the 238px of the iOS kit, whatever these sizes say.`
4726
+ details: `Menu supports several sizing strategies:Natural sizing: use minWidth="auto" to have the menu fit naturally to the size of the content.Fixed size: use width and minWidth to set the menu to a fixed size, regardless of content size.Flexible sizing: 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: 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.Desktop only: on a phone a menu is the 250px panel of iOS menus, unless it sets a width or a larger minimum width of its own.`
4540
4727
  },
4541
4728
  {
4542
4729
  name: "Horizontal (icons only)",
@@ -4555,8 +4742,8 @@ return (
4555
4742
  },
4556
4743
  {
4557
4744
  name: "Grouped items",
4558
- code: '// Desktop\n<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.Item onSelect={() => {}}>Welcome email</Menu.Item>\n <Menu.Item onSelect={() => {}}>Order confirmation</Menu.Item>\n <Menu.Item onSelect={() => {}}>Password reset</Menu.Item>\n </Menu.Group>\n <Menu.Separator />\n <Menu.Group>\n <Menu.Label>Draft</Menu.Label>\n <Menu.Item onSelect={() => {}}>New promo campaign</Menu.Item>\n <Menu.Item onSelect={() => {}}>Account deactivation</Menu.Item>\n </Menu.Group>\n</Menu.Content>\n</Menu>\n\n// Phone: the More button of the top bar opens the menu\n<Toolbar titleStyle="large">\n <Toolbar.Title>Grouped items</Toolbar.Title>\n <Toolbar.Trailing>\n <Menu>\n <Menu.Trigger>\n <Button color="secondary" variant="soft" uniform pill aria-label="More"><DotsHorizontal /></Button>\n </Menu.Trigger>\n <Menu.Content align="end">\n <Menu.Group>\n <Menu.Label>Active</Menu.Label>\n <Menu.Item onSelect={() => {}}>Welcome email</Menu.Item>\n <Menu.Item onSelect={() => {}}>Order confirmation</Menu.Item>\n <Menu.Item onSelect={() => {}}>Password reset</Menu.Item>\n </Menu.Group>\n <Menu.Separator />\n <Menu.Group>\n <Menu.Label>Draft</Menu.Label>\n <Menu.Item onSelect={() => {}}>New promo campaign</Menu.Item>\n <Menu.Item onSelect={() => {}}>Account deactivation</Menu.Item>\n </Menu.Group>\n </Menu.Content>\n </Menu>\n </Toolbar.Trailing>\n</Toolbar>',
4559
- details: "Use Menu.Group and Menu.Label to organize related items into labeled sections."
4745
+ code: '// Desktop\nconst [filter, setFilter] = useState("messages")\n\n<Menu>\n<Menu.Trigger>\n <Button variant="ghost" color="primary"><Filter /> Filter <ChevronDownMd /></Button>\n</Menu.Trigger>\n<Menu.Content minWidth={220}>\n <Menu.RadioGroup value={filter} onChange={setFilter}>\n <Menu.RadioItem value="messages"><Chats /> Messages</Menu.RadioItem>\n <Menu.RadioItem value="spam"><XSquare /> Spam</Menu.RadioItem>\n <Menu.RadioItem value="deleted"><Trash /> Recently Deleted</Menu.RadioItem>\n </Menu.RadioGroup>\n <Menu.Separator />\n <Menu.Group>\n <Menu.Label>Filter By</Menu.Label>\n <Menu.RadioGroup value={filter} onChange={setFilter}>\n <Menu.RadioItem value="unread"><Chat /> Unread</Menu.RadioItem>\n <Menu.RadioItem value="drafts"><ChatTripleDots /> Drafts</Menu.RadioItem>\n </Menu.RadioGroup>\n </Menu.Group>\n <Menu.Separator />\n <Menu.Item onSelect={() => {}}>Manage Filtering</Menu.Item>\n</Menu.Content>\n</Menu>\n\n// Phone: the More button of the top bar opens the menu\n<Toolbar titleStyle="large">\n <Toolbar.Title>Grouped items</Toolbar.Title>\n <Toolbar.Trailing>\n <Menu>\n <Menu.Trigger>\n <Button color="secondary" variant="soft" uniform pill aria-label="More"><DotsHorizontal /></Button>\n </Menu.Trigger>\n <Menu.Content align="end">\n <Menu.RadioGroup value={filter} onChange={setFilter}>\n <Menu.RadioItem value="messages"><Chats /> Messages</Menu.RadioItem>\n <Menu.RadioItem value="spam"><XSquare /> Spam</Menu.RadioItem>\n <Menu.RadioItem value="deleted"><Trash /> Recently Deleted</Menu.RadioItem>\n </Menu.RadioGroup>\n <Menu.Separator />\n <Menu.Group>\n <Menu.Label>Filter By</Menu.Label>\n <Menu.RadioGroup value={filter} onChange={setFilter}>\n <Menu.RadioItem value="unread"><Chat /> Unread</Menu.RadioItem>\n <Menu.RadioItem value="drafts"><ChatTripleDots /> Drafts</Menu.RadioItem>\n </Menu.RadioGroup>\n </Menu.Group>\n <Menu.Separator />\n <Menu.Item onSelect={() => {}}>Manage Filtering</Menu.Item>\n </Menu.Content>\n </Menu>\n </Toolbar.Trailing>\n</Toolbar>',
4746
+ details: "Use Menu.Group and Menu.Label to organize related items into labeled sections. The example is the filter of the message list, as Messages opens it on iOS: the mailboxes, a section labeled Filter By, and Manage Filtering under a separator. Both radio groups hold one value, so one checkmark marks the list shown. On a phone a section label and a row without an icon start after the checkmark column, where the icons of the other rows start, as in iOS menus."
4560
4747
  },
4561
4748
  {
4562
4749
  name: "Submenus",
@@ -4565,8 +4752,8 @@ return (
4565
4752
  },
4566
4753
  {
4567
4754
  name: "Checkbox items",
4568
- code: '// Desktop\nconst [settings, setSettings] = useState({\nshowGrid: true,\nshowLabels: false,\nenableShadows: 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)\n\n// Phone: the More button of the top bar opens the menu\n<Toolbar titleStyle="large">\n <Toolbar.Title>Checkbox items</Toolbar.Title>\n <Toolbar.Trailing>\n <Menu>\n <Menu.Trigger>\n <Button color="secondary" variant="soft" uniform pill aria-label="More"><DotsHorizontal /></Button>\n </Menu.Trigger>\n <Menu.Content align="end">\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 </Toolbar.Trailing>\n</Toolbar>',
4569
- details: 'Use Menu.CheckboxItem with checked, onCheckedChange, and {"onSelect={(evt) => evt.preventDefault()}"} to keep the menu open. On a phone with touch.css the checkmark leads the row, as in iOS menus, whatever indicatorPosition and indicatorVariant say, so the phone view leaves out those controls.'
4755
+ code: '// Desktop\nconst [view, setView] = useState({\ncategories: true,\npriority: true,\ncontactPhotos: false,\n})\n\nreturn (\n<Menu>\n <Menu.Trigger>\n <Button variant="ghost" color="primary">\n View <ChevronDownMd />\n </Button>\n </Menu.Trigger>\n <Menu.Content minWidth={220}>\n <Menu.Label>View Options</Menu.Label>\n <Menu.CheckboxItem\n checked={view.categories}\n onCheckedChange={(checked) => setView((v) => ({ ...v, categories: checked }))}\n onSelect={(evt) => evt.preventDefault()}\n indicatorPosition="end"\n indicatorVariant="solid"\n >\n <Storyboard /> Show Categories\n </Menu.CheckboxItem>\n <Menu.CheckboxItem\n checked={view.priority}\n onCheckedChange={(checked) => setView((v) => ({ ...v, priority: checked }))}\n onSelect={(evt) => evt.preventDefault()}\n indicatorPosition="end"\n indicatorVariant="solid"\n >\n <Atom /> Show Priority\n </Menu.CheckboxItem>\n <Menu.CheckboxItem\n checked={view.contactPhotos}\n onCheckedChange={(checked) => setView((v) => ({ ...v, contactPhotos: checked }))}\n onSelect={(evt) => evt.preventDefault()}\n indicatorPosition="end"\n indicatorVariant="solid"\n >\n <AvatarProfile /> Show Contact Photos\n </Menu.CheckboxItem>\n <Menu.Separator />\n <Menu.Item onSelect={() => {}}><InfoCircle /> About Categories</Menu.Item>\n </Menu.Content>\n</Menu>\n)\n\n// Phone: the More button of the top bar opens the menu\n<Toolbar titleStyle="large">\n <Toolbar.Title>Checkbox items</Toolbar.Title>\n <Toolbar.Trailing>\n <Menu>\n <Menu.Trigger>\n <Button color="secondary" variant="soft" uniform pill aria-label="More"><DotsHorizontal /></Button>\n </Menu.Trigger>\n <Menu.Content align="end">\n <Menu.Label>View Options</Menu.Label>\n <Menu.CheckboxItem\n checked={view.categories}\n onCheckedChange={(checked) => setView((v) => ({ ...v, categories: checked }))}\n onSelect={(evt) => evt.preventDefault()}\n indicatorPosition="end"\n indicatorVariant="solid"\n >\n <Storyboard /> Show Categories\n </Menu.CheckboxItem>\n <Menu.CheckboxItem\n checked={view.priority}\n onCheckedChange={(checked) => setView((v) => ({ ...v, priority: checked }))}\n onSelect={(evt) => evt.preventDefault()}\n indicatorPosition="end"\n indicatorVariant="solid"\n >\n <Atom /> Show Priority\n </Menu.CheckboxItem>\n <Menu.CheckboxItem\n checked={view.contactPhotos}\n onCheckedChange={(checked) => setView((v) => ({ ...v, contactPhotos: checked }))}\n onSelect={(evt) => evt.preventDefault()}\n indicatorPosition="end"\n indicatorVariant="solid"\n >\n <AvatarProfile /> Show Contact Photos\n </Menu.CheckboxItem>\n <Menu.Separator />\n <Menu.Item onSelect={() => {}}><InfoCircle /> About Categories</Menu.Item>\n </Menu.Content>\n </Menu>\n </Toolbar.Trailing>\n</Toolbar>',
4756
+ details: 'Use Menu.CheckboxItem with checked, onCheckedChange, and {"onSelect={(evt) => evt.preventDefault()}"} to keep the menu open. The example is the view options of a mailbox, as Mail opens them on iOS: three options that are each on or off, and About Categories under a separator. On a phone with touch.css the checkmark leads the row, as in iOS menus, whatever indicatorPosition and indicatorVariant say, so the phone view leaves out those controls. A row without a checkmark keeps its column, so every icon and label lines up, and a long label wraps under itself.'
4570
4757
  },
4571
4758
  {
4572
4759
  name: "Radio items",
@@ -4603,8 +4790,8 @@ return (
4603
4790
  {
4604
4791
  name: "width",
4605
4792
  type: 'number | "auto"',
4606
- default: '"auto"',
4607
- description: "Panel width in px"
4793
+ default: "fits content, 250 on a phone",
4794
+ description: 'Panel width in px. Without it the panel fits its content, or with touch.css on a phone is 250px, as an iOS menu; "auto" fits the content there too'
4608
4795
  },
4609
4796
  {
4610
4797
  name: "minWidth",
@@ -5043,7 +5230,7 @@ onChange={(index, { scrubbing }) => {
5043
5230
  {
5044
5231
  name: "Background",
5045
5232
  code: '<Page>\n<Page.Header background="hard">...</Page.Header>\n<Page.Content>...</Page.Content>\n<Page.Footer background="hard">\n <Button color="primary" pill block>New order</Button>\n</Page.Footer>\n</Page>',
5046
- details: "background sets how the header and the footer meet the content scrolled under them on a phone, as a Toolbar does: soft (the default) fades the canvas color out under the bar, hard is an opaque bar with a line, glass a translucent, blurred bar with a hairline, and solid an opaque bar with no line. Hard and glass show themselves once content is under the bar; divider={false} keeps a header without its line."
5233
+ details: "background sets how the header and the footer meet the content scrolled under them on a phone, as a Toolbar does: glass, the header's default, is a translucent, blurred bar with a hairline, soft, the footer's default, fades the canvas color out under a header and leaves the content clear under the footer's actions, as native iOS 26 does, hard is an opaque bar with a line, and solid an opaque bar with no line. Glass and hard show themselves once content is under the bar; divider={false} keeps a header without its line."
5047
5234
  }
5048
5235
  ],
5049
5236
  subComponents: [
@@ -5053,7 +5240,7 @@ onChange={(index, { scrubbing }) => {
5053
5240
  {
5054
5241
  name: "background",
5055
5242
  type: '"soft" | "hard" | "glass" | "solid"',
5056
- default: '"soft"',
5243
+ default: '"glass"',
5057
5244
  description: "How the bar meets the content scrolled under it on a phone, see Background"
5058
5245
  },
5059
5246
  {
@@ -5096,6 +5283,62 @@ onChange={(index, { scrubbing }) => {
5096
5283
  ],
5097
5284
  docsUrl: "https://plexui.com/components/page"
5098
5285
  },
5286
+ {
5287
+ slug: "phone-layout",
5288
+ name: "Phone Layout",
5289
+ description: "Lays out the components inside it as on a phone at any window width, such as a phone frame on a desktop page, and keeps their dialogs inside its box.",
5290
+ importPath: "@plexui/ui/components/PhoneLayout",
5291
+ exports: [
5292
+ "PhoneLayout"
5293
+ ],
5294
+ props: [
5295
+ {
5296
+ name: "className",
5297
+ type: "string",
5298
+ description: "Class on the box, such as its size and corners"
5299
+ },
5300
+ {
5301
+ name: "style",
5302
+ type: "CSSProperties",
5303
+ description: "Style on the box"
5304
+ }
5305
+ ],
5306
+ examples: [
5307
+ {
5308
+ name: "Overview",
5309
+ code: `const [open, setOpen] = useState(false)
5310
+
5311
+ <PhoneLayout style={{ width: 375, height: 600, borderRadius: 40, border: '1px solid var(--color-border)' }}>
5312
+ <Dialog open={open} onOpenChange={setOpen}>
5313
+ <Dialog.Trigger>
5314
+ <Button color="primary">New list</Button>
5315
+ </Dialog.Trigger>
5316
+ <Dialog.Content mobileVariant="sheet">
5317
+ <Dialog.Header>
5318
+ <Dialog.Title>New list</Dialog.Title>
5319
+ </Dialog.Header>
5320
+ <Dialog.Body>
5321
+ <Field label="Name">
5322
+ <Input placeholder="Groceries" />
5323
+ </Field>
5324
+ </Dialog.Body>
5325
+ <Dialog.Footer>
5326
+ <Button color="primary" onClick={() => setOpen(false)}>Create</Button>
5327
+ </Dialog.Footer>
5328
+ </Dialog.Content>
5329
+ </Dialog>
5330
+ </PhoneLayout>`,
5331
+ details: "The box is the screen of the phone: a Dialog opened inside it portals into the box and covers it instead of the window. Give the box a size; let a child of it scroll rather than the box, so a dialog stays in place."
5332
+ }
5333
+ ],
5334
+ hooks: [
5335
+ {
5336
+ name: "usePhoneLayout",
5337
+ description: '`usePhoneLayout()` returns whether components where it is called are laid out as on a phone: inside a PhoneLayout or a Dialog with `layout="phone"`, or in a window 767px wide or narrower. On the server it is false outside a PhoneLayout.'
5338
+ }
5339
+ ],
5340
+ docsUrl: "https://plexui.com/components/phone-layout"
5341
+ },
5099
5342
  {
5100
5343
  slug: "popover",
5101
5344
  name: "Popover",
@@ -7552,6 +7795,102 @@ export default function SliderExample() {
7552
7795
  ],
7553
7796
  docsUrl: "https://plexui.com/components/stepper"
7554
7797
  },
7798
+ {
7799
+ slug: "swipe-actions",
7800
+ name: "Swipe Actions",
7801
+ description: '"Actions behind a row of a list, as the swipe actions of an iOS list: a swipe across the row reveals them, a full swipe runs the first one, and each is a button for the keys."',
7802
+ importPath: "@plexui/ui/components/SwipeActions",
7803
+ exports: [
7804
+ "SwipeActions"
7805
+ ],
7806
+ props: [
7807
+ {
7808
+ name: "leading",
7809
+ type: "SwipeAction[]",
7810
+ default: "[]",
7811
+ description: "Actions revealed by a swipe toward the trailing edge, the first one at the leading edge"
7812
+ },
7813
+ {
7814
+ name: "trailing",
7815
+ type: "SwipeAction[]",
7816
+ default: "[]",
7817
+ description: "Actions revealed by a swipe toward the leading edge, the first one at the trailing edge"
7818
+ },
7819
+ {
7820
+ name: "labelPlacement",
7821
+ type: '"hidden" | "inside" | "below"',
7822
+ default: '"hidden"',
7823
+ description: "Where the actions show their labels: symbols only, inside the capsule, or below it (for rows of 68px and taller)"
7824
+ },
7825
+ {
7826
+ name: "fullSwipe",
7827
+ type: "boolean",
7828
+ default: "true",
7829
+ description: "A swipe past the middle between the open actions and the far edge runs the first action of that side"
7830
+ },
7831
+ {
7832
+ name: "className",
7833
+ type: "string",
7834
+ description: "CSS classes applied to the row"
7835
+ },
7836
+ {
7837
+ name: "children",
7838
+ type: "ReactNode",
7839
+ description: "The content of the row"
7840
+ }
7841
+ ],
7842
+ examples: [
7843
+ {
7844
+ name: "Overview",
7845
+ code: '<FieldGroup>\n{lists.map((name) => (\n <SwipeActions\n key={name}\n leading={[{ id: "pin", label: "Pin", Icon: Pin, color: "warning", onSelect: () => pin(name) }]}\n trailing={[\n { id: "delete", label: "Delete", Icon: RemoveTrash, color: "danger", onSelect: () => remove(name) },\n { id: "archive", label: "Archive", Icon: Archive, color: "info", onSelect: () => archive(name) },\n ]}\n >\n <div className="row">{name}</div>\n </SwipeActions>\n))}\n</FieldGroup>',
7846
+ details: "Wrap the content of a row of a FieldGroup: the swipe row is the data-row of the group, so its separators and corners follow the card. The actions are solid capsules behind the content, 10px apart and 16px from the edge, as in the iOS 27 kit; the first action of a side stands at its edge. Swiped aside, the content becomes a capsule of the row&apos;s color on the background of the list. A swipe past half of the actions opens them, a press on an action runs it and closes the row, and a press on the content or anywhere outside closes it too; one row is open at a time. A swipe past the middle between the open actions and the far edge stretches the first action across the row, and letting go runs it."
7847
+ },
7848
+ {
7849
+ name: "Labels inside",
7850
+ code: '<SwipeActions\nlabelPlacement="inside"\nleading={[{ id: "pin", label: "Pin", Icon: Pin, color: "warning", onSelect: () => pin(name) }]}\ntrailing={[\n { id: "delete", label: "Delete", Icon: RemoveTrash, color: "danger", onSelect: () => remove(name) },\n { id: "archive", label: "Archive", Icon: Archive, color: "info", onSelect: () => archive(name) },\n]}\n>\n<div className="row">{name}</div>\n</SwipeActions>',
7851
+ details: "With labelPlacement=&quot;inside&quot; each capsule holds a smaller symbol and its label, as the kit&apos;s Symbol and Label row. An action without an icon always shows its label inside."
7852
+ },
7853
+ {
7854
+ name: "Labels below",
7855
+ code: '<SwipeActions\nlabelPlacement="below"\nleading={[{ id: "flag", label: "Flag", Icon: Flag, color: "warning", onSelect: () => flag(message) }]}\ntrailing={[\n { id: "delete", label: "Delete", Icon: RemoveTrash, color: "danger", onSelect: () => remove(message) },\n { id: "archive", label: "Archive", Icon: Archive, color: "info", onSelect: () => archive(message) },\n]}\n>\n<div className="row">\n <div>{message.from}</div>\n <div>{message.subject}</div>\n</div>\n</SwipeActions>',
7856
+ details: "With labelPlacement=&quot;below&quot; a lower capsule stands over its label, as the kit&apos;s Symbol and Label Tall row. It takes a row of 68px or taller, such as a row of two lines."
7857
+ }
7858
+ ],
7859
+ subComponents: [
7860
+ {
7861
+ name: "SwipeAction",
7862
+ props: [
7863
+ {
7864
+ name: "id",
7865
+ type: "string",
7866
+ description: "Unique ID of the action within its side"
7867
+ },
7868
+ {
7869
+ name: "label",
7870
+ type: "string",
7871
+ description: "Name of the action: shown inside or below the capsule (and on an action without an icon), and its accessible name always"
7872
+ },
7873
+ {
7874
+ name: "Icon",
7875
+ type: "ComponentType<SVGProps<SVGSVGElement>>",
7876
+ description: "Icon of the action"
7877
+ },
7878
+ {
7879
+ name: "color",
7880
+ type: '"primary" | "secondary" | "danger" | "success" | "info" | "discovery" | "caution" | "warning"',
7881
+ default: '"secondary"',
7882
+ description: "Solid color of the action"
7883
+ },
7884
+ {
7885
+ name: "onSelect",
7886
+ type: "() => void",
7887
+ description: "Called when the action is pressed, or by a full swipe when it is the first action of its side"
7888
+ }
7889
+ ]
7890
+ }
7891
+ ],
7892
+ docsUrl: "https://plexui.com/components/swipe-actions"
7893
+ },
7555
7894
  {
7556
7895
  slug: "switch",
7557
7896
  name: "Switch",
@@ -7668,7 +8007,7 @@ export default function SliderExample() {
7668
8007
  {
7669
8008
  slug: "tab-bar",
7670
8009
  name: "Tab Bar",
7671
- description: '"The tab bar of a phone screen: two to five sections at the bottom in a floating glass capsule, an optional search tab, minimizing while the content scrolls. Desktop layouts navigate with a Sidebar or Tabs, so a tab bar never renders from 768px up."',
8010
+ description: '"The tab bar of a phone screen: two to five sections at the bottom in a floating glass capsule, an optional search or prominent tab, minimizing while the content scrolls. Desktop layouts navigate with a Sidebar or Tabs, so a tab bar never renders from 768px up."',
7672
8011
  importPath: "@plexui/ui/components/TabBar",
7673
8012
  exports: [
7674
8013
  "TabBar"
@@ -7694,6 +8033,11 @@ export default function SliderExample() {
7694
8033
  type: "{ value: string; label?: string }",
7695
8034
  description: "A search tab in a glass circle of its own at the trailing end; label defaults to Search"
7696
8035
  },
8036
+ {
8037
+ name: "prominent",
8038
+ type: "{ value: string; label: string; icon: ReactNode }",
8039
+ description: "A prominent tab in a glass circle of its own at the trailing end, in place of the search circle; search then joins the capsule"
8040
+ },
7697
8041
  {
7698
8042
  name: "minimizeBehavior",
7699
8043
  type: '"never" | "onScrollDown"',
@@ -7762,17 +8106,30 @@ export default function SliderExample() {
7762
8106
  </TabBar>`,
7763
8107
  details: "With search the search tab takes a glass circle of its own at the trailing end, as the iOS 26 search tab does. The capsule of the other tabs keeps to the leading end at their width, as in the kit and in Photos: with two tabs it ends well before the circle, with three it reaches 8px from it. It is a tab like the others: show the search screen when its value is selected."
7764
8108
  },
8109
+ {
8110
+ name: "Prominent tab",
8111
+ code: `<TabBar
8112
+ value={tab}
8113
+ onValueChange={setTab}
8114
+ prominent={{ value: 'bag', label: 'Bag', icon: <ShoppingBag /> }}
8115
+ >
8116
+ <TabBar.Item value="home" label="Home" icon={<Home />} />
8117
+ <TabBar.Item value="calendar" label="Calendar" icon={<Calendar />} />
8118
+ <TabBar.Item value="photos" label="Photos" icon={<Images />} />
8119
+ </TabBar>`,
8120
+ details: "With prominent one destination leaves the capsule for a glass circle of its own at the trailing end, as the prominent tab of iOS 27 and its kit: one key destination, such as a cart or a library. It is still a tab, not an action: it shows a screen of its own when selected. A bar has one such circle, so prominent takes the place of the search circle; with both, search becomes the last tab of the capsule. The circle shows the symbol only, and label names the tab."
8121
+ },
7765
8122
  {
7766
8123
  name: "Minimize on scroll",
7767
8124
  code: `<TabBar value={tab} onValueChange={setTab} search={{ value: 'search' }} minimizeBehavior="onScrollDown">
7768
8125
  ...
7769
8126
  </TabBar>`,
7770
- details: 'With minimizeBehavior="onScrollDown" the bar shrinks to the selected tab while the content scrolls down and comes back when it scrolls up, as in iOS 26. Tap the minimized tab to bring the others back.'
8127
+ details: 'With minimizeBehavior="onScrollDown" the bar shrinks to the selected tab while the content scrolls down and comes back when it scrolls up, as in iOS 26. Minimized, the circles keep 28px from the sides and at least 28px from the bottom, as in the iOS 27 kit. Tap the minimized tab to bring the others back.'
7771
8128
  },
7772
8129
  {
7773
8130
  name: "Background",
7774
8131
  code: '<Page surface="grouped">\n<Toolbar background="hard">...</Toolbar>\n<Page.Content>...</Page.Content>\n<TabBar value={tab} onValueChange={setTab} background="hard">\n ...\n</TabBar>\n</Page>',
7775
- details: "background sets how the bar meets the content scrolled under it, as a Toolbar does: soft (the default) fades the canvas color in under the capsule, hard is an opaque band with a line on top, glass a translucent, blurred band with a hairline, and solid an opaque band with no line. Hard and glass show themselves while the content continues under the bar."
8132
+ details: "background sets how the bar meets the content scrolled under it, as a Toolbar does: soft (the default) leaves the content clear under and around the capsule, as native iOS 26 does, glass is a translucent, blurred band with a hairline, hard is an opaque band with a line on top, and solid an opaque band with no line. Glass and hard show themselves while the content continues under the bar."
7776
8133
  }
7777
8134
  ],
7778
8135
  subComponents: [
@@ -8204,7 +8561,7 @@ export default function SliderExample() {
8204
8561
  {
8205
8562
  name: "Overview",
8206
8563
  code: 'const [tab, setTab] = useState("overview")\nconst [disabled, setDisabled] = useState(false)\nconst [reportsDisabled, setReportsDisabled] = useState(false)\n\nreturn (\n<Tabs value={tab} onChange={setTab} disabled={disabled} aria-label="Demo tabs">\n <Tabs.Tab value="overview">Overview</Tabs.Tab>\n <Tabs.Tab value="analytics">Analytics</Tabs.Tab>\n <Tabs.Tab value="reports" disabled={reportsDisabled}>Reports</Tabs.Tab>\n <Tabs.Tab value="settings">Settings</Tabs.Tab>\n</Tabs>\n)',
8207
- 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. disabled affects the whole group; reportsDisabled disables only Reports. Arrow-key navigation skips disabled tabs without resetting the selected value. On a phone horizontal tabs of either variant are the iOS segmented control on glass, as toolbar buttons are: segments of one width in a 44px capsule, the selection a darker pill inside it with the primary label and the other labels secondary, filling the row of a phone screen; vertical tabs are an inset grouped list. So the phone view has no variant, flush, block, size and pill controls."
8564
+ 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. disabled affects the whole group, which on a phone shows at half opacity when horizontal, as a disabled iOS segmented control does; reportsDisabled disables only Reports. Arrow-key navigation skips disabled tabs without resetting the selected value. On a phone horizontal tabs of either variant are the iOS segmented control on glass, as toolbar buttons are: segments of one width in a 44px capsule, the selection a darker pill inside it with the primary label and the other labels secondary, filling the row of a phone screen; vertical tabs are an inset grouped list. So the phone view has no variant, flush, block, size and pill controls."
8208
8565
  },
8209
8566
  {
8210
8567
  name: "Segmented with icons",
@@ -8698,6 +9055,144 @@ return (
8698
9055
  ],
8699
9056
  docsUrl: "https://plexui.com/components/textarea"
8700
9057
  },
9058
+ {
9059
+ slug: "tip",
9060
+ name: "Tip",
9061
+ description: "A short message that points out a feature people might miss, inside the page or anchored to a control, as TipKit tips do.",
9062
+ importPath: "@plexui/ui/components/Tip",
9063
+ exports: [
9064
+ "Tip"
9065
+ ],
9066
+ props: [
9067
+ {
9068
+ name: "title",
9069
+ type: "ReactNode",
9070
+ description: "The tip's title: what the feature is"
9071
+ },
9072
+ {
9073
+ name: "description",
9074
+ type: "ReactNode",
9075
+ description: "One or two sentences on how to use it"
9076
+ },
9077
+ {
9078
+ name: "icon",
9079
+ type: "ReactNode",
9080
+ description: "A symbol or image that leads the message"
9081
+ },
9082
+ {
9083
+ name: "actions",
9084
+ type: "ReactNode",
9085
+ description: "Buttons for the tip's actions, side by side below the message"
9086
+ },
9087
+ {
9088
+ name: "mobileActions",
9089
+ type: '"rows" | "buttons"',
9090
+ default: '"rows"',
9091
+ description: "How the actions show at 767px and below: rows of text under a separator, or the buttons as given, each a full-width capsule"
9092
+ },
9093
+ {
9094
+ name: "onClose",
9095
+ type: "() => void",
9096
+ description: "Shows the Close button; called when it is clicked"
9097
+ },
9098
+ {
9099
+ name: "arrowEdge",
9100
+ type: '"top" | "bottom" | "left" | "right"',
9101
+ description: "Draws an arrow at this edge, pointing at what the tip explains"
9102
+ },
9103
+ {
9104
+ name: "className",
9105
+ type: "string",
9106
+ description: "CSS classes applied to the tip"
9107
+ },
9108
+ {
9109
+ name: "ref",
9110
+ type: "Ref<HTMLDivElement>",
9111
+ description: "Ref applied to the tip"
9112
+ }
9113
+ ],
9114
+ examples: [
9115
+ {
9116
+ name: "Overview",
9117
+ code: 'const [shown, setShown] = useState(true)\n\n{shown && (\n<Tip\n icon={<Bookmark />}\n title="Save it for later"\n description="Bookmarked articles wait in your reading list, ready offline on every device."\n onClose={() => setShown(false)}\n/>\n)}',
9118
+ details: "An inline tip takes its place in the layout. A symbol leads the message in the secondary color; onClose shows Close in the trailing corner. On a phone the tip is the opaque card of a grouped list, so place it on a grouped background."
9119
+ },
9120
+ {
9121
+ name: "Actions",
9122
+ code: '<Tip\nicon={<Bell />}\ntitle="Turn on notifications"\ndescription="Get a message when someone replies to your post or mentions you."\nactions={\n <>\n <Button color="primary" variant="soft" size="sm" pill>Not now</Button>\n <Button color="primary" size="sm" pill>Turn on</Button>\n </>\n}\n/>',
9123
+ details: "Actions sit side by side below the message, each as wide as the others. Use small pill buttons, as the kit does. On a phone each action is a row of text in its color under a separator, one under another, starting under the text, as iOS cards show them."
9124
+ },
9125
+ {
9126
+ name: "Buttons on a phone",
9127
+ code: '<Tip\nicon={<Bell />}\ntitle="Get alerts"\ndescription="Hear about severe weather where you are, even when the app is closed."\nmobileActions="buttons"\nactions={<Button color="primary" variant="soft" size="sm" pill>Turn on</Button>}\n/>',
9128
+ details: 'With mobileActions="buttons" the actions keep their buttons on a phone: each is a capsule as wide as the tip under the message, 48px tall with a 17px label, in its own color and variant, as the iOS card in Settings that opens the Support app shows. Use it for a card that asks for a permission, with one soft primary button.'
9129
+ },
9130
+ {
9131
+ name: "Arrow",
9132
+ code: '<Button color="secondary" variant="soft" uniform pill aria-label="Filter">\n<Filter />\n</Button>\n<Tip\narrowEdge="top"\ntitle="Filter the list"\ndescription="Show only the items from the last week, month or year."\n/>',
9133
+ details: "With arrowEdge an inline tip points at what it explains with the kit's arrow, centered on that edge. The tip leaves room for the arrow on that side."
9134
+ },
9135
+ {
9136
+ name: "Popover",
9137
+ code: 'const [open, setOpen] = useState(false)\n\n<Tip.Popover\nopen={open}\nonOpenChange={setOpen}\ntitle="Filter the list"\ndescription="Show only the items from the last week, month or year."\n>\n<Button color="secondary" variant="soft" onClick={() => setOpen(!open)}>\n <Filter />\n Filter\n</Button>\n</Tip.Popover>',
9138
+ details: "A popover tip floats next to its control with an arrow that points at it. The control stays usable and keeps the focus. Close, Escape or a press outside the tip and its control closes it, through onOpenChange. Show it when the control appears; here the button shows it."
9139
+ }
9140
+ ],
9141
+ subComponents: [
9142
+ {
9143
+ name: "Tip.Popover",
9144
+ description: "Takes title, description, icon, actions and mobileActions as Tip does, and the control it explains as its only child.",
9145
+ props: [
9146
+ {
9147
+ name: "open",
9148
+ type: "boolean",
9149
+ description: "Shows the tip (controlled)"
9150
+ },
9151
+ {
9152
+ name: "defaultOpen",
9153
+ type: "boolean",
9154
+ default: "false",
9155
+ description: "Shows the tip at first (uncontrolled)"
9156
+ },
9157
+ {
9158
+ name: "onOpenChange",
9159
+ type: "(open: boolean) => void",
9160
+ description: "Called when the tip opens or closes"
9161
+ },
9162
+ {
9163
+ name: "side",
9164
+ type: '"top" | "right" | "bottom" | "left"',
9165
+ default: '"bottom"',
9166
+ description: "The side of the control the tip shows on, turned when it does not fit"
9167
+ },
9168
+ {
9169
+ name: "align",
9170
+ type: '"start" | "center" | "end"',
9171
+ default: '"center"',
9172
+ description: "The alignment against the control"
9173
+ },
9174
+ {
9175
+ name: "sideOffset",
9176
+ type: "number",
9177
+ default: "4",
9178
+ description: "The distance from the control to the arrow's point, in pixels"
9179
+ },
9180
+ {
9181
+ name: "closeButton",
9182
+ type: "boolean",
9183
+ default: "true",
9184
+ description: "Shows the Close button"
9185
+ },
9186
+ {
9187
+ name: "className",
9188
+ type: "string",
9189
+ description: "CSS classes applied to the popover around the tip and its arrow"
9190
+ }
9191
+ ]
9192
+ }
9193
+ ],
9194
+ docsUrl: "https://plexui.com/components/tip"
9195
+ },
8701
9196
  {
8702
9197
  slug: "toast",
8703
9198
  name: "Toast",
@@ -8711,7 +9206,7 @@ return (
8711
9206
  {
8712
9207
  name: "Overview",
8713
9208
  code: 'toast("Inquiry ID copied")',
8714
- details: "On a phone a toast takes the corners, padding and text of an iOS notification banner, the system's own message at the top of the screen: 24px corners and 15pt text, with its buttons drawn as a medium phone Button, a 34px capsule in a 44px target. It stays a message of the page, with its own width and opaque surface."
9209
+ details: "On a phone a toast takes the corners, padding and text of an iOS notification banner, the system's own message at the top of the screen: 24px corners and 15pt text, with its buttons drawn as a medium phone Button, a 34px capsule in a 44px target. Under 600px it fills the width between 14px gutters past the safe area, as notifications do on an iOS phone; a compact toast keeps its own width. It stays a message of the page, with an opaque surface."
8715
9210
  },
8716
9211
  {
8717
9212
  name: "Variants",
@@ -8738,12 +9233,12 @@ return (
8738
9233
  {
8739
9234
  name: "Compact",
8740
9235
  code: 'import { toast, compact } from "@plexui/ui/components/Toast"\n\ntoast.success("Inquiry ID copied", compact)',
8741
- details: "Spread compact into any toast(...) options for a centered-text layout. Toast width shrinks to content by default regardless; the compact helper only adds center alignment for short status messages."
9236
+ details: "Spread compact into any toast(...) options for a centered-text layout. On a desktop a toast shrinks to its content anyway; on a phone, where a toast fills the width, a compact one keeps the width of its content. Both center its text."
8742
9237
  },
8743
9238
  {
8744
9239
  name: "Persistent",
8745
9240
  code: 'const id = toast("Backup scheduled for 2:00 AM UTC", {\ndescription: "This stays open until you dismiss it.",\nduration: Infinity,\naction: { label: "Dismiss", onClick: () => toast.dismiss(id) },\n})',
8746
- details: "Pass duration: Infinity for toasts that stay open until the user acts on them. There are no close (\xD7) affordances, so put a labelled action on the toast so dismissal is intentional and keyboard-accessible."
9241
+ details: "Pass duration: Infinity for toasts that stay open until the user acts on them. There are no close (\xD7) affordances, so put a labelled action on the toast so dismissal is intentional and keyboard-accessible. An error that waits like this is an alert (see Screen readers)."
8747
9242
  }
8748
9243
  ],
8749
9244
  subComponents: [
@@ -8765,13 +9260,14 @@ return (
8765
9260
  {
8766
9261
  name: "offset",
8767
9262
  type: "string | number | { top?; right?; bottom?; left? }",
8768
- description: "Distance from the viewport edge"
9263
+ default: "32px",
9264
+ description: "Distance from the viewport edge. Without a top or bottom edge here, the page's --toast-offset-top or --toast-offset-bottom sets it."
8769
9265
  },
8770
9266
  {
8771
9267
  name: "mobileOffset",
8772
9268
  type: "string | number | { top?; right?; bottom?; left? }",
8773
- default: "16px + safe area per edge",
8774
- description: "Mobile viewport gutter. Explicit edges override their safe defaults; omitted edges keep them."
9269
+ default: "16px top and bottom, 14px at the sides, past the safe area",
9270
+ description: "Gutter under 600px, where a toast fills the width between the side gutters. Explicit edges override their defaults; omitted edges keep them, and the page's --toast-mobile-offset-top or --toast-mobile-offset-bottom sets the top or bottom one."
8775
9271
  },
8776
9272
  {
8777
9273
  name: "gap",
@@ -8813,7 +9309,7 @@ return (
8813
9309
  {
8814
9310
  name: "background",
8815
9311
  type: '"soft" | "hard" | "glass" | "solid"',
8816
- default: '"soft"',
9312
+ default: '"glass" at the top, "soft" at the bottom',
8817
9313
  description: 'How the bar meets the content scrolled under it, see Background. "edge" is the earlier name of "soft"'
8818
9314
  },
8819
9315
  {
@@ -8837,22 +9333,22 @@ return (
8837
9333
  {
8838
9334
  name: "Group",
8839
9335
  code: '<Toolbar>\n<Toolbar.Leading>\n <NavigationButton action="close" />\n</Toolbar.Leading>\n<Toolbar.Trailing>\n <Button color="secondary" variant="soft" uniform pill aria-label="Details"><InfoCircle /></Button>\n <Toolbar.Group aria-label="Order actions">\n <Button color="secondary" variant="soft" uniform pill aria-label="Translate"><Translate /></Button>\n <Button color="secondary" variant="soft" uniform pill aria-label="More"><DotsHorizontal /></Button>\n </Toolbar.Group>\n</Toolbar.Trailing>\n</Toolbar>',
8840
- details: "Toolbar.Group puts related actions in one capsule. A single action keeps a capsule of its own. Name each icon button with aria-label."
9336
+ details: "Toolbar.Group puts related actions in one capsule. A single action keeps a capsule of its own. A ButtonGroup in a toolbar is the same capsule. Name each icon button with aria-label."
8841
9337
  },
8842
9338
  {
8843
9339
  name: "Search",
8844
- code: '<Toolbar>\n<Toolbar.Content fill>\n <Input variant="soft" pill placeholder="Search" aria-label="Search orders" startAdornment={<Search />} />\n</Toolbar.Content>\n<Toolbar.Trailing>\n <NavigationButton action="close" aria-label="Close search" />\n</Toolbar.Trailing>\n</Toolbar>',
9340
+ code: '<Toolbar>\n<Toolbar.Content fill>\n <Input variant="soft" pill placeholder="Search" aria-label="Search orders" startAdornment={<SearchSm />} />\n</Toolbar.Content>\n<Toolbar.Trailing>\n <NavigationButton action="close" aria-label="Close search" />\n</Toolbar.Trailing>\n</Toolbar>',
8845
9341
  details: "Toolbar.Content fill stretches a search field between the leading and trailing items. The field takes the row height and the material of the bar buttons."
8846
9342
  },
8847
9343
  {
8848
9344
  name: "Bottom",
8849
- code: '<Page surface="grouped">\n<Toolbar>\n <Toolbar.Title>Orders</Toolbar.Title>\n</Toolbar>\n<Page.Content>...</Page.Content>\n<Toolbar placement="bottom">\n <Toolbar.Leading>\n <Button color="secondary" variant="soft" uniform pill aria-label="Filter"><Filter /></Button>\n </Toolbar.Leading>\n <Toolbar.Content>\n <Button color="primary" pill>New order</Button>\n </Toolbar.Content>\n <Toolbar.Trailing>\n <Button color="secondary" variant="soft" uniform pill aria-label="Search"><Search /></Button>\n </Toolbar.Trailing>\n</Toolbar>\n</Page>\n\n<Toolbar placement="bottom">\n<Toolbar.Content fill>\n <Button color="primary" pill block><Plus /> New order</Button>\n</Toolbar.Content>\n</Toolbar>',
8850
- details: `placement="bottom" sets the bar at the bottom of the page with 48px targets and the device's bottom safe area. Toolbar.Content centers a main action; with fill a search field or a single action takes the whole row. The page content scrolls under both bars.`
9345
+ code: '<Page surface="grouped">\n<Toolbar>\n <Toolbar.Title>Orders</Toolbar.Title>\n</Toolbar>\n<Page.Content>...</Page.Content>\n<Toolbar placement="bottom">\n <Toolbar.Leading>\n <Button color="secondary" variant="soft" uniform pill aria-label="Filter"><Filter /></Button>\n </Toolbar.Leading>\n <Toolbar.Content>\n <Button color="primary" pill>New order</Button>\n </Toolbar.Content>\n <Toolbar.Trailing>\n <Button color="secondary" variant="soft" uniform pill aria-label="Search"><Search /></Button>\n </Toolbar.Trailing>\n</Toolbar>\n</Page>\n\n<Toolbar placement="bottom">\n<Toolbar.Content fill>\n <Input variant="soft" pill placeholder="Search" aria-label="Search orders" startAdornment={<SearchSm />} />\n</Toolbar.Content>\n<Toolbar.Trailing>\n <Button color="secondary" variant="soft" uniform pill iconSize="sm" aria-label="New order"><PlusLg /></Button>\n</Toolbar.Trailing>\n</Toolbar>\n\n<Toolbar placement="bottom">\n<Toolbar.Content fill>\n <Button color="primary" pill block><Plus /> New order</Button>\n</Toolbar.Content>\n</Toolbar>',
9346
+ details: `placement="bottom" sets the bar at the bottom of the page with 48px targets and the device's bottom safe area. Toolbar.Content centers a main action; with fill a search field or a single action takes the whole row. The 48px items stand 28px from the sides and above the home indicator, 12px apart, and a bottom bar draws no band: the content runs under and around its glass, as in iOS 26 Mail and Contacts. A search field holds SearchSm, set in Regular; a plus next to it takes iconSize="sm", as Contacts draws it smaller than the other symbols. The page content scrolls under both bars.`
8851
9347
  },
8852
9348
  {
8853
9349
  name: "Background",
8854
9350
  code: '<Page surface="grouped">\n<Toolbar background="hard">\n <Toolbar.Leading>\n <NavigationButton action="back" />\n </Toolbar.Leading>\n <Toolbar.Title>Orders</Toolbar.Title>\n</Toolbar>\n<Page.Content>...</Page.Content>\n<Toolbar placement="bottom" background="hard">\n <Toolbar.Content>\n <Button color="primary" pill>New order</Button>\n </Toolbar.Content>\n</Toolbar>\n</Page>',
8855
- details: "background sets how a bar meets the content scrolled under it, after the scroll edge effect of iOS 26. soft, the default, covers the content under the controls with the canvas color and fades it out past them over a light blur, with no line. hard is an opaque bar of the canvas color with a line. glass is a translucent, blurred bar with a hairline. solid is an opaque bar with no line. Hard and glass show themselves once content is under the bar: under the top bar after the content has scrolled, over the bottom bar while it continues below. Page.Header, Page.Footer, the bars of a Sheet and TabBar take the same values."
9351
+ details: "background sets how a bar meets the content scrolled under it. glass, the default of a top bar, is a translucent, blurred bar with a hairline. soft, the default of a bottom bar, is the scroll edge effect of iOS 26 and draws no line: a top bar with a title covers the content under its controls with the canvas color and fades it out past them over a light blur, while a top bar of controls alone on a page covers only the status bar, and a bottom bar on a page leaves the content clear under and around its controls, as native iOS 26 does (Contacts, Notes, Mail); each glass control stands out by itself. A Sheet's bottom bar fades its content into the panel. hard is an opaque bar of the canvas color with a line. solid is an opaque bar with no line. Glass and hard show themselves once content is under the bar: under the top bar after the content has scrolled, over the bottom bar while it continues below. Page.Header, Page.Footer, the bars of a Sheet and TabBar take the same values."
8856
9352
  },
8857
9353
  {
8858
9354
  name: "Disabled",
@@ -8999,7 +9495,7 @@ return (
8999
9495
  docsUrl: "https://plexui.com/components/tooltip"
9000
9496
  }
9001
9497
  ],
9002
- generatedAt: "2026-11-08T18:56:38.218Z"
9498
+ generatedAt: "2026-11-09T09:41:06.946Z"
9003
9499
  };
9004
9500
 
9005
9501
  // src/data/manifest-vue.json
@@ -9041,7 +9537,7 @@ var manifest_vue_default = {
9041
9537
  "outline",
9042
9538
  "ghost"
9043
9539
  ],
9044
- componentCount: 58,
9540
+ componentCount: 62,
9045
9541
  components: [
9046
9542
  {
9047
9543
  slug: "accordion",
@@ -10127,6 +10623,11 @@ const label = (variant: string) => variant.charAt(0).toUpperCase() + variant.sli
10127
10623
  default: '"horizontal"',
10128
10624
  description: "Direction of the joined items"
10129
10625
  },
10626
+ {
10627
+ name: "mobile-material",
10628
+ type: '"glass" | "flat"',
10629
+ description: 'Phone surface with touch.css: "glass" makes the group one glass capsule outside a Toolbar, "flat" keeps the joined buttons in one'
10630
+ },
10130
10631
  {
10131
10632
  name: "class",
10132
10633
  type: "string",
@@ -10136,12 +10637,13 @@ const label = (variant: string) => variant.charAt(0).toUpperCase() + variant.sli
10136
10637
  examples: [
10137
10638
  {
10138
10639
  name: "Overview",
10139
- code: '<ButtonGroup>\n <ButtonGroup>\n <Button variant="outline" color="secondary" :size="size" uniform aria-label="Go back">\n <ArrowLeftSm />\n </Button>\n </ButtonGroup>\n <ButtonGroup>\n <Button variant="outline" color="secondary" :size="size">Pin</Button>\n <Button variant="outline" color="secondary" :size="size">Tag</Button>\n </ButtonGroup>\n <ButtonGroup>\n <Button variant="outline" color="secondary" :size="size">Bookmark</Button>\n <Button variant="outline" color="secondary" :size="size" uniform aria-label="More options">\n <DotsHorizontal />\n </Button>\n </ButtonGroup>\n</ButtonGroup>'
10640
+ code: '<!-- Desktop -->\n<ButtonGroup>\n <ButtonGroup>\n <Button variant="outline" color="secondary" :size="size" uniform aria-label="Go back">\n <ArrowLeftSm />\n </Button>\n </ButtonGroup>\n <ButtonGroup>\n <Button variant="outline" color="secondary" :size="size">Pin</Button>\n <Button variant="outline" color="secondary" :size="size">Tag</Button>\n </ButtonGroup>\n <ButtonGroup>\n <Button variant="outline" color="secondary" :size="size">Bookmark</Button>\n <Button variant="outline" color="secondary" :size="size" uniform aria-label="More options">\n <DotsHorizontal />\n </Button>\n </ButtonGroup>\n</ButtonGroup>\n\n<!-- Phone: the bars of a note -->\n<Page>\n <Toolbar>\n <ToolbarLeading>\n <NavigationButton action="back" />\n </ToolbarLeading>\n <ToolbarTrailing>\n <ButtonGroup aria-label="Note actions">\n <Button color="secondary" variant="soft" uniform pill aria-label="Pin"><Pin /></Button>\n <Button color="secondary" variant="soft" uniform pill aria-label="Tag"><Tag /></Button>\n <Button color="secondary" variant="soft" uniform pill aria-label="More"><DotsHorizontal /></Button>\n </ButtonGroup>\n </ToolbarTrailing>\n </Toolbar>\n <PageContent>\n <div class="flex flex-col gap-3 text-[17px] leading-[22px]">\n <p class="text-[28px] leading-[34px] font-semibold">Weekend trip</p>\n \u2026\n </div>\n </PageContent>\n <Toolbar placement="bottom">\n <ToolbarLeading>\n <ButtonGroup aria-label="Editing tools">\n <Button color="secondary" variant="soft" uniform pill aria-label="Checklist"><NotebookCheck /></Button>\n <Button color="secondary" variant="soft" uniform pill aria-label="Attach"><Clip /></Button>\n <Button color="secondary" variant="soft" uniform pill aria-label="Markup"><Pens /></Button>\n </ButtonGroup>\n </ToolbarLeading>\n <ToolbarTrailing>\n <Button color="secondary" variant="soft" uniform pill aria-label="New note"><PencilSquare /></Button>\n </ToolbarTrailing>\n </Toolbar>\n</Page>',
10641
+ details: "On a phone the example is the bars of a note, as in iOS Notes: Back alone, the note's actions in one glass capsule on top, the editing tools in one at the bottom beside New Note. A ButtonGroup in a Toolbar is that capsule, the Button Group of the iOS 27 kit: 44px buttons 12px apart in a 160px capsule on top, 48px ones 6px apart in a 164px capsule at the bottom, the symbols in the primary label color. Groups side by side stand apart as the bar's items do."
10140
10642
  },
10141
10643
  {
10142
10644
  name: "Orientation",
10143
- code: '<ButtonGroup orientation="vertical" aria-label="Zoom controls">\n <Button variant="outline" color="secondary" :size="size" uniform aria-label="Zoom in">\n <PlusSm />\n </Button>\n <Button variant="outline" color="secondary" :size="size" uniform aria-label="Zoom out">\n <Minus />\n </Button>\n</ButtonGroup>',
10144
- details: 'Pass orientation="vertical" to stack the buttons, useful for + / \u2212 steppers, zoom controls, or any vertical toolbar. On a phone with touch.css a group of icon-only buttons keeps their 44px at each size shown, so the phone view leaves out the size control.'
10645
+ code: '<!-- Desktop -->\n<ButtonGroup orientation="vertical" aria-label="Zoom controls">\n <Button variant="outline" color="secondary" :size="size" uniform aria-label="Zoom in">\n <PlusSm />\n </Button>\n <Button variant="outline" color="secondary" :size="size" uniform aria-label="Zoom out">\n <Minus />\n </Button>\n</ButtonGroup>\n\n<!-- Phone: zoom controls over a canvas -->\n<ButtonGroup orientation="vertical" mobile-material="glass" aria-label="Zoom controls" class="absolute top-2 right-4">\n <Button color="secondary" variant="soft" uniform pill aria-label="Zoom in"><Plus /></Button>\n <Button color="secondary" variant="soft" uniform pill aria-label="Zoom out"><Minus /></Button>\n</ButtonGroup>',
10646
+ details: 'Pass orientation="vertical" to stack the buttons, useful for + / \u2212 steppers, zoom controls, or any vertical toolbar. On a phone the example is zoom controls floating over a canvas, as map controls do: with mobile-material="glass" the group is the vertical Button Group of the iOS 27 kit, a 48px wide capsule of 48px buttons 6px apart. In the content a group of icon-only buttons keeps their 44px at each size, so the phone view leaves out the size control.'
10145
10647
  },
10146
10648
  {
10147
10649
  name: "Size",
@@ -10156,7 +10658,7 @@ const label = (variant: string) => variant.charAt(0).toUpperCase() + variant.sli
10156
10658
  {
10157
10659
  name: "Separator",
10158
10660
  code: '<ButtonGroup>\n <Button variant="soft" color="secondary" :size="size"><Undo /> Undo</Button>\n <ButtonGroupSeparator />\n <Button variant="soft" color="secondary" :size="size">Redo <Redo /></Button>\n</ButtonGroup>',
10159
- details: 'Drop a ButtonGroupSeparator between items to add a visible divider, commonly used for Undo / Redo pairs or any "two related actions" toolbar.'
10661
+ details: 'Drop a ButtonGroupSeparator between items to add a visible divider, commonly used for Undo / Redo pairs or any "two related actions" toolbar. A glass group on a phone draws no line, as the capsule of iOS has none.'
10160
10662
  },
10161
10663
  {
10162
10664
  name: "Split",
@@ -11170,7 +11672,7 @@ const color = ref('#be38f3')
11170
11672
  {
11171
11673
  name: "Overview",
11172
11674
  code: '<ContextMenu>\n <ContextMenuTrigger>\n <div tabindex="0">Groceries</div>\n </ContextMenuTrigger>\n <ContextMenuContent>\n <MenuItem @select="copy"><Copy /> Copy</MenuItem>\n <MenuItem @select="share"><Share /> Share</MenuItem>\n <MenuItem @select="pin"><Pin /> Pin</MenuItem>\n <MenuSeparator />\n <MenuItem danger @select="remove"><RemoveTrash /> Delete</MenuItem>\n </ContextMenuContent>\n</ContextMenu>',
11173
- details: "Wrap the element in ContextMenuTrigger: it keeps its own role and focus. On a touch screen, hold for half a second: the element stays clear above the dimmed, blurred page and the menu opens 18px below it, at the edge on the side of the touch. The press that opened the menu does not activate the element. On a phone the rows sit on the grouped background of an iOS list."
11675
+ details: "Wrap the element in ContextMenuTrigger: it keeps its own role and focus. On a touch screen, hold for half a second: the element stays clear above the dimmed, blurred page and the menu opens 16px below it, as in the iOS 27 kit, at the edge on the side of the touch. The press that opened the menu does not activate the element. On a phone the rows sit on the grouped background of an iOS list."
11174
11676
  },
11175
11677
  {
11176
11678
  name: "Rounded elements",
@@ -12016,7 +12518,7 @@ const disabled = ref(false);
12016
12518
  },
12017
12519
  {
12018
12520
  name: "Text field",
12019
- code: `<Dialog v-model:open="open" @update:open="(next) => { if (next) name = 'New folder' }">
12521
+ code: `<Dialog v-model:open="open" @update:open="(next) => { if (next) folderName = 'New folder' }">
12020
12522
  <DialogTrigger>
12021
12523
  <Button color="secondary" variant="outline">New folder</Button>
12022
12524
  </DialogTrigger>
@@ -12026,13 +12528,13 @@ const disabled = ref(false);
12026
12528
  <DialogDescription>Enter a name for this folder.</DialogDescription>
12027
12529
  </DialogHeader>
12028
12530
  <DialogBody>
12029
- <Input ref="field" v-model="name" aria-label="Folder name" variant="soft" size="3xl" pill @clear="name = ''" />
12531
+ <Input ref="field" v-model:value="folderName" aria-label="Folder name" variant="soft" size="3xl" pill @clear="folderName = ''" />
12030
12532
  </DialogBody>
12031
12533
  <DialogFooter>
12032
12534
  <DialogClose>
12033
12535
  <Button color="primary" variant="soft">Cancel</Button>
12034
12536
  </DialogClose>
12035
- <Button color="primary" :disabled="!name.trim()" @click="open = false">Create</Button>
12537
+ <Button color="primary" :disabled="!folderName.trim()" @click="open = false">Create</Button>
12036
12538
  </DialogFooter>
12037
12539
  </DialogContent>
12038
12540
  </Dialog>`,
@@ -12164,7 +12666,7 @@ const rows = [
12164
12666
  {
12165
12667
  name: "Scrolling body",
12166
12668
  code: '<Dialog v-model:open="open">\n <DialogTrigger>\n <Button color="secondary" variant="outline">Review terms</Button>\n </DialogTrigger>\n <DialogContent mobile-variant="sheet" @open-auto-focus="$event.preventDefault(); $refs.heading.focus({ preventScroll: true })">\n <DialogHeader>\n <div ref="heading" tabindex="-1" class="outline-none"><DialogTitle>Terms of service</DialogTitle></div>\n </DialogHeader>\n <DialogBody>\n <div class="flex flex-col gap-4 text-sm text-secondary max-md:text-[length:var(--dialog-mobile-type-size)] max-md:leading-[var(--dialog-mobile-type-leading)]">\n <p v-for="(term, index) in [...terms, ...terms]" :key="index">{{ term }}</p>\n </div>\n </DialogBody>\n <DialogFooter>\n <DialogClose>\n <Button color="secondary" variant="soft">Decline</Button>\n </DialogClose>\n <Button color="primary" @click="open = false">Accept</Button>\n </DialogFooter>\n </DialogContent>\n</Dialog>',
12167
- details: "Put long content in DialogBody: only the body scrolls, while the header and footer stay in view. Without it the whole panel scrolls. On a phone, background on DialogHeader and DialogFooter sets how the Sheet's bars meet the body scrolled under them, as a Toolbar's do: soft by default, or hard, glass or solid."
12669
+ details: "Put long content in DialogBody: only the body scrolls, while the header and footer stay in view. Without it the whole panel scrolls. On a phone, background on DialogHeader and DialogFooter sets how the Sheet's bars meet the body scrolled under them, as a Toolbar's do: by default glass on the header and soft on the footer, or hard or solid."
12168
12670
  },
12169
12671
  {
12170
12672
  name: "Placement",
@@ -12226,6 +12728,59 @@ const open = ref(false);
12226
12728
  <template>
12227
12729
  <SetupChoicesSheet v-model:open="open" layout="grouped" @add="(method) => console.log(method.id)" />
12228
12730
  </template>`
12731
+ },
12732
+ {
12733
+ name: "Steps",
12734
+ code: `<Dialog v-model:open="open" @update:open="(next) => { if (!next) { step = 'email'; direction = 'forward' } }">
12735
+ <DialogTrigger>
12736
+ <Button color="secondary" variant="outline">Sign in</Button>
12737
+ </DialogTrigger>
12738
+ <DialogContent mobile-variant="sheet" :width="420">
12739
+ <DialogHeader>
12740
+ <DialogTitle>Sign in</DialogTitle>
12741
+ </DialogHeader>
12742
+ <DialogBody>
12743
+ <DialogSteps :step="step" :direction="direction">
12744
+ <Field v-if="step === 'email'" label="Email" description="We send a code to this address.">
12745
+ <Input v-model:value="email" type="email" placeholder="you@example.com" />
12746
+ </Field>
12747
+ <Field v-else label="Code" :description="'Enter the code we sent to ' + (email || 'your email') + '.'">
12748
+ <Input inputmode="numeric" autocomplete="one-time-code" placeholder="6-digit code" />
12749
+ </Field>
12750
+ </DialogSteps>
12751
+ </DialogBody>
12752
+ <DialogFooter>
12753
+ <Button v-if="step === 'code'" color="primary" variant="soft" @click="direction = 'back'; step = 'email'">Back</Button>
12754
+ <Button v-if="step === 'email'" color="primary" @click="direction = 'forward'; step = 'code'">Continue</Button>
12755
+ <Button v-else color="primary" @click="open = false">Verify</Button>
12756
+ </DialogFooter>
12757
+ </DialogContent>
12758
+ </Dialog>`,
12759
+ details: `Put the steps of a flow in DialogSteps: a new step comes in from the trailing edge over the one before, which moves a third of the way out under it, as a push on iOS. direction="back" reverses it for a return to a step before. The new step starts at its top, the steps reach the panel's edges, and focus in the step that leaves moves to the one that comes in. With reduced motion the step changes at once.`
12760
+ },
12761
+ {
12762
+ name: "Keep it open",
12763
+ code: `<Dialog v-model:open="open" @update:open="(next) => { if (!next) { profileName = 'Mia'; held = false } }">
12764
+ <DialogTrigger>
12765
+ <Button color="secondary" variant="outline">Edit profile</Button>
12766
+ </DialogTrigger>
12767
+ <DialogContent :width="420" @escape-key-down="(event) => { if (profileName !== 'Mia') { event.preventDefault(); held = true } }" @interact-outside="(event) => { if (profileName !== 'Mia') { event.preventDefault(); held = true } }">
12768
+ <DialogHeader>
12769
+ <DialogTitle>Edit profile</DialogTitle>
12770
+ <DialogDescription>{{ held ? 'Save or discard your changes to close.' : 'Escape and a press outside close it until you change the name.' }}</DialogDescription>
12771
+ </DialogHeader>
12772
+ <Field label="Name">
12773
+ <Input v-model:value="profileName" @update:value="held = false" />
12774
+ </Field>
12775
+ <DialogFooter>
12776
+ <DialogClose>
12777
+ <Button color="primary" variant="soft">Discard</Button>
12778
+ </DialogClose>
12779
+ <Button color="primary" @click="open = false">Save</Button>
12780
+ </DialogFooter>
12781
+ </DialogContent>
12782
+ </Dialog>`,
12783
+ details: "Escape and a press or a focus outside ask @escape-key-down and @interact-outside first: event.preventDefault() keeps the dialog open, such as for a form with unsaved changes, or for a press on a developer panel over the page. The Close button and DialogClose still close it."
12229
12784
  }
12230
12785
  ],
12231
12786
  subComponents: [
@@ -12280,6 +12835,38 @@ const open = ref(false);
12280
12835
  name: "@close-auto-focus",
12281
12836
  type: "(event: Event) => void",
12282
12837
  description: "Called when focus returns on close; call event.preventDefault() to keep it elsewhere"
12838
+ },
12839
+ {
12840
+ name: "@escape-key-down",
12841
+ type: "(event: KeyboardEvent) => void",
12842
+ description: "Called on Escape before the dialog closes; call event.preventDefault() to keep it open"
12843
+ },
12844
+ {
12845
+ name: "@interact-outside",
12846
+ type: "(event: DialogInteractOutsideEvent) => void",
12847
+ description: "Called on a press or a focus outside the dialog before it closes; call event.preventDefault() to keep it open. event.detail.originalEvent is the browser's event"
12848
+ },
12849
+ {
12850
+ name: "z-index",
12851
+ type: "number",
12852
+ default: "50",
12853
+ description: "Stack order of the overlay and the dialog, for a page whose own layers sit above 50"
12854
+ },
12855
+ {
12856
+ name: "root-props",
12857
+ type: "DialogLayerProps",
12858
+ description: "class, style and data attributes for the fixed layer that holds the panel. Data attributes on DialogContent land there too"
12859
+ },
12860
+ {
12861
+ name: "overlay-props",
12862
+ type: "DialogLayerProps",
12863
+ description: "class, style and data attributes for the overlay"
12864
+ },
12865
+ {
12866
+ name: "layout",
12867
+ type: '"auto" | "phone"',
12868
+ default: '"auto"',
12869
+ description: "phone lays the dialog out as on a phone at any window width, with the dialogs it opens. Inside a PhoneLayout it always is"
12283
12870
  }
12284
12871
  ]
12285
12872
  },
@@ -12299,6 +12886,28 @@ const open = ref(false);
12299
12886
  description: "Action layout at 767px and below. Defaults to inline for alert, stacked otherwise. Inline actions wrap when they do not fit"
12300
12887
  }
12301
12888
  ]
12889
+ },
12890
+ {
12891
+ name: "DialogSteps",
12892
+ description: "the steps of a flow, such as a sign-in that asks for an email and then a code. Place it in DialogBody.",
12893
+ props: [
12894
+ {
12895
+ name: "step",
12896
+ type: "string | number",
12897
+ description: "The step on screen. A new value brings its content in"
12898
+ },
12899
+ {
12900
+ name: "direction",
12901
+ type: '"forward" | "back"',
12902
+ default: '"forward"',
12903
+ description: "forward brings the new step in from the trailing edge over the one before; back returns to a step before it"
12904
+ },
12905
+ {
12906
+ name: "class",
12907
+ type: "string",
12908
+ description: "Additional CSS class on the steps"
12909
+ }
12910
+ ]
12302
12911
  }
12303
12912
  ],
12304
12913
  docsUrl: "https://plexui.com/vue/components/dialog"
@@ -13317,6 +13926,46 @@ const removable = ref(true);
13317
13926
  ],
13318
13927
  docsUrl: "https://plexui.com/vue/components/floating-label-input"
13319
13928
  },
13929
+ {
13930
+ slug: "index-bar",
13931
+ name: "Index Bar",
13932
+ description: '"The section index of a phone list: a column of letters at the trailing edge that jumps to a section under the finger, as the iOS section index does. Desktop lists have no index, so an index bar never renders from 768px up."',
13933
+ importPath: "@plexui/vue/components/IndexBar",
13934
+ exports: [
13935
+ "IndexBar"
13936
+ ],
13937
+ props: [
13938
+ {
13939
+ name: "items",
13940
+ type: "string[]",
13941
+ description: "The titles of the sections, in order: usually letters, such as A to Z and #"
13942
+ },
13943
+ {
13944
+ name: "id",
13945
+ type: "string",
13946
+ description: "The id of the index bar"
13947
+ },
13948
+ {
13949
+ name: "aria-label",
13950
+ type: "string",
13951
+ default: '"Section index"',
13952
+ description: "Name of the index bar"
13953
+ },
13954
+ {
13955
+ name: "aria-controls",
13956
+ type: "string",
13957
+ description: "The id of the list the index scrolls"
13958
+ }
13959
+ ],
13960
+ examples: [
13961
+ {
13962
+ name: "Overview",
13963
+ code: '<Page surface="grouped">\n <Toolbar>\n <ToolbarTitle>Countries</ToolbarTitle>\n </Toolbar>\n <PageContent ref="content">\n <FieldGroup v-for="section in sections" :key="section.letter" :title="section.letter" :data-section="section.letter">\n ...\n </FieldGroup>\n </PageContent>\n <IndexBar :items="sections.map((section) => section.letter)" @select="(letter) => scrollToSection(content, letter)" />\n</Page>',
13964
+ details: "Put the bar in a Page, or in any positioned container that holds the list: it runs down the trailing edge, centred between the bars of a Page (or the safe areas), as iOS centres its index, its letters 14px apart and centred 8px from the edge, in the info color, as native iOS draws its tint (Contacts and the iOS 27 kit). The rows keep the margins of the list, and the letters sit in the trailing one, as in Contacts. A press shows the section of the letter under the finger, and a drag moves through the sections letter by letter; the select event receives the title and its index, and the list scrolls itself there. Where the letters do not fit, as in a short sheet, every other letter gives way to a dot, as UIKit&apos;s index does, and a drag over a dot still reaches the letters behind it."
13965
+ }
13966
+ ],
13967
+ docsUrl: "https://plexui.com/vue/components/index-bar"
13968
+ },
13320
13969
  {
13321
13970
  slug: "indicators",
13322
13971
  name: "Indicators",
@@ -14911,12 +15560,12 @@ const errorMessage = ref(false);
14911
15560
  </Menu>
14912
15561
  </ToolbarTrailing>
14913
15562
  </Toolbar>`,
14914
- details: "A folder's menu, as Files opens it on iOS: actions with their icons, then the view and the sort order as radio items, Date with its order under it (description) while it is the chosen sort, and Group By in a submenu. Turn off icons for the same menu in plain text, and checkmarks for the folder's actions alone, a shorter menu with no checkmark column. pill on MenuContent makes a rounder list with fully rounded rows, for menus beside pill controls. On a phone each example is a screen whose menu opens from the More button of its top bar, as iOS opens the menu of an album in Photos. There the panel covers the button, its top and end at the button's top and end, and is the 238px panel of the iOS kit, so the phone code leaves out width, min-width and size. A pill menu is the same panel there, so the phone view leaves out pill."
15563
+ details: "A folder's menu, as Files opens it on iOS: actions with their icons, then the view and the sort order as radio items, Date with its order under it (description) while it is the chosen sort, and Group By in a submenu. Turn off icons for the same menu in plain text, and checkmarks for the folder's actions alone, a shorter menu with no checkmark column. pill on MenuContent makes a rounder list with fully rounded rows, for menus beside pill controls. On a phone each example is a screen whose menu opens from the More button of its top bar, as iOS opens the menu of an album in Photos. There the panel covers the button, its top and end at the button's top and end, and is the 250px panel of iOS menus, so the phone code leaves out width, min-width and size. A pill menu is the same panel there, so the phone view leaves out pill."
14915
15564
  },
14916
15565
  {
14917
15566
  name: "Natural sizing",
14918
15567
  code: '<Menu>\n <MenuTrigger>\n <Button color="primary" size="lg" variant="ghost" class="font-semibold gap-1.5"><Tools /> Tools</Button>\n </MenuTrigger>\n <MenuContent min-width="auto">\n <MenuItem @select="() => {}"><Functions :height="18" :width="18" /> Function</MenuItem>\n <MenuItem @select="() => {}"><Search :height="18" :width="18" /> File Search</MenuItem>\n <MenuItem @select="() => {}"><Globe :height="18" :width="18" /> Web Search</MenuItem>\n <MenuItem @select="() => {}"><Code :height="18" :width="18" /> Code Interpreter</MenuItem>\n <MenuItem @select="() => {}"><ImageSquare :height="18" :width="18" /> Image Generation</MenuItem>\n </MenuContent>\n</Menu>',
14919
- details: `Menu supports several sizing strategies:Natural sizing: use min-width="auto" to have the menu fit naturally to the size of the content.Fixed size: use width and min-width to set the menu to a fixed size, regardless of content size.Flexible sizing: min-width 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: apply max-width to the children of MenuContent. maxWidth isn't exposed as a prop because it's reserved for the custom properties used in collision detection.Desktop only: on a phone a menu is the iOS panel, at least the 238px of the iOS kit, whatever these sizes say.`
15568
+ details: `Menu supports several sizing strategies:Natural sizing: use min-width="auto" to have the menu fit naturally to the size of the content.Fixed size: use width and min-width to set the menu to a fixed size, regardless of content size.Flexible sizing: min-width 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: apply max-width to the children of MenuContent. maxWidth isn't exposed as a prop because it's reserved for the custom properties used in collision detection.Desktop only: on a phone a menu is the 250px panel of iOS menus, unless it sets a width or a larger minimum width of its own.`
14920
15569
  },
14921
15570
  {
14922
15571
  name: "Horizontal (icons only)",
@@ -14935,8 +15584,8 @@ const errorMessage = ref(false);
14935
15584
  },
14936
15585
  {
14937
15586
  name: "Grouped items",
14938
- code: '<!-- Desktop -->\n<Menu>\n <MenuTrigger>\n <Button color="primary" size="lg" variant="ghost" class="font-semibold gap-1.5"><FileDocument /> Templates <ChevronDownMd /></Button>\n </MenuTrigger>\n <MenuContent :min-width="220">\n <MenuGroup>\n <MenuLabel>Active</MenuLabel>\n <MenuItem @select="() => {}">Welcome email</MenuItem>\n <MenuItem @select="() => {}">Order confirmation</MenuItem>\n <MenuItem @select="() => {}">Password reset</MenuItem>\n </MenuGroup>\n <MenuSeparator />\n <MenuGroup>\n <MenuLabel>Draft</MenuLabel>\n <MenuItem @select="() => {}">New promo campaign</MenuItem>\n <MenuItem @select="() => {}">Account deactivation</MenuItem>\n </MenuGroup>\n </MenuContent>\n</Menu>\n\n<!-- Phone: the More button of the top bar opens the menu -->\n<Toolbar title-style="large">\n <ToolbarTitle>Grouped items</ToolbarTitle>\n <ToolbarTrailing>\n <Menu>\n <MenuTrigger>\n <Button color="secondary" variant="soft" uniform pill aria-label="More"><DotsHorizontal /></Button>\n </MenuTrigger>\n <MenuContent align="end">\n <MenuGroup>\n <MenuLabel>Active</MenuLabel>\n <MenuItem @select="() => {}">Welcome email</MenuItem>\n <MenuItem @select="() => {}">Order confirmation</MenuItem>\n <MenuItem @select="() => {}">Password reset</MenuItem>\n </MenuGroup>\n <MenuSeparator />\n <MenuGroup>\n <MenuLabel>Draft</MenuLabel>\n <MenuItem @select="() => {}">New promo campaign</MenuItem>\n <MenuItem @select="() => {}">Account deactivation</MenuItem>\n </MenuGroup>\n </MenuContent>\n </Menu>\n </ToolbarTrailing>\n</Toolbar>',
14939
- details: "Use MenuGroup and MenuLabel to organize related items into labeled sections."
15587
+ code: '<!-- Desktop -->\n<Menu>\n <MenuTrigger>\n <Button variant="ghost" color="primary"><Filter /> Filter <ChevronDownMd /></Button>\n </MenuTrigger>\n <MenuContent :min-width="220">\n <MenuRadioGroup v-model:value="filter">\n <MenuRadioItem value="messages"><Chats /> Messages</MenuRadioItem>\n <MenuRadioItem value="spam"><XSquare /> Spam</MenuRadioItem>\n <MenuRadioItem value="deleted"><Trash /> Recently Deleted</MenuRadioItem>\n </MenuRadioGroup>\n <MenuSeparator />\n <MenuGroup>\n <MenuLabel>Filter By</MenuLabel>\n <MenuRadioGroup v-model:value="filter">\n <MenuRadioItem value="unread"><Chat /> Unread</MenuRadioItem>\n <MenuRadioItem value="drafts"><ChatTripleDots /> Drafts</MenuRadioItem>\n </MenuRadioGroup>\n </MenuGroup>\n <MenuSeparator />\n <MenuItem @select="() => {}">Manage Filtering</MenuItem>\n </MenuContent>\n</Menu>\n\n<!-- Phone: the More button of the top bar opens the menu -->\n<Toolbar title-style="large">\n <ToolbarTitle>Grouped items</ToolbarTitle>\n <ToolbarTrailing>\n <Menu>\n <MenuTrigger>\n <Button color="secondary" variant="soft" uniform pill aria-label="More"><DotsHorizontal /></Button>\n </MenuTrigger>\n <MenuContent align="end">\n <MenuRadioGroup v-model:value="filter">\n <MenuRadioItem value="messages"><Chats /> Messages</MenuRadioItem>\n <MenuRadioItem value="spam"><XSquare /> Spam</MenuRadioItem>\n <MenuRadioItem value="deleted"><Trash /> Recently Deleted</MenuRadioItem>\n </MenuRadioGroup>\n <MenuSeparator />\n <MenuGroup>\n <MenuLabel>Filter By</MenuLabel>\n <MenuRadioGroup v-model:value="filter">\n <MenuRadioItem value="unread"><Chat /> Unread</MenuRadioItem>\n <MenuRadioItem value="drafts"><ChatTripleDots /> Drafts</MenuRadioItem>\n </MenuRadioGroup>\n </MenuGroup>\n <MenuSeparator />\n <MenuItem @select="() => {}">Manage Filtering</MenuItem>\n </MenuContent>\n </Menu>\n </ToolbarTrailing>\n</Toolbar>',
15588
+ details: "Use MenuGroup and MenuLabel to organize related items into labeled sections. The example is the filter of the message list, as Messages opens it on iOS: the mailboxes, a section labeled Filter By, and Manage Filtering under a separator. Both radio groups hold one value, so one checkmark marks the list shown. On a phone a section label and a row without an icon start after the checkmark column, where the icons of the other rows start, as in iOS menus."
14940
15589
  },
14941
15590
  {
14942
15591
  name: "Submenus",
@@ -14945,8 +15594,8 @@ const errorMessage = ref(false);
14945
15594
  },
14946
15595
  {
14947
15596
  name: "Checkbox items",
14948
- code: '<!-- Desktop -->\n<Menu>\n <MenuTrigger>\n <Button variant="ghost" color="primary">Checkbox menu <ChevronDownMd /></Button>\n </MenuTrigger>\n <MenuContent :min-width="200">\n <MenuCheckboxItem\n v-model:checked="settings.showGrid"\n :indicator-position="indicatorPosition"\n :indicator-variant="indicatorVariant"\n @select="(evt) => evt.preventDefault()"\n >\n Show grid lines\n </MenuCheckboxItem>\n <MenuCheckboxItem\n v-model:checked="settings.showLabels"\n :indicator-position="indicatorPosition"\n :indicator-variant="indicatorVariant"\n @select="(evt) => evt.preventDefault()"\n >\n Display labels\n </MenuCheckboxItem>\n <MenuCheckboxItem\n v-model:checked="settings.enableShadows"\n :indicator-position="indicatorPosition"\n :indicator-variant="indicatorVariant"\n @select="(evt) => evt.preventDefault()"\n >\n Enable shadows\n </MenuCheckboxItem>\n </MenuContent>\n</Menu>\n\n<!-- Phone: the More button of the top bar opens the menu -->\n<Toolbar title-style="large">\n <ToolbarTitle>Checkbox items</ToolbarTitle>\n <ToolbarTrailing>\n <Menu>\n <MenuTrigger>\n <Button color="secondary" variant="soft" uniform pill aria-label="More"><DotsHorizontal /></Button>\n </MenuTrigger>\n <MenuContent align="end">\n <MenuCheckboxItem\n v-model:checked="settings.showGrid"\n :indicator-position="indicatorPosition"\n :indicator-variant="indicatorVariant"\n @select="(evt) => evt.preventDefault()"\n >\n Show grid lines\n </MenuCheckboxItem>\n <MenuCheckboxItem\n v-model:checked="settings.showLabels"\n :indicator-position="indicatorPosition"\n :indicator-variant="indicatorVariant"\n @select="(evt) => evt.preventDefault()"\n >\n Display labels\n </MenuCheckboxItem>\n <MenuCheckboxItem\n v-model:checked="settings.enableShadows"\n :indicator-position="indicatorPosition"\n :indicator-variant="indicatorVariant"\n @select="(evt) => evt.preventDefault()"\n >\n Enable shadows\n </MenuCheckboxItem>\n </MenuContent>\n </Menu>\n </ToolbarTrailing>\n</Toolbar>',
14949
- details: `Use MenuCheckboxItem with v-model:checked and {'@select="(evt) => evt.preventDefault()"'} to keep the menu open. On a phone with touch.css the checkmark leads the row, as in iOS menus, whatever indicatorPosition and indicatorVariant say, so the phone view leaves out those controls.`
15597
+ code: '<!-- Desktop -->\n<Menu>\n <MenuTrigger>\n <Button variant="ghost" color="primary">View <ChevronDownMd /></Button>\n </MenuTrigger>\n <MenuContent :min-width="220">\n <MenuLabel>View Options</MenuLabel>\n <MenuCheckboxItem\n v-model:checked="view.categories"\n :indicator-position="indicatorPosition"\n :indicator-variant="indicatorVariant"\n @select="(evt) => evt.preventDefault()"\n >\n <Storyboard /> Show Categories\n </MenuCheckboxItem>\n <MenuCheckboxItem\n v-model:checked="view.priority"\n :indicator-position="indicatorPosition"\n :indicator-variant="indicatorVariant"\n @select="(evt) => evt.preventDefault()"\n >\n <Atom /> Show Priority\n </MenuCheckboxItem>\n <MenuCheckboxItem\n v-model:checked="view.contactPhotos"\n :indicator-position="indicatorPosition"\n :indicator-variant="indicatorVariant"\n @select="(evt) => evt.preventDefault()"\n >\n <AvatarProfile /> Show Contact Photos\n </MenuCheckboxItem>\n <MenuSeparator />\n <MenuItem @select="() => {}"><InfoCircle /> About Categories</MenuItem>\n </MenuContent>\n</Menu>\n\n<!-- Phone: the More button of the top bar opens the menu -->\n<Toolbar title-style="large">\n <ToolbarTitle>Checkbox items</ToolbarTitle>\n <ToolbarTrailing>\n <Menu>\n <MenuTrigger>\n <Button color="secondary" variant="soft" uniform pill aria-label="More"><DotsHorizontal /></Button>\n </MenuTrigger>\n <MenuContent align="end">\n <MenuLabel>View Options</MenuLabel>\n <MenuCheckboxItem\n v-model:checked="view.categories"\n :indicator-position="indicatorPosition"\n :indicator-variant="indicatorVariant"\n @select="(evt) => evt.preventDefault()"\n >\n <Storyboard /> Show Categories\n </MenuCheckboxItem>\n <MenuCheckboxItem\n v-model:checked="view.priority"\n :indicator-position="indicatorPosition"\n :indicator-variant="indicatorVariant"\n @select="(evt) => evt.preventDefault()"\n >\n <Atom /> Show Priority\n </MenuCheckboxItem>\n <MenuCheckboxItem\n v-model:checked="view.contactPhotos"\n :indicator-position="indicatorPosition"\n :indicator-variant="indicatorVariant"\n @select="(evt) => evt.preventDefault()"\n >\n <AvatarProfile /> Show Contact Photos\n </MenuCheckboxItem>\n <MenuSeparator />\n <MenuItem @select="() => {}"><InfoCircle /> About Categories</MenuItem>\n </MenuContent>\n </Menu>\n </ToolbarTrailing>\n</Toolbar>',
15598
+ details: `Use MenuCheckboxItem with v-model:checked and {'@select="(evt) => evt.preventDefault()"'} to keep the menu open. The example is the view options of a mailbox, as Mail opens them on iOS: three options that are each on or off, and About Categories under a separator. On a phone with touch.css the checkmark leads the row, as in iOS menus, whatever indicatorPosition and indicatorVariant say, so the phone view leaves out those controls. A row without a checkmark keeps its column, so every icon and label lines up, and a long label wraps under itself.`
14950
15599
  },
14951
15600
  {
14952
15601
  name: "Radio items",
@@ -14983,8 +15632,8 @@ const errorMessage = ref(false);
14983
15632
  {
14984
15633
  name: "width",
14985
15634
  type: 'number | "auto"',
14986
- default: '"auto"',
14987
- description: "Panel width (numbers are pixels)"
15635
+ default: "fits content, 250 on a phone",
15636
+ description: 'Panel width (numbers are pixels). Without it the panel fits its content, or with touch.css on a phone is 250px, as an iOS menu; "auto" fits the content there too'
14988
15637
  },
14989
15638
  {
14990
15639
  name: "min-width",
@@ -15311,7 +15960,7 @@ const disabled = ref(false);
15311
15960
  {
15312
15961
  name: "Background",
15313
15962
  code: '<Page>\n <PageHeader background="hard">...</PageHeader>\n <PageContent>...</PageContent>\n <PageFooter background="hard">\n <Button color="primary" pill block>New order</Button>\n </PageFooter>\n</Page>',
15314
- details: 'background sets how the header and the footer meet the content scrolled under them on a phone, as a Toolbar does: soft (the default) fades the canvas color out under the bar, hard is an opaque bar with a line, glass a translucent, blurred bar with a hairline, and solid an opaque bar with no line. Hard and glass show themselves once content is under the bar; :divider="false" keeps a header without its line.'
15963
+ details: `background sets how the header and the footer meet the content scrolled under them on a phone, as a Toolbar does: glass, the header's default, is a translucent, blurred bar with a hairline, soft, the footer's default, fades the canvas color out under a header and leaves the content clear under the footer's actions, as native iOS 26 does, hard is an opaque bar with a line, and solid an opaque bar with no line. Glass and hard show themselves once content is under the bar; :divider="false" keeps a header without its line.`
15315
15964
  }
15316
15965
  ],
15317
15966
  subComponents: [
@@ -15321,7 +15970,7 @@ const disabled = ref(false);
15321
15970
  {
15322
15971
  name: "background",
15323
15972
  type: '"soft" | "hard" | "glass" | "solid"',
15324
- default: '"soft"',
15973
+ default: '"glass"',
15325
15974
  description: "How the bar meets the content scrolled under it on a phone, see Background"
15326
15975
  },
15327
15976
  {
@@ -15451,6 +16100,41 @@ const disabled = ref(false);
15451
16100
  ],
15452
16101
  docsUrl: "https://plexui.com/vue/components/page-control"
15453
16102
  },
16103
+ {
16104
+ slug: "phone-layout",
16105
+ name: "Phone Layout",
16106
+ description: "Lays out the components inside it as on a phone at any window width, such as a phone frame on a desktop page, and keeps their dialogs inside its box.",
16107
+ importPath: "@plexui/vue/components/PhoneLayout",
16108
+ exports: [
16109
+ "PhoneLayout"
16110
+ ],
16111
+ props: [
16112
+ {
16113
+ name: "class",
16114
+ type: "string",
16115
+ description: "Class on the box, such as its size and corners"
16116
+ },
16117
+ {
16118
+ name: "style",
16119
+ type: "string | CSSProperties",
16120
+ description: "Style on the box"
16121
+ }
16122
+ ],
16123
+ examples: [
16124
+ {
16125
+ name: "Overview",
16126
+ code: '<PhoneLayout style="width: 375px; height: 600px; border-radius: 40px; border: 1px solid var(--color-border); background: var(--color-background)">\n <div style="display: flex; height: 100%; flex-direction: column; align-items: center; justify-content: center; padding: 24px">\n <Dialog v-model:open="open">\n <DialogTrigger>\n <Button color="primary">New list</Button>\n </DialogTrigger>\n <DialogContent mobile-variant="sheet">\n <DialogHeader>\n <DialogTitle>New list</DialogTitle>\n </DialogHeader>\n <DialogBody>\n <Field label="Name">\n <Input placeholder="Groceries" />\n </Field>\n </DialogBody>\n <DialogFooter>\n <Button color="primary" @click="open = false">Create</Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </div>\n</PhoneLayout>',
16127
+ details: "The box is the screen of the phone: a Dialog opened inside it portals into the box and covers it instead of the window. Give the box a size; let a child of it scroll rather than the box, so a dialog stays in place."
16128
+ }
16129
+ ],
16130
+ hooks: [
16131
+ {
16132
+ name: "usePhoneLayout",
16133
+ description: '`usePhoneLayout()` returns a computed ref: whether components where it is called are laid out as on a phone: inside a PhoneLayout or a Dialog with `layout="phone"`, or in a window 767px wide or narrower. On the server, and until the component mounts, it is false outside a PhoneLayout.'
16134
+ }
16135
+ ],
16136
+ docsUrl: "https://plexui.com/vue/components/phone-layout"
16137
+ },
15454
16138
  {
15455
16139
  slug: "popover",
15456
16140
  name: "Popover",
@@ -19382,6 +20066,125 @@ const disabled = ref(false);
19382
20066
  ],
19383
20067
  docsUrl: "https://plexui.com/vue/components/stepper"
19384
20068
  },
20069
+ {
20070
+ slug: "swipe-actions",
20071
+ name: "Swipe Actions",
20072
+ description: '"Actions behind a row of a list, as the swipe actions of an iOS list: a swipe across the row reveals them, a full swipe runs the first one, and each is a button for the keys."',
20073
+ importPath: "@plexui/vue/components/SwipeActions",
20074
+ exports: [
20075
+ "SwipeActions"
20076
+ ],
20077
+ props: [
20078
+ {
20079
+ name: "leading",
20080
+ type: "SwipeAction[]",
20081
+ default: "[]",
20082
+ description: "Actions revealed by a swipe toward the trailing edge, the first one at the leading edge"
20083
+ },
20084
+ {
20085
+ name: "trailing",
20086
+ type: "SwipeAction[]",
20087
+ default: "[]",
20088
+ description: "Actions revealed by a swipe toward the leading edge, the first one at the trailing edge"
20089
+ },
20090
+ {
20091
+ name: "label-placement",
20092
+ type: '"hidden" | "inside" | "below"',
20093
+ default: '"hidden"',
20094
+ description: "Where the actions show their labels: symbols only, inside the capsule, or below it (for rows of 68px and taller)"
20095
+ },
20096
+ {
20097
+ name: "full-swipe",
20098
+ type: "boolean",
20099
+ default: "true",
20100
+ description: "A swipe past the middle between the open actions and the far edge runs the first action of that side"
20101
+ }
20102
+ ],
20103
+ examples: [
20104
+ {
20105
+ name: "Overview",
20106
+ code: `<FieldGroup>
20107
+ <SwipeActions
20108
+ v-for="list in lists"
20109
+ :key="list.name"
20110
+ :leading="[{ id: 'pin', label: 'Pin', Icon: Pin, color: 'warning', onSelect: () => pin(list) }]"
20111
+ :trailing="[
20112
+ { id: 'delete', label: 'Delete', Icon: RemoveTrash, color: 'danger', onSelect: () => remove(list) },
20113
+ { id: 'archive', label: 'Archive', Icon: Archive, color: 'info', onSelect: () => archive(list) },
20114
+ ]"
20115
+ >
20116
+ <div class="row">{{ list.name }}</div>
20117
+ </SwipeActions>
20118
+ </FieldGroup>`,
20119
+ details: "Wrap the content of a row of a FieldGroup: the swipe row is the data-row of the group, so its separators and corners follow the card. The actions are solid capsules behind the content, 10px apart and 16px from the edge, as in the iOS 27 kit; the first action of a side stands at its edge. Swiped aside, the content becomes a capsule of the row&apos;s color on the background of the list. A swipe past half of the actions opens them, a press on an action runs it and closes the row, and a press on the content or anywhere outside closes it too; one row is open at a time. A swipe past the middle between the open actions and the far edge stretches the first action across the row, and letting go runs it."
20120
+ },
20121
+ {
20122
+ name: "Labels inside",
20123
+ code: `<SwipeActions
20124
+ label-placement="inside"
20125
+ :leading="[{ id: 'pin', label: 'Pin', Icon: Pin, color: 'warning', onSelect: () => pin(list) }]"
20126
+ :trailing="[
20127
+ { id: 'delete', label: 'Delete', Icon: RemoveTrash, color: 'danger', onSelect: () => remove(list) },
20128
+ { id: 'archive', label: 'Archive', Icon: Archive, color: 'info', onSelect: () => archive(list) },
20129
+ ]"
20130
+ >
20131
+ <div class="row">{{ list.name }}</div>
20132
+ </SwipeActions>`,
20133
+ details: "With label-placement=&quot;inside&quot; each capsule holds a smaller symbol and its label, as the kit&apos;s Symbol and Label row. An action without an icon always shows its label inside."
20134
+ },
20135
+ {
20136
+ name: "Labels below",
20137
+ code: `<SwipeActions
20138
+ label-placement="below"
20139
+ :leading="[{ id: 'flag', label: 'Flag', Icon: Flag, color: 'warning', onSelect: () => flag(message) }]"
20140
+ :trailing="[
20141
+ { id: 'delete', label: 'Delete', Icon: RemoveTrash, color: 'danger', onSelect: () => remove(message) },
20142
+ { id: 'archive', label: 'Archive', Icon: Archive, color: 'info', onSelect: () => archive(message) },
20143
+ ]"
20144
+ >
20145
+ <div class="row">
20146
+ <div>{{ message.from }}</div>
20147
+ <div>{{ message.subject }}</div>
20148
+ </div>
20149
+ </SwipeActions>`,
20150
+ details: "With label-placement=&quot;below&quot; a lower capsule stands over its label, as the kit&apos;s Symbol and Label Tall row. It takes a row of 68px or taller, such as a row of two lines."
20151
+ }
20152
+ ],
20153
+ subComponents: [
20154
+ {
20155
+ name: "SwipeAction",
20156
+ props: [
20157
+ {
20158
+ name: "id",
20159
+ type: "string",
20160
+ description: "Unique ID of the action within its side"
20161
+ },
20162
+ {
20163
+ name: "label",
20164
+ type: "string",
20165
+ description: "Name of the action: shown inside or below the capsule (and on an action without an icon), and its accessible name always"
20166
+ },
20167
+ {
20168
+ name: "Icon",
20169
+ type: "Component",
20170
+ description: "Icon of the action, a component (mark it raw in reactive state)"
20171
+ },
20172
+ {
20173
+ name: "color",
20174
+ type: '"primary" | "secondary" | "danger" | "success" | "info" | "discovery" | "caution" | "warning"',
20175
+ default: '"secondary"',
20176
+ description: "Solid color of the action"
20177
+ },
20178
+ {
20179
+ name: "onSelect",
20180
+ type: "() => void",
20181
+ description: "Called when the action is pressed, or by a full swipe when it is the first action of its side"
20182
+ }
20183
+ ]
20184
+ }
20185
+ ],
20186
+ docsUrl: "https://plexui.com/vue/components/swipe-actions"
20187
+ },
19385
20188
  {
19386
20189
  slug: "switch",
19387
20190
  name: "Switch",
@@ -19552,7 +20355,7 @@ const checked = ref(false)
19552
20355
  {
19553
20356
  slug: "tab-bar",
19554
20357
  name: "Tab Bar",
19555
- description: '"The tab bar of a phone screen: two to five sections at the bottom in a floating glass capsule, an optional search tab, minimizing while the content scrolls. Desktop layouts navigate with a Sidebar or Tabs, so a tab bar never renders from 768px up."',
20358
+ description: '"The tab bar of a phone screen: two to five sections at the bottom in a floating glass capsule, an optional search or prominent tab, minimizing while the content scrolls. Desktop layouts navigate with a Sidebar or Tabs, so a tab bar never renders from 768px up."',
19556
20359
  importPath: "@plexui/vue/components/TabBar",
19557
20360
  exports: [
19558
20361
  "TabBar",
@@ -19574,6 +20377,11 @@ const checked = ref(false)
19574
20377
  type: "{ value: string; label?: string }",
19575
20378
  description: "A search tab in a glass circle of its own at the trailing end; label defaults to Search"
19576
20379
  },
20380
+ {
20381
+ name: "prominent",
20382
+ type: "{ value: string; label: string }",
20383
+ description: "A prominent tab in a glass circle of its own at the trailing end, in place of the search circle; its symbol is the prominent-icon slot, and search then joins the capsule"
20384
+ },
19577
20385
  {
19578
20386
  name: "minimize-behavior",
19579
20387
  type: '"never" | "onScrollDown"',
@@ -19625,17 +20433,33 @@ const checked = ref(false)
19625
20433
  </TabBar>`,
19626
20434
  details: "With search the search tab takes a glass circle of its own at the trailing end, as the iOS 26 search tab does. The capsule of the other tabs keeps to the leading end at their width, as in the kit and in Photos: with two tabs it ends well before the circle, with three it reaches 8px from it. It is a tab like the others: show the search screen when its value is selected."
19627
20435
  },
20436
+ {
20437
+ name: "Prominent tab",
20438
+ code: `<TabBar v-model:value="tab" :prominent="{ value: 'bag', label: 'Bag' }">
20439
+ <TabBarItem value="home" label="Home">
20440
+ <template #icon><Home /></template>
20441
+ </TabBarItem>
20442
+ <TabBarItem value="calendar" label="Calendar">
20443
+ <template #icon><Calendar /></template>
20444
+ </TabBarItem>
20445
+ <TabBarItem value="photos" label="Photos">
20446
+ <template #icon><Images /></template>
20447
+ </TabBarItem>
20448
+ <template #prominent-icon><ShoppingBag /></template>
20449
+ </TabBar>`,
20450
+ details: "With prominent one destination leaves the capsule for a glass circle of its own at the trailing end, as the prominent tab of iOS 27 and its kit: one key destination, such as a cart or a library. It is still a tab, not an action: it shows a screen of its own when selected. A bar has one such circle, so prominent takes the place of the search circle; with both, search becomes the last tab of the capsule. The circle shows the symbol from the prominent-icon slot only, and label names the tab."
20451
+ },
19628
20452
  {
19629
20453
  name: "Minimize on scroll",
19630
20454
  code: `<TabBar v-model:value="tab" :search="{ value: 'search' }" minimize-behavior="onScrollDown">
19631
20455
  ...
19632
20456
  </TabBar>`,
19633
- details: 'With minimize-behavior="onScrollDown" the bar shrinks to the selected tab while the content scrolls down and comes back when it scrolls up, as in iOS 26. Tap the minimized tab to bring the others back.'
20457
+ details: 'With minimize-behavior="onScrollDown" the bar shrinks to the selected tab while the content scrolls down and comes back when it scrolls up, as in iOS 26. Minimized, the circles keep 28px from the sides and at least 28px from the bottom, as in the iOS 27 kit. Tap the minimized tab to bring the others back.'
19634
20458
  },
19635
20459
  {
19636
20460
  name: "Background",
19637
20461
  code: '<Page surface="grouped">\n <Toolbar background="hard">...</Toolbar>\n <PageContent>...</PageContent>\n <TabBar v-model:value="tab" background="hard">\n ...\n </TabBar>\n</Page>',
19638
- details: "background sets how the bar meets the content scrolled under it, as a Toolbar does: soft (the default) fades the canvas color in under the capsule, hard is an opaque band with a line on top, glass a translucent, blurred band with a hairline, and solid an opaque band with no line. Hard and glass show themselves while the content continues under the bar."
20462
+ details: "background sets how the bar meets the content scrolled under it, as a Toolbar does: soft (the default) leaves the content clear under and around the capsule, as native iOS 26 does, glass is a translucent, blurred band with a hairline, hard is an opaque band with a line on top, and solid an opaque band with no line. Glass and hard show themselves while the content continues under the bar."
19639
20463
  }
19640
20464
  ],
19641
20465
  subComponents: [
@@ -20178,7 +21002,7 @@ const columns: ColumnDef<User>[] = [
20178
21002
  {
20179
21003
  name: "Overview",
20180
21004
  code: '<Tabs\n v-model:value="tab"\n :variant="variant"\n :orientation="orientation"\n :size="size"\n :pill="pill"\n :flush="flush"\n :block="block"\n :disabled="disabled"\n aria-label="Demo tabs"\n>\n <TabsTab value="overview">Overview</TabsTab>\n <TabsTab value="analytics">Analytics</TabsTab>\n <TabsTab value="reports" :disabled="reportsDisabled">Reports</TabsTab>\n <TabsTab value="settings">Settings</TabsTab>\n</Tabs>',
20181
- 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. Bind the selected tab with v-model:value; the examples keep it in a tab variable. disabled affects the whole group; reportsDisabled disables only Reports. Arrow-key navigation skips disabled tabs without resetting the selected value. On a phone horizontal tabs of either variant are the iOS segmented control on glass, as toolbar buttons are: segments of one width in a 44px capsule, the selection a darker pill inside it with the primary label and the other labels secondary, filling the row of a phone screen; vertical tabs are an inset grouped list. So the phone view has no variant, flush, block, size and pill controls."
21005
+ 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. Bind the selected tab with v-model:value; the examples keep it in a tab variable. disabled affects the whole group, which on a phone shows at half opacity when horizontal, as a disabled iOS segmented control does; reportsDisabled disables only Reports. Arrow-key navigation skips disabled tabs without resetting the selected value. On a phone horizontal tabs of either variant are the iOS segmented control on glass, as toolbar buttons are: segments of one width in a 44px capsule, the selection a darker pill inside it with the primary label and the other labels secondary, filling the row of a phone screen; vertical tabs are an inset grouped list. So the phone view has no variant, flush, block, size and pill controls."
20182
21006
  },
20183
21007
  {
20184
21008
  name: "Segmented with icons",
@@ -20810,6 +21634,115 @@ const available = computed(() => {
20810
21634
  ],
20811
21635
  docsUrl: "https://plexui.com/vue/components/textarea"
20812
21636
  },
21637
+ {
21638
+ slug: "tip",
21639
+ name: "Tip",
21640
+ description: "A short message that points out a feature people might miss, inside the page or anchored to a control, as TipKit tips do.",
21641
+ importPath: "@plexui/vue/components/Tip",
21642
+ exports: [
21643
+ "Tip",
21644
+ "TipPopover"
21645
+ ],
21646
+ props: [
21647
+ {
21648
+ name: "title",
21649
+ type: "string",
21650
+ description: "The tip's title: what the feature is (or the title slot)"
21651
+ },
21652
+ {
21653
+ name: "description",
21654
+ type: "string",
21655
+ description: "One or two sentences on how to use it (or the description slot)"
21656
+ },
21657
+ {
21658
+ name: "on-close",
21659
+ type: "() => void",
21660
+ description: "Shows the Close button; called when it is clicked"
21661
+ },
21662
+ {
21663
+ name: "arrow-edge",
21664
+ type: '"top" | "bottom" | "left" | "right"',
21665
+ description: "Draws an arrow at this edge, pointing at what the tip explains"
21666
+ },
21667
+ {
21668
+ name: "mobile-actions",
21669
+ type: '"rows" | "buttons"',
21670
+ default: '"rows"',
21671
+ description: "How the actions show at 767px and below: rows of text under a separator, or the buttons as given, each a full-width capsule"
21672
+ }
21673
+ ],
21674
+ examples: [
21675
+ {
21676
+ name: "Overview",
21677
+ code: '<div class="flex w-full max-w-[360px] flex-col items-center gap-3">\n <Tip\n v-if="shown"\n title="Save it for later"\n description="Bookmarked articles wait in your reading list, ready offline on every device."\n class="w-full"\n :on-close="() => (shown = false)"\n >\n <template #icon><Bookmark /></template>\n </Tip>\n <Button v-else color="secondary" variant="soft" @click="shown = true">Show the tip again</Button>\n</div>',
21678
+ details: "An inline tip takes its place in the layout. A symbol in the icon slot leads the message in the secondary color; on-close shows Close in the trailing corner. On a phone the tip is the opaque card of a grouped list, so place it on a grouped background."
21679
+ },
21680
+ {
21681
+ name: "Actions",
21682
+ code: '<div class="w-full max-w-[360px]">\n <Tip\n title="Turn on notifications"\n description="Get a message when someone replies to your post or mentions you."\n >\n <template #icon><Bell /></template>\n <template #actions>\n <Button color="primary" variant="soft" size="sm" pill>Not now</Button>\n <Button color="primary" size="sm" pill>Turn on</Button>\n </template>\n </Tip>\n</div>',
21683
+ details: "Actions in the actions slot sit side by side below the message, each as wide as the others. Use small pill buttons, as the kit does. On a phone each action is a row of text in its color under a separator, one under another, starting under the text, as iOS cards show them."
21684
+ },
21685
+ {
21686
+ name: "Buttons on a phone",
21687
+ code: '<div class="w-full max-w-[360px]">\n <Tip\n title="Get alerts"\n description="Hear about severe weather where you are, even when the app is closed."\n mobile-actions="buttons"\n >\n <template #icon><Bell /></template>\n <template #actions>\n <Button color="primary" variant="soft" size="sm" pill>Turn on</Button>\n </template>\n </Tip>\n</div>',
21688
+ details: 'With mobile-actions="buttons" the actions keep their buttons on a phone: each is a capsule as wide as the tip under the message, 48px tall with a 17px label, in its own color and variant, as the iOS card in Settings that opens the Support app shows. Use it for a card that asks for a permission, with one soft primary button.'
21689
+ },
21690
+ {
21691
+ name: "Arrow",
21692
+ code: '<div class="flex w-full max-w-[360px] flex-col items-center gap-1">\n <Button color="secondary" variant="soft" uniform pill aria-label="Filter">\n <Filter />\n </Button>\n <Tip\n arrow-edge="top"\n title="Filter the list"\n description="Show only the items from the last week, month or year."\n class="w-full"\n />\n</div>',
21693
+ details: "With arrow-edge an inline tip points at what it explains with the kit's arrow, centered on that edge. The tip leaves room for the arrow on that side."
21694
+ },
21695
+ {
21696
+ name: "Popover",
21697
+ code: '<TipPopover\n v-model:open="open"\n title="Filter the list"\n description="Show only the items from the last week, month or year."\n>\n <Button color="secondary" variant="soft" @click="open = !open">\n <Filter />\n Filter\n </Button>\n</TipPopover>',
21698
+ details: "A popover tip floats next to its control, the default slot, with an arrow that points at it. The control stays usable and keeps the focus. Close, Escape or a press outside the tip and its control closes it, through v-model:open. Show it when the control appears; here the button shows it."
21699
+ }
21700
+ ],
21701
+ subComponents: [
21702
+ {
21703
+ name: "TipPopover",
21704
+ description: "Takes title, description and the slots as Tip does; the default slot is the control it explains. Tip.Popover is the same component.",
21705
+ props: [
21706
+ {
21707
+ name: "open",
21708
+ type: "boolean",
21709
+ description: "Shows the tip (controlled); bind with v-model:open"
21710
+ },
21711
+ {
21712
+ name: "default-open",
21713
+ type: "boolean",
21714
+ default: "false",
21715
+ description: "Shows the tip at first (uncontrolled)"
21716
+ },
21717
+ {
21718
+ name: "side",
21719
+ type: '"top" | "right" | "bottom" | "left"',
21720
+ default: '"bottom"',
21721
+ description: "The side of the control the tip shows on, turned when it does not fit"
21722
+ },
21723
+ {
21724
+ name: "align",
21725
+ type: '"start" | "center" | "end"',
21726
+ default: '"center"',
21727
+ description: "The alignment against the control"
21728
+ },
21729
+ {
21730
+ name: "side-offset",
21731
+ type: "number",
21732
+ default: "4",
21733
+ description: "The distance from the control to the arrow's point, in pixels"
21734
+ },
21735
+ {
21736
+ name: "close-button",
21737
+ type: "boolean",
21738
+ default: "true",
21739
+ description: "Shows the Close button"
21740
+ }
21741
+ ]
21742
+ }
21743
+ ],
21744
+ docsUrl: "https://plexui.com/vue/components/tip"
21745
+ },
20813
21746
  {
20814
21747
  slug: "toast",
20815
21748
  name: "Toast",
@@ -20825,7 +21758,7 @@ const available = computed(() => {
20825
21758
  code: `<Button color="secondary" variant="outline" @click="toast('Inquiry ID copied')">
20826
21759
  Show toast
20827
21760
  </Button>`,
20828
- details: "On a phone a toast takes the corners, padding and text of an iOS notification banner, the system's own message at the top of the screen: 24px corners and 15pt text, with its buttons drawn as a medium phone Button, a 34px capsule in a 44px target. It stays a message of the page, with its own width and opaque surface."
21761
+ details: "On a phone a toast takes the corners, padding and text of an iOS notification banner, the system's own message at the top of the screen: 24px corners and 15pt text, with its buttons drawn as a medium phone Button, a 34px capsule in a 44px target. Under 600px it fills the width between 14px gutters past the safe area, as notifications do on an iOS phone; a compact toast keeps its own width. It stays a message of the page, with an opaque surface."
20829
21762
  },
20830
21763
  {
20831
21764
  name: "Variants",
@@ -20893,7 +21826,7 @@ const available = computed(() => {
20893
21826
  code: `<Button color="secondary" variant="outline" @click="toast.success('Inquiry ID copied', compact)">
20894
21827
  Compact toast
20895
21828
  </Button>`,
20896
- details: 'Spread compact ({`import { toast, compact } from "@plexui/vue/components/Toast"`}) into any toast(...) options for a centered-text layout. Toast width shrinks to content by default regardless; the compact helper only adds center alignment for short status messages.'
21829
+ details: 'Spread compact ({`import { toast, compact } from "@plexui/vue/components/Toast"`}) into any toast(...) options for a centered-text layout. On a desktop a toast shrinks to its content anyway; on a phone, where a toast fills the width, a compact one keeps the width of its content. Both center its text.'
20897
21830
  },
20898
21831
  {
20899
21832
  name: "Persistent",
@@ -20910,7 +21843,7 @@ const available = computed(() => {
20910
21843
  >
20911
21844
  Persistent toast
20912
21845
  </Button>`,
20913
- details: "Pass duration: Infinity for toasts that stay open until the user acts on them. There are no close (\xD7) affordances: put a labelled action on the toast so dismissal is intentional and keyboard-accessible."
21846
+ details: "Pass duration: Infinity for toasts that stay open until the user acts on them. There are no close (\xD7) affordances: put a labelled action on the toast so dismissal is intentional and keyboard-accessible. An error that waits like this is an alert (see Screen readers)."
20914
21847
  }
20915
21848
  ],
20916
21849
  subComponents: [
@@ -20932,13 +21865,14 @@ const available = computed(() => {
20932
21865
  {
20933
21866
  name: "offset",
20934
21867
  type: "string | number | { top?; right?; bottom?; left? }",
20935
- description: "Distance from the viewport edge"
21868
+ default: "32px",
21869
+ description: "Distance from the viewport edge. Without a top or bottom edge here, the page's --toast-offset-top or --toast-offset-bottom sets it."
20936
21870
  },
20937
21871
  {
20938
21872
  name: "mobile-offset",
20939
21873
  type: "string | number | { top?; right?; bottom?; left? }",
20940
- default: "16px + safe area per edge",
20941
- description: "Mobile viewport gutter. Explicit edges override their safe defaults; omitted edges keep them."
21874
+ default: "16px top and bottom, 14px at the sides, past the safe area",
21875
+ description: "Gutter under 600px, where a toast fills the width between the side gutters. Explicit edges override their defaults; omitted edges keep them, and the page's --toast-mobile-offset-top or --toast-mobile-offset-bottom sets the top or bottom one."
20942
21876
  },
20943
21877
  {
20944
21878
  name: "gap",
@@ -20983,7 +21917,7 @@ const available = computed(() => {
20983
21917
  {
20984
21918
  name: "background",
20985
21919
  type: '"soft" | "hard" | "glass" | "solid"',
20986
- default: '"soft"',
21920
+ default: '"glass" at the top, "soft" at the bottom',
20987
21921
  description: "How the bar meets the content scrolled under it, see Background. edge is the earlier name of soft"
20988
21922
  },
20989
21923
  {
@@ -21006,22 +21940,22 @@ const available = computed(() => {
21006
21940
  {
21007
21941
  name: "Group",
21008
21942
  code: '<Toolbar>\n <ToolbarLeading>\n <NavigationButton action="close" />\n </ToolbarLeading>\n <ToolbarTrailing>\n <Button color="secondary" variant="soft" uniform pill aria-label="Details"><InfoCircle /></Button>\n <ToolbarGroup aria-label="Order actions">\n <Button color="secondary" variant="soft" uniform pill aria-label="Translate"><Translate /></Button>\n <Button color="secondary" variant="soft" uniform pill aria-label="More"><DotsHorizontal /></Button>\n </ToolbarGroup>\n </ToolbarTrailing>\n</Toolbar>',
21009
- details: "ToolbarGroup puts related actions in one capsule. A single action keeps a capsule of its own. Name each icon button with aria-label."
21943
+ details: "ToolbarGroup puts related actions in one capsule. A single action keeps a capsule of its own. A ButtonGroup in a toolbar is the same capsule. Name each icon button with aria-label."
21010
21944
  },
21011
21945
  {
21012
21946
  name: "Search",
21013
- code: '<Toolbar>\n <ToolbarContent fill>\n <Input variant="soft" pill placeholder="Search" aria-label="Search orders">\n <template #startAdornment>\n <Search />\n </template>\n </Input>\n </ToolbarContent>\n <ToolbarTrailing>\n <NavigationButton action="close" aria-label="Close search" />\n </ToolbarTrailing>\n</Toolbar>',
21947
+ code: '<Toolbar>\n <ToolbarContent fill>\n <Input variant="soft" pill placeholder="Search" aria-label="Search orders">\n <template #startAdornment>\n <SearchSm />\n </template>\n </Input>\n </ToolbarContent>\n <ToolbarTrailing>\n <NavigationButton action="close" aria-label="Close search" />\n </ToolbarTrailing>\n</Toolbar>',
21014
21948
  details: "ToolbarContent fill stretches a search field between the leading and trailing items. The field takes the row height and the material of the bar buttons."
21015
21949
  },
21016
21950
  {
21017
21951
  name: "Bottom",
21018
- code: '<Page surface="grouped">\n <Toolbar>\n <ToolbarTitle>Orders</ToolbarTitle>\n </Toolbar>\n <PageContent>...</PageContent>\n <Toolbar placement="bottom">\n <ToolbarLeading>\n <Button color="secondary" variant="soft" uniform pill aria-label="Filter"><Filter /></Button>\n </ToolbarLeading>\n <ToolbarContent>\n <Button color="primary" pill>New order</Button>\n </ToolbarContent>\n <ToolbarTrailing>\n <Button color="secondary" variant="soft" uniform pill aria-label="Search"><Search /></Button>\n </ToolbarTrailing>\n </Toolbar>\n</Page>\n\n<Toolbar placement="bottom">\n <ToolbarContent fill>\n <Button color="primary" pill block><Plus /> New order</Button>\n </ToolbarContent>\n</Toolbar>',
21019
- details: `placement="bottom" sets the bar at the bottom of the page with 48px targets and the device's bottom safe area. ToolbarContent centers a main action; with fill a search field or a single action takes the whole row. The page content scrolls under both bars.`
21952
+ code: '<Page surface="grouped">\n <Toolbar>\n <ToolbarTitle>Orders</ToolbarTitle>\n </Toolbar>\n <PageContent>...</PageContent>\n <Toolbar placement="bottom">\n <ToolbarLeading>\n <Button color="secondary" variant="soft" uniform pill aria-label="Filter"><Filter /></Button>\n </ToolbarLeading>\n <ToolbarContent>\n <Button color="primary" pill>New order</Button>\n </ToolbarContent>\n <ToolbarTrailing>\n <Button color="secondary" variant="soft" uniform pill aria-label="Search"><Search /></Button>\n </ToolbarTrailing>\n </Toolbar>\n</Page>\n\n<Toolbar placement="bottom">\n <ToolbarContent fill>\n <Input variant="soft" pill placeholder="Search" aria-label="Search orders">\n <template #startAdornment>\n <SearchSm />\n </template>\n </Input>\n </ToolbarContent>\n <ToolbarTrailing>\n <Button color="secondary" variant="soft" uniform pill icon-size="sm" aria-label="New order"><PlusLg /></Button>\n </ToolbarTrailing>\n</Toolbar>\n\n<Toolbar placement="bottom">\n <ToolbarContent fill>\n <Button color="primary" pill block><Plus /> New order</Button>\n </ToolbarContent>\n</Toolbar>',
21953
+ details: `placement="bottom" sets the bar at the bottom of the page with 48px targets and the device's bottom safe area. ToolbarContent centers a main action; with fill a search field or a single action takes the whole row. The 48px items stand 28px from the sides and above the home indicator, 12px apart, and a bottom bar draws no band: the content runs under and around its glass, as in iOS 26 Mail and Contacts. A search field holds SearchSm, set in Regular; a plus next to it takes iconSize="sm", as Contacts draws it smaller than the other symbols. The page content scrolls under both bars.`
21020
21954
  },
21021
21955
  {
21022
21956
  name: "Background",
21023
21957
  code: '<Page surface="grouped">\n <Toolbar background="hard">\n <ToolbarLeading>\n <NavigationButton action="back" />\n </ToolbarLeading>\n <ToolbarTitle>Orders</ToolbarTitle>\n </Toolbar>\n <PageContent>...</PageContent>\n <Toolbar placement="bottom" background="hard">\n <ToolbarContent>\n <Button color="primary" pill>New order</Button>\n </ToolbarContent>\n </Toolbar>\n</Page>',
21024
- details: "background sets how a bar meets the content scrolled under it, after the scroll edge effect of iOS 26. soft, the default, covers the content under the controls with the canvas color and fades it out past them over a light blur, with no line. hard is an opaque bar of the canvas color with a line. glass is a translucent, blurred bar with a hairline. solid is an opaque bar with no line. Hard and glass show themselves once content is under the bar: under the top bar after the content has scrolled, over the bottom bar while it continues below. PageHeader, PageFooter, the bars of a Sheet and TabBar take the same values."
21958
+ details: "background sets how a bar meets the content scrolled under it. glass, the default of a top bar, is a translucent, blurred bar with a hairline. soft, the default of a bottom bar, is the scroll edge effect of iOS 26 and draws no line: a top bar with a title covers the content under its controls with the canvas color and fades it out past them over a light blur, while a top bar of controls alone on a page covers only the status bar, and a bottom bar on a page leaves the content clear under and around its controls, as native iOS 26 does (Contacts, Notes, Mail); each glass control stands out by itself. A Sheet's bottom bar fades its content into the panel. hard is an opaque bar of the canvas color with a line. solid is an opaque bar with no line. Glass and hard show themselves once content is under the bar: under the top bar after the content has scrolled, over the bottom bar while it continues below. PageHeader, PageFooter, the bars of a Sheet and TabBar take the same values."
21025
21959
  },
21026
21960
  {
21027
21961
  name: "Disabled",
@@ -21178,7 +22112,7 @@ const available = computed(() => {
21178
22112
  docsUrl: "https://plexui.com/vue/components/tooltip"
21179
22113
  }
21180
22114
  ],
21181
- generatedAt: "2026-11-08T18:56:39.760Z"
22115
+ generatedAt: "2026-11-09T09:41:08.373Z"
21182
22116
  };
21183
22117
 
21184
22118
  // src/index.ts
@@ -21271,7 +22205,7 @@ function fuzzyMatch(query, text) {
21271
22205
  }
21272
22206
  var server = new McpServer({
21273
22207
  name: "plexui",
21274
- version: "0.2.2"
22208
+ version: "0.2.3"
21275
22209
  });
21276
22210
  server.tool(
21277
22211
  "list_components",