mirror of
https://github.com/langgenius/dify.git
synced 2026-07-21 10:38:32 +08:00
fix(dify-ui): standardize story focus indicators (#38417)
This commit is contained in:
parent
a0f8347513
commit
5d7ae56deb
@ -13,7 +13,7 @@ import {
|
||||
} from '.'
|
||||
import { Button } from '../button'
|
||||
|
||||
const triggerButtonClassName = 'rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-1.5 text-sm text-text-secondary shadow-xs hover:bg-state-base-hover'
|
||||
const triggerButtonClassName = 'rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-1.5 text-sm text-text-secondary shadow-xs outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/UI/AlertDialog',
|
||||
|
||||
@ -97,7 +97,7 @@ export const Controlled: Story = {
|
||||
<div className="flex flex-col items-start gap-3">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-1.5 system-sm-medium text-components-button-secondary-text shadow-xs shadow-shadow-shadow-3 hover:bg-state-base-hover focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
className="rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-1.5 system-sm-medium text-components-button-secondary-text shadow-xs shadow-shadow-shadow-3 outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
onClick={() => setOpen(value => !value)}
|
||||
>
|
||||
{open ? 'Close panel' : 'Open panel'}
|
||||
|
||||
@ -22,7 +22,7 @@ import {
|
||||
const TriggerArea = ({ label = 'Right-click inside this area' }: { label?: string }) => (
|
||||
<ContextMenuTrigger
|
||||
aria-label="context menu trigger area"
|
||||
render={<button type="button" className="flex h-44 w-80 items-center justify-center rounded-xl border border-divider-subtle bg-background-default-subtle px-6 text-center text-sm text-text-tertiary select-none" />}
|
||||
render={<button type="button" className="flex h-44 w-80 items-center justify-center rounded-xl border border-divider-subtle bg-background-default-subtle px-6 text-center text-sm text-text-tertiary outline-hidden select-none focus-visible:ring-2 focus-visible:ring-state-accent-solid" />}
|
||||
>
|
||||
{label}
|
||||
</ContextMenuTrigger>
|
||||
|
||||
@ -3,17 +3,27 @@ import * as React from 'react'
|
||||
import { expect, waitFor, within } from 'storybook/test'
|
||||
import {
|
||||
Dialog,
|
||||
DialogBackdrop,
|
||||
DialogCloseButton,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogPopup,
|
||||
DialogPortal,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
DialogViewport,
|
||||
} from '.'
|
||||
import { Button } from '../button'
|
||||
import { FieldControl, FieldDescription, FieldError, FieldLabel, FieldRoot } from '../field'
|
||||
import { Form } from '../form'
|
||||
|
||||
const triggerButtonClassName = 'rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-1.5 text-sm text-text-secondary shadow-xs hover:bg-state-base-hover'
|
||||
import { Input } from '../input'
|
||||
import {
|
||||
ScrollAreaContent,
|
||||
ScrollAreaRoot,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
} from '../scroll-area'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/UI/Dialog',
|
||||
@ -32,11 +42,65 @@ const meta = {
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const releaseNoteItems = Array.from({ length: 24 }, (_, index) => ({
|
||||
id: `improvement-${index + 1}`,
|
||||
title: `Improvement #${index + 1}`,
|
||||
body: 'Refined a workflow behavior so long content naturally overflows and scrolls inside the dialog.',
|
||||
}))
|
||||
|
||||
function ReleaseNoteHeader({
|
||||
title,
|
||||
description,
|
||||
}: {
|
||||
title: string
|
||||
description: string
|
||||
}) {
|
||||
return (
|
||||
<div className="flex shrink-0 flex-col gap-2 p-6 pr-12 pb-4">
|
||||
<DialogTitle className="text-lg leading-7 font-semibold text-text-primary">
|
||||
{title}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-sm leading-5 text-text-secondary">
|
||||
{description}
|
||||
</DialogDescription>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ReleaseNoteSections() {
|
||||
return (
|
||||
<div className="flex flex-col gap-3 px-6 py-2 text-sm leading-5 text-text-secondary">
|
||||
{releaseNoteItems.map(item => (
|
||||
<section key={item.id} className="rounded-lg bg-background-default-subtle px-3 py-2">
|
||||
<h3 className="font-medium text-text-primary">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p>{item.body}</p>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ReleaseNoteFooter({
|
||||
onClose,
|
||||
}: {
|
||||
onClose: () => void
|
||||
}) {
|
||||
return (
|
||||
<div className="flex shrink-0 justify-end border-t border-divider-subtle p-4">
|
||||
<Button onClick={onClose}>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<Dialog>
|
||||
<DialogTrigger
|
||||
render={<button type="button" className={triggerButtonClassName} />}
|
||||
render={<Button />}
|
||||
>
|
||||
Open dialog
|
||||
</DialogTrigger>
|
||||
@ -54,11 +118,10 @@ export const Default: Story = {
|
||||
<label className="text-xs font-medium text-text-tertiary" htmlFor="invite-email">
|
||||
Email address
|
||||
</label>
|
||||
<input
|
||||
<Input
|
||||
id="invite-email"
|
||||
type="email"
|
||||
placeholder="teammate@example.com"
|
||||
className="h-9 w-full rounded-lg border-[0.5px] border-components-input-border-active bg-components-input-bg-normal px-3 text-sm text-components-input-text-filled outline-hidden placeholder:text-components-input-text-placeholder focus:border-components-input-border-hover"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-6 flex items-center justify-end">
|
||||
@ -89,7 +152,7 @@ export const WithoutCloseButton: Story = {
|
||||
render: () => (
|
||||
<Dialog>
|
||||
<DialogTrigger
|
||||
render={<button type="button" className={triggerButtonClassName} />}
|
||||
render={<Button />}
|
||||
>
|
||||
Start onboarding
|
||||
</DialogTrigger>
|
||||
@ -135,10 +198,10 @@ const ControlledDemo = () => {
|
||||
The workspace URL will stay the same, but the display name updates everywhere.
|
||||
</DialogDescription>
|
||||
</div>
|
||||
<input
|
||||
<Input
|
||||
type="text"
|
||||
defaultValue="Acme Workspace"
|
||||
className="mt-4 h-9 w-full rounded-lg border-[0.5px] border-components-input-border-active bg-components-input-bg-normal px-3 text-sm text-components-input-text-filled outline-hidden focus:border-components-input-border-hover"
|
||||
className="mt-4"
|
||||
/>
|
||||
<div className="mt-6 flex items-center justify-end gap-2">
|
||||
<Button variant="secondary" onClick={() => setOpen(false)}>
|
||||
@ -170,7 +233,7 @@ const FormDialogDemo = () => {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen} disablePointerDismissal>
|
||||
<DialogTrigger
|
||||
render={<button type="button" className={triggerButtonClassName} />}
|
||||
render={<Button />}
|
||||
>
|
||||
Configure API extension
|
||||
</DialogTrigger>
|
||||
@ -201,7 +264,7 @@ const FormDialogDemo = () => {
|
||||
href="https://docs.dify.ai/use-dify/workspace/api-extension/api-extension"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex w-fit items-center text-text-accent focus-visible:ring-1 focus-visible:ring-components-input-border-active focus-visible:outline-hidden"
|
||||
className="inline-flex w-fit items-center text-text-accent outline-hidden focus-visible:ring-1 focus-visible:ring-components-input-border-active"
|
||||
>
|
||||
View API extension docs
|
||||
</a>
|
||||
@ -241,38 +304,128 @@ export const FormDialog: Story = {
|
||||
render: () => <FormDialogDemo />,
|
||||
}
|
||||
|
||||
export const ScrollingContent: Story = {
|
||||
const OutsideScrollingContentDemo = () => {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const popupRef = React.useRef<HTMLDivElement>(null)
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger
|
||||
render={<Button />}
|
||||
>
|
||||
Review long release notes
|
||||
</DialogTrigger>
|
||||
<DialogPortal>
|
||||
<DialogBackdrop className="duration-[600ms] ease-[cubic-bezier(0.22,1,0.36,1)] data-ending-style:duration-[350ms] data-ending-style:ease-[cubic-bezier(0.375,0.015,0.545,0.455)]" />
|
||||
<DialogViewport className="group/dialog">
|
||||
<ScrollAreaRoot className="h-full overscroll-contain group-data-ending-style/dialog:pointer-events-none">
|
||||
<ScrollAreaViewport aria-label="Scrollable dialog viewport" role="region" className="h-full max-h-full max-w-full overscroll-contain group-data-ending-style/dialog:pointer-events-none">
|
||||
<ScrollAreaContent className="flex min-h-full items-center justify-center px-4 py-16">
|
||||
<DialogPopup
|
||||
ref={popupRef}
|
||||
initialFocus={popupRef}
|
||||
className="relative mx-auto flex w-120 max-w-[calc(100vw-2rem)] flex-col overflow-hidden outline-hidden transition-[translate] duration-[700ms] ease-[cubic-bezier(0.45,1.005,0,1.005)] data-starting-style:translate-y-[100dvh] data-starting-style:scale-100 data-starting-style:opacity-100 data-ending-style:translate-y-[max(100dvh,100%)] data-ending-style:scale-100 data-ending-style:opacity-100 data-ending-style:duration-[350ms] data-ending-style:ease-[cubic-bezier(0.375,0.015,0.545,0.455)]"
|
||||
>
|
||||
<DialogCloseButton />
|
||||
<ReleaseNoteHeader
|
||||
title="Long release notes"
|
||||
description="This layout lets the outer dialog viewport scroll while the popup keeps its natural height."
|
||||
/>
|
||||
<ReleaseNoteSections />
|
||||
<ReleaseNoteFooter onClose={() => setOpen(false)} />
|
||||
</DialogPopup>
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollAreaRoot>
|
||||
</DialogViewport>
|
||||
</DialogPortal>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export const OutsideScrollingContent: Story = {
|
||||
render: () => <OutsideScrollingContentDemo />,
|
||||
}
|
||||
|
||||
export const OutsidePopupElements: Story = {
|
||||
render: () => (
|
||||
<Dialog>
|
||||
<DialogTrigger
|
||||
render={<button type="button" className={triggerButtonClassName} />}
|
||||
render={<Button />}
|
||||
>
|
||||
Review release notes
|
||||
Open uncontained dialog
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogCloseButton />
|
||||
<div className="flex flex-col gap-2 pr-8">
|
||||
<DialogTitle className="text-lg leading-7 font-semibold text-text-primary">
|
||||
Release notes
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-sm leading-5 text-text-secondary">
|
||||
Highlights from the latest workspace update.
|
||||
</DialogDescription>
|
||||
</div>
|
||||
<ul className="mt-4 flex flex-col gap-3 text-sm leading-5 text-text-secondary">
|
||||
{Array.from({ length: 24 }, (_, index) => `improvement-${index + 1}`).map((id, index) => (
|
||||
<li key={id} className="rounded-lg bg-background-default-subtle px-3 py-2">
|
||||
<span className="font-medium text-text-primary">
|
||||
Improvement #
|
||||
{index + 1}
|
||||
:
|
||||
</span>
|
||||
{' '}
|
||||
Refined a workflow behavior so long content naturally overflows and scrolls inside the dialog.
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</DialogContent>
|
||||
<DialogPortal>
|
||||
<DialogBackdrop className="min-h-dvh" />
|
||||
<DialogViewport className="grid place-items-center px-4 py-12 xl:py-6">
|
||||
<DialogPopup className="group/popup relative flex h-full w-full max-w-[70rem] justify-center border-0 bg-transparent shadow-none pointer-events-none transition-opacity data-starting-style:scale-100 data-starting-style:opacity-0 data-ending-style:scale-100 data-ending-style:opacity-0">
|
||||
<DialogCloseButton
|
||||
aria-label="Close"
|
||||
className="pointer-events-auto absolute right-0 -top-10 z-10 flex size-8 items-center justify-center rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg text-text-tertiary shadow-xs outline-hidden hover:bg-components-button-secondary-bg-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid xl:top-0"
|
||||
>
|
||||
</DialogCloseButton>
|
||||
<div className="pointer-events-auto flex h-full w-full max-w-[70rem] flex-col overflow-hidden rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-6 shadow-xl transition-[scale] duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] group-data-starting-style/popup:scale-105">
|
||||
<div className="grid gap-2">
|
||||
<DialogTitle className="text-lg leading-7 font-semibold text-text-primary">
|
||||
Knowledge review
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-sm leading-5 text-text-secondary">
|
||||
The close button is visually outside this panel but remains inside the popup for focus order and screen reader context.
|
||||
</DialogDescription>
|
||||
</div>
|
||||
<div className="mt-6 grid flex-1 place-items-center rounded-xl bg-background-default-subtle text-sm text-text-tertiary">
|
||||
Panel content
|
||||
</div>
|
||||
</div>
|
||||
</DialogPopup>
|
||||
</DialogViewport>
|
||||
</DialogPortal>
|
||||
</Dialog>
|
||||
),
|
||||
}
|
||||
|
||||
const InsideScrollingContentDemo = () => {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger
|
||||
render={<Button />}
|
||||
>
|
||||
Review release notes
|
||||
</DialogTrigger>
|
||||
<DialogPortal>
|
||||
<DialogBackdrop />
|
||||
<DialogViewport className="flex items-center justify-center overflow-hidden p-4">
|
||||
<DialogPopup
|
||||
className="relative flex h-[min(44rem,calc(100dvh-2rem))] w-120 max-w-[calc(100vw-2rem)] min-h-0 flex-col overflow-hidden"
|
||||
>
|
||||
<DialogCloseButton />
|
||||
<ReleaseNoteHeader
|
||||
title="Release notes"
|
||||
description="Highlights from the latest workspace update."
|
||||
/>
|
||||
<ScrollAreaRoot className="relative flex min-h-0 flex-auto overflow-hidden">
|
||||
<ScrollAreaViewport aria-label="Release note improvements" role="region" className="h-full max-h-full max-w-full overflow-y-auto overscroll-contain">
|
||||
<ScrollAreaContent>
|
||||
<ReleaseNoteSections />
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollAreaRoot>
|
||||
<ReleaseNoteFooter onClose={() => setOpen(false)} />
|
||||
</DialogPopup>
|
||||
</DialogViewport>
|
||||
</DialogPortal>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export const InsideScrollingContent: Story = {
|
||||
render: () => <InsideScrollingContentDemo />,
|
||||
}
|
||||
|
||||
@ -8,6 +8,63 @@ export const Dialog = BaseDialog.Root
|
||||
export const DialogTrigger = BaseDialog.Trigger
|
||||
export const DialogTitle = BaseDialog.Title
|
||||
export const DialogDescription = BaseDialog.Description
|
||||
export const DialogPortal = BaseDialog.Portal
|
||||
|
||||
type DialogBackdropProps = Omit<BaseDialog.Backdrop.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function DialogBackdrop({
|
||||
className,
|
||||
...props
|
||||
}: DialogBackdropProps) {
|
||||
return (
|
||||
<BaseDialog.Backdrop
|
||||
{...props}
|
||||
className={cn(
|
||||
'absolute inset-0 z-50 bg-background-overlay',
|
||||
'transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 motion-reduce:transition-none',
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type DialogViewportProps = Omit<BaseDialog.Viewport.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function DialogViewport({
|
||||
className,
|
||||
...props
|
||||
}: DialogViewportProps) {
|
||||
return (
|
||||
<BaseDialog.Viewport
|
||||
className={cn('fixed inset-0 z-50', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type DialogPopupProps = Omit<BaseDialog.Popup.Props, 'className'> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function DialogPopup({
|
||||
className,
|
||||
...props
|
||||
}: DialogPopupProps) {
|
||||
return (
|
||||
<BaseDialog.Popup
|
||||
className={cn(
|
||||
'z-50 rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl',
|
||||
'transition-[transform,scale,opacity] duration-150 data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0 motion-reduce:transition-none',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type DialogCloseButtonProps = Omit<BaseDialog.Close.Props, 'children'>
|
||||
|
||||
@ -21,7 +78,7 @@ export function DialogCloseButton({
|
||||
aria-label={ariaLabel}
|
||||
{...props}
|
||||
className={cn(
|
||||
'absolute top-6 end-6 z-10 flex h-5 w-5 cursor-pointer items-center justify-center rounded-2xl hover:bg-state-base-hover focus-visible:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'absolute top-6 end-6 z-10 flex h-5 w-5 cursor-pointer items-center justify-center rounded-2xl hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
@ -44,24 +101,16 @@ export function DialogContent({
|
||||
backdropProps,
|
||||
}: DialogContentProps) {
|
||||
return (
|
||||
<BaseDialog.Portal>
|
||||
<BaseDialog.Backdrop
|
||||
{...backdropProps}
|
||||
<DialogPortal>
|
||||
<DialogBackdrop {...backdropProps} className={backdropClassName} />
|
||||
<DialogPopup
|
||||
className={cn(
|
||||
'absolute inset-0 z-50 bg-background-overlay',
|
||||
'transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 motion-reduce:transition-none',
|
||||
backdropClassName,
|
||||
)}
|
||||
/>
|
||||
<BaseDialog.Popup
|
||||
className={cn(
|
||||
'fixed top-1/2 left-1/2 z-50 max-h-[80dvh] w-120 max-w-[calc(100vw-2rem)] -translate-x-1/2 -translate-y-1/2 overflow-y-auto overscroll-contain rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-6 shadow-xl',
|
||||
'transition-[transform,scale,opacity] duration-150 data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0 motion-reduce:transition-none',
|
||||
'fixed top-1/2 left-1/2 max-h-[80dvh] w-120 max-w-[calc(100vw-2rem)] -translate-x-1/2 -translate-y-1/2 overflow-y-auto overscroll-contain p-6',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</BaseDialog.Popup>
|
||||
</BaseDialog.Portal>
|
||||
</DialogPopup>
|
||||
</DialogPortal>
|
||||
)
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ import {
|
||||
|
||||
const TriggerButton = ({ label = 'Open Menu' }: { label?: string }) => (
|
||||
<DropdownMenuTrigger
|
||||
render={<button type="button" className="rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-1.5 text-sm text-text-secondary shadow-xs hover:bg-state-base-hover" />}
|
||||
render={<button type="button" className="rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-1.5 text-sm text-text-secondary shadow-xs outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid" />}
|
||||
>
|
||||
{label}
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
@ -170,7 +170,7 @@ export const InTooltip: Story = {
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Collapse sidebar"
|
||||
className="inline-flex size-8 items-center justify-center rounded-lg border border-divider-subtle bg-components-button-secondary-bg text-text-secondary shadow-xs"
|
||||
className="inline-flex size-8 items-center justify-center rounded-lg border border-divider-subtle bg-components-button-secondary-bg text-text-secondary shadow-xs outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
>
|
||||
<span aria-hidden className="i-ri-sidebar-fold-line size-4" />
|
||||
</button>
|
||||
@ -204,7 +204,7 @@ export const InContextMenu: Story = {
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-28 w-60 items-center justify-center rounded-xl border border-divider-subtle bg-background-default-subtle px-6 text-center system-sm-regular text-text-tertiary"
|
||||
className="flex h-28 w-60 items-center justify-center rounded-xl border border-divider-subtle bg-background-default-subtle px-6 text-center system-sm-regular text-text-tertiary outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
/>
|
||||
)}
|
||||
>
|
||||
|
||||
@ -12,7 +12,7 @@ import {
|
||||
} from '.'
|
||||
import { Kbd, KbdGroup } from '../kbd'
|
||||
|
||||
const triggerButtonClassName = 'rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-1.5 text-sm text-text-secondary shadow-xs hover:bg-state-base-hover'
|
||||
const triggerButtonClassName = 'rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-1.5 text-sm text-text-secondary shadow-xs outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/UI/Popover',
|
||||
@ -81,7 +81,7 @@ export const WithActions: Story = {
|
||||
<input
|
||||
type="email"
|
||||
placeholder="teammate@example.com"
|
||||
className="h-8 rounded-md border-[0.5px] border-components-input-border-active bg-components-input-bg-normal px-2 text-sm text-components-input-text-filled outline-hidden placeholder:text-components-input-text-placeholder focus:border-components-input-border-hover"
|
||||
className="h-8 rounded-md border border-transparent bg-components-input-bg-normal px-2 text-sm text-components-input-text-filled outline-hidden placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs"
|
||||
/>
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<PopoverClose
|
||||
@ -169,7 +169,7 @@ const PlacementsDemo = () => {
|
||||
key={value}
|
||||
type="button"
|
||||
onClick={() => setPlacement(value)}
|
||||
className={`rounded-md border border-divider-subtle px-2 py-1 text-text-secondary ${
|
||||
className={`rounded-md border border-divider-subtle px-2 py-1 text-text-secondary outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid ${
|
||||
placement === value ? 'bg-state-base-hover' : 'bg-components-button-secondary-bg'
|
||||
}`}
|
||||
>
|
||||
|
||||
@ -9,13 +9,13 @@ import {
|
||||
} from '.'
|
||||
|
||||
const rowButtonClassName
|
||||
= 'flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-sm text-text-secondary hover:bg-state-base-hover'
|
||||
= 'flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-sm text-text-secondary outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid'
|
||||
|
||||
const triggerButtonClassName
|
||||
= 'rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-1.5 text-sm text-text-secondary shadow-xs hover:bg-state-base-hover'
|
||||
= 'rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-1.5 text-sm text-text-secondary shadow-xs outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid'
|
||||
|
||||
const inlineLinkClassName
|
||||
= 'text-text-accent underline decoration-text-accent/60 decoration-1 underline-offset-2 outline-hidden hover:decoration-text-accent focus-visible:rounded-xs focus-visible:no-underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-text-accent data-[popup-open]:decoration-text-accent'
|
||||
= 'text-text-accent underline decoration-text-accent/60 decoration-1 underline-offset-2 outline-hidden hover:decoration-text-accent focus-visible:rounded-xs focus-visible:no-underline focus-visible:ring-1 focus-visible:ring-components-input-border-active data-[popup-open]:decoration-text-accent'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/UI/PreviewCard',
|
||||
@ -154,7 +154,7 @@ const PlacementsDemo = () => {
|
||||
key={value}
|
||||
type="button"
|
||||
onClick={() => setPlacement(value)}
|
||||
className={`rounded-md border border-divider-subtle px-2 py-1 text-text-secondary ${
|
||||
className={`rounded-md border border-divider-subtle px-2 py-1 text-text-secondary outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid ${
|
||||
placement === value ? 'bg-state-base-hover' : 'bg-components-button-secondary-bg'
|
||||
}`}
|
||||
>
|
||||
|
||||
@ -136,7 +136,7 @@ function OptionCardsDemo() {
|
||||
variant="unstyled"
|
||||
nativeButton
|
||||
render={<button type="button" />}
|
||||
className="w-full rounded-xl border border-components-option-card-option-border bg-components-option-card-option-bg p-4 text-left transition-colors hover:bg-state-base-hover data-checked:border-components-option-card-option-selected-border data-checked:bg-components-option-card-option-selected-bg"
|
||||
className="w-full rounded-xl border border-components-option-card-option-border bg-components-option-card-option-bg p-4 text-left outline-hidden transition-colors hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid data-checked:border-components-option-card-option-selected-border data-checked:bg-components-option-card-option-selected-bg"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
|
||||
@ -29,21 +29,6 @@ type Story = StoryObj<typeof meta>
|
||||
|
||||
const verticalContentStyle = { minWidth: 0 } satisfies React.CSSProperties
|
||||
|
||||
const appRows = [
|
||||
{ name: 'Invoice Copilot', meta: 'Pinned', icon: 'i-ri-file-list-3-line', selected: true, pinned: true },
|
||||
{ name: 'RAG Ops Console', meta: 'Ops', icon: 'i-ri-database-2-line', selected: false, pinned: true },
|
||||
{ name: 'Knowledge Studio', meta: 'Docs', icon: 'i-ri-book-open-line', selected: false, pinned: true },
|
||||
{ name: 'Workflow Studio', meta: 'Build', icon: 'i-ri-flow-chart', selected: false, pinned: true },
|
||||
{ name: 'Agent Playground', meta: 'Lab', icon: 'i-ri-robot-2-line', selected: false, pinned: false },
|
||||
{ name: 'Sales Briefing', meta: 'Team', icon: 'i-ri-presentation-line', selected: false, pinned: false },
|
||||
{ name: 'Support Triage', meta: 'Queue', icon: 'i-ri-customer-service-2-line', selected: false, pinned: false },
|
||||
{ name: 'Legal Review', meta: 'Beta', icon: 'i-ri-scales-3-line', selected: false, pinned: false },
|
||||
{ name: 'Release Watcher', meta: 'Feed', icon: 'i-ri-rocket-line', selected: false, pinned: false },
|
||||
{ name: 'Security Radar', meta: 'Risk', icon: 'i-ri-shield-check-line', selected: false, pinned: false },
|
||||
{ name: 'Partner Portal', meta: 'Ext', icon: 'i-ri-handshake-line', selected: false, pinned: false },
|
||||
{ name: 'QA Replays', meta: 'Debug', icon: 'i-ri-replay-line', selected: false, pinned: false },
|
||||
] as const
|
||||
|
||||
const articleParagraphs = [
|
||||
'Vernacular architecture is building done outside any academic tradition, and without professional guidance. It is not a particular architectural movement or style, but rather a broad category, encompassing a wide range and variety of building types, with differing methods of construction, from around the world, both historical and extant and classical and modern.',
|
||||
'This type of architecture usually serves immediate, local needs, is constrained by the materials available in its particular region, and reflects local traditions and cultural practices. The study of vernacular architecture does not examine formally schooled architects, but instead the design skills and tradition of local builders.',
|
||||
@ -292,63 +277,3 @@ export const BothAxes: Story = {
|
||||
</StorySection>
|
||||
),
|
||||
}
|
||||
|
||||
export const AppSidebar: Story = {
|
||||
render: () => {
|
||||
const pinnedCount = appRows.filter(row => row.pinned).length
|
||||
|
||||
return (
|
||||
<StorySection
|
||||
eyebrow="Application"
|
||||
title="Main navigation list"
|
||||
description="A Dify-like sidebar keeps business UI outside the primitive while preserving the same Root, Viewport, Content, Scrollbar anatomy."
|
||||
>
|
||||
<div className="w-full max-w-70 rounded-xl bg-background-default-subtle p-3">
|
||||
<div className="mb-4 flex h-8 items-center gap-2 rounded-lg bg-state-base-active px-2 text-text-accent">
|
||||
<span className="i-ri-apps-fill size-4 shrink-0" aria-hidden />
|
||||
<span className="min-w-0 truncate system-sm-semibold">Explore</span>
|
||||
</div>
|
||||
<div className="mb-1.5 flex items-center justify-between px-2">
|
||||
<span className="system-xs-medium-uppercase text-text-tertiary">Web apps</span>
|
||||
<span className="system-xs-medium text-text-quaternary">{appRows.length}</span>
|
||||
</div>
|
||||
<ScrollAreaRoot className="relative h-76 min-w-0">
|
||||
<ScrollAreaViewport aria-label="Web apps" role="region" className="h-full max-h-full max-w-full rounded-lg bg-transparent">
|
||||
<VerticalContent className="space-y-0.5 pr-3">
|
||||
{appRows.map((row, index) => (
|
||||
<div key={row.name} className="space-y-0.5">
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
'flex h-8 w-full min-w-0 items-center justify-between gap-2 rounded-lg px-2 text-left transition-colors outline-none focus-visible:outline-2 focus-visible:outline-offset-0 focus-visible:outline-solid focus-visible:outline-state-accent-solid',
|
||||
row.selected
|
||||
? 'bg-state-base-active text-components-menu-item-text-active'
|
||||
: 'text-components-menu-item-text hover:bg-state-base-hover hover:text-components-menu-item-text-hover',
|
||||
)}
|
||||
>
|
||||
<span className="flex min-w-0 items-center gap-2">
|
||||
<span className="flex size-5 shrink-0 items-center justify-center rounded-md bg-components-icon-bg-blue-solid text-components-avatar-shape-fill-stop-100">
|
||||
<span aria-hidden className={cn(row.icon, 'size-3.5')} />
|
||||
</span>
|
||||
<span className="min-w-0 truncate system-sm-regular">{row.name}</span>
|
||||
</span>
|
||||
<span className="shrink-0 rounded-md border border-divider-subtle bg-components-panel-bg-alt px-1.5 py-0.5 system-2xs-medium-uppercase text-text-quaternary">
|
||||
{row.meta}
|
||||
</span>
|
||||
</button>
|
||||
{index === pinnedCount - 1 && index !== appRows.length - 1 && (
|
||||
<div className="my-1 h-px bg-divider-subtle" />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</VerticalContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollAreaRoot>
|
||||
</div>
|
||||
</StorySection>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
@ -225,7 +225,7 @@ const LoadingDemo = () => {
|
||||
return (
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<button
|
||||
className="rounded-sm border px-2 py-1 text-xs"
|
||||
className="rounded-sm border px-2 py-1 text-xs outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
onClick={() => setLoading(!loading)}
|
||||
>
|
||||
{loading ? 'Stop Loading' : 'Start Loading'}
|
||||
|
||||
@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
|
||||
import * as React from 'react'
|
||||
import { toast, ToastHost } from '.'
|
||||
|
||||
const buttonClassName = 'rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-2 text-sm text-text-secondary shadow-xs transition-colors hover:bg-state-base-hover'
|
||||
const buttonClassName = 'rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-2 text-sm text-text-secondary shadow-xs outline-hidden transition-colors hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid'
|
||||
const cardClassName = 'flex min-h-[220px] flex-col gap-4 rounded-2xl border border-divider-subtle bg-components-panel-bg p-6 shadow-sm shadow-shadow-shadow-3'
|
||||
|
||||
const ExampleCard = ({
|
||||
|
||||
@ -8,8 +8,8 @@ import {
|
||||
TooltipTrigger,
|
||||
} from '.'
|
||||
|
||||
const iconButtonClassName = 'inline-flex h-8 w-8 items-center justify-center rounded-lg border border-divider-subtle bg-components-button-secondary-bg text-text-secondary shadow-xs hover:bg-state-base-hover'
|
||||
const triggerButtonClassName = 'rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-1.5 text-sm text-text-secondary shadow-xs hover:bg-state-base-hover'
|
||||
const iconButtonClassName = 'inline-flex h-8 w-8 items-center justify-center rounded-lg border border-divider-subtle bg-components-button-secondary-bg text-text-secondary shadow-xs outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid'
|
||||
const triggerButtonClassName = 'rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-1.5 text-sm text-text-secondary shadow-xs outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/UI/Tooltip',
|
||||
@ -117,7 +117,7 @@ const PlacementsDemo = () => {
|
||||
key={value}
|
||||
type="button"
|
||||
onClick={() => setPlacement(value)}
|
||||
className={`rounded-md border border-divider-subtle px-2 py-1 text-text-secondary ${
|
||||
className={`rounded-md border border-divider-subtle px-2 py-1 text-text-secondary outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid ${
|
||||
placement === value ? 'bg-state-base-hover' : 'bg-components-button-secondary-bg'
|
||||
}`}
|
||||
>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user