mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
docs(design-system): document split button with dropdown pattern (#47422)
## Summary - Add `button-split-dropdown` and `admonition-button-split` design-system examples for primary action + chevron menu. - Document the shared middle border pattern (`-ml-px`, `hover:z-10`; avoid `border-l-0`) in Button and Admonition docs. - Mirrors the approach established in the custom SMTP email templates admonition. ## Test plan - [ ] Open design-system site and verify `Button → Split with dropdown` preview renders correctly - [ ] Open `Admonition → Split button with dropdown` preview and confirm middle border looks correct on hover/focus - [ ] Confirm peek code shows the documented class names <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added new split-button dropdown live demos, including icon/chevron-triggered menus with actionable items. * Added an admonition demo demonstrating a split primary button plus dropdown trigger with responsive layout. * **Documentation** * Expanded “Split with dropdown” guidance with pairing/when-to-use rules, required styling/border behavior, and responsive layout instructions for the primary + chevron actions. * Updated button documentation heading capitalization to match typography standards. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
4f05124ce7
commit
be01b38118
6 files changed
+161
-2
No files matched your search
@@ -159,6 +159,17 @@ export const Index: Record<string, any> = {
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"admonition-button-split": {
|
||||
name: "admonition-button-split",
|
||||
type: "components:example",
|
||||
registryDependencies: ["admonition","button","dropdown-menu"],
|
||||
component: React.lazy(() => import("@/registry/default/example/admonition-button-split")),
|
||||
source: "",
|
||||
files: ["registry/default/example/admonition-button-split.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"admonition-description-only": {
|
||||
name: "admonition-description-only",
|
||||
type: "components:example",
|
||||
@@ -577,6 +588,17 @@ export const Index: Record<string, any> = {
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"button-split-dropdown": {
|
||||
name: "button-split-dropdown",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","dropdown-menu"],
|
||||
component: React.lazy(() => import("@/registry/default/example/button-split-dropdown")),
|
||||
source: "",
|
||||
files: ["registry/default/example/button-split-dropdown.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"calendar-demo": {
|
||||
name: "calendar-demo",
|
||||
type: "components:example",
|
||||
|
||||
@@ -105,7 +105,7 @@ Used for actions that are not as important as the primary action, or for actions
|
||||
|
||||
<ComponentPreview name="button-link" />
|
||||
|
||||
### Only an Icon
|
||||
### Only an icon
|
||||
|
||||
Displaying only an Icon in a button.
|
||||
|
||||
@@ -115,12 +115,28 @@ Displaying only an Icon in a button.
|
||||
|
||||
<ComponentPreview name="button-icon" />
|
||||
|
||||
### As Child
|
||||
### As child
|
||||
|
||||
Supports slot behavior with `asChild` prop.
|
||||
|
||||
<ComponentPreview name="button-as-child" />
|
||||
|
||||
### Split with dropdown
|
||||
|
||||
Pair a button with a chevron `DropdownMenu` trigger when there are variations of the same action, or alternative ways to accomplish the same goal. The default or most likely option should be used on the exposed button.
|
||||
|
||||
When secondary actions are related but distinct—not alternatives to the primary action—display the primary action as a button and place the rest in an overflow menu instead. See [Table multiple actions](./table#multiple-actions).
|
||||
|
||||
<ComponentPreview name="button-split-dropdown" peekCode />
|
||||
|
||||
The shared middle border is the tricky part. Do **not** use `border-l-0` on the chevron button — that drops the divider on hover/focus. Instead:
|
||||
|
||||
- Primary: `rounded-r-none` and `hover:z-10` so its border stacks above the chevron on hover.
|
||||
- Chevron trigger: `rounded-l-none`, `shrink-0`, `px-[4px] py-[5px]`, and `-ml-px` to overlap the adjacent border by one pixel.
|
||||
- Chevron trigger only: `aria-label` describing the menu (the icon is decorative).
|
||||
|
||||
Inside [Admonition](../fragments/admonition#split-button-with-dropdown) actions, also use `flex w-full @lg:w-auto` with `flex-1 @lg:flex-none` on the primary when `layout="responsive"`.
|
||||
|
||||
## Accessibility
|
||||
|
||||
[Keyboard focus](../accessibility#focus-management) is automatically handled:
|
||||
|
||||
@@ -46,6 +46,18 @@ Style the color of the button based upon the _context_ of the Admonition, not it
|
||||
|
||||
Only ever use the `primary` (green) button `type` on a `default` Admonition. Even then—given that Admonition is an isolated callout—the `primary` button `type` should rarely be used.
|
||||
|
||||
### Split button with dropdown
|
||||
|
||||
When a callout offers alternative ways to accomplish the same goal, use a [split button](../components/button#split-with-dropdown) instead of crowding the callout with multiple buttons. For related but distinct actions, use an overflow menu. See [Table multiple actions](../components/table#multiple-actions).
|
||||
|
||||
<ComponentPreview
|
||||
name="admonition-button-split"
|
||||
className="[&_.preview>[data-orientation=vertical]]:sm:max-w-[70%]"
|
||||
peekCode
|
||||
/>
|
||||
|
||||
In responsive Admonitions, wrap the pair in `flex w-full @lg:w-auto`, give the primary `flex-1 @lg:flex-none`, and keep the chevron `shrink-0`.
|
||||
|
||||
## Examples
|
||||
|
||||
<ComponentPreview
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
import {
|
||||
Button,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from 'ui'
|
||||
import { Admonition } from 'ui-patterns/admonition'
|
||||
|
||||
export default function AdmonitionButtonSplitDemo() {
|
||||
return (
|
||||
<Admonition
|
||||
type="default"
|
||||
layout="responsive"
|
||||
title="Set up custom SMTP to edit templates"
|
||||
description="Emails will be sent using the default templates. Set up custom SMTP to edit their subject and body."
|
||||
actions={
|
||||
<div className="flex w-full @lg:w-auto">
|
||||
<Button
|
||||
type="button"
|
||||
variant="default"
|
||||
className="flex-1 rounded-r-none px-3 @lg:flex-none hover:z-10"
|
||||
>
|
||||
Set up SMTP
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="default"
|
||||
aria-label="More email template editing options"
|
||||
className="shrink-0 rounded-l-none px-[4px] py-[5px] -ml-px"
|
||||
icon={<ChevronDown />}
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-52">
|
||||
<DropdownMenuItem>
|
||||
<div className="flex flex-col gap-y-0.5">
|
||||
<p className="block text-foreground">Upgrade to Pro</p>
|
||||
<p className="block text-foreground-lighter text-balance">
|
||||
Customize templates while using Supabase’s email service
|
||||
</p>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<div className="flex flex-col gap-y-0.5">
|
||||
<p className="block text-foreground">Configure Send Email hook</p>
|
||||
<p className="block text-foreground-lighter text-balance">
|
||||
Send auth emails through your own workflow
|
||||
</p>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
import {
|
||||
Button,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from 'ui'
|
||||
|
||||
export default function ButtonSplitDropdownDemo() {
|
||||
return (
|
||||
<div className="flex w-fit">
|
||||
<Button type="button" variant="default" className="rounded-r-none hover:z-10">
|
||||
Primary action
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="default"
|
||||
aria-label="More actions"
|
||||
className="shrink-0 rounded-l-none px-[4px] py-[5px] -ml-px"
|
||||
icon={<ChevronDown />}
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-48">
|
||||
<DropdownMenuItem>Secondary action</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem className="text-destructive focus:text-destructive">
|
||||
Destructive action
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -25,6 +25,12 @@ export const examples: Registry = [
|
||||
registryDependencies: ['admonition'],
|
||||
files: ['example/admonition-button.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'admonition-button-split',
|
||||
type: 'components:example',
|
||||
registryDependencies: ['admonition', 'button', 'dropdown-menu'],
|
||||
files: ['example/admonition-button-split.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'admonition-description-only',
|
||||
type: 'components:example',
|
||||
@@ -253,6 +259,12 @@ export const examples: Registry = [
|
||||
registryDependencies: ['button'],
|
||||
files: ['example/button-as-child.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'button-split-dropdown',
|
||||
type: 'components:example',
|
||||
registryDependencies: ['button', 'dropdown-menu'],
|
||||
files: ['example/button-split-dropdown.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'calendar-demo',
|
||||
type: 'components:example',
|
||||
|
||||
Reference in new issue
Block a user