mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
improvement(knowledge): align connected-sources rows and move source chip left of filter/sort (#5117)
* improvement(knowledge): align connected-sources rows and move source chip left of filter/sort - Drop the -mx-2 on the connectors list so rows respect the ChipModalBody gutter: the row hover no longer bleeds to the modal edges and row content lines up with the px-4 header. - Add a 'leading' slot to ResourceOptions (left of the filter/sort cluster) and render the knowledge connected-source chip there instead of the far-right 'aside', so it reads as part of the control row. 'aside' stays right-aligned for the table editor's run/stop control. * improvement(resource): render options aside left of filter/sort The options-bar aside has a single other consumer (the table editor's embedded run/stop control), so instead of adding a separate slot, render aside itself to the left of the filter/sort cluster. Drops the extra slot and keeps one canonical control position; the run/stop control moves left too, which is fine for a status widget. * fix(resource): keep options aside grouped with filter/sort without a search bar Group aside + the filter/sort cluster in one ml-auto right-aligned container instead of relying on the search's flex-1 to anchor them. Without this, an options bar with no search (the embedded mothership table editor) split aside to the far left and filter/sort to the far right via justify-between. * docs(resource): clarify aside groups with filter/sort regardless of search
This commit is contained in:
+65
-61
@@ -83,11 +83,11 @@ interface ResourceOptionsProps {
|
||||
filter?: FilterConfig
|
||||
filterTags?: FilterTag[]
|
||||
/**
|
||||
* Supplementary right-aligned slot (pushed opposite the left-aligned
|
||||
* filter/sort via `justify-between`) for lightweight status content — e.g.
|
||||
* the knowledge list's connector badges or the table editor's run/stop
|
||||
* control in embedded mode. Keep it to badges/status widgets; primary
|
||||
* actions belong in the header's `actions`, not here.
|
||||
* Lightweight control rendered immediately to the LEFT of the filter/sort
|
||||
* cluster; the two form one right-aligned group, with or without a search —
|
||||
* e.g. the knowledge view's connected-source badge or the table editor's
|
||||
* embedded run/stop control. Keep it to badges/status widgets; primary actions
|
||||
* belong in the header's `actions`.
|
||||
*/
|
||||
aside?: ReactNode
|
||||
}
|
||||
@@ -115,64 +115,68 @@ export const ResourceOptions = memo(function ResourceOptions({
|
||||
|
||||
return (
|
||||
<div className={cn('border-[var(--border)] border-b py-2.5', search ? 'px-6' : 'px-4')}>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex items-center'>
|
||||
{search && <SearchSection search={search} />}
|
||||
<div className='flex items-center'>
|
||||
{filterTags?.map((tag) => (
|
||||
<Chip key={tag.label} rightIcon={X} onClick={tag.onRemove}>
|
||||
{tag.label}
|
||||
</Chip>
|
||||
))}
|
||||
{isToggleFilter && filter.mode === 'toggle' ? (
|
||||
<Chip active={filter.active} leftIcon={ListFilter} onClick={filter.onToggle}>
|
||||
Filter
|
||||
</Chip>
|
||||
) : popoverFilter ? (
|
||||
<PopoverPrimitive.Root
|
||||
open={openMenu === 'filter'}
|
||||
onOpenChange={(open) =>
|
||||
setOpenMenu((current) => (open ? 'filter' : current === 'filter' ? null : current))
|
||||
}
|
||||
>
|
||||
<PopoverPrimitive.Anchor asChild>
|
||||
<div className='flex items-center'>
|
||||
<PopoverPrimitive.Trigger asChild>
|
||||
<Chip active={popoverFilter.active} leftIcon={ListFilter}>
|
||||
Filter
|
||||
</Chip>
|
||||
</PopoverPrimitive.Trigger>
|
||||
{sort && (
|
||||
<SortDropdown
|
||||
config={sort}
|
||||
open={openMenu === 'sort'}
|
||||
onOpenChange={(open) =>
|
||||
setOpenMenu((current) =>
|
||||
open ? 'sort' : current === 'sort' ? null : current
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</PopoverPrimitive.Anchor>
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
align='end'
|
||||
alignOffset={RESOURCE_MENU_EDGE_OFFSET}
|
||||
collisionPadding={6}
|
||||
sideOffset={6}
|
||||
className={cn(
|
||||
POPOVER_ANIMATION_CLASSES,
|
||||
'z-50 w-fit origin-[--radix-popover-content-transform-origin] rounded-xl border border-[var(--border)] bg-[var(--bg)] shadow-sm'
|
||||
)}
|
||||
>
|
||||
{popoverFilter.content}
|
||||
</PopoverPrimitive.Content>
|
||||
</PopoverPrimitive.Portal>
|
||||
</PopoverPrimitive.Root>
|
||||
) : null}
|
||||
{sort && (isToggleFilter || !popoverFilter) && <SortDropdown config={sort} />}
|
||||
<div className='ml-auto flex shrink-0 items-center gap-1.5'>
|
||||
{aside}
|
||||
<div className='flex items-center'>
|
||||
{filterTags?.map((tag) => (
|
||||
<Chip key={tag.label} rightIcon={X} onClick={tag.onRemove}>
|
||||
{tag.label}
|
||||
</Chip>
|
||||
))}
|
||||
{isToggleFilter && filter.mode === 'toggle' ? (
|
||||
<Chip active={filter.active} leftIcon={ListFilter} onClick={filter.onToggle}>
|
||||
Filter
|
||||
</Chip>
|
||||
) : popoverFilter ? (
|
||||
<PopoverPrimitive.Root
|
||||
open={openMenu === 'filter'}
|
||||
onOpenChange={(open) =>
|
||||
setOpenMenu((current) =>
|
||||
open ? 'filter' : current === 'filter' ? null : current
|
||||
)
|
||||
}
|
||||
>
|
||||
<PopoverPrimitive.Anchor asChild>
|
||||
<div className='flex items-center'>
|
||||
<PopoverPrimitive.Trigger asChild>
|
||||
<Chip active={popoverFilter.active} leftIcon={ListFilter}>
|
||||
Filter
|
||||
</Chip>
|
||||
</PopoverPrimitive.Trigger>
|
||||
{sort && (
|
||||
<SortDropdown
|
||||
config={sort}
|
||||
open={openMenu === 'sort'}
|
||||
onOpenChange={(open) =>
|
||||
setOpenMenu((current) =>
|
||||
open ? 'sort' : current === 'sort' ? null : current
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</PopoverPrimitive.Anchor>
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
align='end'
|
||||
alignOffset={RESOURCE_MENU_EDGE_OFFSET}
|
||||
collisionPadding={6}
|
||||
sideOffset={6}
|
||||
className={cn(
|
||||
POPOVER_ANIMATION_CLASSES,
|
||||
'z-50 w-fit origin-[--radix-popover-content-transform-origin] rounded-xl border border-[var(--border)] bg-[var(--bg)] shadow-sm'
|
||||
)}
|
||||
>
|
||||
{popoverFilter.content}
|
||||
</PopoverPrimitive.Content>
|
||||
</PopoverPrimitive.Portal>
|
||||
</PopoverPrimitive.Root>
|
||||
) : null}
|
||||
{sort && (isToggleFilter || !popoverFilter) && <SortDropdown config={sort} />}
|
||||
</div>
|
||||
</div>
|
||||
{aside && <div className='flex shrink-0 items-center gap-1.5'>{aside}</div>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
+1
-1
@@ -202,7 +202,7 @@ export function ConnectorsSection({
|
||||
No connected sources yet. Connect an external source to automatically sync documents.
|
||||
</p>
|
||||
) : (
|
||||
<div className='-mx-2 mt-2 flex flex-col gap-0.5'>
|
||||
<div className='mt-2 flex flex-col gap-0.5'>
|
||||
{connectors.map((connector) => (
|
||||
<ConnectorCard
|
||||
key={connector.id}
|
||||
|
||||
@@ -640,9 +640,9 @@ export function Table({
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{/* Sort + filter render in both modes (left-aligned). In embedded (mothership)
|
||||
mode there's no Resource.Header, so the run/stop control rides in the options
|
||||
bar's right-aligned `aside` slot — opposite the left-aligned filter/sort. */}
|
||||
{/* Sort + filter render in both modes. In embedded (mothership) mode there's no
|
||||
Resource.Header, so the run/stop control rides in the options bar's `aside`
|
||||
slot, just left of filter/sort. */}
|
||||
<Resource.Options
|
||||
sort={sortConfig}
|
||||
filter={filterConfig}
|
||||
|
||||
Reference in New Issue
Block a user