@reshape-biotech/design-system 0.0.49 → 0.0.50
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.
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import { defineMeta } from '@storybook/addon-svelte-csf';
|
|
3
|
+
import { userEvent, within } from '@storybook/test';
|
|
4
|
+
import { DateTime } from 'luxon';
|
|
5
|
+
import DatePicker from './DatePicker.svelte';
|
|
6
|
+
import Dropdown from '../dropdown/Dropdown.svelte';
|
|
7
|
+
import Button from '../button/Button.svelte';
|
|
8
|
+
import { Icon } from '../icons';
|
|
9
|
+
|
|
10
|
+
const { Story } = defineMeta({
|
|
11
|
+
component: DatePicker,
|
|
12
|
+
title: 'Design System/DatePicker',
|
|
13
|
+
tags: ['autodocs'],
|
|
14
|
+
parameters: {
|
|
15
|
+
design: {
|
|
16
|
+
type: 'figma',
|
|
17
|
+
url: 'https://www.figma.com/design/WkxhG4H2Ybc7STFi1VJIau/%F0%9F%92%A0-Reshape-Design-System%3A-V1?node-id=4381-32638&t=Epi2Ul7Cw7sCJpGy-0'
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
let selectedDate = $state<DateTime | undefined>(undefined);
|
|
23
|
+
let loggedDate = $state<string | undefined>(undefined);
|
|
24
|
+
|
|
25
|
+
function handleDateSelection(date: DateTime) {
|
|
26
|
+
loggedDate = date.toFormat('yyyy-MM-dd');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// States for the dropdown story
|
|
30
|
+
let dropdownDate = $state<DateTime | undefined>(undefined);
|
|
31
|
+
let displayText = $state('Select a date');
|
|
32
|
+
let isOpen = $state(false);
|
|
33
|
+
|
|
34
|
+
// States for the form test
|
|
35
|
+
let formDropdownDate = $state<DateTime | undefined>(undefined);
|
|
36
|
+
let formDisplayText = $state('Select a date');
|
|
37
|
+
let formIsOpen = $state(false);
|
|
38
|
+
|
|
39
|
+
function handleDropdownSelection(date: DateTime) {
|
|
40
|
+
dropdownDate = date;
|
|
41
|
+
displayText = date.toFormat('MMM dd, yyyy');
|
|
42
|
+
isOpen = false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function handleFormDateSelection(date: DateTime) {
|
|
46
|
+
formDropdownDate = date;
|
|
47
|
+
formDisplayText = date.toFormat('MMM dd, yyyy');
|
|
48
|
+
formIsOpen = false;
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<Story name="Default">
|
|
53
|
+
<div class="p-4">
|
|
54
|
+
<DatePicker bind:selectedDate />
|
|
55
|
+
</div>
|
|
56
|
+
</Story>
|
|
57
|
+
|
|
58
|
+
<Story name="With Selected Date">
|
|
59
|
+
<div class="p-4">
|
|
60
|
+
<DatePicker selectedDate={DateTime.local(2023, 10, 15)} />
|
|
61
|
+
</div>
|
|
62
|
+
</Story>
|
|
63
|
+
|
|
64
|
+
<Story name="With Date Change Handler">
|
|
65
|
+
<div class="p-4">
|
|
66
|
+
<div class="mb-4">
|
|
67
|
+
<DatePicker {selectedDate} onClick={handleDateSelection} />
|
|
68
|
+
</div>
|
|
69
|
+
{#if loggedDate}
|
|
70
|
+
<div class="mt-4 rounded bg-neutral p-2">
|
|
71
|
+
<p>Selected date: <strong>{loggedDate}</strong></p>
|
|
72
|
+
</div>
|
|
73
|
+
{/if}
|
|
74
|
+
</div>
|
|
75
|
+
</Story>
|
|
76
|
+
|
|
77
|
+
<Story name="Month Navigation">
|
|
78
|
+
<div class="p-4">
|
|
79
|
+
<DatePicker selectedDate={DateTime.local()} />
|
|
80
|
+
<div class="mt-4 text-sm text-tertiary">
|
|
81
|
+
<p>Click the arrow buttons to navigate between months</p>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</Story>
|
|
85
|
+
|
|
86
|
+
<Story
|
|
87
|
+
name="Interaction Test"
|
|
88
|
+
play={async ({ canvasElement }) => {
|
|
89
|
+
const canvas = within(canvasElement);
|
|
90
|
+
|
|
91
|
+
// Click next month button
|
|
92
|
+
const nextMonthButton = canvas.getAllByRole('button')[1]; // Second button is next month
|
|
93
|
+
await userEvent.click(nextMonthButton);
|
|
94
|
+
|
|
95
|
+
// Wait a moment
|
|
96
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
97
|
+
|
|
98
|
+
// Click a date (the 15th of the month)
|
|
99
|
+
const dateButtons = canvas.getAllByRole('button').filter((button) => {
|
|
100
|
+
// Find buttons with just numbers as their text content
|
|
101
|
+
const text = button.textContent?.trim();
|
|
102
|
+
return text && !isNaN(Number(text));
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Find the button with text "15" if it exists
|
|
106
|
+
const date15Button = dateButtons.find((button) => button.textContent?.trim() === '15');
|
|
107
|
+
|
|
108
|
+
if (date15Button) {
|
|
109
|
+
await userEvent.click(date15Button);
|
|
110
|
+
}
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
<div class="p-4">
|
|
114
|
+
<DatePicker bind:selectedDate />
|
|
115
|
+
{#if selectedDate}
|
|
116
|
+
<div class="mt-4 rounded bg-neutral p-2">
|
|
117
|
+
<p>Selected date: <strong>{selectedDate.toFormat('yyyy-MM-dd')}</strong></p>
|
|
118
|
+
</div>
|
|
119
|
+
{/if}
|
|
120
|
+
</div>
|
|
121
|
+
</Story>
|
|
122
|
+
|
|
123
|
+
<Story name="Multiple DatePickers">
|
|
124
|
+
<div class="flex flex-col gap-4 p-4 md:flex-row">
|
|
125
|
+
<div>
|
|
126
|
+
<h3 class="mb-2 text-sm font-medium">Start Date</h3>
|
|
127
|
+
<DatePicker selectedDate={DateTime.local().minus({ days: 7 })} />
|
|
128
|
+
</div>
|
|
129
|
+
<div>
|
|
130
|
+
<h3 class="mb-2 text-sm font-medium">End Date</h3>
|
|
131
|
+
<DatePicker selectedDate={DateTime.local()} />
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</Story>
|
|
135
|
+
|
|
136
|
+
<Story name="In Dropdown">
|
|
137
|
+
<div class="p-4">
|
|
138
|
+
<Dropdown bind:open={isOpen} side="dropdown-bottom" alignEnd={false}>
|
|
139
|
+
{#snippet trigger({ Trigger })}
|
|
140
|
+
<Trigger>
|
|
141
|
+
<Icon iconName="CalendarBlank" class="mr-2" size={16} />
|
|
142
|
+
{displayText}
|
|
143
|
+
</Trigger>
|
|
144
|
+
{/snippet}
|
|
145
|
+
|
|
146
|
+
{#snippet content()}
|
|
147
|
+
<DatePicker selectedDate={dropdownDate} onClick={handleDropdownSelection} />
|
|
148
|
+
{/snippet}
|
|
149
|
+
</Dropdown>
|
|
150
|
+
|
|
151
|
+
{#if dropdownDate}
|
|
152
|
+
<div class="mt-4 rounded bg-neutral p-2">
|
|
153
|
+
<p>You selected: <strong>{dropdownDate.toFormat('MMMM dd, yyyy')}</strong></p>
|
|
154
|
+
</div>
|
|
155
|
+
{/if}
|
|
156
|
+
</div>
|
|
157
|
+
</Story>
|
|
158
|
+
|
|
159
|
+
<Story
|
|
160
|
+
name="Form Submit Prevention Test"
|
|
161
|
+
play={async ({ canvasElement }) => {
|
|
162
|
+
const canvas = within(canvasElement);
|
|
163
|
+
|
|
164
|
+
// Setup spy on form submission
|
|
165
|
+
const form = canvas.getByTestId('test-form');
|
|
166
|
+
let submissionCount = 0;
|
|
167
|
+
form.addEventListener('submit', (e) => {
|
|
168
|
+
e.preventDefault();
|
|
169
|
+
submissionCount++;
|
|
170
|
+
console.log('Form was submitted!');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// First open the dropdown
|
|
174
|
+
const dropdownTrigger = canvas.getByTestId('form-date-trigger');
|
|
175
|
+
await userEvent.click(dropdownTrigger);
|
|
176
|
+
|
|
177
|
+
// Click prev/next month and verify no submission
|
|
178
|
+
const buttons = document.querySelectorAll('button');
|
|
179
|
+
const prevMonthButton = Array.from(buttons).find((btn) => btn.innerHTML.includes('CaretLeft'));
|
|
180
|
+
const nextMonthButton = Array.from(buttons).find((btn) => btn.innerHTML.includes('CaretRight'));
|
|
181
|
+
|
|
182
|
+
if (prevMonthButton) {
|
|
183
|
+
await userEvent.click(prevMonthButton);
|
|
184
|
+
console.log('Clicked prev month, submission count:', submissionCount);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (nextMonthButton) {
|
|
188
|
+
await userEvent.click(nextMonthButton);
|
|
189
|
+
console.log('Clicked next month, submission count:', submissionCount);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Success message if no form submission occurred
|
|
193
|
+
if (submissionCount === 0) {
|
|
194
|
+
console.log('TEST PASSED: Month navigation did not trigger form submission');
|
|
195
|
+
} else {
|
|
196
|
+
console.error('TEST FAILED: Month navigation triggered form submission');
|
|
197
|
+
}
|
|
198
|
+
}}
|
|
199
|
+
>
|
|
200
|
+
<div class="p-4">
|
|
201
|
+
<form data-testid="test-form" onsubmit={(e) => e.preventDefault()}>
|
|
202
|
+
<div class="mb-4">
|
|
203
|
+
<label for="name" class="mb-1 block text-sm font-medium">Name</label>
|
|
204
|
+
<input id="name" type="text" class="input input-bordered w-full" />
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<div class="mb-4">
|
|
208
|
+
<Dropdown bind:open={formIsOpen} side="dropdown-bottom" alignEnd={false}>
|
|
209
|
+
{#snippet trigger({ Trigger })}
|
|
210
|
+
<Trigger>
|
|
211
|
+
<div data-testid="form-date-trigger" class="flex items-center gap-2">
|
|
212
|
+
<Icon iconName="CalendarBlank" class="mr-2" size={16} />
|
|
213
|
+
{formDisplayText}
|
|
214
|
+
</div>
|
|
215
|
+
</Trigger>
|
|
216
|
+
{/snippet}
|
|
217
|
+
|
|
218
|
+
{#snippet content()}
|
|
219
|
+
<DatePicker selectedDate={formDropdownDate} onClick={handleFormDateSelection} />
|
|
220
|
+
{/snippet}
|
|
221
|
+
</Dropdown>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<button type="submit" class="btn btn-primary mt-4">Submit Form</button>
|
|
225
|
+
</form>
|
|
226
|
+
</div>
|
|
227
|
+
</Story>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import DatePicker from './DatePicker.svelte';
|
|
2
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
3
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
4
|
+
$$bindings?: Bindings;
|
|
5
|
+
} & Exports;
|
|
6
|
+
(internal: unknown, props: {
|
|
7
|
+
$$events?: Events;
|
|
8
|
+
$$slots?: Slots;
|
|
9
|
+
}): Exports & {
|
|
10
|
+
$set?: any;
|
|
11
|
+
$on?: any;
|
|
12
|
+
};
|
|
13
|
+
z_$$bindings?: Bindings;
|
|
14
|
+
}
|
|
15
|
+
declare const DatePicker: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
16
|
+
[evt: string]: CustomEvent<any>;
|
|
17
|
+
}, {}, {}, string>;
|
|
18
|
+
type DatePicker = InstanceType<typeof DatePicker>;
|
|
19
|
+
export default DatePicker;
|
|
@@ -95,11 +95,11 @@
|
|
|
95
95
|
<div class="calendar">
|
|
96
96
|
<div class="calendar-header">
|
|
97
97
|
<div class="month-year">
|
|
98
|
-
<IconButton onclick={previousMonth}>
|
|
98
|
+
<IconButton onclick={previousMonth} type="button">
|
|
99
99
|
<Icon iconName="CaretLeft" color="secondary" size="0.75rem" />
|
|
100
100
|
</IconButton>
|
|
101
101
|
<h5>{DateTime.local(shownYear, shownMonth).toFormat('MMM yyyy')}</h5>
|
|
102
|
-
<IconButton onclick={nextMonth}>
|
|
102
|
+
<IconButton onclick={nextMonth} type="button">
|
|
103
103
|
<Icon iconName="CaretRight" color="secondary" size="0.75rem" />
|
|
104
104
|
</IconButton>
|
|
105
105
|
</div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reshape-biotech/design-system",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.50",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev",
|
|
6
6
|
"build": "vite build",
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"svelte": "^5.0.0",
|
|
37
37
|
"marked": "^15.0.0",
|
|
38
38
|
"luxon": "^3.5.0",
|
|
39
|
-
"svelte-select": "^5.8.1"
|
|
39
|
+
"svelte-select": "^5.8.1",
|
|
40
|
+
"echarts": "^5.6.0"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"@eslint/compat": "^1.2.3",
|