feat(app): updates to design system (#34066)
This commit is contained in:
@@ -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"] {
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user