Skip to content

Commit 59cde30

Browse files
feat: update sidebar and knowledge
1 parent bc4b7f5 commit 59cde30

File tree

11 files changed

+511
-247
lines changed

11 files changed

+511
-247
lines changed

apps/sim/app/workspace/[workspaceId]/components/resource/components/owner-cell/owner-cell.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
import { memo } from 'react'
12
import type { ResourceCell } from '@/app/workspace/[workspaceId]/components/resource/resource'
23
import type { WorkspaceMember } from '@/hooks/queries/workspace'
34

4-
function OwnerAvatar({ name, image }: { name: string; image: string | null }) {
5+
interface OwnerAvatarProps {
6+
name: string
7+
image: string | null
8+
}
9+
10+
const OwnerAvatar = memo(function OwnerAvatar({ name, image }: OwnerAvatarProps) {
511
if (image) {
612
return (
713
<img
@@ -18,7 +24,7 @@ function OwnerAvatar({ name, image }: { name: string; image: string | null }) {
1824
{name.charAt(0).toUpperCase()}
1925
</span>
2026
)
21-
}
27+
})
2228

2329
/**
2430
* Resolves a user ID into a ResourceCell with an avatar icon and display name.

apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-header/resource-header.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -132,19 +132,21 @@ export const ResourceHeader = memo(function ResourceHeader({
132132
)
133133
})
134134

135-
function BreadcrumbSegment({
136-
icon: Icon,
137-
label,
138-
onClick,
139-
dropdownItems,
140-
editing,
141-
}: {
135+
interface BreadcrumbSegmentProps {
142136
icon?: React.ElementType
143137
label: string
144138
onClick?: () => void
145139
dropdownItems?: DropdownOption[]
146140
editing?: BreadcrumbEditing
147-
}) {
141+
}
142+
143+
const BreadcrumbSegment = memo(function BreadcrumbSegment({
144+
icon: Icon,
145+
label,
146+
onClick,
147+
dropdownItems,
148+
editing,
149+
}: BreadcrumbSegmentProps) {
148150
if (editing?.isEditing) {
149151
return (
150152
<span className='inline-flex items-center px-2 py-1'>
@@ -203,4 +205,4 @@ function BreadcrumbSegment({
203205
{content}
204206
</span>
205207
)
206-
}
208+
})

apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options-bar/resource-options-bar.tsx

Lines changed: 89 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { memo, type ReactNode } from 'react'
1+
import { memo, type ReactNode, useCallback, useRef, useState } from 'react'
22
import * as PopoverPrimitive from '@radix-ui/react-popover'
33
import {
44
ArrowDown,
@@ -16,6 +16,12 @@ import {
1616
} from '@/components/emcn'
1717
import { cn } from '@/lib/core/utils/cn'
1818

19+
const SEARCH_ICON = (
20+
<Search className='pointer-events-none h-[14px] w-[14px] shrink-0 text-[var(--text-icon)]' />
21+
)
22+
const FILTER_ICON = <ListFilter className='mr-1.5 h-[14px] w-[14px] text-[var(--text-icon)]' />
23+
const SORT_ICON = <ArrowUpDown className='mr-1.5 h-[14px] w-[14px] text-[var(--text-icon)]' />
24+
1925
type SortDirection = 'asc' | 'desc'
2026

2127
export interface ColumnOption {
@@ -79,56 +85,7 @@ export const ResourceOptionsBar = memo(function ResourceOptionsBar({
7985
return (
8086
<div className={cn('border-[var(--border)] border-b py-2.5', search ? 'px-6' : 'px-4')}>
8187
<div className='flex items-center justify-between'>
82-
{search && (
83-
<div className='relative flex flex-1 items-center'>
84-
<Search className='pointer-events-none h-[14px] w-[14px] shrink-0 text-[var(--text-icon)]' />
85-
<div className='flex flex-1 items-center gap-1.5 overflow-x-auto pl-2.5 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden'>
86-
{search.tags?.map((tag, i) => (
87-
<Button
88-
key={`${tag.label}-${tag.value}-${i}`}
89-
variant='subtle'
90-
className={cn(
91-
'shrink-0 px-2 py-1 text-caption',
92-
search.highlightedTagIndex === i &&
93-
'ring-1 ring-[var(--border-focus)] ring-offset-1'
94-
)}
95-
onClick={tag.onRemove}
96-
>
97-
{tag.label}: {tag.value}
98-
<span className='ml-1 text-[var(--text-icon)] text-micro'></span>
99-
</Button>
100-
))}
101-
<input
102-
ref={search.inputRef}
103-
type='text'
104-
value={search.value}
105-
onChange={(e) => search.onChange(e.target.value)}
106-
onKeyDown={search.onKeyDown}
107-
onFocus={search.onFocus}
108-
onBlur={search.onBlur}
109-
placeholder={search.tags?.length ? '' : (search.placeholder ?? 'Search...')}
110-
className='min-w-[80px] flex-1 bg-transparent py-1 text-[var(--text-secondary)] text-caption outline-none placeholder:text-[var(--text-subtle)]'
111-
/>
112-
</div>
113-
{search.tags?.length || search.value ? (
114-
<button
115-
type='button'
116-
className='mr-0.5 flex h-[14px] w-[14px] shrink-0 items-center justify-center text-[var(--text-subtle)] transition-colors hover-hover:text-[var(--text-secondary)]'
117-
onClick={search.onClearAll}
118-
>
119-
<span className='text-caption'></span>
120-
</button>
121-
) : null}
122-
{search.dropdown && (
123-
<div
124-
ref={search.dropdownRef}
125-
className='absolute top-full left-0 z-50 mt-1.5 w-full rounded-lg border border-[var(--border)] bg-[var(--bg)] shadow-sm'
126-
>
127-
{search.dropdown}
128-
</div>
129-
)}
130-
</div>
131-
)}
88+
{search && <SearchSection search={search} />}
13289
<div className='flex items-center gap-1.5'>
13390
{extras}
13491
{filterTags?.map((tag) => (
@@ -146,7 +103,7 @@ export const ResourceOptionsBar = memo(function ResourceOptionsBar({
146103
<PopoverPrimitive.Root>
147104
<PopoverPrimitive.Trigger asChild>
148105
<Button variant='subtle' className='px-2 py-1 text-caption'>
149-
<ListFilter className='mr-1.5 h-[14px] w-[14px] text-[var(--text-icon)]' />
106+
{FILTER_ICON}
150107
Filter
151108
</Button>
152109
</PopoverPrimitive.Trigger>
@@ -170,14 +127,91 @@ export const ResourceOptionsBar = memo(function ResourceOptionsBar({
170127
)
171128
})
172129

173-
function SortDropdown({ config }: { config: SortConfig }) {
130+
const SearchSection = memo(function SearchSection({ search }: { search: SearchConfig }) {
131+
const [localValue, setLocalValue] = useState(search.value)
132+
133+
const lastReportedRef = useRef(search.value)
134+
135+
if (search.value !== lastReportedRef.current) {
136+
setLocalValue(search.value)
137+
lastReportedRef.current = search.value
138+
}
139+
140+
const handleInputChange = useCallback(
141+
(e: React.ChangeEvent<HTMLInputElement>) => {
142+
const next = e.target.value
143+
setLocalValue(next)
144+
lastReportedRef.current = next
145+
search.onChange(next)
146+
},
147+
[search.onChange]
148+
)
149+
150+
const handleClearAll = useCallback(() => {
151+
setLocalValue('')
152+
lastReportedRef.current = ''
153+
search.onClearAll?.()
154+
}, [search.onClearAll])
155+
156+
return (
157+
<div className='relative flex flex-1 items-center'>
158+
{SEARCH_ICON}
159+
<div className='flex flex-1 items-center gap-1.5 overflow-x-auto pl-2.5 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden'>
160+
{search.tags?.map((tag, i) => (
161+
<Button
162+
key={`${tag.label}-${tag.value}-${i}`}
163+
variant='subtle'
164+
className={cn(
165+
'shrink-0 px-2 py-1 text-caption',
166+
search.highlightedTagIndex === i && 'ring-1 ring-[var(--border-focus)] ring-offset-1'
167+
)}
168+
onClick={tag.onRemove}
169+
>
170+
{tag.label}: {tag.value}
171+
<span className='ml-1 text-[var(--text-icon)] text-micro'></span>
172+
</Button>
173+
))}
174+
<input
175+
ref={search.inputRef}
176+
type='text'
177+
value={localValue}
178+
onChange={handleInputChange}
179+
onKeyDown={search.onKeyDown}
180+
onFocus={search.onFocus}
181+
onBlur={search.onBlur}
182+
placeholder={search.tags?.length ? '' : (search.placeholder ?? 'Search...')}
183+
className='min-w-[80px] flex-1 bg-transparent py-1 text-[var(--text-secondary)] text-caption outline-none placeholder:text-[var(--text-subtle)]'
184+
/>
185+
</div>
186+
{search.tags?.length || localValue ? (
187+
<button
188+
type='button'
189+
className='mr-0.5 flex h-[14px] w-[14px] shrink-0 items-center justify-center text-[var(--text-subtle)] transition-colors hover-hover:text-[var(--text-secondary)]'
190+
onClick={handleClearAll}
191+
>
192+
<span className='text-caption'></span>
193+
</button>
194+
) : null}
195+
{search.dropdown && (
196+
<div
197+
ref={search.dropdownRef}
198+
className='absolute top-full left-0 z-50 mt-1.5 w-full rounded-lg border border-[var(--border)] bg-[var(--bg)] shadow-sm'
199+
>
200+
{search.dropdown}
201+
</div>
202+
)}
203+
</div>
204+
)
205+
})
206+
207+
const SortDropdown = memo(function SortDropdown({ config }: { config: SortConfig }) {
174208
const { options, active, onSort, onClear } = config
175209

176210
return (
177211
<DropdownMenu>
178212
<DropdownMenuTrigger asChild>
179213
<Button variant='subtle' className='px-2 py-1 text-caption'>
180-
<ArrowUpDown className='mr-1.5 h-[14px] w-[14px] text-[var(--text-icon)]' />
214+
{SORT_ICON}
181215
Sort
182216
</Button>
183217
</DropdownMenuTrigger>
@@ -218,4 +252,4 @@ function SortDropdown({ config }: { config: SortConfig }) {
218252
</DropdownMenuContent>
219253
</DropdownMenu>
220254
)
221-
}
255+
})

0 commit comments

Comments
 (0)