Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion apps/www/src/components/datatable-demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,27 @@ const DataTableVirtualizedDemo = () => {
);
};

export { DataTableDemo, DataTableVirtualizedDemo };
const DataTableSearchDemo = () => {
const data = useMemo(() => generateData(50), []);

return (
<Flex
direction='column'
gap={4}
width='full'
style={{ height: 400, padding: '20px' }}
>
<DataTable
data={data}
mode='client'
columns={columns}
defaultSort={{ name: 'email', order: 'asc' }}
>
<DataTable.Search placeholder='Search payments…' showClearButton />
<DataTable.Content />
</DataTable>
</Flex>
);
};

export { DataTableDemo, DataTableSearchDemo, DataTableVirtualizedDemo };
7 changes: 6 additions & 1 deletion apps/www/src/components/demo/demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ import dayjs from 'dayjs';
import { Home, Info, Laugh, X } from 'lucide-react';
import NextLink from 'next/link';
import { Suspense } from 'react';
import { DataTableDemo, DataTableVirtualizedDemo } from '../datatable-demo';
import {
DataTableDemo,
DataTableSearchDemo,
DataTableVirtualizedDemo
} from '../datatable-demo';
import DataTableSelectionDemo from '../datatable-selection-demo';
import ChipInputDemo from '../inputfield-chip-demo';
import LinearMenuDemo from '../linear-dropdown-demo';
Expand All @@ -56,6 +60,7 @@ export default function Demo(props: DemoProps) {
OrganizationIcon,
SidebarIcon,
DataTableDemo,
DataTableSearchDemo,
DataTableVirtualizedDemo,
ChipInputDemo,
DataTableSelectionDemo,
Expand Down
23 changes: 23 additions & 0 deletions apps/www/src/content/docs/components/datatable/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,29 @@ export const virtualizedPreview = {
]
};

export const searchPreview = {
type: 'code',
style: {
padding: 0
},
previewCode: false,
code: `<DataTableSearchDemo />`,
codePreview: [
{
label: 'index.tsx',
code: `
<DataTable
data={data}
mode="client"
columns={columns}
defaultSort={{ name: "email", order: "asc" }}>
<DataTable.Search placeholder="Search payments…" />
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep preview snippet consistent with the live demo behavior.

Line 68 omits showClearButton, while DataTableSearchDemo renders it. This makes the docs code preview inconsistent with what users see in the demo.

Suggested doc snippet fix
-        <DataTable.Search placeholder="Search payments…" />
+        <DataTable.Search placeholder="Search payments…" showClearButton />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<DataTable.Search placeholder="Search payments…" />
<DataTable.Search placeholder="Search payments…" showClearButton />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/www/src/content/docs/components/datatable/demo.ts` at line 68, The
preview snippet omits the showClearButton prop causing inconsistency with the
live demo; update the demo preview so DataTable.Search includes showClearButton
(matching DataTableSearchDemo) by adding the showClearButton prop to the
DataTable.Search element and ensure its placeholder remains "Search payments…",
so the preview and the component DataTableSearchDemo render identically.

<DataTable.Content />
</DataTable>`
}
]
};

export const rowSelectionDemo = {
type: 'code',
previewCode: false,
Expand Down
4 changes: 3 additions & 1 deletion apps/www/src/content/docs/components/datatable/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: An advanced React table that supports filtering, sorting, and pagin
source: packages/raystack/components/datatable
---

import { preview, virtualizedPreview, rowSelectionDemo } from "./demo.ts";
import { preview, virtualizedPreview, searchPreview, rowSelectionDemo } from "./demo.ts";

<Demo data={preview} />

Expand Down Expand Up @@ -329,6 +329,8 @@ onColumnVisibilityChange={handleColumnVisibilityChange}>

The `DataTable.Search` component provides search functionality that automatically integrates with the table query. By default, it is disabled in zero state (when no data and no filters/search applied).

<Demo data={searchPreview} />

```tsx
<DataTable
data={data}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,27 @@ describe('DataTable', () => {

expect(screen.getByLabelText('Search')).toBeInTheDocument();
});

it('clears the search input when the clear button is clicked', async () => {
const user = userEvent.setup();
render(
<DataTable
data={mockData}
columns={mockColumns}
defaultSort={{ name: 'name', order: 'asc' }}
>
<DataTable.Search showClearButton />
<DataTable.Content />
</DataTable>
);

const input = screen.getByLabelText('Search') as HTMLInputElement;
await user.type(input, 'hello');
expect(input.value).toBe('hello');

await user.click(screen.getByLabelText('Clear search'));
expect(input.value).toBe('');
});
});

describe('Zero State and Empty State', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function TableSearch({
<Search
{...props}
onChange={handleSearch}
value={tableQuery?.search}
value={tableQuery?.search ?? ''}
onClear={handleClear}
disabled={isDisabled}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function DataViewSearch({
<Search
{...props}
onChange={handleSearch}
value={tableQuery?.search}
value={tableQuery?.search ?? ''}
onClear={handleClear}
disabled={isDisabled}
/>
Expand Down
1 change: 0 additions & 1 deletion packages/raystack/components/input/input.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@

.input-field:focus {
border-color: var(--rs-color-border-accent-emphasis);
background: var(--rs-color-background-base-primary);
}

.input-field[data-disabled] {
Expand Down
4 changes: 2 additions & 2 deletions packages/raystack/components/search/__tests__/search.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ describe('Search', () => {
expect(screen.getByLabelText('Clear search')).toBeInTheDocument();
});

it('hides clear button when no value', () => {
it('renders clear button regardless of value (CSS hides it when empty)', () => {
render(<Search showClearButton value='' />);
expect(screen.queryByLabelText('Clear search')).not.toBeInTheDocument();
expect(screen.getByLabelText('Clear search')).toBeInTheDocument();
});

it('calls onClear when clear button clicked', () => {
Expand Down
4 changes: 4 additions & 0 deletions packages/raystack/components/search/search.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
justify-content: center;
}

.container:has(input:placeholder-shown) .clearButtonWrapper {
display: none;
}

.clearButton {
color: var(--rs-color-foreground-base-tertiary);
}
Expand Down
37 changes: 18 additions & 19 deletions packages/raystack/components/search/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,24 @@ export function Search({
variant = 'default',
...props
}: SearchProps) {
const trailingIconWithClear =
showClearButton && value ? (
<div className={styles.clearButtonWrapper}>
<IconButton
size={size === 'small' ? 2 : 3}
onClick={e => {
e.stopPropagation();
if (!disabled && onClear) {
onClear();
}
}}
disabled={disabled}
aria-label='Clear search'
className={styles.clearButton}
>
<CrossCircledIcon />
</IconButton>
</div>
) : undefined;
const trailingIconWithClear = showClearButton ? (
<div className={styles.clearButtonWrapper}>
<IconButton
size={size === 'small' ? 2 : 3}
onClick={e => {
e.stopPropagation();
if (!disabled && onClear) {
onClear();
}
}}
disabled={disabled}
aria-label='Clear search'
className={styles.clearButton}
>
<CrossCircledIcon />
</IconButton>
</div>
) : undefined;

return (
<div className={styles.container} role='search' style={{ width }}>
Expand Down
Loading