feat(app): updates to design system (#34066)

This commit is contained in:
Aarav Sareen
2026-06-26 13:22:35 +00:00
committed by GitHub
parent 6e8e772582
commit 9c38b91e91
13 changed files with 286 additions and 100 deletions
@@ -1,6 +1,6 @@
import "@pierre/trees/web-components" import "@pierre/trees/web-components"
import { FileTree } from "@pierre/trees" import { FileTree } from "@pierre/trees"
import { Dialog, DialogFooter } from "@opencode-ai/ui/v2/dialog-v2" import { Dialog, DialogBody, DialogFooter, DialogHeader, DialogTitle } from "@opencode-ai/ui/v2/dialog-v2"
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2" import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2" import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
import { useDialog } from "@opencode-ai/ui/context/dialog" import { useDialog } from "@opencode-ai/ui/context/dialog"
@@ -27,6 +27,7 @@ import {
pickerRoot, pickerRoot,
} from "./directory-picker-domain" } from "./directory-picker-domain"
import "./dialog-select-directory-v2.css" import "./dialog-select-directory-v2.css"
import { DividerV2 } from "@opencode-ai/ui/v2/divider-v2"
interface DialogSelectDirectoryV2Props { interface DialogSelectDirectoryV2Props {
title?: string title?: string
@@ -266,8 +267,12 @@ export function DialogSelectDirectoryV2(props: DialogSelectDirectoryV2Props) {
onCleanup(() => tree?.cleanUp()) onCleanup(() => tree?.cleanUp())
return ( return (
<Dialog title={props.title ?? language.t("command.project.open")} size="large" class="directory-picker-v2"> <Dialog size="large" class="directory-picker-v2">
<div class="directory-picker-v2-body"> <DialogHeader>
<DialogTitle>{props.title ?? language.t("command.project.open")}</DialogTitle>
</DialogHeader>
<DividerV2 />
<DialogBody class="directory-picker-v2-body pt-4!">
<div class="directory-picker-v2-path" ref={pathArea}> <div class="directory-picker-v2-path" ref={pathArea}>
<TextInputV2 <TextInputV2
value={input()} value={input()}
@@ -349,7 +354,7 @@ export function DialogSelectDirectoryV2(props: DialogSelectDirectoryV2Props) {
</Show> </Show>
</div> </div>
<div class="directory-picker-v2-selection">{policy.result(root(), selected(), rootValid())}</div> <div class="directory-picker-v2-selection">{policy.result(root(), selected(), rootValid())}</div>
</div> </DialogBody>
<DialogFooter> <DialogFooter>
<ButtonV2 variant="neutral" onClick={() => dialog.close()}> <ButtonV2 variant="neutral" onClick={() => dialog.close()}>
{language.t("common.cancel")} {language.t("common.cancel")}
@@ -1,5 +1,6 @@
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2" import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
import { Dialog, DialogFooter } from "@opencode-ai/ui/v2/dialog-v2" import { Dialog, DialogBody, DialogFooter, DialogHeader, DialogTitle } from "@opencode-ai/ui/v2/dialog-v2"
import { DividerV2 } from "@opencode-ai/ui/v2/divider-v2"
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2" import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
import { useDialog } from "@opencode-ai/ui/context/dialog" import { useDialog } from "@opencode-ai/ui/context/dialog"
import { type Component, Show, createEffect, createSignal, onCleanup, onMount } from "solid-js" import { type Component, Show, createEffect, createSignal, onCleanup, onMount } from "solid-js"
@@ -52,8 +53,12 @@ export const DialogServerV2: Component<{
} }
return ( return (
<Dialog title={title()} fit class="settings-v2-server-dialog"> <Dialog fit class="settings-v2-server-dialog">
<div class="flex w-full min-w-0 flex-1 flex-col px-4"> <DialogHeader hideClose={true}>
<DialogTitle>{title()}</DialogTitle>
</DialogHeader>
<DividerV2 />
<DialogBody class="flex w-full min-w-0 flex-1 flex-col px-4 pt-4 pb-2">
<div class="flex w-full min-w-0 flex-col gap-6"> <div class="flex w-full min-w-0 flex-col gap-6">
<div class="flex w-full min-w-0 flex-col gap-2"> <div class="flex w-full min-w-0 flex-col gap-2">
<label class="settings-v2-server-dialog-label">{language.t("dialog.server.add.url")}</label> <label class="settings-v2-server-dialog-label">{language.t("dialog.server.add.url")}</label>
@@ -115,7 +120,7 @@ export const DialogServerV2: Component<{
</div> </div>
</div> </div>
</div> </div>
</div> </DialogBody>
<DialogFooter> <DialogFooter>
<ButtonV2 variant="neutral" disabled={controller.formBusy()} onClick={() => dialog.close()}> <ButtonV2 variant="neutral" disabled={controller.formBusy()} onClick={() => dialog.close()}>
{language.t("common.cancel")} {language.t("common.cancel")}
@@ -633,7 +633,7 @@
[data-component="dialog-v2"].settings-v2-server-dialog [data-slot="dialog-header"] { [data-component="dialog-v2"].settings-v2-server-dialog [data-slot="dialog-header"] {
align-items: center; align-items: center;
padding: 24px 24px 0; padding: 24px 24px 16px;
} }
[data-component="dialog-v2"].settings-v2-server-dialog [data-slot="dialog-body"] { [data-component="dialog-v2"].settings-v2-server-dialog [data-slot="dialog-body"] {
+9 -2
View File
@@ -1,7 +1,8 @@
import { useDialog } from "@opencode-ai/ui/context/dialog" import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Tag } from "@opencode-ai/ui/v2/badge-v2" import { Tag } from "@opencode-ai/ui/v2/badge-v2"
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2" import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
import { Dialog } from "@opencode-ai/ui/v2/dialog-v2" import { Dialog, DialogBody, DialogHeader, DialogTitle } from "@opencode-ai/ui/v2/dialog-v2"
import { DividerV2 } from "@opencode-ai/ui/v2/divider-v2"
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon" import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2" import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2" import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
@@ -30,8 +31,14 @@ export function AddServerMenu(props: { onAddServer: () => void }) {
const language = useLanguage() const language = useLanguage()
const openAddWsl = () => { const openAddWsl = () => {
dialog.push(() => ( dialog.push(() => (
<Dialog title={language.t("wsl.server.add")} size="large" fit class="settings-v2-wsl-dialog"> <Dialog size="large" fit class="settings-v2-wsl-dialog">
<DialogHeader hideClose={true}>
<DialogTitle>{language.t("wsl.server.add")}</DialogTitle>
</DialogHeader>
<DividerV2 />
<DialogBody>
<DialogAddWslServer /> <DialogAddWslServer />
</DialogBody>
</Dialog> </Dialog>
)) ))
} }
@@ -89,6 +89,30 @@
cursor: not-allowed; cursor: not-allowed;
} }
/* Danger */
[data-component="button-v2"][data-variant="danger"] {
background-color: var(--v2-background-bg-button-neutral);
color: var(--v2-state-fg-danger);
box-shadow: var(--v2-elevation-button-neutral);
}
[data-component="button-v2"][data-variant="danger"]:is(:hover, [data-state="hover"]):not(:disabled) {
background-image:
linear-gradient(90deg, var(--v2-overlay-simple-overlay-hover) 0%, var(--v2-overlay-simple-overlay-hover) 100%),
linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%);
}
[data-component="button-v2"][data-variant="danger"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-image:
linear-gradient(90deg, var(--v2-overlay-simple-overlay-pressed) 0%, var(--v2-overlay-simple-overlay-pressed) 100%),
linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%);
}
[data-component="button-v2"][data-variant="danger"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.5;
cursor: not-allowed;
}
/* Contrast */ /* Contrast */
[data-component="button-v2"][data-variant="contrast"] { [data-component="button-v2"][data-variant="contrast"] {
background-image: background-image:
@@ -4,7 +4,7 @@ const docs = `### Overview
Button v2 with three visual variants and two sizes. Button v2 with three visual variants and two sizes.
### API ### API
- \`variant\`: "neutral" | "contrast" | "ghost" | "ghost-muted". - \`variant\`: "neutral" | "danger" | "contrast" | "ghost" | "ghost-muted".
- \`size\`: "normal" | "large". - \`size\`: "normal" | "large".
- \`icon\`: Optional icon name. - \`icon\`: Optional icon name.
- Inherits Kobalte Button props and native button attributes. - Inherits Kobalte Button props and native button attributes.
@@ -39,7 +39,7 @@ export default {
}, },
variant: { variant: {
control: "select", control: "select",
options: ["neutral", "contrast", "ghost", "ghost-muted"], options: ["neutral", "danger", "contrast", "ghost", "ghost-muted"],
}, },
size: { size: {
control: "select", control: "select",
@@ -61,6 +61,7 @@ export const Variants = {
}} }}
> >
<ButtonV2 variant="neutral">Neutral</ButtonV2> <ButtonV2 variant="neutral">Neutral</ButtonV2>
<ButtonV2 variant="danger">Danger</ButtonV2>
<ButtonV2 variant="contrast">Contrast</ButtonV2> <ButtonV2 variant="contrast">Contrast</ButtonV2>
<ButtonV2 variant="ghost">Ghost</ButtonV2> <ButtonV2 variant="ghost">Ghost</ButtonV2>
<ButtonV2 variant="ghost-muted" icon="edit"> <ButtonV2 variant="ghost-muted" icon="edit">
@@ -115,7 +116,7 @@ export const Icon = {
export const AllStates = { export const AllStates = {
render: () => { render: () => {
const variants = ["neutral", "contrast", "ghost", "ghost-muted"] as const const variants = ["neutral", "danger", "contrast", "ghost", "ghost-muted"] as const
const states = ["default", "hover", "pressed", "focus", "disabled"] as const const states = ["default", "hover", "pressed", "focus", "disabled"] as const
const toTitleCase = (value: string) => value.charAt(0).toUpperCase() + value.slice(1) const toTitleCase = (value: string) => value.charAt(0).toUpperCase() + value.slice(1)
return ( return (
+1 -1
View File
@@ -7,7 +7,7 @@ export interface ButtonV2Props
extends ComponentProps<typeof Kobalte>, extends ComponentProps<typeof Kobalte>,
Pick<ComponentProps<"button">, "class" | "classList" | "children"> { Pick<ComponentProps<"button">, "class" | "classList" | "children"> {
size?: "small" | "normal" | "large" size?: "small" | "normal" | "large"
variant?: "neutral" | "contrast" | "ghost" | "ghost-muted" variant?: "neutral" | "danger" | "contrast" | "ghost" | "ghost-muted"
icon?: IconProps["name"] icon?: IconProps["name"]
} }
+32 -8
View File
@@ -49,34 +49,51 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: flex-start; align-items: flex-start;
padding: 16px;
gap: 8px;
flex-shrink: 0;
align-self: stretch; align-self: stretch;
width: 100%;
gap: 0;
padding: 16px;
flex-shrink: 0;
[data-slot="dialog-header-title"] {
margin: 0;
flex: 1;
min-width: 0;
font-weight: 530;
font-size: 13px;
line-height: 16px;
letter-spacing: -0.04px;
color: var(--v2-text-text-base);
font-variation-settings: "slnt" 0;
}
[data-slot="dialog-title-group"] { [data-slot="dialog-title-group"] {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
gap: 8px; gap: 4px;
flex: 1; flex: 1;
min-width: 0; min-width: 0;
} }
[data-slot="dialog-title"] { [data-slot="dialog-title"] {
margin: 0; margin: 0;
flex: none;
flex-grow: 0;
font-weight: 530; font-weight: 530;
font-size: 15px; font-size: 15px;
line-height: 100%; line-height: 20px;
letter-spacing: -0.13px; letter-spacing: -0.13px;
color: var(--v2-text-text-base); color: var(--v2-text-text-base);
font-variation-settings: "slnt" 0; font-variation-settings: "slnt" 0;
} }
[data-slot="dialog-description"] { [data-slot="dialog-description"] {
flex: none;
flex-grow: 0;
font-weight: 440; font-weight: 440;
font-size: 13px; font-size: 13px;
line-height: 100%; line-height: 20px;
letter-spacing: -0.04px; letter-spacing: -0.04px;
color: var(--v2-text-text-muted); color: var(--v2-text-text-muted);
font-variation-settings: "slnt" 0; font-variation-settings: "slnt" 0;
@@ -86,8 +103,11 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 24px; width: 20px;
height: 24px; height: 20px;
margin-top: -2px;
margin-right: -2px;
margin-left: auto;
border-radius: 4px; border-radius: 4px;
flex-shrink: 0; flex-shrink: 0;
cursor: pointer; cursor: pointer;
@@ -97,6 +117,10 @@
background: var(--v2-overlay-simple-overlay-hover); background: var(--v2-overlay-simple-overlay-hover);
} }
} }
&[data-hide-close] [data-slot="dialog-close-button"] {
display: none;
}
} }
[data-slot="dialog-footer"] { [data-slot="dialog-footer"] {
@@ -1,17 +1,17 @@
import { Dialog as KobalteDialog } from "@kobalte/core/dialog" import { Dialog as KobalteDialog } from "@kobalte/core/dialog"
import { Dialog, DialogFooter } from "./dialog-v2" import { Dialog, DialogBody, DialogFooter, DialogHeader, DialogTitle, DialogTitleGroup } from "./dialog-v2"
import { ButtonV2 } from "./button-v2" import { ButtonV2 } from "./button-v2"
const docs = `### Overview const docs = `### Overview
Dialog content wrapper built on Kobalte's dialog primitive with v2 styling. Dialog content wrapper built on Kobalte's dialog primitive with v2 styling.
### API Compose with \`DialogHeader\`, \`DialogTitle\`, \`DialogTitleGroup\`, \`DialogBody\`, and \`DialogFooter\`.
- Optional: \`title\`, \`description\`, \`action\`.
- \`size\`: normal | large | x-large.
- \`fit\` and \`transition\` control layout and animation.
### Variants and states ### API
- Sizes and optional header/action controls. - \`Dialog\`: \`size\` (normal | large | x-large), \`variant\`, \`fit\`.
- \`DialogHeader\`: row container with optional \`closeLabel\` and \`hideClose\`.
- \`DialogTitle\`: accessible single-line header title.
- \`DialogTitleGroup\`: column with \`title\` and required \`description\`.
### Accessibility ### Accessibility
- Focus trapping and aria attributes provided by Kobalte Dialog. - Focus trapping and aria attributes provided by Kobalte Dialog.
@@ -34,6 +34,14 @@ export default {
}, },
} }
function dialogHeader(title: string, description: string) {
return (
<DialogHeader>
<DialogTitleGroup title={title} description={description} />
</DialogHeader>
)
}
export const Basic = { export const Basic = {
render: () => ( render: () => (
<KobalteDialog defaultOpen> <KobalteDialog defaultOpen>
@@ -42,8 +50,9 @@ export const Basic = {
</KobalteDialog.Trigger> </KobalteDialog.Trigger>
<KobalteDialog.Portal> <KobalteDialog.Portal>
<KobalteDialog.Overlay /> <KobalteDialog.Overlay />
<Dialog title="Dialog" description="Description"> <Dialog>
Dialog body content. {dialogHeader("Dialog", "Description")}
<DialogBody>Dialog body content.</DialogBody>
</Dialog> </Dialog>
</KobalteDialog.Portal> </KobalteDialog.Portal>
</KobalteDialog> </KobalteDialog>
@@ -59,8 +68,9 @@ export const Sizes = {
</KobalteDialog.Trigger> </KobalteDialog.Trigger>
<KobalteDialog.Portal> <KobalteDialog.Portal>
<KobalteDialog.Overlay /> <KobalteDialog.Overlay />
<Dialog title="Normal" description="Normal size"> <Dialog>
Normal dialog content. {dialogHeader("Normal", "Normal size")}
<DialogBody>Normal dialog content.</DialogBody>
</Dialog> </Dialog>
</KobalteDialog.Portal> </KobalteDialog.Portal>
</KobalteDialog> </KobalteDialog>
@@ -71,8 +81,9 @@ export const Sizes = {
</KobalteDialog.Trigger> </KobalteDialog.Trigger>
<KobalteDialog.Portal> <KobalteDialog.Portal>
<KobalteDialog.Overlay /> <KobalteDialog.Overlay />
<Dialog size="large" title="Large" description="Large size"> <Dialog size="large">
Large dialog content. {dialogHeader("Large", "Large size")}
<DialogBody>Large dialog content.</DialogBody>
</Dialog> </Dialog>
</KobalteDialog.Portal> </KobalteDialog.Portal>
</KobalteDialog> </KobalteDialog>
@@ -83,8 +94,9 @@ export const Sizes = {
</KobalteDialog.Trigger> </KobalteDialog.Trigger>
<KobalteDialog.Portal> <KobalteDialog.Portal>
<KobalteDialog.Overlay /> <KobalteDialog.Overlay />
<Dialog size="x-large" title="Extra large" description="X-large size"> <Dialog size="x-large">
X-large dialog content. {dialogHeader("Extra large", "X-large size")}
<DialogBody>X-large dialog content.</DialogBody>
</Dialog> </Dialog>
</KobalteDialog.Portal> </KobalteDialog.Portal>
</KobalteDialog> </KobalteDialog>
@@ -92,24 +104,41 @@ export const Sizes = {
), ),
} }
export const CustomAction = { export const TitleOnly = {
render: () => ( render: () => (
<KobalteDialog> <KobalteDialog defaultOpen>
<KobalteDialog.Trigger as={ButtonV2} variant="neutral"> <KobalteDialog.Trigger as={ButtonV2} variant="neutral">
Open action dialog Open dialog
</KobalteDialog.Trigger> </KobalteDialog.Trigger>
<KobalteDialog.Portal> <KobalteDialog.Portal>
<KobalteDialog.Overlay /> <KobalteDialog.Overlay />
<Dialog <Dialog>
title="Custom action" <DialogHeader>
description="Dialog with a custom header action" <DialogTitle>Open project</DialogTitle>
action={ </DialogHeader>
<DialogBody>Dialog body content.</DialogBody>
</Dialog>
</KobalteDialog.Portal>
</KobalteDialog>
),
}
export const HeaderControls = {
render: () => (
<KobalteDialog>
<KobalteDialog.Trigger as={ButtonV2} variant="neutral">
Open dialog
</KobalteDialog.Trigger>
<KobalteDialog.Portal>
<KobalteDialog.Overlay />
<Dialog>
<DialogHeader>
<DialogTitleGroup title="Custom header" description="Dialog with an extra header control" />
<ButtonV2 variant="neutral" size="small"> <ButtonV2 variant="neutral" size="small">
Help Help
</ButtonV2> </ButtonV2>
} </DialogHeader>
> <DialogBody>Dialog body content.</DialogBody>
Dialog body content.
</Dialog> </Dialog>
</KobalteDialog.Portal> </KobalteDialog.Portal>
</KobalteDialog> </KobalteDialog>
@@ -124,7 +153,8 @@ export const WithFooter = {
</KobalteDialog.Trigger> </KobalteDialog.Trigger>
<KobalteDialog.Portal> <KobalteDialog.Portal>
<KobalteDialog.Overlay /> <KobalteDialog.Overlay />
<Dialog title="Save changes" description="Your changes will be lost if you don't save them." fit> <Dialog fit>
{dialogHeader("Save changes", "Your changes will be lost if you don't save them.")}
<DialogFooter> <DialogFooter>
<ButtonV2 variant="neutral">Cancel</ButtonV2> <ButtonV2 variant="neutral">Cancel</ButtonV2>
<ButtonV2 variant="contrast">Save</ButtonV2> <ButtonV2 variant="contrast">Save</ButtonV2>
@@ -143,7 +173,8 @@ export const WithFooterThreeButtons = {
</KobalteDialog.Trigger> </KobalteDialog.Trigger>
<KobalteDialog.Portal> <KobalteDialog.Portal>
<KobalteDialog.Overlay /> <KobalteDialog.Overlay />
<Dialog title="Unsaved changes" description="You have unsaved changes. What would you like to do?" fit> <Dialog fit>
{dialogHeader("Unsaved changes", "You have unsaved changes. What would you like to do?")}
<DialogFooter> <DialogFooter>
<span style={{ "margin-right": "auto" }}> <span style={{ "margin-right": "auto" }}>
<ButtonV2 variant="ghost">Remind me later</ButtonV2> <ButtonV2 variant="ghost">Remind me later</ButtonV2>
@@ -165,8 +196,9 @@ export const Fit = {
</KobalteDialog.Trigger> </KobalteDialog.Trigger>
<KobalteDialog.Portal> <KobalteDialog.Portal>
<KobalteDialog.Overlay /> <KobalteDialog.Overlay />
<Dialog title="Fit content" fit> <Dialog fit>
Dialog fits its content. {dialogHeader("Fit content", "Dialog fits its content.")}
<DialogBody>Dialog fits its content.</DialogBody>
</Dialog> </Dialog>
</KobalteDialog.Portal> </KobalteDialog.Portal>
</KobalteDialog> </KobalteDialog>
+70 -51
View File
@@ -3,9 +3,6 @@ import { type ComponentProps, type JSXElement, type ParentProps, Show, children,
import "./dialog-v2.css" import "./dialog-v2.css"
export interface DialogProps extends ParentProps { export interface DialogProps extends ParentProps {
title?: JSXElement
description?: JSXElement
action?: JSXElement
size?: "normal" | "large" | "x-large" size?: "normal" | "large" | "x-large"
variant?: "default" | "settings" variant?: "default" | "settings"
class?: ComponentProps<"div">["class"] class?: ComponentProps<"div">["class"]
@@ -13,61 +10,54 @@ export interface DialogProps extends ParentProps {
fit?: boolean fit?: boolean
} }
export interface DialogHeaderProps extends ParentProps {
closeLabel?: string
hideClose?: boolean
}
export interface DialogTitleGroupProps {
title?: JSXElement
description: JSXElement
}
export function DialogFooter(props: ParentProps) { export function DialogFooter(props: ParentProps) {
return <div data-slot="dialog-footer">{props.children}</div> return <div data-slot="dialog-footer">{props.children}</div>
} }
export function Dialog(props: DialogProps) { export function DialogBody(props: ParentProps & { class?: ComponentProps<"div">["class"] }) {
const [local] = splitProps(props, [ const [local] = splitProps(props, ["class", "children"])
"title", return (
"description", <div data-slot="dialog-body" class={local.class}>
"action", {local.children}
"size", </div>
"variant", )
"class", }
"classList",
"fit", export function DialogTitle(props: ParentProps) {
"children", return <Kobalte.Title data-slot="dialog-header-title">{props.children}</Kobalte.Title>
]) }
const title = children(() => local.title)
const description = children(() => local.description) export function DialogTitleGroup(props: DialogTitleGroupProps) {
const action = children(() => local.action) const title = children(() => props.title)
const hasHeader = () => title() || action() const description = children(() => props.description)
return ( return (
<div
data-component="dialog-v2"
data-variant={local.variant === "settings" ? "settings" : undefined}
data-fit={local.fit ? true : undefined}
data-size={local.size || "normal"}
>
<div data-slot="dialog-container">
<Kobalte.Content
data-slot="dialog-content"
data-no-header={!hasHeader() ? "" : undefined}
classList={{
...local.classList,
[local.class ?? ""]: !!local.class,
}}
onOpenAutoFocus={(e) => {
const target = e.currentTarget as HTMLElement | null
const autofocusEl = target?.querySelector("[autofocus]") as HTMLElement | null
if (autofocusEl) {
e.preventDefault()
autofocusEl.focus()
}
}}
>
<Show when={hasHeader()}>
<div data-slot="dialog-header">
<div data-slot="dialog-title-group"> <div data-slot="dialog-title-group">
<Show when={title()}>{(t) => <Kobalte.Title data-slot="dialog-title">{t()}</Kobalte.Title>}</Show> <Show when={title()}>{(t) => <Kobalte.Title data-slot="dialog-title">{t()}</Kobalte.Title>}</Show>
<Show when={description()}> <Kobalte.Description data-slot="dialog-description">{description()}</Kobalte.Description>
{(d) => <Kobalte.Description data-slot="dialog-description">{d()}</Kobalte.Description>}
</Show>
</div> </div>
<Show when={action()}>{(a) => a()}</Show> )
<Kobalte.CloseButton data-slot="dialog-close-button" aria-label="Close"> }
export function DialogHeader(props: DialogHeaderProps) {
const [local] = splitProps(props, ["closeLabel", "hideClose", "children"])
const hideClose = () => local.hideClose === true
return (
<div data-slot="dialog-header" data-hide-close={hideClose() ? "" : undefined}>
{local.children}
{!hideClose() && (
<Kobalte.CloseButton data-slot="dialog-close-button" aria-label={local.closeLabel ?? "Close"}>
<svg <svg
width="16" width="16"
height="16" height="16"
@@ -83,9 +73,38 @@ export function Dialog(props: DialogProps) {
/> />
</svg> </svg>
</Kobalte.CloseButton> </Kobalte.CloseButton>
)}
</div> </div>
</Show> )
<div data-slot="dialog-body">{local.children}</div> }
export function Dialog(props: DialogProps) {
const [local] = splitProps(props, ["size", "variant", "class", "classList", "fit", "children"])
return (
<div
data-component="dialog-v2"
data-variant={local.variant === "settings" ? "settings" : undefined}
data-fit={local.fit ? true : undefined}
data-size={local.size || "normal"}
>
<div data-slot="dialog-container">
<Kobalte.Content
data-slot="dialog-content"
classList={{
...local.classList,
[local.class ?? ""]: !!local.class,
}}
onOpenAutoFocus={(e) => {
const target = e.currentTarget as HTMLElement | null
const autofocusEl = target?.querySelector("[autofocus]") as HTMLElement | null
if (autofocusEl) {
e.preventDefault()
autofocusEl.focus()
}
}}
>
{local.children}
</Kobalte.Content> </Kobalte.Content>
</div> </div>
</div> </div>
@@ -0,0 +1,11 @@
[data-component="divider-v2"] {
box-sizing: border-box;
width: 100%;
height: 0.5px;
margin-block: 0.25px;
border: none;
background: var(--v2-border-border-strong);
flex: none;
align-self: stretch;
flex-grow: 0;
}
@@ -0,0 +1,38 @@
import { DividerV2 } from "./divider-v2"
const docs = `### Overview
Horizontal hairline divider for v2 layouts.
### API
- Inherits native div attributes.
- Stretches to full width of its flex parent.
### Theming/tokens
- Uses \`data-component="divider-v2"\`.
- Border color: \`--v2-border-border-strong\`.
`
export default {
title: "UI V2/Divider",
id: "components-divider-v2",
component: DividerV2,
tags: ["autodocs"],
parameters: {
frameWidth: "320px",
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = {
render: () => (
<div style={{ display: "flex", "flex-direction": "column", gap: "16px", padding: "16px" }}>
<span>Above</span>
<DividerV2 />
<span>Below</span>
</div>
),
}
@@ -0,0 +1,20 @@
import { type ComponentProps, splitProps } from "solid-js"
import "./divider-v2.css"
export interface DividerV2Props extends ComponentProps<"div"> {}
export function DividerV2(props: DividerV2Props) {
const [local, rest] = splitProps(props, ["class", "classList"])
return (
<div
{...rest}
role="separator"
aria-orientation="horizontal"
data-component="divider-v2"
classList={{
...local.classList,
[local.class ?? ""]: !!local.class,
}}
/>
)
}