@ponti-studios/ui 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/package.json +1 -1
  2. package/src/components/data-display/accordion.tsx +10 -1
  3. package/src/components/feedback/empty-state.tsx +1 -1
  4. package/src/components/feedback/file-upload-status.tsx +14 -6
  5. package/src/components/feedback/progress-bar.tsx +3 -2
  6. package/src/components/feedback/spinner.tsx +3 -4
  7. package/src/components/feedback/update-guard.stories.tsx +1 -1
  8. package/src/components/feedback/update-guard.tsx +7 -3
  9. package/src/components/filters/active-filters-bar.stories.tsx +36 -36
  10. package/src/components/filters/active-filters-bar.tsx +21 -23
  11. package/src/components/filters/filter-select.stories.tsx +26 -26
  12. package/src/components/filters/filter-select.tsx +5 -8
  13. package/src/components/filters/index.ts +0 -4
  14. package/src/components/forms/date-month-select.tsx +2 -2
  15. package/src/components/forms/date-picker.tsx +2 -2
  16. package/src/components/forms/drop-zone.stories.tsx +112 -28
  17. package/src/components/forms/drop-zone.tsx +265 -112
  18. package/src/components/forms/input.tsx +5 -1
  19. package/src/components/forms/radio-group.tsx +3 -2
  20. package/src/components/forms/select.tsx +14 -4
  21. package/src/components/forms/slider.stories.tsx +2 -2
  22. package/src/components/forms/slider.tsx +12 -9
  23. package/src/components/forms/switch.tsx +5 -2
  24. package/src/components/forms/textarea.tsx +5 -1
  25. package/src/components/layout/index.ts +0 -2
  26. package/src/components/layout/section-intro.tsx +1 -1
  27. package/src/components/layout/surface-panel.tsx +10 -2
  28. package/src/components/navigation/app-navigation.stories.tsx +5 -1
  29. package/src/components/navigation/app-navigation.tsx +19 -4
  30. package/src/components/navigation/tabs.tsx +4 -1
  31. package/src/components/overlays/command.tsx +39 -14
  32. package/src/components/overlays/popover.stories.tsx +16 -4
  33. package/src/components/primitives/badge.tsx +1 -1
  34. package/src/components/primitives/button.tsx +6 -1
  35. package/src/hooks/use-derived-filter-state.ts +1 -3
  36. package/src/hooks/use-filter-state.ts +1 -2
  37. package/src/native/card.tsx +3 -1
  38. package/src/native/index.ts +21 -11
  39. package/src/native/list-row.tsx +67 -0
  40. package/src/styles/tokens/generated/native.generated.ts +34 -6
  41. package/src/styles/tokens/index.ts +2 -0
  42. package/src/styles/tokens/normalize-native.ts +5 -3
  43. package/src/styles/tokens/source/native.tokens.json +42 -7
  44. package/src/styles/tokens/style-dictionary.native.mjs +4 -1
  45. package/src/components/filters/filters.stories.tsx +0 -72
  46. package/src/components/filters/filters.tsx +0 -78
  47. package/src/components/filters/search-filter-bar.stories.tsx +0 -41
  48. package/src/components/filters/search-filter-bar.tsx +0 -48
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ponti-studios/ui",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "private": false,
5
5
  "description": "Shared UI component library for Ponti Studios",
6
6
  "license": "MIT",
@@ -6,8 +6,9 @@ import { cn } from "../../lib/utils";
6
6
 
