feat(app): updates to design system (#34066)
This commit is contained in:
parent
6e8e772582
commit
9c38b91e91
13 changed files with 284 additions and 98 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import "@pierre/trees/web-components"
|
||||
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 { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
|
|
@ -27,6 +27,7 @@ import {
|
|||
pickerRoot,
|
||||
} from "./directory-picker-domain"
|
||||
import "./dialog-select-directory-v2.css"
|
||||
import { DividerV2 } from "@opencode-ai/ui/v2/divider-v2"
|
||||
|
||||
interface DialogSelectDirectoryV2Props {
|
||||
title?: string
|
||||
|
|
@ -266,8 +267,12 @@ export function DialogSelectDirectoryV2(props: DialogSelectDirectoryV2Props) {
|
|||
onCleanup(() => tree?.cleanUp())
|
||||
|
||||
return (
|
||||
<Dialog title={props.title ?? language.t("command.project.open")} size="large" class="directory-picker-v2">
|
||||
<div class="directory-picker-v2-body">
|
||||
<Dialog size="large" class="directory-picker-v2">
|
||||
<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}>
|
||||
<TextInputV2
|
||||
value={input()}
|
||||
|
|
@ -349,7 +354,7 @@ export function DialogSelectDirectoryV2(props: DialogSelectDirectoryV2Props) {
|
|||
</Show>
|
||||
</div>
|
||||
<div class="directory-picker-v2-selection">{policy.result(root(), selected(), rootValid())}</div>
|
||||
</div>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<ButtonV2 variant="neutral" onClick={() => dialog.close()}>
|
||||
{language.t("common.cancel")}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
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 { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { type Component, Show, createEffect, createSignal, onCleanup, onMount } from "solid-js"
|
||||
|
|
@ -52,8 +53,12 @@ export const DialogServerV2: Component<{
|
|||
}
|
||||
|
||||
return (
|
||||
<Dialog title={title()} fit class="settings-v2-server-dialog">
|
||||
<div class="flex w-full min-w-0 flex-1 flex-col px-4">
|
||||
<Dialog fit class="settings-v2-server-dialog">
|
||||
<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-2">
|
||||
<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>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<ButtonV2 variant="neutral" disabled={controller.formBusy()} onClick={() => dialog.close()}>
|
||||
{language.t("common.cancel")}
|
||||
|
|
|
|||
|
|
@ -633,7 +633,7 @@
|
|||
|
||||
[data-component="dialog-v2"].settings-v2-server-dialog [data-slot="dialog-header"] {
|
||||
align-items: center;
|
||||
padding: 24px 24px 0;
|
||||
padding: 24px 24px 16px;
|
||||
}
|
||||
|
||||
[data-component="dialog-v2"].settings-v2-server-dialog [data-slot="dialog-body"] {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { Tag } from "@opencode-ai/ui/v2/badge-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 { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
||||
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
|
||||
|
|
@ -30,8 +31,14 @@ export function AddServerMenu(props: { onAddServer: () => void }) {
|
|||
const language = useLanguage()
|
||||
const openAddWsl = () => {
|
||||
dialog.push(() => (
|
||||
<Dialog title={language.t("wsl.server.add")} size="large" fit class="settings-v2-wsl-dialog">
|
||||
<DialogAddWslServer />
|
||||
<Dialog size="large" fit class="settings-v2-wsl-dialog">
|
||||
<DialogHeader hideClose={true}>
|
||||
<DialogTitle>{language.t("wsl.server.add")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DividerV2 />
|
||||
<DialogBody>
|
||||
<DialogAddWslServer />
|
||||
</DialogBody>
|
||||
</Dialog>
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,6 +89,30 @@
|
|||
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 */
|
||||
[data-component="button-v2"][data-variant="contrast"] {
|
||||
background-image:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ const docs = `### Overview
|
|||
Button v2 with three visual variants and two sizes.
|
||||
|
||||
### API
|
||||
- \`variant\`: "neutral" | "contrast" | "ghost" | "ghost-muted".
|
||||
- \`variant\`: "neutral" | "danger" | "contrast" | "ghost" | "ghost-muted".
|
||||
- \`size\`: "normal" | "large".
|
||||
- \`icon\`: Optional icon name.
|
||||
- Inherits Kobalte Button props and native button attributes.
|
||||
|
|
@ -39,7 +39,7 @@ export default {
|
|||
},
|
||||
variant: {
|
||||
control: "select",
|
||||
options: ["neutral", "contrast", "ghost", "ghost-muted"],
|
||||
options: ["neutral", "danger", "contrast", "ghost", "ghost-muted"],
|
||||
},
|
||||
size: {
|
||||
control: "select",
|
||||
|
|
@ -61,6 +61,7 @@ export const Variants = {
|
|||
}}
|
||||
>
|
||||
<ButtonV2 variant="neutral">Neutral</ButtonV2>
|
||||
<ButtonV2 variant="danger">Danger</ButtonV2>
|
||||
<ButtonV2 variant="contrast">Contrast</ButtonV2>
|
||||
<ButtonV2 variant="ghost">Ghost</ButtonV2>
|
||||
<ButtonV2 variant="ghost-muted" icon="edit">
|
||||
|
|
@ -115,7 +116,7 @@ export const Icon = {
|
|||
|
||||
export const AllStates = {
|
||||
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 toTitleCase = (value: string) => value.charAt(0).toUpperCase() + value.slice(1)
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export interface ButtonV2Props
|
|||
extends ComponentProps<typeof Kobalte>,
|
||||
Pick<ComponentProps<"button">, "class" | "classList" | "children"> {
|
||||
size?: "small" | "normal" | "large"
|
||||
variant?: "neutral" | "contrast" | "ghost" | "ghost-muted"
|
||||
variant?: "neutral" | "danger" | "contrast" | "ghost" | "ghost-muted"
|
||||
icon?: IconProps["name"]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,34 +49,51 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
padding: 16px;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
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"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
gap: 4px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
[data-slot="dialog-title"] {
|
||||
margin: 0;
|
||||
flex: none;
|
||||
flex-grow: 0;
|
||||
font-weight: 530;
|
||||
font-size: 15px;
|
||||
line-height: 100%;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.13px;
|
||||
color: var(--v2-text-text-base);
|
||||
font-variation-settings: "slnt" 0;
|
||||
}
|
||||
|
||||
[data-slot="dialog-description"] {
|
||||
flex: none;
|
||||
flex-grow: 0;
|
||||
font-weight: 440;
|
||||
font-size: 13px;
|
||||
line-height: 100%;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.04px;
|
||||
color: var(--v2-text-text-muted);
|
||||
font-variation-settings: "slnt" 0;
|
||||
|
|
@ -86,8 +103,11 @@
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: -2px;
|
||||
margin-right: -2px;
|
||||
margin-left: auto;
|
||||
border-radius: 4px;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
|
|
@ -97,6 +117,10 @@
|
|||
background: var(--v2-overlay-simple-overlay-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-hide-close] [data-slot="dialog-close-button"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="dialog-footer"] {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
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"
|
||||
|
||||
const docs = `### Overview
|
||||
Dialog content wrapper built on Kobalte's dialog primitive with v2 styling.
|
||||
|
||||
### API
|
||||
- Optional: \`title\`, \`description\`, \`action\`.
|
||||
- \`size\`: normal | large | x-large.
|
||||
- \`fit\` and \`transition\` control layout and animation.
|
||||
Compose with \`DialogHeader\`, \`DialogTitle\`, \`DialogTitleGroup\`, \`DialogBody\`, and \`DialogFooter\`.
|
||||
|
||||
### Variants and states
|
||||
- Sizes and optional header/action controls.
|
||||
### API
|
||||
- \`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
|
||||
- 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 = {
|
||||
render: () => (
|
||||
<KobalteDialog defaultOpen>
|
||||
|
|
@ -42,8 +50,9 @@ export const Basic = {
|
|||
</KobalteDialog.Trigger>
|
||||
<KobalteDialog.Portal>
|
||||
<KobalteDialog.Overlay />
|
||||
<Dialog title="Dialog" description="Description">
|
||||
Dialog body content.
|
||||
<Dialog>
|
||||
{dialogHeader("Dialog", "Description")}
|
||||
<DialogBody>Dialog body content.</DialogBody>
|
||||
</Dialog>
|
||||
</KobalteDialog.Portal>
|
||||
</KobalteDialog>
|
||||
|
|
@ -59,8 +68,9 @@ export const Sizes = {
|
|||
</KobalteDialog.Trigger>
|
||||
<KobalteDialog.Portal>
|
||||
<KobalteDialog.Overlay />
|
||||
<Dialog title="Normal" description="Normal size">
|
||||
Normal dialog content.
|
||||
<Dialog>
|
||||
{dialogHeader("Normal", "Normal size")}
|
||||
<DialogBody>Normal dialog content.</DialogBody>
|
||||
</Dialog>
|
||||
</KobalteDialog.Portal>
|
||||
</KobalteDialog>
|
||||
|
|
@ -71,8 +81,9 @@ export const Sizes = {
|
|||
</KobalteDialog.Trigger>
|
||||
<KobalteDialog.Portal>
|
||||
<KobalteDialog.Overlay />
|
||||
<Dialog size="large" title="Large" description="Large size">
|
||||
Large dialog content.
|
||||
<Dialog size="large">
|
||||
{dialogHeader("Large", "Large size")}
|
||||
<DialogBody>Large dialog content.</DialogBody>
|
||||
</Dialog>
|
||||
</KobalteDialog.Portal>
|
||||
</KobalteDialog>
|
||||
|
|
@ -83,8 +94,9 @@ export const Sizes = {
|
|||
</KobalteDialog.Trigger>
|
||||
<KobalteDialog.Portal>
|
||||
<KobalteDialog.Overlay />
|
||||
<Dialog size="x-large" title="Extra large" description="X-large size">
|
||||
X-large dialog content.
|
||||
<Dialog size="x-large">
|
||||
{dialogHeader("Extra large", "X-large size")}
|
||||
<DialogBody>X-large dialog content.</DialogBody>
|
||||
</Dialog>
|
||||
</KobalteDialog.Portal>
|
||||
</KobalteDialog>
|
||||
|
|
@ -92,24 +104,41 @@ export const Sizes = {
|
|||
),
|
||||
}
|
||||
|
||||
export const CustomAction = {
|
||||
export const TitleOnly = {
|
||||
render: () => (
|
||||
<KobalteDialog>
|
||||
<KobalteDialog defaultOpen>
|
||||
<KobalteDialog.Trigger as={ButtonV2} variant="neutral">
|
||||
Open action dialog
|
||||
Open dialog
|
||||
</KobalteDialog.Trigger>
|
||||
<KobalteDialog.Portal>
|
||||
<KobalteDialog.Overlay />
|
||||
<Dialog
|
||||
title="Custom action"
|
||||
description="Dialog with a custom header action"
|
||||
action={
|
||||
<Dialog>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Open project</DialogTitle>
|
||||
</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">
|
||||
Help
|
||||
</ButtonV2>
|
||||
}
|
||||
>
|
||||
Dialog body content.
|
||||
</DialogHeader>
|
||||
<DialogBody>Dialog body content.</DialogBody>
|
||||
</Dialog>
|
||||
</KobalteDialog.Portal>
|
||||
</KobalteDialog>
|
||||
|
|
@ -124,7 +153,8 @@ export const WithFooter = {
|
|||
</KobalteDialog.Trigger>
|
||||
<KobalteDialog.Portal>
|
||||
<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>
|
||||
<ButtonV2 variant="neutral">Cancel</ButtonV2>
|
||||
<ButtonV2 variant="contrast">Save</ButtonV2>
|
||||
|
|
@ -143,7 +173,8 @@ export const WithFooterThreeButtons = {
|
|||
</KobalteDialog.Trigger>
|
||||
<KobalteDialog.Portal>
|
||||
<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>
|
||||
<span style={{ "margin-right": "auto" }}>
|
||||
<ButtonV2 variant="ghost">Remind me later</ButtonV2>
|
||||
|
|
@ -165,8 +196,9 @@ export const Fit = {
|
|||
</KobalteDialog.Trigger>
|
||||
<KobalteDialog.Portal>
|
||||
<KobalteDialog.Overlay />
|
||||
<Dialog title="Fit content" fit>
|
||||
Dialog fits its content.
|
||||
<Dialog fit>
|
||||
{dialogHeader("Fit content", "Dialog fits its content.")}
|
||||
<DialogBody>Dialog fits its content.</DialogBody>
|
||||
</Dialog>
|
||||
</KobalteDialog.Portal>
|
||||
</KobalteDialog>
|
||||
|
|
|
|||
|
|
@ -3,9 +3,6 @@ import { type ComponentProps, type JSXElement, type ParentProps, Show, children,
|
|||
import "./dialog-v2.css"
|
||||
|
||||
export interface DialogProps extends ParentProps {
|
||||
title?: JSXElement
|
||||
description?: JSXElement
|
||||
action?: JSXElement
|
||||
size?: "normal" | "large" | "x-large"
|
||||
variant?: "default" | "settings"
|
||||
class?: ComponentProps<"div">["class"]
|
||||
|
|
@ -13,26 +10,76 @@ export interface DialogProps extends ParentProps {
|
|||
fit?: boolean
|
||||
}
|
||||
|
||||
export interface DialogHeaderProps extends ParentProps {
|
||||
closeLabel?: string
|
||||
hideClose?: boolean
|
||||
}
|
||||
|
||||
export interface DialogTitleGroupProps {
|
||||
title?: JSXElement
|
||||
description: JSXElement
|
||||
}
|
||||
|
||||
export function DialogFooter(props: ParentProps) {
|
||||
return <div data-slot="dialog-footer">{props.children}</div>
|
||||
}
|
||||
|
||||
export function DialogBody(props: ParentProps & { class?: ComponentProps<"div">["class"] }) {
|
||||
const [local] = splitProps(props, ["class", "children"])
|
||||
return (
|
||||
<div data-slot="dialog-body" class={local.class}>
|
||||
{local.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function DialogTitle(props: ParentProps) {
|
||||
return <Kobalte.Title data-slot="dialog-header-title">{props.children}</Kobalte.Title>
|
||||
}
|
||||
|
||||
export function DialogTitleGroup(props: DialogTitleGroupProps) {
|
||||
const title = children(() => props.title)
|
||||
const description = children(() => props.description)
|
||||
|
||||
return (
|
||||
<div data-slot="dialog-title-group">
|
||||
<Show when={title()}>{(t) => <Kobalte.Title data-slot="dialog-title">{t()}</Kobalte.Title>}</Show>
|
||||
<Kobalte.Description data-slot="dialog-description">{description()}</Kobalte.Description>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M12.4446 3.55469L3.55566 12.4436M3.55566 3.55469L12.4446 12.4436"
|
||||
stroke="currentColor"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</Kobalte.CloseButton>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function Dialog(props: DialogProps) {
|
||||
const [local] = splitProps(props, [
|
||||
"title",
|
||||
"description",
|
||||
"action",
|
||||
"size",
|
||||
"variant",
|
||||
"class",
|
||||
"classList",
|
||||
"fit",
|
||||
"children",
|
||||
])
|
||||
const title = children(() => local.title)
|
||||
const description = children(() => local.description)
|
||||
const action = children(() => local.action)
|
||||
const hasHeader = () => title() || action()
|
||||
const [local] = splitProps(props, ["size", "variant", "class", "classList", "fit", "children"])
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -44,7 +91,6 @@ export function Dialog(props: DialogProps) {
|
|||
<div data-slot="dialog-container">
|
||||
<Kobalte.Content
|
||||
data-slot="dialog-content"
|
||||
data-no-header={!hasHeader() ? "" : undefined}
|
||||
classList={{
|
||||
...local.classList,
|
||||
[local.class ?? ""]: !!local.class,
|
||||
|
|
@ -58,34 +104,7 @@ export function Dialog(props: DialogProps) {
|
|||
}
|
||||
}}
|
||||
>
|
||||
<Show when={hasHeader()}>
|
||||
<div data-slot="dialog-header">
|
||||
<div data-slot="dialog-title-group">
|
||||
<Show when={title()}>{(t) => <Kobalte.Title data-slot="dialog-title">{t()}</Kobalte.Title>}</Show>
|
||||
<Show when={description()}>
|
||||
{(d) => <Kobalte.Description data-slot="dialog-description">{d()}</Kobalte.Description>}
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={action()}>{(a) => a()}</Show>
|
||||
<Kobalte.CloseButton data-slot="dialog-close-button" aria-label="Close">
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M12.4446 3.55469L3.55566 12.4436M3.55566 3.55469L12.4446 12.4436"
|
||||
stroke="currentColor"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</Kobalte.CloseButton>
|
||||
</div>
|
||||
</Show>
|
||||
<div data-slot="dialog-body">{local.children}</div>
|
||||
{local.children}
|
||||
</Kobalte.Content>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
11
packages/ui/src/v2/components/divider-v2.css
Normal file
11
packages/ui/src/v2/components/divider-v2.css
Normal file
|
|
@ -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;
|
||||
}
|
||||
38
packages/ui/src/v2/components/divider-v2.stories.tsx
Normal file
38
packages/ui/src/v2/components/divider-v2.stories.tsx
Normal file
|
|
@ -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>
|
||||
),
|
||||
}
|
||||
20
packages/ui/src/v2/components/divider-v2.tsx
Normal file
20
packages/ui/src/v2/components/divider-v2.tsx
Normal file
|
|
@ -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,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue