@ponti-studios/ui 0.7.1 → 0.8.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.
- package/README.md +13 -0
- package/package.json +22 -20
- package/src/components/data-display/accordion.tsx +1 -1
- package/src/components/data-display/sort-controls.tsx +3 -3
- package/src/components/feedback/index.ts +0 -2
- package/src/components/forms/date-month-select.tsx +12 -3
- package/src/components/forms/date-picker.tsx +16 -12
- package/src/components/forms/drop-zone.tsx +1 -1
- package/src/components/forms/index.ts +4 -8
- package/src/components/forms/passkey-management.tsx +4 -10
- package/src/components/forms/radio-group.tsx +8 -4
- package/src/components/forms/select.tsx +1 -1
- package/src/components/forms/text-field.tsx +40 -5
- package/src/components/navigation/app-navigation.stories.tsx +0 -1
- package/src/components/navigation/app-navigation.tsx +67 -61
- package/src/components/navigation/index.ts +7 -4
- package/src/components/navigation/pagination-controls.stories.tsx +1 -1
- package/src/components/navigation/pagination-controls.tsx +10 -14
- package/src/components/overlays/dropdown-menu.stories.tsx +2 -2
- package/src/components/primitives/card.tsx +6 -10
- package/src/components/primitives/label.stories.tsx +0 -18
- package/src/components/primitives/label.tsx +12 -12
- package/src/constants/chart-colors.ts +16 -16
- package/src/styles/animations.css +0 -22
- package/src/styles/index.css +60 -6
- package/src/styles/tokens/color-systems.stories.tsx +2 -1
- package/src/styles/tokens/generated/light.colors.generated.ts +3 -3
- package/src/styles/tokens/generated/light.css +3 -3
- package/src/styles/tokens/source/light.tokens.json +3 -3
- package/src/styles/tokens/style-dictionary.light-storybook.json +16 -0
- package/src/components/feedback/file-upload-status-badge.stories.tsx +0 -28
- package/src/components/feedback/file-upload-status-badge.tsx +0 -33
- package/src/components/forms/field.stories.tsx +0 -62
- package/src/components/forms/field.tsx +0 -65
- package/src/components/forms/field.types.ts +0 -7
- package/src/components/forms/group-by-select.stories.tsx +0 -26
- package/src/components/forms/group-by-select.tsx +0 -47
- package/src/components/navigation/route-link.stories.tsx +0 -30
- package/src/components/navigation/route-link.tsx +0 -42
- package/src/hooks/use-derived-filter-state.stories.tsx +0 -136
- package/src/hooks/use-filter-state.stories.tsx +0 -102
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type { ComponentPropsWithoutRef, ElementType, MouseEvent, ReactNode } from "react";
|
|
2
|
-
import { useCallback } from "react";
|
|
3
|
-
|
|
4
|
-
type RouteLinkOwnProps<T extends ElementType> = {
|
|
5
|
-
as?: T;
|
|
6
|
-
children: ReactNode;
|
|
7
|
-
/** Called before the click is forwarded — e.g. to flip a route-loading store. */
|
|
8
|
-
onNavigate?: () => void;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export type RouteLinkProps<T extends ElementType = "a"> = RouteLinkOwnProps<T> &
|
|
12
|
-
Omit<ComponentPropsWithoutRef<T>, keyof RouteLinkOwnProps<T>>;
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Polymorphic link that fires `onNavigate` before `onClick`. Pass your
|
|
16
|
-
* router's `Link` component as `as` for client-side navigation (e.g.
|
|
17
|
-
* `as={Link} to="/accounts"`); the host app owns any route-loading state via
|
|
18
|
-
* `onNavigate` instead of a hardcoded store.
|
|
19
|
-
*/
|
|
20
|
-
export function RouteLink<T extends ElementType = "a">({
|
|
21
|
-
as,
|
|
22
|
-
children,
|
|
23
|
-
onNavigate,
|
|
24
|
-
onClick,
|
|
25
|
-
...props
|
|
26
|
-
}: RouteLinkProps<T>) {
|
|
27
|
-
const Component = (as ?? "a") as ElementType;
|
|
28
|
-
|
|
29
|
-
const handleClick = useCallback(
|
|
30
|
-
(e: MouseEvent) => {
|
|
31
|
-
onNavigate?.();
|
|
32
|
-
(onClick as ((e: MouseEvent) => void) | undefined)?.(e);
|
|
33
|
-
},
|
|
34
|
-
[onNavigate, onClick],
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<Component onClick={handleClick} {...props}>
|
|
39
|
-
{children}
|
|
40
|
-
</Component>
|
|
41
|
-
);
|
|
42
|
-
}
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
-
import { expect, userEvent, within } from 'storybook/test';
|
|
3
|
-
import { useRef } from 'react';
|
|
4
|
-
|
|
5
|
-
import { useDerivedFilterState, type FilterConfig } from './use-derived-filter-state';
|
|
6
|
-
|
|
7
|
-
function createMemorySync(initial: Record<string, string>) {
|
|
8
|
-
const store = { ...initial };
|
|
9
|
-
return {
|
|
10
|
-
read: () => ({ ...store }),
|
|
11
|
-
write: (values: Record<string, string>) => Object.assign(store, values),
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const fields = {
|
|
16
|
-
status: { default: "", label: (v: string) => `Status: ${v}` } satisfies FilterConfig,
|
|
17
|
-
search: { default: "" } satisfies FilterConfig,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
function Harness() {
|
|
21
|
-
const sync = useRef(createMemorySync({ status: "", search: "" }));
|
|
22
|
-
const { values, setValue, activeFilters, clearAll, hasActive } =
|
|
23
|
-
useDerivedFilterState({ fields, sync: sync.current });
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<div data-testid="harness">
|
|
27
|
-
<span data-testid="values">{JSON.stringify(values)}</span>
|
|
28
|
-
<span data-testid="active-filters">
|
|
29
|
-
{activeFilters.map((f) => f.label).join(',')}
|
|
30
|
-
</span>
|
|
31
|
-
<span data-testid="has-active">{String(hasActive)}</span>
|
|
32
|
-
<button
|
|
33
|
-
data-testid="set-status"
|
|
34
|
-
onClick={() => setValue('status', 'active')}
|
|
35
|
-
>
|
|
36
|
-
Set status
|
|
37
|
-
</button>
|
|
38
|
-
<button
|
|
39
|
-
data-testid="set-search"
|
|
40
|
-
onClick={() => setValue('search', 'hello')}
|
|
41
|
-
>
|
|
42
|
-
Set search
|
|
43
|
-
</button>
|
|
44
|
-
<button
|
|
45
|
-
data-testid="clear-status"
|
|
46
|
-
onClick={() => setValue('status', '')}
|
|
47
|
-
>
|
|
48
|
-
Clear status
|
|
49
|
-
</button>
|
|
50
|
-
<button data-testid="clear-all" onClick={() => clearAll()}>
|
|
51
|
-
Clear all
|
|
52
|
-
</button>
|
|
53
|
-
</div>
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const meta: Meta<typeof Harness> = {
|
|
58
|
-
title: 'Internal/useDerivedFilterState',
|
|
59
|
-
component: Harness,
|
|
60
|
-
tags: ['autodocs'],
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
export default meta;
|
|
64
|
-
type Story = StoryObj<typeof meta>;
|
|
65
|
-
|
|
66
|
-
export const InitialState: Story = {
|
|
67
|
-
play: async ({ canvasElement }) => {
|
|
68
|
-
const canvas = within(canvasElement);
|
|
69
|
-
await expect(canvas.getByTestId('values')).toHaveTextContent(
|
|
70
|
-
'{"status":"","search":""}',
|
|
71
|
-
);
|
|
72
|
-
await expect(canvas.getByTestId('has-active')).toHaveTextContent('false');
|
|
73
|
-
await expect(canvas.getByTestId('active-filters')).toHaveTextContent('');
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
export const SetValueCreatesActiveFilter: Story = {
|
|
78
|
-
play: async ({ canvasElement }) => {
|
|
79
|
-
const canvas = within(canvasElement);
|
|
80
|
-
await userEvent.click(canvas.getByTestId('set-status'));
|
|
81
|
-
await expect(canvas.getByTestId('values')).toHaveTextContent(
|
|
82
|
-
'{"status":"active","search":""}',
|
|
83
|
-
);
|
|
84
|
-
await expect(canvas.getByTestId('active-filters')).toHaveTextContent(
|
|
85
|
-
'Status: active',
|
|
86
|
-
);
|
|
87
|
-
await expect(canvas.getByTestId('has-active')).toHaveTextContent('true');
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
export const ClearValueRemovesActiveFilter: Story = {
|
|
92
|
-
play: async ({ canvasElement }) => {
|
|
93
|
-
const canvas = within(canvasElement);
|
|
94
|
-
await userEvent.click(canvas.getByTestId('set-status'));
|
|
95
|
-
await userEvent.click(canvas.getByTestId('clear-status'));
|
|
96
|
-
await expect(canvas.getByTestId('values')).toHaveTextContent(
|
|
97
|
-
'{"status":"","search":""}',
|
|
98
|
-
);
|
|
99
|
-
await expect(canvas.getByTestId('has-active')).toHaveTextContent('false');
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
export const ClearAllResetsEverything: Story = {
|
|
104
|
-
play: async ({ canvasElement }) => {
|
|
105
|
-
const canvas = within(canvasElement);
|
|
106
|
-
await userEvent.click(canvas.getByTestId('set-status'));
|
|
107
|
-
await userEvent.click(canvas.getByTestId('set-search'));
|
|
108
|
-
await userEvent.click(canvas.getByTestId('clear-all'));
|
|
109
|
-
await expect(canvas.getByTestId('values')).toHaveTextContent(
|
|
110
|
-
'{"status":"","search":""}',
|
|
111
|
-
);
|
|
112
|
-
await expect(canvas.getByTestId('has-active')).toHaveTextContent('false');
|
|
113
|
-
await expect(canvas.getByTestId('active-filters')).toHaveTextContent('');
|
|
114
|
-
},
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
export const ActiveFilterOnRemoveClearsValue: Story = {
|
|
118
|
-
play: async ({ canvasElement }) => {
|
|
119
|
-
const canvas = within(canvasElement);
|
|
120
|
-
await userEvent.click(canvas.getByTestId('set-status'));
|
|
121
|
-
await expect(canvas.getByTestId('active-filters')).toHaveTextContent(
|
|
122
|
-
'Status: active',
|
|
123
|
-
);
|
|
124
|
-
},
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
export const MultipleActiveFilters: Story = {
|
|
128
|
-
play: async ({ canvasElement }) => {
|
|
129
|
-
const canvas = within(canvasElement);
|
|
130
|
-
await userEvent.click(canvas.getByTestId('set-status'));
|
|
131
|
-
await userEvent.click(canvas.getByTestId('set-search'));
|
|
132
|
-
await expect(canvas.getByTestId('active-filters')).toHaveTextContent(
|
|
133
|
-
'Status: active,search: hello',
|
|
134
|
-
);
|
|
135
|
-
},
|
|
136
|
-
};
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
-
import { expect, userEvent, within } from 'storybook/test';
|
|
3
|
-
import { useFilterState } from './use-filter-state';
|
|
4
|
-
|
|
5
|
-
type Filters = Record<string, string>;
|
|
6
|
-
|
|
7
|
-
function Harness({
|
|
8
|
-
initialFilters,
|
|
9
|
-
debounceMs,
|
|
10
|
-
}: {
|
|
11
|
-
initialFilters: Filters;
|
|
12
|
-
debounceMs?: number;
|
|
13
|
-
}) {
|
|
14
|
-
const { filters, updateFilter, setFilters, clearFilters } = useFilterState<Filters>({
|
|
15
|
-
initialFilters,
|
|
16
|
-
debounceMs,
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
return (
|
|
20
|
-
<div data-testid="harness">
|
|
21
|
-
<span data-testid="filters">{JSON.stringify(filters)}</span>
|
|
22
|
-
<button
|
|
23
|
-
data-testid="update-status"
|
|
24
|
-
onClick={() => updateFilter('status', 'active')}
|
|
25
|
-
>
|
|
26
|
-
Set status to active
|
|
27
|
-
</button>
|
|
28
|
-
<button
|
|
29
|
-
data-testid="update-search"
|
|
30
|
-
onClick={() => updateFilter('search', 'hello')}
|
|
31
|
-
>
|
|
32
|
-
Set search to hello
|
|
33
|
-
</button>
|
|
34
|
-
<button
|
|
35
|
-
data-testid="set-filters"
|
|
36
|
-
onClick={() => setFilters({ status: 'rejected', search: 'test' })}
|
|
37
|
-
>
|
|
38
|
-
Set all filters
|
|
39
|
-
</button>
|
|
40
|
-
<button data-testid="clear" onClick={() => clearFilters()}>
|
|
41
|
-
Clear
|
|
42
|
-
</button>
|
|
43
|
-
</div>
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const meta: Meta<typeof Harness> = {
|
|
48
|
-
title: 'Internal/useFilterState',
|
|
49
|
-
component: Harness,
|
|
50
|
-
tags: ['autodocs'],
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export default meta;
|
|
54
|
-
type Story = StoryObj<typeof meta>;
|
|
55
|
-
|
|
56
|
-
export const InitialState: Story = {
|
|
57
|
-
args: {
|
|
58
|
-
initialFilters: { status: '', search: '' },
|
|
59
|
-
},
|
|
60
|
-
play: async ({ canvasElement }) => {
|
|
61
|
-
const canvas = within(canvasElement);
|
|
62
|
-
const output = canvas.getByTestId('filters');
|
|
63
|
-
await expect(output).toHaveTextContent('{"status":"","search":""}');
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
export const UpdateSingleFilter: Story = {
|
|
68
|
-
args: {
|
|
69
|
-
initialFilters: { status: '', search: '' },
|
|
70
|
-
},
|
|
71
|
-
play: async ({ canvasElement }) => {
|
|
72
|
-
const canvas = within(canvasElement);
|
|
73
|
-
await userEvent.click(canvas.getByTestId('update-status'));
|
|
74
|
-
const output = canvas.getByTestId('filters');
|
|
75
|
-
await expect(output).toHaveTextContent('{"status":"active","search":""}');
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
export const SetAllFilters: Story = {
|
|
80
|
-
args: {
|
|
81
|
-
initialFilters: { status: '', search: '' },
|
|
82
|
-
},
|
|
83
|
-
play: async ({ canvasElement }) => {
|
|
84
|
-
const canvas = within(canvasElement);
|
|
85
|
-
await userEvent.click(canvas.getByTestId('set-filters'));
|
|
86
|
-
const output = canvas.getByTestId('filters');
|
|
87
|
-
await expect(output).toHaveTextContent('{"status":"rejected","search":"test"}');
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
export const ClearResetsToInitial: Story = {
|
|
92
|
-
args: {
|
|
93
|
-
initialFilters: { status: 'pending', search: 'default' },
|
|
94
|
-
},
|
|
95
|
-
play: async ({ canvasElement }) => {
|
|
96
|
-
const canvas = within(canvasElement);
|
|
97
|
-
await userEvent.click(canvas.getByTestId('update-status'));
|
|
98
|
-
await userEvent.click(canvas.getByTestId('clear'));
|
|
99
|
-
const output = canvas.getByTestId('filters');
|
|
100
|
-
await expect(output).toHaveTextContent('{"status":"pending","search":"default"}');
|
|
101
|
-
},
|
|
102
|
-
};
|