mirror of
https://github.com/langgenius/dify.git
synced 2026-05-26 04:00:39 -04:00
fix(web): use popup-open selectors for trigger styles (#36471)
This commit is contained in:
@@ -11,6 +11,8 @@ Shared design tokens, the `cn()` utility, CSS-first Tailwind styles, and headles
|
||||
- Props pattern: `Omit<BaseXxx.Root.Props, 'className' | ...> & VariantProps<typeof xxxVariants> & { /* custom */ }`.
|
||||
- Use plain `Omit<...>` only for non-union Base UI props. When a prop changes the valid shape of related props (for example `value` / `defaultValue`, `multiple` / `value`, or `clearable` / `onChange`), model that relationship with an explicit discriminated union or a distributive helper instead of flattening the props.
|
||||
- When a component accepts a prop typed from a shared internal module, `export type` it from that component so consumers import it from the component subpath.
|
||||
- Prefer Base UI data attributes and CSS variables for visual states; do not mirror state in React solely to add classes.
|
||||
- When a Base UI API or selector contract is unclear, read the docs linked from `README.md` and the local `@base-ui/react` `.d.ts` files before coding.
|
||||
|
||||
## Overlay Primitive Selection: Tooltip vs PreviewCard vs Popover
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
Shared UI primitives, design tokens, CSS-first Tailwind styles, and the `cn()` utility consumed by Dify's `web/` app.
|
||||
|
||||
The primitives are thin, opinionated wrappers around [Base UI] headless components, styled with `cva` + `cn` and Dify design tokens.
|
||||
For upstream component docs, start from the [Base UI docs index].
|
||||
|
||||
> `private: true` — this package is consumed by `web/` via the pnpm workspace and is not published to npm. Treat the API as internal to Dify, but stable within the workspace.
|
||||
|
||||
@@ -39,12 +40,16 @@ Importing from `@langgenius/dify-ui` (no subpath) is intentionally not supported
|
||||
|
||||
## Primitives
|
||||
|
||||
| Category | Subpath | Notes |
|
||||
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
|
||||
| Overlay | `./alert-dialog`, `./autocomplete`, `./combobox`, `./context-menu`, `./dialog`, `./drawer`, `./dropdown-menu`, `./popover`, `./select`, `./toast`, `./tooltip` | Portalled. See [Overlay & portal contract] below. |
|
||||
| Form | `./form`, `./field`, `./fieldset`, `./checkbox`, `./checkbox-group`, `./number-field`, `./select`, `./slider`, `./switch` | Native form boundary, field semantics, and controls. |
|
||||
| Layout | `./scroll-area` | Custom-styled scrollbar over the host viewport. |
|
||||
| Media | `./avatar`, `./button` | Button exposes `cva` variants. |
|
||||
| Category | Subpath | Notes |
|
||||
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
|
||||
| Actions | `./button` | Design-system CTA primitive with `cva` variants. |
|
||||
| Feedback | `./meter`, `./toast` | Meter is inline status; Toast owns the `z-60` layer. |
|
||||
| Form | `./form`, `./field`, `./fieldset`, `./input`, `./checkbox`, `./checkbox-group`, `./radio`, `./radio-group`, `./number-field`, `./select`, `./slider`, `./switch` | Native form boundary, field semantics, and controls. |
|
||||
| Layout | `./scroll-area` | Custom-styled scrollbar over the host viewport. |
|
||||
| Media | `./avatar` | Avatar root, image, and fallback primitives. |
|
||||
| Navigation | `./tabs`, `./toggle-group` | Tabs for panels; ToggleGroup for segmented modes. |
|
||||
| Overlay / menu | `./alert-dialog`, `./context-menu`, `./dialog`, `./drawer`, `./dropdown-menu`, `./popover`, `./preview-card`, `./tooltip` | Portalled. See [Overlay & portal contract] below. |
|
||||
| Search / pickers | `./autocomplete`, `./combobox`, `./select` | Search input, searchable picker, and closed picker. |
|
||||
|
||||
Utilities:
|
||||
|
||||
@@ -174,5 +179,6 @@ See `[AGENTS.md](./AGENTS.md)` for:
|
||||
[Base UI Fieldset]: https://base-ui.com/react/components/fieldset
|
||||
[Base UI Form]: https://base-ui.com/react/components/form
|
||||
[Base UI Portal]: https://base-ui.com/react/overview/quick-start#portals
|
||||
[Base UI docs index]: https://base-ui.com/llms.txt
|
||||
[Base UI]: https://base-ui.com/react
|
||||
[Overlay & portal contract]: #overlay--portal-contract
|
||||
|
||||
@@ -30,7 +30,6 @@ export type AutocompleteRootHighlightEventDetails = BaseAutocomplete.Root.Highli
|
||||
|
||||
const autocompletePopupClassName = [
|
||||
'w-(--anchor-width) max-w-[min(28rem,var(--available-width))] overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg outline-hidden',
|
||||
'data-side-top:origin-bottom data-side-bottom:origin-top data-side-left:origin-right data-side-right:origin-left',
|
||||
]
|
||||
|
||||
const autocompleteListClassName = [
|
||||
|
||||
@@ -273,6 +273,7 @@ describe('Combobox wrappers', () => {
|
||||
it('should render item text indicator status and empty wrappers with design classes', async () => {
|
||||
const screen = await renderSelectLikeCombobox({ open: true })
|
||||
|
||||
expect(screen.getByRole('option', { name: 'Workflow' }).element().className).not.toContain('mx-1')
|
||||
await expect.element(screen.getByTestId('list').getByText('Workflow')).toHaveClass('system-sm-medium')
|
||||
await expect.element(screen.getByTestId('status')).toHaveClass('text-text-tertiary')
|
||||
await expect.element(screen.getByTestId('empty')).toHaveClass('system-sm-regular')
|
||||
|
||||
@@ -29,6 +29,11 @@ import {
|
||||
useComboboxFilteredItems,
|
||||
} from '.'
|
||||
import { cn } from '../cn'
|
||||
import {
|
||||
FieldDescription,
|
||||
FieldLabel,
|
||||
FieldRoot,
|
||||
} from '../field'
|
||||
|
||||
type Option = {
|
||||
value: string
|
||||
@@ -44,8 +49,6 @@ type OptionGroup = {
|
||||
}
|
||||
|
||||
const fieldWidth = 'w-80'
|
||||
const wideFieldWidth = 'w-[520px]'
|
||||
const nativeFieldLabelClassName = 'mb-1 block text-text-secondary system-sm-medium'
|
||||
|
||||
type StoryVirtualizer = Virtualizer<HTMLDivElement, Element>
|
||||
|
||||
@@ -174,11 +177,11 @@ const defaultDataSource = dataSourceOptions[0]!
|
||||
const defaultPopupDataSource = dataSourceOptions[1]!
|
||||
const readOnlyDataSource = dataSourceOptions[2]!
|
||||
const defaultTool = toolGroups[0]!.items[0]!
|
||||
const defaultReviewers = [reviewerOptions[0]!, reviewerOptions[2]!]
|
||||
const defaultReviewers = [reviewerOptions[0]!, reviewerOptions[1]!, reviewerOptions[2]!, reviewerOptions[3]!]
|
||||
const defaultTag = tagOptions[2]!
|
||||
|
||||
const renderOptionItem = (option: Option, index?: number) => (
|
||||
<ComboboxItem key={option.value} value={option} index={index} disabled={option.disabled}>
|
||||
<ComboboxItem key={option.value} value={option} index={index} disabled={option.disabled} className="h-auto min-h-8 py-1.5">
|
||||
<ComboboxItemText className="flex items-center gap-2 px-0">
|
||||
{option.icon && <span aria-hidden className={cn(option.icon, 'size-4 shrink-0 text-text-tertiary')} />}
|
||||
<span className="min-w-0 flex-1">
|
||||
@@ -204,18 +207,20 @@ const PopupSearchInput = ({
|
||||
label: string
|
||||
placeholder: string
|
||||
}) => (
|
||||
<ComboboxInputGroup className="mb-1 border-divider-subtle bg-components-input-bg-normal">
|
||||
<span aria-hidden className="ml-2 i-ri-search-line size-4 shrink-0 text-text-tertiary" />
|
||||
<ComboboxInput aria-label={label} placeholder={`${placeholder}…`} className="pl-2" />
|
||||
<ComboboxClear />
|
||||
</ComboboxInputGroup>
|
||||
<div className="p-1 pb-0">
|
||||
<ComboboxInputGroup className="h-8 min-h-8 px-2">
|
||||
<span aria-hidden className="mr-0.5 i-ri-search-line size-4 shrink-0 text-components-input-text-placeholder" />
|
||||
<ComboboxInput aria-label={label} placeholder={`${placeholder}…`} className="block h-4.5 grow px-1 py-0 system-sm-regular text-components-input-text-filled" />
|
||||
<ComboboxClear className="mr-0" />
|
||||
</ComboboxInputGroup>
|
||||
</div>
|
||||
)
|
||||
|
||||
const GroupedToolList = () => {
|
||||
const groups = useComboboxFilteredItems<OptionGroup>()
|
||||
|
||||
return (
|
||||
<ComboboxList className="p-0">
|
||||
<ComboboxList>
|
||||
{groups.map((group, groupIndex) => (
|
||||
<ComboboxGroup key={group.label} items={group.items}>
|
||||
{groupIndex > 0 && <ComboboxSeparator />}
|
||||
@@ -318,7 +323,7 @@ const VirtualizedLongListDemo = () => {
|
||||
<ComboboxTrigger aria-label="Model catalog">
|
||||
<ComboboxValue placeholder="Select model" />
|
||||
</ComboboxTrigger>
|
||||
<ComboboxContent popupClassName="w-[440px] p-1">
|
||||
<ComboboxContent popupClassName="w-[440px]">
|
||||
<PopupSearchInput label="Filter model catalog" placeholder="Filter 1,000 models" />
|
||||
<FilteredModelStatus />
|
||||
<VirtualizedModelList virtualizerRef={virtualizerRef} />
|
||||
@@ -351,7 +356,8 @@ const AsyncDirectoryDemo = () => {
|
||||
}, [inputValue])
|
||||
|
||||
return (
|
||||
<div className={fieldWidth}>
|
||||
<FieldRoot name="owner" className={fieldWidth}>
|
||||
<FieldLabel>Owner</FieldLabel>
|
||||
<Combobox
|
||||
items={value && !items.some(item => item.value === value.value) ? [value, ...items] : items}
|
||||
value={value}
|
||||
@@ -360,15 +366,12 @@ const AsyncDirectoryDemo = () => {
|
||||
onInputValueChange={setInputValue}
|
||||
autoHighlight
|
||||
>
|
||||
<label className={nativeFieldLabelClassName}>
|
||||
Owner
|
||||
<ComboboxInputGroup className="mt-1">
|
||||
<span aria-hidden className="ml-3 i-ri-search-line size-4 shrink-0 text-text-tertiary" />
|
||||
<ComboboxInput placeholder="Search owners…" className="pl-2" />
|
||||
<ComboboxClear />
|
||||
<ComboboxInputTrigger />
|
||||
</ComboboxInputGroup>
|
||||
</label>
|
||||
<ComboboxInputGroup className="h-8 min-h-8 px-2">
|
||||
<span aria-hidden className="mr-0.5 i-ri-search-line size-4 shrink-0 text-components-input-text-placeholder" />
|
||||
<ComboboxInput placeholder="Search owners…" className="block h-4.5 grow px-1 py-0 system-sm-regular text-components-input-text-filled" />
|
||||
<ComboboxClear className="mr-0.5" />
|
||||
<ComboboxInputTrigger className="mr-0" />
|
||||
</ComboboxInputGroup>
|
||||
<ComboboxContent popupClassName="w-[420px]">
|
||||
<ComboboxStatus className="border-b border-divider-subtle">
|
||||
{loading ? 'Loading directory matches…' : `${items.length} selectable owners`}
|
||||
@@ -377,12 +380,12 @@ const AsyncDirectoryDemo = () => {
|
||||
<ComboboxEmpty>No owner matches this query</ComboboxEmpty>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</div>
|
||||
</FieldRoot>
|
||||
)
|
||||
}
|
||||
|
||||
const meta = {
|
||||
title: 'Base/UI/Combobox',
|
||||
title: 'Base/Form/Combobox',
|
||||
component: Combobox,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
@@ -398,24 +401,46 @@ const meta = {
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const SelectLikeDefault: Story = {
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<div className={fieldWidth}>
|
||||
<Combobox items={providerOptions} defaultValue={defaultProvider} autoHighlight>
|
||||
<ComboboxLabel>Model provider</ComboboxLabel>
|
||||
<ComboboxTrigger aria-label="Model provider">
|
||||
<ComboboxValue placeholder="Select provider" />
|
||||
</ComboboxTrigger>
|
||||
<ComboboxContent popupClassName="p-1">
|
||||
<PopupSearchInput label="Search model providers" placeholder="Search providers" />
|
||||
<ComboboxList className="p-0">{renderOptionItem}</ComboboxList>
|
||||
<Combobox items={dataSourceOptions} defaultValue={defaultDataSource} autoHighlight>
|
||||
<ComboboxLabel>Connect source</ComboboxLabel>
|
||||
<ComboboxInputGroup className="h-8 min-h-8 px-2">
|
||||
<span aria-hidden className="mr-0.5 i-ri-search-line size-4 shrink-0 text-components-input-text-placeholder" />
|
||||
<ComboboxInput placeholder="Search data sources…" className="block h-4.5 grow px-1 py-0 system-sm-regular text-components-input-text-filled" />
|
||||
<ComboboxClear className="mr-0.5" />
|
||||
<ComboboxInputTrigger className="mr-0" />
|
||||
</ComboboxInputGroup>
|
||||
<ComboboxContent>
|
||||
<ComboboxList>{renderSimpleOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export const PopupInputSearchableSelect: Story = {
|
||||
export const FormField: Story = {
|
||||
render: () => (
|
||||
<FieldRoot name="sourceConnector" className={fieldWidth}>
|
||||
<FieldLabel>Connect source</FieldLabel>
|
||||
<Combobox items={dataSourceOptions} defaultValue={defaultDataSource} autoHighlight>
|
||||
<ComboboxInputGroup className="h-8 min-h-8 px-2">
|
||||
<span aria-hidden className="mr-0.5 i-ri-search-line size-4 shrink-0 text-components-input-text-placeholder" />
|
||||
<ComboboxInput placeholder="Search data sources…" className="block h-4.5 grow px-1 py-0 system-sm-regular text-components-input-text-filled" />
|
||||
<ComboboxClear className="mr-0.5" />
|
||||
<ComboboxInputTrigger className="mr-0" />
|
||||
</ComboboxInputGroup>
|
||||
<ComboboxContent>
|
||||
<ComboboxList>{renderSimpleOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
<FieldDescription>Type to filter, then choose a remembered data source.</FieldDescription>
|
||||
</FieldRoot>
|
||||
),
|
||||
}
|
||||
|
||||
export const CompactTriggerWithPopupSearch: Story = {
|
||||
render: () => (
|
||||
<div className={fieldWidth}>
|
||||
<Combobox items={dataSourceOptions} defaultValue={defaultPopupDataSource} autoHighlight>
|
||||
@@ -423,9 +448,9 @@ export const PopupInputSearchableSelect: Story = {
|
||||
<ComboboxTrigger aria-label="Data source">
|
||||
<ComboboxValue placeholder="Choose source" />
|
||||
</ComboboxTrigger>
|
||||
<ComboboxContent popupClassName="p-1">
|
||||
<ComboboxContent>
|
||||
<PopupSearchInput label="Search data sources" placeholder="Search sources" />
|
||||
<ComboboxList className="p-0">{renderOptionItem}</ComboboxList>
|
||||
<ComboboxList>{renderOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</div>
|
||||
@@ -436,38 +461,20 @@ export const AsyncSearchSingle: Story = {
|
||||
render: () => <AsyncDirectoryDemo />,
|
||||
}
|
||||
|
||||
export const InputGroupSearchable: Story = {
|
||||
render: () => (
|
||||
<div className={fieldWidth}>
|
||||
<Combobox items={dataSourceOptions} defaultValue={defaultDataSource} autoHighlight>
|
||||
<label className={nativeFieldLabelClassName}>
|
||||
Connect source
|
||||
<ComboboxInputGroup className="mt-1">
|
||||
<span aria-hidden className="ml-3 i-ri-search-line size-4 shrink-0 text-text-tertiary" />
|
||||
<ComboboxInput placeholder="Search data sources…" className="pl-2" />
|
||||
<ComboboxClear />
|
||||
<ComboboxInputTrigger />
|
||||
</ComboboxInputGroup>
|
||||
</label>
|
||||
<ComboboxContent>
|
||||
<ComboboxList>{renderOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export const Sizes: Story = {
|
||||
render: () => (
|
||||
<div className="flex w-80 flex-col gap-3">
|
||||
{(['small', 'medium', 'large'] as const).map(size => (
|
||||
<Combobox key={size} items={sizeOptions} defaultValue={defaultProvider} autoHighlight>
|
||||
<ComboboxTrigger aria-label={`${size} model provider`} size={size}>
|
||||
<ComboboxValue />
|
||||
</ComboboxTrigger>
|
||||
<ComboboxContent popupClassName="p-1">
|
||||
<PopupSearchInput label={`Search ${size} model providers`} placeholder="Search providers" />
|
||||
<ComboboxList className="p-0">{renderOptionItem}</ComboboxList>
|
||||
<ComboboxLabel>{`${size[0]!.toUpperCase()}${size.slice(1)}`}</ComboboxLabel>
|
||||
<ComboboxInputGroup size={size} className="px-2">
|
||||
<span aria-hidden className="mr-0.5 i-ri-search-line size-4 shrink-0 text-components-input-text-placeholder" />
|
||||
<ComboboxInput size={size} placeholder="Search providers…" className="px-1" />
|
||||
<ComboboxClear size={size} className="mr-0.5" />
|
||||
<ComboboxInputTrigger size={size} className="mr-0" />
|
||||
</ComboboxInputGroup>
|
||||
<ComboboxContent>
|
||||
<ComboboxList>{renderOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
))}
|
||||
@@ -483,7 +490,7 @@ export const Grouped: Story = {
|
||||
<ComboboxTrigger aria-label="Workflow tool">
|
||||
<ComboboxValue placeholder="Select tool" />
|
||||
</ComboboxTrigger>
|
||||
<ComboboxContent popupClassName="p-1">
|
||||
<ComboboxContent>
|
||||
<PopupSearchInput label="Search workflow tools" placeholder="Search workflow tools" />
|
||||
<GroupedToolList />
|
||||
</ComboboxContent>
|
||||
@@ -496,35 +503,34 @@ const MultipleChipsDemo = () => {
|
||||
const [value, setValue] = useState<Option[]>(defaultReviewers)
|
||||
|
||||
return (
|
||||
<div className={wideFieldWidth}>
|
||||
<FieldRoot name="reviewers" className={fieldWidth}>
|
||||
<FieldLabel>Reviewers</FieldLabel>
|
||||
<Combobox items={reviewerOptions} multiple value={value} onValueChange={setValue} autoHighlight>
|
||||
<label className={nativeFieldLabelClassName}>
|
||||
Reviewers
|
||||
<ComboboxInputGroup className="mt-1 h-auto min-h-8 flex-nowrap py-1">
|
||||
<ComboboxInputGroup className="h-auto min-h-8 items-start py-1 pr-1">
|
||||
<ComboboxChips>
|
||||
<ComboboxValue>
|
||||
{(selectedValue: Option[]) => (
|
||||
<>
|
||||
<ComboboxChips className="flex-nowrap">
|
||||
{selectedValue.map(item => (
|
||||
<ComboboxChip key={item.value}>
|
||||
<span className="max-w-32 truncate">{item.label}</span>
|
||||
<ComboboxChipRemove aria-label={`Remove ${item.label}`} />
|
||||
</ComboboxChip>
|
||||
))}
|
||||
</ComboboxChips>
|
||||
<ComboboxInput placeholder={selectedValue.length ? '' : 'Assign reviewers…'} className="min-w-16 px-2" />
|
||||
{selectedValue.map(item => (
|
||||
<ComboboxChip key={item.value}>
|
||||
<span className="max-w-32 truncate">{item.label}</span>
|
||||
<ComboboxChipRemove aria-label={`Remove ${item.label}`} />
|
||||
</ComboboxChip>
|
||||
))}
|
||||
<ComboboxInput placeholder={selectedValue.length ? '' : 'Assign reviewers…'} className="min-w-24 px-1 py-0.5" />
|
||||
</>
|
||||
)}
|
||||
</ComboboxValue>
|
||||
<ComboboxClear />
|
||||
<ComboboxInputTrigger />
|
||||
</ComboboxInputGroup>
|
||||
</label>
|
||||
</ComboboxChips>
|
||||
<ComboboxClear className="mt-0.5 mr-0.5" />
|
||||
<ComboboxInputTrigger className="mt-0.5 mr-0" />
|
||||
</ComboboxInputGroup>
|
||||
<ComboboxContent>
|
||||
<ComboboxList>{renderOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</div>
|
||||
<FieldDescription>Selected reviewers wrap inside the input instead of scrolling horizontally.</FieldDescription>
|
||||
</FieldRoot>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -538,53 +544,53 @@ export const VirtualizedLongList: Story = {
|
||||
|
||||
export const EmptyAndStatus: Story = {
|
||||
render: () => (
|
||||
<div className={fieldWidth}>
|
||||
<FieldRoot name="connector" className={fieldWidth}>
|
||||
<FieldLabel>Connector</FieldLabel>
|
||||
<Combobox items={emptyOptions} defaultInputValue="salesforce" autoHighlight>
|
||||
<label className={nativeFieldLabelClassName}>
|
||||
Connector
|
||||
<ComboboxInputGroup className="mt-1">
|
||||
<span aria-hidden className="ml-3 i-ri-search-line size-4 shrink-0 text-text-tertiary" />
|
||||
<ComboboxInput placeholder="Search connectors…" className="pl-2" />
|
||||
<ComboboxClear />
|
||||
<ComboboxInputTrigger />
|
||||
</ComboboxInputGroup>
|
||||
</label>
|
||||
<ComboboxInputGroup className="h-8 min-h-8 px-2">
|
||||
<span aria-hidden className="mr-0.5 i-ri-search-line size-4 shrink-0 text-components-input-text-placeholder" />
|
||||
<ComboboxInput placeholder="Search connectors…" className="block h-4.5 grow px-1 py-0 system-sm-regular text-components-input-text-filled" />
|
||||
<ComboboxClear className="mr-0.5" />
|
||||
<ComboboxInputTrigger className="mr-0" />
|
||||
</ComboboxInputGroup>
|
||||
<ComboboxContent>
|
||||
<ComboboxStatus>Search workspace connectors</ComboboxStatus>
|
||||
<ComboboxEmpty>No connectors found</ComboboxEmpty>
|
||||
<ComboboxList>{renderSimpleOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</div>
|
||||
</FieldRoot>
|
||||
),
|
||||
}
|
||||
|
||||
export const DisabledAndReadOnly: Story = {
|
||||
render: () => (
|
||||
<div className="flex w-80 flex-col gap-3">
|
||||
<Combobox items={providerOptions} defaultValue={disabledProvider} disabled>
|
||||
<ComboboxLabel>Disabled provider</ComboboxLabel>
|
||||
<ComboboxTrigger aria-label="Disabled model provider">
|
||||
<ComboboxValue />
|
||||
</ComboboxTrigger>
|
||||
<ComboboxContent popupClassName="p-1">
|
||||
<PopupSearchInput label="Search disabled providers" placeholder="Search providers" />
|
||||
<ComboboxList className="p-0">{renderOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
<Combobox items={dataSourceOptions} defaultValue={readOnlyDataSource} readOnly>
|
||||
<label className={nativeFieldLabelClassName}>
|
||||
Read-only source
|
||||
<ComboboxInputGroup className="mt-1">
|
||||
<ComboboxInput placeholder="Read-only data source…" />
|
||||
<ComboboxClear />
|
||||
<ComboboxInputTrigger />
|
||||
<FieldRoot name="disabledProvider" disabled>
|
||||
<FieldLabel>Disabled provider</FieldLabel>
|
||||
<Combobox items={providerOptions} defaultValue={disabledProvider} disabled>
|
||||
<ComboboxTrigger aria-label="Disabled model provider">
|
||||
<ComboboxValue />
|
||||
</ComboboxTrigger>
|
||||
<ComboboxContent>
|
||||
<PopupSearchInput label="Search disabled providers" placeholder="Search providers" />
|
||||
<ComboboxList>{renderOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="readOnlySource">
|
||||
<FieldLabel>Read-only source</FieldLabel>
|
||||
<Combobox items={dataSourceOptions} defaultValue={readOnlyDataSource} readOnly>
|
||||
<ComboboxInputGroup className="h-8 min-h-8 px-2">
|
||||
<ComboboxInput placeholder="Read-only data source…" className="block h-4.5 grow px-1 py-0 system-sm-regular text-components-input-text-filled" />
|
||||
<ComboboxClear className="mr-0.5" />
|
||||
<ComboboxInputTrigger className="mr-0" />
|
||||
</ComboboxInputGroup>
|
||||
</label>
|
||||
<ComboboxContent>
|
||||
<ComboboxList>{renderOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
<ComboboxContent>
|
||||
<ComboboxList>{renderOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</FieldRoot>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
@@ -594,16 +600,18 @@ const ControlledDemo = () => {
|
||||
|
||||
return (
|
||||
<div className="flex w-80 flex-col items-start gap-3">
|
||||
<Combobox items={tagOptions} value={value} onValueChange={setValue}>
|
||||
<ComboboxLabel>Default app tag</ComboboxLabel>
|
||||
<ComboboxTrigger aria-label="Default app tag">
|
||||
<ComboboxValue placeholder="Select tag" />
|
||||
</ComboboxTrigger>
|
||||
<ComboboxContent popupClassName="p-1">
|
||||
<PopupSearchInput label="Search app tags" placeholder="Search tags" />
|
||||
<ComboboxList className="p-0">{renderSimpleOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
<div className="w-full">
|
||||
<Combobox items={tagOptions} value={value} onValueChange={setValue}>
|
||||
<ComboboxLabel>Default app tag</ComboboxLabel>
|
||||
<ComboboxTrigger aria-label="Default app tag">
|
||||
<ComboboxValue placeholder="Select tag" />
|
||||
</ComboboxTrigger>
|
||||
<ComboboxContent>
|
||||
<PopupSearchInput label="Search app tags" placeholder="Search tags" />
|
||||
<ComboboxList>{renderSimpleOptionItem}</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</div>
|
||||
<span className="rounded-md border border-divider-subtle bg-components-panel-bg px-2 py-1 text-text-tertiary system-xs-regular">
|
||||
Selected:
|
||||
{' '}
|
||||
|
||||
@@ -31,7 +31,6 @@ export type ComboboxRootHighlightEventDetails = BaseCombobox.Root.HighlightEvent
|
||||
|
||||
const comboboxPopupClassName = [
|
||||
'w-(--anchor-width) max-w-[min(28rem,var(--available-width))] overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg outline-hidden',
|
||||
'data-side-top:origin-bottom data-side-bottom:origin-top data-side-left:origin-right data-side-right:origin-left',
|
||||
]
|
||||
|
||||
const comboboxListClassName = [
|
||||
@@ -40,7 +39,7 @@ const comboboxListClassName = [
|
||||
]
|
||||
|
||||
const comboboxItemClassName = [
|
||||
'mx-1 grid min-h-8 cursor-pointer select-none grid-cols-[1fr_auto] items-center gap-2 rounded-lg px-2 py-1.5 text-text-secondary outline-hidden transition-colors',
|
||||
'grid min-h-8 cursor-pointer select-none grid-cols-[1fr_auto] items-center gap-2 rounded-lg px-2 py-1.5 text-text-secondary outline-hidden transition-colors',
|
||||
'hover:bg-state-base-hover-alt hover:text-text-primary',
|
||||
'data-highlighted:bg-state-base-hover data-highlighted:text-text-primary',
|
||||
'data-selected:text-text-primary',
|
||||
@@ -51,7 +50,7 @@ const comboboxItemClassName = [
|
||||
const comboboxTriggerVariants = cva(
|
||||
[
|
||||
'group/combobox-trigger flex w-full min-w-0 items-center border-0 bg-components-input-bg-normal text-left text-components-input-text-filled outline-hidden transition-colors',
|
||||
'hover:bg-state-base-hover-alt focus-visible:bg-state-base-hover-alt data-open:bg-state-base-hover-alt',
|
||||
'hover:bg-state-base-hover-alt focus-visible:bg-state-base-hover-alt data-popup-open:bg-state-base-hover-alt',
|
||||
'focus-visible:ring-1 focus-visible:ring-components-input-border-active focus-visible:ring-inset',
|
||||
'data-placeholder:text-components-input-text-placeholder',
|
||||
'data-readonly:cursor-default data-readonly:bg-transparent data-readonly:hover:bg-transparent',
|
||||
@@ -101,7 +100,7 @@ export function ComboboxTrigger({
|
||||
{children}
|
||||
</span>
|
||||
{icon !== false && (
|
||||
<BaseCombobox.Icon className="shrink-0 text-text-quaternary transition-colors group-hover/combobox-trigger:text-text-secondary group-data-open/combobox-trigger:text-text-secondary group-data-readonly/combobox-trigger:hidden">
|
||||
<BaseCombobox.Icon className="shrink-0 text-text-quaternary transition-colors group-hover/combobox-trigger:text-text-secondary group-data-popup-open/combobox-trigger:text-text-secondary group-data-readonly/combobox-trigger:hidden">
|
||||
{icon ?? <span className="i-ri-arrow-down-s-line h-4 w-4" aria-hidden="true" />}
|
||||
</BaseCombobox.Icon>
|
||||
)}
|
||||
@@ -115,7 +114,7 @@ const comboboxInputGroupVariants = cva(
|
||||
'hover:border-components-input-border-hover hover:bg-components-input-bg-hover',
|
||||
'focus-within:border-components-input-border-active focus-within:bg-components-input-bg-active focus-within:shadow-xs',
|
||||
'data-focused:border-components-input-border-active data-focused:bg-components-input-bg-active data-focused:shadow-xs',
|
||||
'data-open:border-components-input-border-active data-open:bg-components-input-bg-active',
|
||||
'data-popup-open:border-components-input-border-active data-popup-open:bg-components-input-bg-active',
|
||||
'data-disabled:cursor-not-allowed data-disabled:border-transparent data-disabled:bg-components-input-bg-disabled data-disabled:text-components-input-text-filled-disabled',
|
||||
'data-disabled:hover:border-transparent data-disabled:hover:bg-components-input-bg-disabled',
|
||||
'data-readonly:shadow-none data-readonly:hover:border-transparent data-readonly:hover:bg-components-input-bg-normal',
|
||||
|
||||
@@ -174,7 +174,7 @@ describe('Select wrappers', () => {
|
||||
it('should include open state feedback classes', async () => {
|
||||
const screen = await renderOpenSelect()
|
||||
|
||||
expect(screen.getByRole('combobox', { name: 'city select' }).element().className).toContain('data-open:bg-state-base-hover-alt')
|
||||
expect(screen.getByRole('combobox', { name: 'city select' }).element().className).toContain('data-popup-open:bg-state-base-hover-alt')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export const SelectGroup = BaseSelect.Group
|
||||
const selectTriggerVariants = cva(
|
||||
[
|
||||
'group flex w-full items-center border-0 bg-components-input-bg-normal text-left text-components-input-text-filled outline-hidden',
|
||||
'hover:bg-state-base-hover-alt focus-visible:bg-state-base-hover-alt data-open:bg-state-base-hover-alt',
|
||||
'hover:bg-state-base-hover-alt focus-visible:bg-state-base-hover-alt data-popup-open:bg-state-base-hover-alt',
|
||||
'data-placeholder:text-components-input-text-placeholder',
|
||||
'data-readonly:cursor-default data-readonly:bg-transparent data-readonly:hover:bg-transparent',
|
||||
'data-disabled:cursor-not-allowed data-disabled:bg-components-input-bg-disabled data-disabled:text-components-input-text-filled-disabled data-disabled:hover:bg-components-input-bg-disabled',
|
||||
@@ -60,7 +60,7 @@ export function SelectTrigger({
|
||||
<span className="min-w-0 grow truncate">
|
||||
{children}
|
||||
</span>
|
||||
<BaseSelect.Icon className="shrink-0 text-text-quaternary transition-colors group-hover:text-text-secondary group-data-readonly:hidden data-open:text-text-secondary">
|
||||
<BaseSelect.Icon className="shrink-0 text-text-quaternary transition-colors group-hover:text-text-secondary group-data-readonly:hidden data-popup-open:text-text-secondary">
|
||||
<span className="i-ri-arrow-down-s-line h-4 w-4" aria-hidden="true" />
|
||||
</BaseSelect.Icon>
|
||||
</BaseSelect.Trigger>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite'
|
||||
import type { ReactNode } from 'react'
|
||||
import { toast } from '.'
|
||||
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 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'
|
||||
@@ -272,28 +272,31 @@ const UpdateExamples = () => {
|
||||
|
||||
const ToastDocsDemo = () => {
|
||||
return (
|
||||
<div className="min-h-screen bg-background-default-subtle px-6 py-12">
|
||||
<div className="mx-auto flex w-full max-w-6xl flex-col gap-8">
|
||||
<div className="space-y-3">
|
||||
<div className="text-xs tracking-[0.18em] text-text-tertiary uppercase">
|
||||
Base UI toast docs
|
||||
<>
|
||||
<ToastHost />
|
||||
<div className="min-h-screen bg-background-default-subtle px-6 py-12">
|
||||
<div className="mx-auto flex w-full max-w-6xl flex-col gap-8">
|
||||
<div className="space-y-3">
|
||||
<div className="text-xs tracking-[0.18em] text-text-tertiary uppercase">
|
||||
Base UI toast docs
|
||||
</div>
|
||||
<h2 className="text-[24px] leading-8 font-semibold text-text-primary">
|
||||
Shared stacked toast examples
|
||||
</h2>
|
||||
<p className="max-w-3xl text-sm leading-6 text-text-secondary">
|
||||
Each example card below triggers the same shared toast viewport in the top-right corner, so you can review stacking, state transitions, actions, and tone variants the same way the official Base UI documentation demonstrates toast behavior.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 xl:grid-cols-2">
|
||||
<VariantExamples />
|
||||
<StackExamples />
|
||||
<PromiseExamples />
|
||||
<ActionExamples />
|
||||
<UpdateExamples />
|
||||
</div>
|
||||
<h2 className="text-[24px] leading-8 font-semibold text-text-primary">
|
||||
Shared stacked toast examples
|
||||
</h2>
|
||||
<p className="max-w-3xl text-sm leading-6 text-text-secondary">
|
||||
Each example card below triggers the same shared toast viewport in the top-right corner, so you can review stacking, state transitions, actions, and tone variants the same way the official Base UI documentation demonstrates toast behavior.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 xl:grid-cols-2">
|
||||
<VariantExamples />
|
||||
<StackExamples />
|
||||
<PromiseExamples />
|
||||
<ActionExamples />
|
||||
<UpdateExamples />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -99,6 +99,7 @@ export const PopoverTrigger = ({
|
||||
...childProps,
|
||||
'data-testid': childProps['data-testid'] ?? triggerProps['data-testid'] ?? 'popover-trigger',
|
||||
'data-popover-trigger': 'true',
|
||||
'data-popup-open': open ? '' : undefined,
|
||||
'onClick': (event: React.MouseEvent<HTMLElement>) => {
|
||||
childProps.onClick?.(event)
|
||||
onClick?.(event)
|
||||
@@ -113,6 +114,7 @@ export const PopoverTrigger = ({
|
||||
<div
|
||||
data-testid="popover-trigger"
|
||||
data-popover-trigger="true"
|
||||
data-popup-open={open ? '' : undefined}
|
||||
onClick={(event) => {
|
||||
onClick?.(event)
|
||||
if (event.defaultPrevented)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { FC } from 'react'
|
||||
import type { PeriodParamsWithTimeRange, TimeRange } from '@/app/components/app/overview/app-chart'
|
||||
import type { I18nKeysByPrefix } from '@/types/i18n'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
|
||||
import { RiArrowDownSLine } from '@remixicon/react'
|
||||
import dayjs from 'dayjs'
|
||||
@@ -74,9 +73,9 @@ const RangeSelector: FC<Props> = ({
|
||||
<SelectTrigger
|
||||
className="h-auto w-fit max-w-none border-0 bg-transparent p-0 hover:bg-transparent focus-visible:bg-transparent [&>*:last-child]:hidden"
|
||||
>
|
||||
<div className={cn('flex h-8 cursor-pointer items-center space-x-1.5 rounded-lg bg-components-input-bg-normal pr-2 pl-3', open && 'bg-state-base-hover-alt')}>
|
||||
<div className="flex h-8 cursor-pointer items-center space-x-1.5 rounded-lg bg-components-input-bg-normal pr-2 pl-3 group-data-popup-open:bg-state-base-hover-alt">
|
||||
<div className="system-sm-regular text-components-input-text-filled">{isCustomRange ? t('filter.period.custom', { ns: 'appLog' }) : selectedItem?.name}</div>
|
||||
<RiArrowDownSLine className={cn('size-4 text-text-quaternary', open && 'text-text-secondary')} />
|
||||
<RiArrowDownSLine className="size-4 text-text-quaternary group-data-popup-open:text-text-secondary" />
|
||||
</div>
|
||||
</SelectTrigger>
|
||||
<SelectContent className="translate-x-[-24px]" popupClassName="w-[200px]" listClassName="p-1">
|
||||
|
||||
@@ -49,7 +49,7 @@ const AppSidebarDropdown = ({ navigation, appInfoActions }: Props) => {
|
||||
aria-label={t('operation.more', { ns: 'common' })}
|
||||
className={cn(
|
||||
'flex cursor-pointer items-center rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-1 shadow-lg backdrop-blur-xs hover:bg-background-default-hover',
|
||||
open && 'bg-background-default-hover',
|
||||
'data-popup-open:bg-background-default-hover',
|
||||
)}
|
||||
>
|
||||
<AppIcon
|
||||
|
||||
@@ -63,7 +63,7 @@ const DatasetSidebarDropdown = ({
|
||||
aria-label={t('operation.more', { ns: 'common' })}
|
||||
className={cn(
|
||||
'flex cursor-pointer items-center rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-1 shadow-lg backdrop-blur-xs hover:bg-background-default-hover',
|
||||
open && 'bg-background-default-hover',
|
||||
'data-popup-open:bg-background-default-hover',
|
||||
)}
|
||||
>
|
||||
<AppIcon
|
||||
|
||||
@@ -109,7 +109,7 @@ export default function AddMemberOrGroupDialog() {
|
||||
aria-label={t('operation.add', { ns: 'common' })}
|
||||
icon={false}
|
||||
size="small"
|
||||
className="flex h-6 w-auto shrink-0 items-center gap-x-0.5 rounded-md border-0 bg-transparent px-2 py-0 text-xs font-medium text-components-button-secondary-accent-text hover:bg-state-accent-hover focus-visible:bg-state-accent-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid data-open:bg-state-accent-hover"
|
||||
className="flex h-6 w-auto shrink-0 items-center gap-x-0.5 rounded-md border-0 bg-transparent px-2 py-0 text-xs font-medium text-components-button-secondary-accent-text hover:bg-state-accent-hover focus-visible:bg-state-accent-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid data-popup-open:bg-state-accent-hover"
|
||||
>
|
||||
<RiAddCircleFill className="size-4" aria-hidden="true" />
|
||||
<span>{t('operation.add', { ns: 'common' })}</span>
|
||||
|
||||
@@ -52,11 +52,11 @@ const VarPicker: FC<Props> = ({
|
||||
<PopoverTrigger
|
||||
nativeButton={false}
|
||||
render={(
|
||||
<div className={cn(triggerClassName)}>
|
||||
<div className={cn('group', triggerClassName)}>
|
||||
<div className={cn(
|
||||
className,
|
||||
notSetVar ? 'border-[#FEDF89] bg-[#FFFCF5] text-[#DC6803]' : 'border-components-button-secondary-border text-text-accent hover:bg-components-button-secondary-bg',
|
||||
open ? 'bg-components-button-secondary-bg' : 'bg-transparent',
|
||||
'bg-transparent group-data-popup-open:bg-components-button-secondary-bg',
|
||||
`
|
||||
flex h-8 cursor-pointer items-center justify-center space-x-1 rounded-lg border px-2 text-[13px]
|
||||
font-medium shadow-xs
|
||||
@@ -74,7 +74,7 @@ const VarPicker: FC<Props> = ({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<ChevronDownIcon className={cn(open && 'rotate-180 text-text-tertiary', 'size-3.5')} />
|
||||
<ChevronDownIcon className="size-3.5 group-data-popup-open:rotate-180 group-data-popup-open:text-text-tertiary" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -71,10 +71,10 @@ const ModelInfo: FC<Props> = ({
|
||||
<div className="relative">
|
||||
<PopoverTrigger
|
||||
render={(
|
||||
<button type="button" className="block border-none bg-transparent p-0">
|
||||
<button type="button" className="group block border-none bg-transparent p-0">
|
||||
<div className={cn(
|
||||
'cursor-pointer rounded-r-lg bg-components-button-tertiary-bg p-2 hover:bg-components-button-tertiary-bg-hover',
|
||||
open && 'bg-components-button-tertiary-bg-hover',
|
||||
'group-data-popup-open:bg-components-button-tertiary-bg-hover',
|
||||
)}
|
||||
>
|
||||
<RiInformation2Line className="size-4 text-text-tertiary" />
|
||||
|
||||
@@ -153,7 +153,7 @@ export function DocumentPicker({
|
||||
aria-label={value?.name || t('operation.search', { ns: 'common' })}
|
||||
icon={false}
|
||||
className={cn(
|
||||
'ml-1 flex size-auto rounded-lg border-0 bg-transparent px-2 py-1 hover:bg-state-base-hover focus-visible:bg-state-base-hover focus-visible:ring-1 focus-visible:ring-components-input-border-active data-open:bg-state-base-hover',
|
||||
'ml-1 flex size-auto rounded-lg border-0 bg-transparent px-2 py-1 hover:bg-state-base-hover focus-visible:bg-state-base-hover focus-visible:ring-1 focus-visible:ring-components-input-border-active data-popup-open:bg-state-base-hover',
|
||||
)}
|
||||
>
|
||||
<ComboboxValue>
|
||||
|
||||
@@ -247,9 +247,8 @@ const Operations = ({ embeddingAvailable, datasetId, detail, selectedIds, onSele
|
||||
isListScene ? s.actionIconWrapperList : s.actionIconWrapperDetail,
|
||||
'inline-flex items-center justify-center',
|
||||
!isListScene && 'h-8! w-8! rounded-lg backdrop-blur-[5px]',
|
||||
isOperationsMenuOpen
|
||||
? 'shadow-none! hover:bg-state-base-hover!'
|
||||
: isListScene && 'bg-transparent!',
|
||||
isListScene && 'bg-transparent!',
|
||||
'data-popup-open:shadow-none! data-popup-open:hover:bg-state-base-hover!',
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
|
||||
@@ -113,7 +113,7 @@ function ModelSelector({
|
||||
<ComboboxTrigger
|
||||
aria-label={t('detailPanel.configureModel', { ns: 'plugin' })}
|
||||
icon={false}
|
||||
className="block h-auto w-full border-0 bg-transparent p-0 text-left hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 data-open:bg-transparent"
|
||||
className="block h-auto w-full border-0 bg-transparent p-0 text-left hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 data-popup-open:bg-transparent"
|
||||
disabled={readonly}
|
||||
>
|
||||
<ModelSelectorTrigger
|
||||
|
||||
@@ -134,7 +134,7 @@ export function AppPicker({
|
||||
<ComboboxTrigger
|
||||
aria-label={t('appSelector.label', { ns: 'app' })}
|
||||
icon={false}
|
||||
className="block h-auto w-full border-0 bg-transparent p-0 text-left hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 data-open:bg-transparent"
|
||||
className="block h-auto w-full border-0 bg-transparent p-0 text-left hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 data-popup-open:bg-transparent"
|
||||
>
|
||||
{trigger}
|
||||
</ComboboxTrigger>
|
||||
|
||||
@@ -43,7 +43,7 @@ const CategoriesFilter = ({
|
||||
<div className={cn(
|
||||
'flex h-8 cursor-pointer items-center rounded-lg bg-components-input-bg-normal px-2 py-1 text-text-tertiary hover:bg-state-base-hover-alt',
|
||||
selectedTagsLength && 'text-text-secondary',
|
||||
open && 'bg-state-base-hover',
|
||||
'data-popup-open:bg-state-base-hover',
|
||||
)}
|
||||
>
|
||||
<div className={cn(
|
||||
|
||||
@@ -43,7 +43,7 @@ const TagsFilter = ({
|
||||
<div className={cn(
|
||||
'flex h-8 cursor-pointer items-center rounded-lg bg-components-input-bg-normal px-2 py-1 text-text-tertiary select-none hover:bg-state-base-hover-alt',
|
||||
selectedTagsLength && 'text-text-secondary',
|
||||
open && 'bg-state-base-hover',
|
||||
'data-popup-open:bg-state-base-hover',
|
||||
)}
|
||||
>
|
||||
<div className={cn(
|
||||
|
||||
@@ -47,7 +47,7 @@ function LabelSelector({
|
||||
<PopoverTrigger
|
||||
className={cn(
|
||||
'flex h-10 cursor-pointer items-center gap-1 rounded-lg border-[0.5px] border-transparent bg-components-input-bg-normal px-3 text-left hover:bg-components-input-bg-hover',
|
||||
open && 'bg-components-input-bg-hover hover:bg-components-input-bg-hover',
|
||||
'data-popup-open:bg-components-input-bg-hover data-popup-open:hover:bg-components-input-bg-hover',
|
||||
)}
|
||||
>
|
||||
<div title={value.length > 0 ? selectedLabels : ''} className={cn('grow truncate text-[13px] leading-4.5 text-text-secondary', !value.length && 'text-text-quaternary!')}>
|
||||
|
||||
@@ -196,8 +196,9 @@ describe('MethodSelector', () => {
|
||||
await user.click(trigger)
|
||||
|
||||
await waitFor(() => {
|
||||
const openTrigger = document.querySelector('.bg-background-section-burn\\!')
|
||||
expect(openTrigger)!.toBeInTheDocument()
|
||||
const openTrigger = screen.getByTestId('popover-trigger')
|
||||
expect(openTrigger).toHaveAttribute('data-popup-open')
|
||||
expect(openTrigger).toHaveClass('data-popup-open:bg-background-section-burn!')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ const MethodSelector: FC<MethodSelectorProps> = ({
|
||||
render={(
|
||||
<div className={cn(
|
||||
'flex h-9 min-h-[56px] cursor-pointer items-center gap-1 bg-transparent px-3 py-2 hover:bg-background-section-burn',
|
||||
open && 'bg-background-section-burn! hover:bg-background-section-burn',
|
||||
'data-popup-open:bg-background-section-burn! data-popup-open:hover:bg-background-section-burn',
|
||||
)}
|
||||
>
|
||||
<div className={cn('grow truncate text-[13px] leading-[18px] text-text-secondary')}>
|
||||
|
||||
@@ -74,17 +74,17 @@ const WorkflowChecklist = ({
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
'relative ml-0.5 flex size-7 items-center justify-center rounded-md border-none bg-transparent p-0',
|
||||
'group relative ml-0.5 flex size-7 items-center justify-center rounded-md border-none bg-transparent p-0',
|
||||
disabled && 'cursor-not-allowed opacity-50',
|
||||
)}
|
||||
disabled={disabled || undefined}
|
||||
aria-label={checklistLabel}
|
||||
>
|
||||
<span
|
||||
className={cn('group flex size-full items-center justify-center rounded-md hover:bg-state-accent-hover', open && 'bg-state-accent-hover')}
|
||||
className="flex size-full items-center justify-center rounded-md group-data-popup-open:bg-state-accent-hover hover:bg-state-accent-hover"
|
||||
>
|
||||
<span
|
||||
className={cn('i-ri-list-check-3 size-4 group-hover:text-components-button-secondary-accent-text', open ? 'text-components-button-secondary-accent-text' : 'text-components-button-ghost-text')}
|
||||
className="i-ri-list-check-3 size-4 text-components-button-ghost-text group-hover:text-components-button-secondary-accent-text group-data-popup-open:text-components-button-secondary-accent-text"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
|
||||
@@ -79,7 +79,7 @@ const ViewHistory = ({
|
||||
className={cn(
|
||||
'flex h-8 items-center rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-3 shadow-xs',
|
||||
'cursor-pointer text-[13px] font-medium text-components-button-secondary-text hover:bg-components-button-secondary-bg-hover',
|
||||
open && 'bg-components-button-secondary-bg-hover',
|
||||
'data-popup-open:bg-components-button-secondary-bg-hover',
|
||||
)}
|
||||
>
|
||||
<span className="mr-1 i-custom-vender-line-time-clock-play size-4" />
|
||||
@@ -98,12 +98,12 @@ const ViewHistory = ({
|
||||
<button
|
||||
type="button"
|
||||
aria-label={t('common.viewRunHistory', { ns: 'workflow' })}
|
||||
className={cn('group flex size-7 cursor-pointer items-center justify-center rounded-md hover:bg-state-accent-hover', open && 'bg-state-accent-hover')}
|
||||
className="group flex size-7 cursor-pointer items-center justify-center rounded-md hover:bg-state-accent-hover data-popup-open:bg-state-accent-hover"
|
||||
onClick={() => {
|
||||
onClearLogAndMessageModal?.()
|
||||
}}
|
||||
>
|
||||
<span className={cn('i-custom-vender-line-time-clock-play', 'size-4 group-hover:text-components-button-secondary-accent-text', open ? 'text-components-button-secondary-accent-text' : 'text-components-button-ghost-text')} />
|
||||
<span className="i-custom-vender-line-time-clock-play size-4 text-components-button-ghost-text group-hover:text-components-button-secondary-accent-text group-data-popup-open:text-components-button-secondary-accent-text" />
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -157,7 +157,7 @@ const ViewWorkflowHistory = () => {
|
||||
aria-label={t('changeHistory.title', { ns: 'workflow' })}
|
||||
disabled={nodesReadOnly}
|
||||
className={
|
||||
cn('box-border inline-flex size-8 max-h-8 min-h-8 max-w-8 min-w-8 shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-md p-0 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary', open && 'bg-state-accent-active text-text-accent', nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled')
|
||||
cn('box-border inline-flex size-8 max-h-8 min-h-8 max-w-8 min-w-8 shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-md p-0 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary data-popup-open:bg-state-accent-active data-popup-open:text-text-accent', nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled')
|
||||
}
|
||||
onClick={() => {
|
||||
if (nodesReadOnly)
|
||||
|
||||
@@ -55,7 +55,7 @@ const ButtonStyleDropdown: FC<Props> = ({
|
||||
>
|
||||
<PopoverTrigger
|
||||
render={(
|
||||
<div className={cn('flex items-center justify-center rounded-lg bg-components-button-tertiary-bg p-1', !readonly && 'cursor-pointer hover:bg-components-button-tertiary-bg-hover', open && 'bg-components-button-tertiary-bg-hover')}>
|
||||
<div className={cn('flex items-center justify-center rounded-lg bg-components-button-tertiary-bg p-1 data-popup-open:bg-components-button-tertiary-bg-hover', !readonly && 'cursor-pointer hover:bg-components-button-tertiary-bg-hover')}>
|
||||
<Button size="small" className="pointer-events-none px-1" variant={currentStyle}>
|
||||
<RiFontSize className="size-4" />
|
||||
</Button>
|
||||
|
||||
@@ -74,7 +74,7 @@ export const TagFilter = ({
|
||||
aria-label={triggerLabel}
|
||||
icon={false}
|
||||
className={cn(
|
||||
'flex h-8 max-w-60 min-w-28 cursor-pointer items-center gap-1 rounded-lg border-[0.5px] border-transparent bg-components-input-bg-normal px-2 py-0 text-left select-none hover:bg-components-input-bg-normal focus-visible:bg-components-input-bg-normal data-open:bg-components-input-bg-normal',
|
||||
'flex h-8 max-w-60 min-w-28 cursor-pointer items-center gap-1 rounded-lg border-[0.5px] border-transparent bg-components-input-bg-normal px-2 py-0 text-left select-none hover:bg-components-input-bg-normal focus-visible:bg-components-input-bg-normal data-popup-open:bg-components-input-bg-normal',
|
||||
!!value.length && 'pr-6 shadow-xs',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -214,8 +214,7 @@ export const TagSelector = ({
|
||||
<ComboboxTrigger
|
||||
aria-label={triggerLabel}
|
||||
className={cn(
|
||||
open ? 'bg-state-base-hover' : 'bg-transparent',
|
||||
'block h-auto w-full rounded-lg border-0 bg-transparent p-0 text-left hover:bg-transparent focus:outline-hidden focus-visible:bg-transparent focus-visible:ring-1 focus-visible:ring-components-input-border-hover focus-visible:ring-inset data-open:bg-state-base-hover data-open:hover:bg-state-base-hover',
|
||||
'block h-auto w-full rounded-lg border-0 bg-transparent p-0 text-left hover:bg-transparent focus:outline-hidden focus-visible:bg-transparent focus-visible:ring-1 focus-visible:ring-components-input-border-hover focus-visible:ring-inset data-popup-open:bg-state-base-hover data-popup-open:hover:bg-state-base-hover',
|
||||
)}
|
||||
icon={false}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user