7
7
  function Accordion({
8
8
  type,
9
- collapsible: _collapsible,
9
+ collapsible = true,
10
10
  className,
11
+ onValueChange,
11
12
  ...props
12
13
  }: React.ComponentProps<typeof AccordionPrimitive.Root> & {
13
14
  type?: "single" | "multiple";
@@ -17,6 +18,14 @@ function Accordion({
17
18
  <AccordionPrimitive.Root
18
19
  data-slot="accordion"
19
20
  multiple={type === "multiple"}
21
+ onValueChange={(value, details) => {
22
+ if (type !== "multiple" && !collapsible && value.length === 0) {
23
+ details.cancel();
24
+ return;
25
+ }
26
+
27
+ onValueChange?.(value, details);
28
+ }}
20
29
  {...props}
21
30
  className={cn("w-full", className)}
22
31
  />
@@ -40,7 +40,7 @@ export function EmptyState({
40
40
  variant === "default" && "bg-card border",
41
41
  variant === "dashed" && "bg-card border border-dashed",
42
42
  variant === "search" && "bg-muted border border-dashed",
43
- variant === "quiet" && "bg-surface",
43
+ variant === "quiet" && "bg-card",
44
44
  centered
45
45
  ? cn(
46
46
  "flex flex-col items-center justify-center text-center",
@@ -1,7 +1,6 @@
1
1
  import { XIcon } from "lucide-react";
2
2
  import { memo } from "react";
3
3
 
4
- import { cn } from "../../lib/utils";
5
4
  import { ProgressBar } from "./progress-bar";
6
5
 
7
6
  export interface FileUploadStats {
@@ -47,11 +46,12 @@ export const FileUploadStatus = memo(function FileUploadStatus({
47
46
  {typeof progress === "number" ? (
48
47
  <ProgressBar
49
48
  progress={progress}
50
- className={cn("bg-muted h-2", "before:bg-emphasis-high")}
49
+ className="bg-muted h-2"
50
+ indicatorClassName="border-primary"
51
51
  />
52
52
  ) : (
53
53
  <div className="bg-muted h-2 overflow-hidden">
54
- <div className="bg-emphasis-high void-anim-breezy-progress h-full" />
54
+ <div className="bg-primary void-anim-breezy-progress h-full" />
55
55
  </div>
56
56
  )}
57
57
  </div>
@@ -66,7 +66,11 @@ export const FileUploadStatus = memo(function FileUploadStatus({
66
66
  <span className="text-muted-foreground text-sm font-medium">Queue position</span>
67
67
  </div>
68
68
  <div className="w-full">
69
- <ProgressBar progress={0} className="bg-warning-subtle before:bg-warning h-2" />
69
+ <ProgressBar
70
+ progress={0}
71
+ className="bg-warning/10 h-2"
72
+ indicatorClassName="border-warning"
73
+ />
70
74
  </div>
71
75
  </output>
72
76
  );
@@ -76,7 +80,11 @@ export const FileUploadStatus = memo(function FileUploadStatus({
76
80
  return (
77
81
  <output className="space-y-3">
78
82
  <div className="w-full">
79
- <ProgressBar progress={100} className="bg-emphasis-minimal before:bg-emphasis-high h-2" />
83
+ <ProgressBar
84
+ progress={100}
85
+ className="bg-muted h-2"
86
+ indicatorClassName="border-primary"
87
+ />
80
88
  </div>
81
89
  {stats && <ProcessingStats stats={stats} />}
82
90
  </output>
@@ -87,7 +95,7 @@ export const FileUploadStatus = memo(function FileUploadStatus({
87
95
  return (
88
96
  <output className="mt-2" role="alert">
89
97
  <div className="bg-destructive/10 text-destructive-text flex items-start gap-2 p-3">
90
- <XIcon className="mt-0.5 h-5 w-5 flex-shrink-0" aria-hidden="true" />
98
+ <XIcon className="mt-0.5 h-5 w-5 shrink-0" aria-hidden="true" />
91
99
  <span className="text-sm">{error}</span>
92
100
  </div>
93
101
  </output>
@@ -2,15 +2,16 @@ import { cn } from "../../lib/utils";
2
2
 
3
3
  export interface ProgressBarProps {
4
4
  className?: string;
5
+ indicatorClassName?: string;
5
6
  progress?: number;
6
7
  }
7
8
 
8
9
  /** Thin linear progress indicator, distinct from the Radix-backed `Progress`. */
9
- export function ProgressBar({ className, progress = 0 }: ProgressBarProps) {
10
+ export function ProgressBar({ className, indicatorClassName, progress = 0 }: ProgressBarProps) {
10
11
  return (
11
12
  <div className={cn("border-border h-[2px] w-full overflow-hidden border-t", className)}>
12
13
  <div
13
- className={cn("border-warning h-full border-t")}
14
+ className={cn("border-warning h-full border-t", indicatorClassName)}
14
15
  style={{
15
16
  width: `${Math.min(100, Math.max(0, progress))}%`,
16
17
  opacity: progress === 100 ? 0 : 0.8,
@@ -9,7 +9,7 @@ const sizeClasses = {
9
9
  xl: "size-12",
10
10
  } as const;
11
11
 
12
- export interface SpinnerProps extends React.ComponentProps<"div"> {
12
+ export interface SpinnerProps extends React.ComponentProps<"output"> {
13
13
  label?: string | false;
14
14
  presentation?: "inline" | "centered";
15
15
  size?: keyof typeof sizeClasses;
@@ -25,13 +25,12 @@ export function Spinner({
25
25
  const accessibleLabel = typeof label === "string" ? label : "Loading";
26
26
 
27
27
  return (
28
- <div
28
+ <output
29
29
  className={cn(
30
30
  "inline-flex items-center justify-center gap-2",
31
31
  presentation === "centered" && "flex w-full py-12",
32
32
  className,
33
33
  )}
34
- role="status"
35
34
  aria-label={accessibleLabel}
36
35
  {...props}
37
36
  >
@@ -43,6 +42,6 @@ export function Spinner({
43
42
  aria-hidden="true"
44
43
  />
45
44
  {label && <span className="text-muted-foreground text-xs">{label}</span>}
46
- </div>
45
+ </output>
47
46
  );
48
47
  }
@@ -23,7 +23,7 @@ export const Default: Story = {
23
23
  args: {
24
24
  hideInDev: false,
25
25
  children: (
26
- <div className="border-border bg-surface text-muted-foreground rounded-md border p-6 text-sm">
26
+ <div className="border-border bg-card text-muted-foreground rounded-md border p-6 text-sm">
27
27
  App content renders here. Offline/update prompts appear fixed to the bottom of the viewport
28
28
  when the service worker reports a change.
29
29
  </div>
@@ -196,7 +196,11 @@ function UpdateGuardClient({
196
196
  updateServiceWorker,
197
197
  } = useRegisterSW({ serviceWorkerPath });
198
198
 
199
- const isOnline = useSyncExternalStore(subscribeOnline, getOnlineSnapshot, getOnlineServerSnapshot);
199
+ const isOnline = useSyncExternalStore(
200
+ subscribeOnline,
201
+ getOnlineSnapshot,
202
+ getOnlineServerSnapshot,
203
+ );
200
204
 
201
205
  const closePrompt = () => {
202
206
  setOfflineReady(false);
@@ -221,7 +225,7 @@ function UpdateGuardClient({
221
225
  <>
222
226
  {offlineMessage ? (
223
227
  <div className="fixed inset-x-0 bottom-16 z-50 flex justify-center px-4">
224
- <div className="border-border bg-surface flex items-center gap-3 rounded-md border px-4 py-2">
228
+ <div className="border-border bg-card flex items-center gap-3 rounded-md border px-4 py-2">
225
229
  <span className="text-foreground text-sm">{offlineMessage}</span>
226
230
  </div>
227
231
  </div>
@@ -229,7 +233,7 @@ function UpdateGuardClient({
229
233
 
230
234
  {offlineReady || needRefresh ? (
231
235
  <div className="fixed inset-x-0 bottom-4 z-50 flex justify-center px-4">
232
- <div className="border-border bg-surface flex items-center gap-3 rounded-md border px-4 py-2">
236
+ <div className="border-border bg-card flex items-center gap-3 rounded-md border px-4 py-2">
233
237
  <span className="text-foreground text-sm">
234
238
  {offlineReady
235
239
  ? (copy?.offlineReady ?? "App ready to work offline")
@@ -1,13 +1,13 @@
1
- import type { Meta, StoryObj } from '@storybook/react-vite';
2
- import { expect, userEvent, within } from 'storybook/test';
3
- import { fn } from 'storybook/test';
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { expect, userEvent, within } from "storybook/test";
3
+ import { fn } from "storybook/test";
4
4
 
5
- import { ActiveFiltersBar } from './active-filters-bar';
5
+ import { ActiveFiltersBar } from "./active-filters-bar";
6
6
 
7
7
  const meta = {
8
- title: 'Filters/ActiveFiltersBar',
8
+ title: "Filters/ActiveFiltersBar",
9
9
  component: ActiveFiltersBar,
10
- tags: ['autodocs'],
10
+ tags: ["autodocs"],
11
11
  decorators: [
12
12
  (Story) => (
13
13
  <div className="w-[600px]">
@@ -23,36 +23,37 @@ type Story = StoryObj<typeof meta>;
23
23
  export const Default: Story = {
24
24
  args: {
25
25
  filters: [
26
- { id: '1', label: 'Status: Active', onRemove: fn() },
27
- { id: '2', label: 'Created after Jan 1', onRemove: fn() },
26
+ { id: "1", label: "Status: Active", onRemove: fn() },
27
+ { id: "2", label: "Created after Jan 1", onRemove: fn() },
28
28
  ],
29
+ onClearAll: fn(),
29
30
  },
30
31
  play: async ({ canvasElement }) => {
31
32
  const canvas = within(canvasElement);
32
- const list = canvas.getByRole('list');
33
+ const list = canvas.getByRole("list");
33
34
  await expect(list).toBeInTheDocument();
34
- const items = within(list).getAllByRole('listitem');
35
+ const items = within(list).getAllByRole("listitem");
35
36
  expect(items.length).toBe(2);
36
- await expect(canvas.getByText('Status: Active')).toBeInTheDocument();
37
- await expect(canvas.getByText('Created after Jan 1')).toBeInTheDocument();
38
- await expect(canvas.getByRole('button', { name: 'Clear all' })).toBeInTheDocument();
37
+ await expect(canvas.getByText("Status: Active")).toBeInTheDocument();
38
+ await expect(canvas.getByText("Created after Jan 1")).toBeInTheDocument();
39
+ await expect(canvas.getByRole("button", { name: "Clear all" })).toBeInTheDocument();
39
40
  },
40
41
  };
41
42
 
42
43
  export const WithClearAll: Story = {
43
44
  args: {
44
45
  filters: [
45
- { id: '1', label: 'Status: Active', onRemove: fn() },
46
- { id: '2', label: 'Role: Engineer', onRemove: fn() },
46
+ { id: "1", label: "Status: Active", onRemove: fn() },
47
+ { id: "2", label: "Role: Engineer", onRemove: fn() },
47
48
  ],
48
49
  onClearAll: fn(),
49
50
  },
50
51
  play: async ({ canvasElement, args }) => {
51
52
  const canvas = within(canvasElement);
52
- const list = canvas.getByRole('list');
53
- const items = within(list).getAllByRole('listitem');
53
+ const list = canvas.getByRole("list");
54
+ const items = within(list).getAllByRole("listitem");
54
55
  expect(items.length).toBe(2);
55
- const clearButton = canvas.getByRole('button', { name: 'Clear all' });
56
+ const clearButton = canvas.getByRole("button", { name: "Clear all" });
56
57
  await expect(clearButton).toBeInTheDocument();
57
58
  await userEvent.click(clearButton);
58
59
  await expect(args.onClearAll).toHaveBeenCalledTimes(1);
@@ -61,36 +62,35 @@ export const WithClearAll: Story = {
61
62
 
62
63
  export const WithSortOnly: Story = {
63
64
  args: {
64
- filters: [
65
- { id: '1', label: 'Date', onRemove: fn(), variant: 'sort', direction: 'desc' },
66
- ],
65
+ filters: [{ id: "1", label: "Date", onRemove: fn(), variant: "sort", direction: "desc" }],
66
+ onClearAll: fn(),
67
67
  },
68
68
  play: async ({ canvasElement }) => {
69
69
  const canvas = within(canvasElement);
70
- const list = canvas.getByRole('list');
71
- const items = within(list).getAllByRole('listitem');
70
+ const list = canvas.getByRole("list");
71
+ const items = within(list).getAllByRole("listitem");
72
72
  expect(items.length).toBe(1);
73
- await expect(canvas.getByText('Date')).toBeInTheDocument();
74
- await expect(canvas.getByRole('button', { name: 'Clear all' })).toBeInTheDocument();
73
+ await expect(canvas.getByText("Date")).toBeInTheDocument();
74
+ await expect(canvas.getByRole("button", { name: "Clear all" })).toBeInTheDocument();
75
75
  },
76
76
  };
77
77
 
78
78
  export const WithSortAndFilter: Story = {
79
79
  args: {
80
80
  filters: [
81
- { id: 'filter-1', label: 'Status: Active', onRemove: fn(), variant: 'filter' },
82
- { id: 'sort-1', label: 'Date', onRemove: fn(), variant: 'sort', direction: 'desc' },
81
+ { id: "filter-1", label: "Status: Active", onRemove: fn(), variant: "filter" },
82
+ { id: "sort-1", label: "Date", onRemove: fn(), variant: "sort", direction: "desc" },
83
83
  ],
84
84
  onClearAll: fn(),
85
85
  },
86
86
  play: async ({ canvasElement }) => {
87
87
  const canvas = within(canvasElement);
88
- const list = canvas.getByRole('list');
89
- const items = within(list).getAllByRole('listitem');
88
+ const list = canvas.getByRole("list");
89
+ const items = within(list).getAllByRole("listitem");
90
90
  expect(items.length).toBe(2);
91
- await expect(canvas.getByText('Status: Active')).toBeInTheDocument();
92
- await expect(canvas.getByText('Date')).toBeInTheDocument();
93
- await expect(canvas.getByRole('button', { name: 'Clear all' })).toBeInTheDocument();
91
+ await expect(canvas.getByText("Status: Active")).toBeInTheDocument();
92
+ await expect(canvas.getByText("Date")).toBeInTheDocument();
93
+ await expect(canvas.getByRole("button", { name: "Clear all" })).toBeInTheDocument();
94
94
  },
95
95
  };
96
96
 
@@ -98,17 +98,17 @@ export const Empty: Story = {
98
98
  args: { filters: [] },
99
99
  play: async ({ canvasElement }) => {
100
100
  const canvas = within(canvasElement);
101
- expect(canvas.queryByRole('list')).toBeNull();
101
+ expect(canvas.queryByRole("list")).toBeNull();
102
102
  },
103
103
  };
104
104
 
105
105
  export const WithClassName: Story = {
106
106
  args: {
107
- filters: [{ id: '1', label: 'Status: Active', onRemove: fn() }],
108
- className: 'mt-4',
107
+ filters: [{ id: "1", label: "Status: Active", onRemove: fn() }],
108
+ className: "mt-4",
109
109
  },
110
110
  play: async ({ canvasElement }) => {
111
- const container = canvasElement.querySelector('.mt-4');
111
+ const container = canvasElement.querySelector(".mt-4");
112
112
  await expect(container).not.toBeNull();
113
113
  },
114
114
  };
@@ -33,31 +33,29 @@ export function ActiveFiltersBar({ filters, className, onClearAll }: ActiveFilte
33
33
  <span className="text-muted-foreground shrink-0 self-stretch flex items-center text-sm border-r px-4">
34
34
  Filters
35
35
  </span>
36
- <div
37
- role="list"
38
- className="flex flex-1 min-w-0 items-center gap-2 overflow-x-auto scroll-fade-x pl-4 py-1.5"
39
- >
36
+ <ul className="flex flex-1 min-w-0 items-center gap-2 overflow-x-auto scroll-fade-x pl-4 py-1.5">
40
37
  {filters.map((filter) => (
41
- <FilterChip
42
- key={filter.id}
43
- role="listitem"
44
- className="shrink-0"
45
- label={filter.label}
46
- onRemove={filter.onRemove}
47
- onClick={filter.onClick}
48
- variant={filter.variant === "sort" ? "sort" : "default"}
49
- direction={filter.direction}
50
- />
38
+ <li key={filter.id} className="shrink-0">
39
+ <FilterChip
40
+ label={filter.label}
41
+ onRemove={filter.onRemove}
42
+ onClick={filter.onClick}
43
+ variant={filter.variant === "sort" ? "sort" : "default"}
44
+ direction={filter.direction}
45
+ />
46
+ </li>
51
47
  ))}
52
- </div>
53
- <button
54
- type="button"
55
- onClick={onClearAll}
56
- className="hover:bg-muted text-muted-foreground hover:text-foreground inline-flex shrink-0 items-center justify-center self-stretch aspect-square rounded-r-lg border-l border-border transition-colors"
57
- aria-label="Clear all"
58
- >
59
- <X className="size-4" />
60
- </button>
48
+ </ul>
49
+ {onClearAll ? (
50
+ <button
51
+ type="button"
52
+ onClick={onClearAll}
53
+ className="hover:bg-muted text-muted-foreground hover:text-foreground focus-visible:ring-ring inline-flex shrink-0 items-center justify-center self-stretch aspect-square rounded-r-lg border-l border-border transition-colors focus-visible:ring-2 focus-visible:outline-none"
54
+ aria-label="Clear all"
55
+ >
56
+ <X className="size-4" aria-hidden />
57
+ </button>
58
+ ) : null}
61
59
  </div>
62
60
  );
63
61
  }
@@ -1,22 +1,22 @@
1
- import type { Meta, StoryObj } from '@storybook/react-vite';
2
- import { expect, within } from 'storybook/test';
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { expect, within } from "storybook/test";
3
3
 
4
- import { FilterSelect } from './filter-select';
4
+ import { FilterSelect } from "./filter-select";
5
5
 
6
6
  const meta = {
7
- title: 'Filters/FilterSelect',
7
+ title: "Filters/FilterSelect",
8
8
  component: FilterSelect,
9
- tags: ['autodocs'],
9
+ tags: ["autodocs"],
10
10
  args: {
11
11
  options: [
12
- { value: 'active', label: 'Active' },
13
- { value: 'interview', label: 'Interview' },
14
- { value: 'offer', label: 'Offer' },
15
- { value: 'rejected', label: 'Rejected' },
16
- { value: 'archived', label: 'Archived' },
12
+ { value: "active", label: "Active" },
13
+ { value: "interview", label: "Interview" },
14
+ { value: "offer", label: "Offer" },
15
+ { value: "rejected", label: "Rejected" },
16
+ { value: "archived", label: "Archived" },
17
17
  ],
18
- placeholder: 'All statuses',
19
- label: 'Status',
18
+ placeholder: "All statuses",
19
+ label: "Status",
20
20
  onChange: () => {},
21
21
  },
22
22
  } satisfies Meta<typeof FilterSelect>;
@@ -25,47 +25,47 @@ export default meta;
25
25
  type Story = StoryObj<typeof meta>;
26
26
 
27
27
  export const Default: Story = {
28
- args: { value: '' },
28
+ args: { value: "" },
29
29
  play: async ({ canvasElement }) => {
30
30
  const canvas = within(canvasElement);
31
- const trigger = canvas.getByRole('combobox', { name: 'Status' });
31
+ const trigger = canvas.getByRole("combobox", { name: "Status" });
32
32
  await expect(trigger).toBeInTheDocument();
33
- await expect(trigger).toHaveTextContent('All statuses');
33
+ await expect(trigger).toHaveTextContent("All statuses");
34
34
  },
35
35
  };
36
36
 
37
37
  export const WithValue: Story = {
38
- args: { value: 'active' },
38
+ args: { value: "active" },
39
39
  play: async ({ canvasElement }) => {
40
40
  const canvas = within(canvasElement);
41
- const trigger = canvas.getByRole('combobox', { name: 'Status' });
42
- await expect(trigger).toHaveTextContent('Active');
41
+ const trigger = canvas.getByRole("combobox", { name: "Status" });
42
+ await expect(trigger).toHaveTextContent("Active");
43
43
  },
44
44
  };
45
45
 
46
46
  export const Loading: Story = {
47
- args: { value: '', isLoading: true, options: [] },
47
+ args: { value: "", isLoading: true, options: [] },
48
48
  play: async ({ canvasElement }) => {
49
49
  const canvas = within(canvasElement);
50
- await expect(canvas.getByRole('combobox', { name: 'Status' })).toBeInTheDocument();
51
- await expect(canvas.getByText('Status')).toBeInTheDocument();
50
+ await expect(canvas.getByRole("combobox", { name: "Status" })).toBeInTheDocument();
51
+ await expect(canvas.getByText("Status")).toBeInTheDocument();
52
52
  },
53
53
  };
54
54
 
55
55
  export const Empty: Story = {
56
- args: { value: '', options: [], emptyLabel: 'No statuses found' },
56
+ args: { value: "", options: [], emptyLabel: "No statuses found" },
57
57
  play: async ({ canvasElement }) => {
58
58
  const canvas = within(canvasElement);
59
- await expect(canvas.getByRole('combobox', { name: 'Status' })).toBeInTheDocument();
60
- await expect(canvas.getByText('Status')).toBeInTheDocument();
59
+ await expect(canvas.getByRole("combobox", { name: "Status" })).toBeInTheDocument();
60
+ await expect(canvas.getByText("Status")).toBeInTheDocument();
61
61
  },
62
62
  };
63
63
 
64
64
  export const Disabled: Story = {
65
- args: { value: 'active', disabled: true },
65
+ args: { value: "active", disabled: true },
66
66
  play: async ({ canvasElement }) => {
67
67
  const canvas = within(canvasElement);
68
- const trigger = canvas.getByRole('combobox', { name: 'Status' });
68
+ const trigger = canvas.getByRole("combobox", { name: "Status" });
69
69
  await expect(trigger).toBeDisabled();
70
70
  },
71
71
  };
@@ -14,6 +14,7 @@ export interface FilterSelectProps {
14
14
  onChange: (value: string) => void;
15
15
  options: FilterSelectOption[];
16
16
  label: string;
17
+ loadingText?: string;
17
18
  placeholder?: string;
18
19
  disabled?: boolean;
19
20
  isLoading?: boolean;
@@ -29,6 +30,7 @@ export function FilterSelect({
29
30
  value,
30
31
  options,
31
32
  onChange,
33
+ loadingText = "Loading...",
32
34
  placeholder = "All",
33
35
  disabled = false,
34
36
  isLoading = false,
@@ -44,21 +46,16 @@ export function FilterSelect({
44
46
  );
45
47
 
46
48
  return (
47
- <div className={cn("space-y-2 min-w-36 bg-background", className)}>
49
+ <div className={cn("space-y-2 min-w-36", className)}>
48
50
  <Label htmlFor={selectId}>{label}</Label>
49
51
  <Select value={value} onValueChange={onChange} items={items}>
50
- <SelectTrigger
51
- id={selectId}
52
- disabled={disabled}
53
- aria-label={label}
54
- className="w-full"
55
- >
52
+ <SelectTrigger id={selectId} disabled={disabled} aria-label={label} className="w-full">
56
53
  <SelectValue placeholder={placeholder} />
57
54
  </SelectTrigger>
58
55
  <SelectContent className="bg-background max-h-[250px] min-w-36 overflow-y-auto">
59
56
  <SelectItem value={ALL_VALUE}>{placeholder}</SelectItem>
60
57
  {isLoading ? (
61
- <div className="text-muted-foreground px-2 py-1.5 text-sm">Loading…</div>
58
+ <div className="text-muted-foreground px-2 py-1.5 text-sm">{loadingText}</div>
62
59
  ) : options.length === 0 ? (
63
60
  <div className="text-muted-foreground px-2 py-1.5 text-sm">{emptyLabel}</div>
64
61
  ) : (
@@ -4,7 +4,3 @@ export { FilterChip } from "./filter-chip";
4
4
  export type { FilterChipProps } from "./filter-chip";
5
5
  export { FilterSelect } from "./filter-select";
6
6
  export type { FilterSelectOption, FilterSelectProps } from "./filter-select";
7
- export { Filters } from "./filters";
8
- export type { FiltersProps } from "./filters";
9
- export { SearchFilterBar } from "./search-filter-bar";
10
- export type { SearchFilterBarProps } from "./search-filter-bar";
@@ -42,7 +42,7 @@ export function DateMonthSelect({
42
42
 
43
43
  return (
44
44
  <div className="flex items-center">
45
- <div className="flex items-center justify-center size-10 border rounded-l-xl bg-surface-2 text-text-1">
45
+ <div className="border-border bg-background text-foreground flex size-10 items-center justify-center rounded-l-xl border">
46
46
  <Calendar className="size-4" />
47
47
  </div>
48
48
  <Select
@@ -51,7 +51,7 @@ export function DateMonthSelect({
51
51
  >
52
52
  <SelectTrigger
53
53
  className={cn(
54
- "rounded-r-xl rounded-l-none min-h-10 max-h-10 bg-surface-2 text-text-1 border-l-0",
54
+ "border-border bg-background text-foreground min-h-10 max-h-10 rounded-l-none rounded-r-xl border-l-0",
55
55
  className,
56
56
  )}
57
57
  aria-label={placeholder}
@@ -41,7 +41,7 @@ export function DatePicker({
41
41
  ) : null}
42
42
  <Popover>
43
43
  <div className="flex w-full items-center">
44
- <div className="border-border bg-surface-2 text-text-1 flex size-10 shrink-0 items-center justify-center rounded-l-xl border">
44
+ <div className="border-border bg-background text-foreground flex size-10 shrink-0 items-center justify-center rounded-l-xl border">
45
45
  <CalendarIcon className="size-4" aria-hidden />
46
46
  </div>
47
47
  <PopoverTrigger asChild>
@@ -49,7 +49,7 @@ export function DatePicker({
49
49
  id={id}
50
50
  variant={variant}
51
51
  disabled={disabled}
52
- className="border-border bg-surface-2 text-text-1 placeholder:text-muted-foreground min-h-10 max-h-10 w-full justify-start rounded-l-none rounded-r-xl border-l-0 text-left font-normal"
52
+ className="border-border bg-background text-foreground placeholder:text-muted-foreground min-h-10 max-h-10 w-full justify-start rounded-l-none rounded-r-xl border-l-0 text-left font-normal"
53
53
  >
54
54
  {value ? format(value, dateFormat) : <span>{placeholder}</span>}
55
55
  </Button>