@flightlesslabs/dodo-ui-date 0.2.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 (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/dist/components/Calendar/Calendar.scss +147 -0
  4. package/dist/components/Calendar/Calendar.stories.svelte +136 -0
  5. package/dist/components/Calendar/Calendar.stories.svelte.d.ts +22 -0
  6. package/dist/components/Calendar/Calendar.svelte +59 -0
  7. package/dist/components/Calendar/Calendar.svelte.d.ts +6 -0
  8. package/dist/components/Calendar/CalendarGrid/CalendarGrid.svelte +16 -0
  9. package/dist/components/Calendar/CalendarGrid/CalendarGrid.svelte.d.ts +4 -0
  10. package/dist/components/Calendar/CalendarGrid/TableBody.svelte +25 -0
  11. package/dist/components/Calendar/CalendarGrid/TableBody.svelte.d.ts +8 -0
  12. package/dist/components/Calendar/CalendarGrid/TableHead.svelte +19 -0
  13. package/dist/components/Calendar/CalendarGrid/TableHead.svelte.d.ts +6 -0
  14. package/dist/components/Calendar/Header.svelte +25 -0
  15. package/dist/components/Calendar/Header.svelte.d.ts +3 -0
  16. package/dist/components/DatePicker/DatePicker.scss +55 -0
  17. package/dist/components/DatePicker/DatePicker.stories.svelte +136 -0
  18. package/dist/components/DatePicker/DatePicker.stories.svelte.d.ts +22 -0
  19. package/dist/components/DatePicker/DatePicker.svelte +134 -0
  20. package/dist/components/DatePicker/DatePicker.svelte.d.ts +63 -0
  21. package/dist/components/DatePicker/DatePickerInput/DatePickerInput.svelte +95 -0
  22. package/dist/components/DatePicker/DatePickerInput/DatePickerInput.svelte.d.ts +22 -0
  23. package/dist/components/DatePicker/DatePickerInput/Segments.svelte +17 -0
  24. package/dist/components/DatePicker/DatePickerInput/Segments.svelte.d.ts +8 -0
  25. package/dist/components/DatePicker/DatePickerInput/utils.d.ts +10 -0
  26. package/dist/components/DatePicker/DatePickerInput/utils.js +26 -0
  27. package/dist/components/DatePicker/DatePickerPopup/CalendarGrid.svelte +35 -0
  28. package/dist/components/DatePicker/DatePickerPopup/CalendarGrid.svelte.d.ts +4 -0
  29. package/dist/components/DatePicker/DatePickerPopup/DatePickerPopup.svelte +50 -0
  30. package/dist/components/DatePicker/DatePickerPopup/DatePickerPopup.svelte.d.ts +6 -0
  31. package/dist/components/DatePicker/DatePickerPopup/Header.svelte +25 -0
  32. package/dist/components/DatePicker/DatePickerPopup/Header.svelte.d.ts +3 -0
  33. package/dist/index.d.ts +10 -0
  34. package/dist/index.js +10 -0
  35. package/dist/storybook-types.d.ts +129 -0
  36. package/dist/storybook-types.js +1 -0
  37. package/dist/styles/global/_breakpoints.scss +38 -0
  38. package/dist/styles/main.css +155 -0
  39. package/dist/styles/main.css.map +1 -0
  40. package/dist/styles/main.scss +2 -0
  41. package/dist/test/setup.d.ts +1 -0
  42. package/dist/test/setup.js +12 -0
  43. package/package.json +114 -0
  44. package/src/lib/components/Calendar/Calendar.scss +147 -0
  45. package/src/lib/components/Calendar/Calendar.stories.svelte +136 -0
  46. package/src/lib/components/Calendar/Calendar.svelte +59 -0
  47. package/src/lib/components/Calendar/CalendarGrid/CalendarGrid.svelte +16 -0
  48. package/src/lib/components/Calendar/CalendarGrid/TableBody.svelte +25 -0
  49. package/src/lib/components/Calendar/CalendarGrid/TableHead.svelte +19 -0
  50. package/src/lib/components/Calendar/Header.svelte +25 -0
  51. package/src/lib/components/DatePicker/DatePicker.scss +55 -0
  52. package/src/lib/components/DatePicker/DatePicker.stories.svelte +136 -0
  53. package/src/lib/components/DatePicker/DatePicker.svelte +134 -0
  54. package/src/lib/components/DatePicker/DatePickerInput/DatePickerInput.svelte +95 -0
  55. package/src/lib/components/DatePicker/DatePickerInput/Segments.svelte +17 -0
  56. package/src/lib/components/DatePicker/DatePickerInput/utils.ts +55 -0
  57. package/src/lib/components/DatePicker/DatePickerPopup/CalendarGrid.svelte +35 -0
  58. package/src/lib/components/DatePicker/DatePickerPopup/DatePickerPopup.svelte +50 -0
  59. package/src/lib/components/DatePicker/DatePickerPopup/Header.svelte +25 -0
  60. package/src/lib/index.ts +16 -0
  61. package/src/lib/storybook-types.ts +182 -0
  62. package/src/lib/styles/global/_breakpoints.scss +38 -0
  63. package/src/lib/styles/main.scss +2 -0
  64. package/src/lib/test/setup.ts +13 -0
@@ -0,0 +1,95 @@
1
+ <script lang="ts" module>
2
+ import type { Snippet } from 'svelte';
3
+
4
+ export type DatePickerInputProps = {
5
+ size?: ComponentSize;
6
+ roundness?: ComponentRoundnessShape;
7
+ outline?: boolean;
8
+ class?: string;
9
+ error?: boolean;
10
+ focused?: boolean;
11
+ before?: Snippet;
12
+ after?: Snippet;
13
+ placeholder?: string;
14
+ dateFieldInputProps?: DateFieldInputProps;
15
+ datePickerTriggerProps?: DatePickerTriggerProps;
16
+ disabled?: boolean;
17
+ dateFormat?: DatePickerFormat;
18
+ };
19
+ </script>
20
+
21
+ <script lang="ts">
22
+ import { DatePicker, type DateFieldInputProps, type DatePickerTriggerProps } from 'bits-ui';
23
+ import Icon from '@iconify/svelte';
24
+ import Segments from './Segments.svelte';
25
+ import type { DatePickerFormat } from './utils.js';
26
+ import type { ComponentSize, ComponentRoundnessShape } from '@flightlesslabs/dodo-ui';
27
+ import { InputEnclosure } from '@flightlesslabs/dodo-ui';
28
+
29
+ let {
30
+ size = 'normal',
31
+ class: className = '',
32
+ disabled = false,
33
+ error = false,
34
+ focused: forcedFocused = false,
35
+ placeholder,
36
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
37
+ after,
38
+ dateFieldInputProps,
39
+ datePickerTriggerProps,
40
+ dateFormat = 'dd/mm/yyyy',
41
+ ...restProps
42
+ }: DatePickerInputProps = $props();
43
+
44
+ let isFocused = $state(false);
45
+
46
+ function handleFocus() {
47
+ isFocused = true;
48
+ }
49
+
50
+ function handleBlur() {
51
+ isFocused = false;
52
+ }
53
+
54
+ const classes = $derived(['dodo-ui-DatePicker', className].filter(Boolean));
55
+
56
+ const triggerClasses = $derived(
57
+ [
58
+ 'dodo-ui-UtilityButton',
59
+ `size--${size}`,
60
+ 'compact',
61
+ 'color--primary',
62
+ 'roundness--full',
63
+ error && 'error',
64
+ disabled && 'disabled',
65
+ ].filter(Boolean),
66
+ );
67
+ </script>
68
+
69
+ <InputEnclosure
70
+ {size}
71
+ {disabled}
72
+ {error}
73
+ class={classes.join(' ')}
74
+ focused={forcedFocused || isFocused}
75
+ {...restProps}
76
+ >
77
+ <DatePicker.Input
78
+ onfocus={handleFocus}
79
+ onblur={handleBlur}
80
+ {placeholder}
81
+ class="InputBox"
82
+ {...dateFieldInputProps}
83
+ >
84
+ {#snippet children({ segments })}
85
+ <Segments {segments} {dateFormat} />
86
+ {/snippet}
87
+ </DatePicker.Input>
88
+
89
+ {#snippet after()}
90
+ <DatePicker.Trigger class={triggerClasses.join(' ')} {...datePickerTriggerProps}>
91
+ <Icon icon="material-symbols:calendar-month-sharp" />
92
+ </DatePicker.Trigger>
93
+ {@render after?.()}
94
+ {/snippet}
95
+ </InputEnclosure>
@@ -0,0 +1,17 @@
1
+ <script lang="ts">
2
+ import { DatePicker } from 'bits-ui';
3
+ import { formatSegments, type DatePickerFormat, type DateSegment } from './utils.js';
4
+
5
+ type Props = {
6
+ segments: DateSegment[];
7
+ dateFormat: DatePickerFormat;
8
+ };
9
+
10
+ let { segments, dateFormat }: Props = $props();
11
+ </script>
12
+
13
+ {#each formatSegments(segments, dateFormat) as { part, value }, i (part + i)}
14
+ <DatePicker.Segment {part}>
15
+ {value}
16
+ </DatePicker.Segment>
17
+ {/each}
@@ -0,0 +1,55 @@
1
+ import type { SegmentPart } from 'bits-ui';
2
+
3
+ // ---- Segment Types ----
4
+ export type DateSegment = {
5
+ part: SegmentPart;
6
+ value: string;
7
+ };
8
+
9
+ // ---- Supported format tokens ----
10
+ export type FormatToken = 'dd' | 'mm' | 'yyyy' | 'hh' | 'min' | 'ss';
11
+
12
+ // ---- Optional: stricter format typing ----
13
+ type Separator = '/' | '-' | '.' | ' ';
14
+
15
+ export type DatePickerFormat =
16
+ | `${FormatToken}${Separator}${FormatToken}${Separator}${FormatToken}`
17
+ | `${FormatToken}${Separator}${FormatToken}`
18
+ | `${FormatToken}`;
19
+
20
+ // ---- Formatter ----
21
+ export function formatSegments(segments: DateSegment[], format: DatePickerFormat): DateSegment[] {
22
+ const segmentMap: Partial<Record<SegmentPart, DateSegment>> = Object.fromEntries(
23
+ segments.map((s) => [s.part, s]),
24
+ );
25
+
26
+ const tokens = format.match(/dd|mm|yyyy|hh|min|ss|./g) ?? [];
27
+
28
+ return tokens.map((token): DateSegment => {
29
+ switch (token) {
30
+ case 'dd':
31
+ return segmentMap.day ?? { part: 'day', value: '' };
32
+
33
+ case 'mm':
34
+ return segmentMap.month ?? { part: 'month', value: '' };
35
+
36
+ case 'yyyy':
37
+ return segmentMap.year ?? { part: 'year', value: '' };
38
+
39
+ case 'hh':
40
+ return segmentMap.hour ?? { part: 'hour', value: '' };
41
+
42
+ case 'min':
43
+ return segmentMap.minute ?? { part: 'minute', value: '' };
44
+
45
+ case 'ss':
46
+ return segmentMap.second ?? { part: 'second', value: '' };
47
+
48
+ default:
49
+ return {
50
+ part: 'literal',
51
+ value: token,
52
+ };
53
+ }
54
+ });
55
+ }
@@ -0,0 +1,35 @@
1
+ <script lang="ts">
2
+ import { DatePicker, type CalendarRootSnippetProps } from 'bits-ui';
3
+
4
+ let { months, weekdays }: CalendarRootSnippetProps = $props();
5
+ </script>
6
+
7
+ <div class="CalendarGrid">
8
+ {#each months as month (month.value)}
9
+ <DatePicker.Grid>
10
+ <DatePicker.GridHead>
11
+ <DatePicker.GridRow>
12
+ {#each weekdays as day (day)}
13
+ <DatePicker.HeadCell>
14
+ <div>{day.slice(0, 2)}</div>
15
+ </DatePicker.HeadCell>
16
+ {/each}
17
+ </DatePicker.GridRow>
18
+ </DatePicker.GridHead>
19
+ <DatePicker.GridBody>
20
+ {#each month.weeks as weekDates (weekDates)}
21
+ <DatePicker.GridRow>
22
+ {#each weekDates as date (date)}
23
+ <DatePicker.Cell {date} month={month.value}>
24
+ <DatePicker.Day>
25
+ <div></div>
26
+ {date.day}
27
+ </DatePicker.Day>
28
+ </DatePicker.Cell>
29
+ {/each}
30
+ </DatePicker.GridRow>
31
+ {/each}
32
+ </DatePicker.GridBody>
33
+ </DatePicker.Grid>
34
+ {/each}
35
+ </div>
@@ -0,0 +1,50 @@
1
+ <script lang="ts" module>
2
+ export type DatePickerPopupProps = DatePickerContentProps & Omit<CardProps, 'children' | 'ref'>;
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ import { DatePicker, type DatePickerContentProps } from 'bits-ui';
7
+ import Header from './Header.svelte';
8
+ import CalendarGrid from './CalendarGrid.svelte';
9
+ import { type CardProps, useThemeContext } from '@flightlesslabs/dodo-ui';
10
+ let {
11
+ roundness = 1,
12
+ outline = false,
13
+ class: className = '',
14
+ theme: cardTheme,
15
+ color = 'default',
16
+ variant = 'text',
17
+ shadow = 2,
18
+ active = false,
19
+ sideOffset = 10,
20
+ align = 'end',
21
+ ...restProps
22
+ }: DatePickerPopupProps = $props();
23
+
24
+ const themeContext = useThemeContext();
25
+ const theme = $derived(cardTheme ? cardTheme : themeContext.theme);
26
+
27
+ const popupClasses = $derived(
28
+ [
29
+ 'dodo-ui-Card',
30
+ 'dodo-ui-Calendar',
31
+ `color--${color}`,
32
+ `variant--${variant}`,
33
+ `roundness--${roundness}`,
34
+ `dodo-shadow-${shadow}`,
35
+ outline && 'outline',
36
+ active && 'active',
37
+ theme ? `dodo-theme--${theme}` : '',
38
+ className,
39
+ ].filter(Boolean),
40
+ );
41
+ </script>
42
+
43
+ <DatePicker.Content class={popupClasses.join(' ')} {...restProps} {sideOffset} {align}>
44
+ <DatePicker.Calendar>
45
+ {#snippet children(calendarRootSnippetProps)}
46
+ <Header />
47
+ <CalendarGrid {...calendarRootSnippetProps} />
48
+ {/snippet}
49
+ </DatePicker.Calendar>
50
+ </DatePicker.Content>
@@ -0,0 +1,25 @@
1
+ <script lang="ts">
2
+ import Icon from '@iconify/svelte';
3
+ import { DatePicker } from 'bits-ui';
4
+
5
+ const triggerClasses = $derived(
6
+ [
7
+ 'dodo-ui-Button',
8
+ 'compact',
9
+ 'size--normal',
10
+ 'variant--text',
11
+ 'color--primary',
12
+ 'roundness--1',
13
+ ].filter(Boolean),
14
+ );
15
+ </script>
16
+
17
+ <DatePicker.Header>
18
+ <DatePicker.PrevButton class={triggerClasses.join(' ')}>
19
+ <Icon icon="material-symbols:chevron-left-rounded" />
20
+ </DatePicker.PrevButton>
21
+ <DatePicker.Heading />
22
+ <DatePicker.NextButton class={triggerClasses.join(' ')}>
23
+ <Icon icon="material-symbols:chevron-right-rounded" />
24
+ </DatePicker.NextButton>
25
+ </DatePicker.Header>
@@ -0,0 +1,16 @@
1
+ /**
2
+ * DatePicker component and related prop types.
3
+ */
4
+ export {
5
+ default as DatePicker,
6
+ type DatePickerProps,
7
+ } from './components/DatePicker/DatePicker.svelte';
8
+
9
+ export { type DatePickerInputProps } from './components/DatePicker/DatePickerInput/DatePickerInput.svelte';
10
+
11
+ export { type DatePickerPopupProps } from './components/DatePicker/DatePickerPopup/DatePickerPopup.svelte';
12
+
13
+ /**
14
+ * Calendar component and related prop types.
15
+ */
16
+ export { default as Calendar, type CalendarProps } from './components/Calendar/Calendar.svelte';
@@ -0,0 +1,182 @@
1
+ type ControlType =
2
+ | 'object'
3
+ | 'boolean'
4
+ | 'check'
5
+ | 'inline-check'
6
+ | 'radio'
7
+ | 'inline-radio'
8
+ | 'select'
9
+ | 'multi-select'
10
+ | 'number'
11
+ | 'range'
12
+ | 'file'
13
+ | 'color'
14
+ | 'date'
15
+ | 'text';
16
+
17
+ interface ControlBase {
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ [key: string]: any;
20
+ /** @see https://storybook.js.org/docs/api/arg-types#controltype */
21
+ type?: ControlType;
22
+ disable?: boolean;
23
+ }
24
+
25
+ type Control =
26
+ | ControlType
27
+ | false
28
+ | (ControlBase &
29
+ (
30
+ | ControlBase
31
+ | {
32
+ type: 'color';
33
+ /** @see https://storybook.js.org/docs/api/arg-types#controlpresetcolors */
34
+ presetColors?: string[];
35
+ }
36
+ | {
37
+ type: 'file';
38
+ /** @see https://storybook.js.org/docs/api/arg-types#controlaccept */
39
+ accept?: string;
40
+ }
41
+ | {
42
+ type: 'inline-check' | 'radio' | 'inline-radio' | 'select' | 'multi-select';
43
+ /** @see https://storybook.js.org/docs/api/arg-types#controllabels */
44
+ labels?: {
45
+ [options: string]: string;
46
+ };
47
+ }
48
+ | {
49
+ type: 'number' | 'range';
50
+ /** @see https://storybook.js.org/docs/api/arg-types#controlmax */
51
+ max?: number;
52
+ /** @see https://storybook.js.org/docs/api/arg-types#controlmin */
53
+ min?: number;
54
+ /** @see https://storybook.js.org/docs/api/arg-types#controlstep */
55
+ step?: number;
56
+ }
57
+ ));
58
+
59
+ type ConditionalTest =
60
+ | {
61
+ truthy?: boolean;
62
+ }
63
+ | {
64
+ exists: boolean;
65
+ }
66
+ | {
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ eq: any;
69
+ }
70
+ | {
71
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
72
+ neq: any;
73
+ };
74
+ type ConditionalValue =
75
+ | {
76
+ arg: string;
77
+ }
78
+ | {
79
+ global: string;
80
+ };
81
+ type Conditional = ConditionalValue & ConditionalTest;
82
+
83
+ interface Args {
84
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
85
+ [name: string]: any;
86
+ }
87
+
88
+ interface SBBaseType {
89
+ required?: boolean;
90
+ raw?: string;
91
+ }
92
+ type SBScalarType = SBBaseType & {
93
+ name: 'boolean' | 'string' | 'number' | 'function' | 'symbol';
94
+ };
95
+ type SBArrayType = SBBaseType & {
96
+ name: 'array';
97
+ value: SBType;
98
+ };
99
+ type SBObjectType = SBBaseType & {
100
+ name: 'object';
101
+ value: Record<string, SBType>;
102
+ };
103
+ type SBEnumType = SBBaseType & {
104
+ name: 'enum';
105
+ value: (string | number)[];
106
+ };
107
+ type SBIntersectionType = SBBaseType & {
108
+ name: 'intersection';
109
+ value: SBType[];
110
+ };
111
+ type SBUnionType = SBBaseType & {
112
+ name: 'union';
113
+ value: SBType[];
114
+ };
115
+ type SBOtherType = SBBaseType & {
116
+ name: 'other';
117
+ value: string;
118
+ };
119
+
120
+ type SBType =
121
+ | SBScalarType
122
+ | SBEnumType
123
+ | SBArrayType
124
+ | SBObjectType
125
+ | SBIntersectionType
126
+ | SBUnionType
127
+ | SBOtherType;
128
+
129
+ interface InputType {
130
+ /** @see https://storybook.js.org/docs/api/arg-types#control */
131
+ control?: Control;
132
+ /** @see https://storybook.js.org/docs/api/arg-types#description */
133
+ description?: string;
134
+ /** @see https://storybook.js.org/docs/api/arg-types#if */
135
+ if?: Conditional;
136
+ /** @see https://storybook.js.org/docs/api/arg-types#mapping */
137
+ mapping?: {
138
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
139
+ [key: string]: any;
140
+ };
141
+ /** @see https://storybook.js.org/docs/api/arg-types#name */
142
+ name?: string;
143
+ /** @see https://storybook.js.org/docs/api/arg-types#options */
144
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
145
+ options?: readonly any[];
146
+ /** @see https://storybook.js.org/docs/api/arg-types#table */
147
+ table?: {
148
+ [key: string]: unknown;
149
+ /** @see https://storybook.js.org/docs/api/arg-types#tablecategory */
150
+ category?: string;
151
+ /** @see https://storybook.js.org/docs/api/arg-types#tabledefaultvalue */
152
+ defaultValue?: {
153
+ summary?: string;
154
+ detail?: string;
155
+ };
156
+ /** @see https://storybook.js.org/docs/api/arg-types#tabledisable */
157
+ disable?: boolean;
158
+ /** @see https://storybook.js.org/docs/api/arg-types#tablesubcategory */
159
+ subcategory?: string;
160
+ /** @see https://storybook.js.org/docs/api/arg-types#tabletype */
161
+ type?: {
162
+ summary?: string;
163
+ detail?: string;
164
+ };
165
+ };
166
+ /** @see https://storybook.js.org/docs/api/arg-types#type */
167
+ type?: SBType | SBScalarType['name'];
168
+ /**
169
+ * @deprecated Use `table.defaultValue.summary` instead.
170
+ * @see https://storybook.js.org/docs/api/arg-types#defaultvalue
171
+ */
172
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
173
+ defaultValue?: any;
174
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
175
+ [key: string]: any;
176
+ }
177
+
178
+ export type ArgTypes<TArgs = Args> = {
179
+ [name in keyof TArgs]: InputType;
180
+ };
181
+
182
+ export type StoryBookArgTypes = Partial<ArgTypes<Args>>;
@@ -0,0 +1,38 @@
1
+ @use 'sass:map';
2
+
3
+ $breakpoints-default: (
4
+ sm: 40rem,
5
+ md: 48rem,
6
+ lg: 64rem,
7
+ xl: 80rem,
8
+ xxl: 96rem,
9
+ );
10
+
11
+ $breakpoints: () !default;
12
+
13
+ $breakpoints: map.merge($breakpoints-default, $breakpoints);
14
+
15
+ @function breakpoint($key) {
16
+ @if not map.has-key($breakpoints, $key) {
17
+ @error "Invalid breakpoint: #{$key}";
18
+ }
19
+ @return map.get($breakpoints, $key);
20
+ }
21
+
22
+ @mixin up($key) {
23
+ @media (min-width: breakpoint($key)) {
24
+ @content;
25
+ }
26
+ }
27
+
28
+ @mixin down($key) {
29
+ @media (max-width: breakpoint($key)) {
30
+ @content;
31
+ }
32
+ }
33
+
34
+ @mixin between($min, $max) {
35
+ @media (min-width: breakpoint($min)) and (max-width: breakpoint($max)) {
36
+ @content;
37
+ }
38
+ }
@@ -0,0 +1,2 @@
1
+ @use '../components/Calendar/Calendar.scss';
2
+ @use '../components/DatePicker/DatePicker.scss';
@@ -0,0 +1,13 @@
1
+ import '@testing-library/jest-dom/vitest';
2
+ import { vi } from 'vitest';
3
+
4
+ // mock matchMedia globally
5
+ Object.defineProperty(window, 'matchMedia', {
6
+ writable: true,
7
+ value: vi.fn().mockImplementation((query: string) => ({
8
+ matches: false,
9
+ media: query,
10
+ addEventListener: vi.fn(),
11
+ removeEventListener: vi.fn(),
12
+ })),
13
+ });