@postxl/generators 1.1.1 → 1.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 (161) hide show
  1. package/dist/frontend-core/frontend.generator.d.ts +0 -58
  2. package/dist/frontend-core/frontend.generator.js +6 -172
  3. package/dist/frontend-core/frontend.generator.js.map +1 -1
  4. package/dist/frontend-core/template/README.md +1 -1
  5. package/dist/frontend-core/template/src/components/admin/table-filter.tsx +1 -5
  6. package/dist/frontend-core/template/src/components/ui/color-mode-toggle/color-mode-toggle.tsx +10 -4
  7. package/dist/frontend-core/template/src/pages/dashboard/dashboard.page.tsx +2 -3
  8. package/dist/frontend-core/template/src/pages/error/default-error.page.tsx +1 -1
  9. package/dist/frontend-core/template/src/pages/error/not-found-error.page.tsx +1 -1
  10. package/dist/frontend-core/template/src/styles/styles.css +13 -1
  11. package/dist/frontend-core/template/tsconfig.json +2 -0
  12. package/dist/frontend-core/types/component.d.ts +1 -1
  13. package/dist/frontend-forms/generators/discriminated-union/fields.generator.js +4 -6
  14. package/dist/frontend-forms/generators/discriminated-union/fields.generator.js.map +1 -1
  15. package/dist/frontend-forms/generators/discriminated-union/inputs.generator.js +1 -1
  16. package/dist/frontend-forms/generators/discriminated-union/inputs.generator.js.map +1 -1
  17. package/dist/frontend-forms/generators/enum/inputs.generator.js +1 -1
  18. package/dist/frontend-forms/generators/enum/inputs.generator.js.map +1 -1
  19. package/dist/frontend-forms/generators/model/forms.generator.js +8 -12
  20. package/dist/frontend-forms/generators/model/forms.generator.js.map +1 -1
  21. package/dist/frontend-forms/generators/model/inputs.generator.js +2 -6
  22. package/dist/frontend-forms/generators/model/inputs.generator.js.map +1 -1
  23. package/dist/frontend-forms/template/src/components/ui/field/field.tsx +1 -4
  24. package/dist/frontend-tables/generators/model-table.generator.js +1 -5
  25. package/dist/frontend-tables/generators/model-table.generator.js.map +1 -1
  26. package/package.json +3 -2
  27. package/dist/frontend-core/template/src/components/ui/accordion/accordion.stories.tsx +0 -47
  28. package/dist/frontend-core/template/src/components/ui/accordion/accordion.tsx +0 -52
  29. package/dist/frontend-core/template/src/components/ui/admin-sidebar/admin-sidebar.tsx +0 -195
  30. package/dist/frontend-core/template/src/components/ui/alert/alert.stories.tsx +0 -61
  31. package/dist/frontend-core/template/src/components/ui/alert/alert.tsx +0 -45
  32. package/dist/frontend-core/template/src/components/ui/alert-dialog/alert-dialog.stories.tsx +0 -52
  33. package/dist/frontend-core/template/src/components/ui/alert-dialog/alert-dialog.tsx +0 -105
  34. package/dist/frontend-core/template/src/components/ui/avatar/avatar.stories.tsx +0 -30
  35. package/dist/frontend-core/template/src/components/ui/avatar/avatar.tsx +0 -39
  36. package/dist/frontend-core/template/src/components/ui/badge/badge.stories.tsx +0 -78
  37. package/dist/frontend-core/template/src/components/ui/badge/badge.tsx +0 -48
  38. package/dist/frontend-core/template/src/components/ui/breadcrumb/breadcrumb.stories.tsx +0 -67
  39. package/dist/frontend-core/template/src/components/ui/breadcrumb/breadcrumb.tsx +0 -85
  40. package/dist/frontend-core/template/src/components/ui/button/button.stories.tsx +0 -150
  41. package/dist/frontend-core/template/src/components/ui/button/button.tsx +0 -68
  42. package/dist/frontend-core/template/src/components/ui/calendar/calendar.stories.tsx +0 -160
  43. package/dist/frontend-core/template/src/components/ui/calendar/calendar.tsx +0 -293
  44. package/dist/frontend-core/template/src/components/ui/card/card.stories.tsx +0 -77
  45. package/dist/frontend-core/template/src/components/ui/card/card.tsx +0 -45
  46. package/dist/frontend-core/template/src/components/ui/card-hover/card-hover.stories.tsx +0 -29
  47. package/dist/frontend-core/template/src/components/ui/card-hover/card-hover.tsx +0 -28
  48. package/dist/frontend-core/template/src/components/ui/carousel/carousel.stories.tsx +0 -154
  49. package/dist/frontend-core/template/src/components/ui/carousel/carousel.tsx +0 -227
  50. package/dist/frontend-core/template/src/components/ui/checkbox/checkbox.stories.tsx +0 -106
  51. package/dist/frontend-core/template/src/components/ui/checkbox/checkbox.tsx +0 -88
  52. package/dist/frontend-core/template/src/components/ui/checkbox/shadcn-checkbox.stories.tsx +0 -90
  53. package/dist/frontend-core/template/src/components/ui/checkbox/shadcn-checkbox.tsx +0 -54
  54. package/dist/frontend-core/template/src/components/ui/collapse/collapse.stories.tsx +0 -52
  55. package/dist/frontend-core/template/src/components/ui/collapse/collapse.tsx +0 -9
  56. package/dist/frontend-core/template/src/components/ui/combobox/combobox.stories.tsx +0 -207
  57. package/dist/frontend-core/template/src/components/ui/combobox/combobox.tsx +0 -79
  58. package/dist/frontend-core/template/src/components/ui/command/command.stories.tsx +0 -186
  59. package/dist/frontend-core/template/src/components/ui/command/command.tsx +0 -165
  60. package/dist/frontend-core/template/src/components/ui/command-palette/command-palette.stories.tsx +0 -160
  61. package/dist/frontend-core/template/src/components/ui/command-palette/command-palette.tsx +0 -134
  62. package/dist/frontend-core/template/src/components/ui/content-frame/content-frame.stories.tsx +0 -198
  63. package/dist/frontend-core/template/src/components/ui/content-frame/content-frame.tsx +0 -100
  64. package/dist/frontend-core/template/src/components/ui/context-menu/context-menu.stories.tsx +0 -78
  65. package/dist/frontend-core/template/src/components/ui/context-menu/context-menu.tsx +0 -179
  66. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/cell-variant-types.ts +0 -11
  67. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/checkbox-cell.tsx +0 -116
  68. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/date-cell.tsx +0 -157
  69. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/gantt-cell.tsx +0 -82
  70. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/long-text-cell.tsx +0 -180
  71. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/multi-select-cell.tsx +0 -280
  72. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/number-cell.tsx +0 -169
  73. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/react-node-cell.tsx +0 -33
  74. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/select-cell.tsx +0 -175
  75. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/short-text-cell.tsx +0 -138
  76. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/utils/gantt-timeline.tsx +0 -92
  77. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/utils/gantt-timerange-picker.tsx +0 -330
  78. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-cell-wrapper.tsx +0 -212
  79. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-cell.tsx +0 -157
  80. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-column-header.tsx +0 -340
  81. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-context-menu.tsx +0 -271
  82. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-row.tsx +0 -123
  83. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-search.tsx +0 -211
  84. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-types.ts +0 -159
  85. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-utils.ts +0 -67
  86. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-view-menu.tsx +0 -360
  87. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid.stories.tsx +0 -780
  88. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid.tsx +0 -217
  89. package/dist/frontend-core/template/src/components/ui/data-grid/hooks/use-callback-ref.ts +0 -22
  90. package/dist/frontend-core/template/src/components/ui/data-grid/hooks/use-data-grid.tsx +0 -1892
  91. package/dist/frontend-core/template/src/components/ui/data-grid/hooks/use-debounced-callback.ts +0 -19
  92. package/dist/frontend-core/template/src/components/ui/data-grid/styles.css +0 -3
  93. package/dist/frontend-core/template/src/components/ui/data-table/context-menu-simple.tsx +0 -141
  94. package/dist/frontend-core/template/src/components/ui/data-table/data-table.stories.tsx +0 -146
  95. package/dist/frontend-core/template/src/components/ui/data-table/data-table.tsx +0 -447
  96. package/dist/frontend-core/template/src/components/ui/data-table/renderers/country-array-cell-renderer.tsx +0 -77
  97. package/dist/frontend-core/template/src/components/ui/data-table/renderers/country-cell-renderer.tsx +0 -56
  98. package/dist/frontend-core/template/src/components/ui/data-table/renderers/favorite-cell-renderer.tsx +0 -68
  99. package/dist/frontend-core/template/src/components/ui/data-table/renderers/links-cell-renderer.tsx +0 -205
  100. package/dist/frontend-core/template/src/components/ui/data-table/utils/columns.ts +0 -351
  101. package/dist/frontend-core/template/src/components/ui/data-table/utils/data-table.utils.ts +0 -49
  102. package/dist/frontend-core/template/src/components/ui/date-picker/date-picker.stories.tsx +0 -149
  103. package/dist/frontend-core/template/src/components/ui/date-picker/date-picker.tsx +0 -30
  104. package/dist/frontend-core/template/src/components/ui/dialog/dialog.stories.tsx +0 -80
  105. package/dist/frontend-core/template/src/components/ui/dialog/dialog.tsx +0 -134
  106. package/dist/frontend-core/template/src/components/ui/drawer/drawer.stories.tsx +0 -104
  107. package/dist/frontend-core/template/src/components/ui/drawer/drawer.tsx +0 -87
  108. package/dist/frontend-core/template/src/components/ui/dropdown-menu/dropdown-menu.stories.tsx +0 -168
  109. package/dist/frontend-core/template/src/components/ui/dropdown-menu/dropdown-menu.tsx +0 -225
  110. package/dist/frontend-core/template/src/components/ui/input/input.stories.tsx +0 -141
  111. package/dist/frontend-core/template/src/components/ui/input/input.tsx +0 -47
  112. package/dist/frontend-core/template/src/components/ui/label/label.stories.tsx +0 -41
  113. package/dist/frontend-core/template/src/components/ui/label/label.tsx +0 -20
  114. package/dist/frontend-core/template/src/components/ui/loader/loader.stories.tsx +0 -45
  115. package/dist/frontend-core/template/src/components/ui/loader/loader.tsx +0 -17
  116. package/dist/frontend-core/template/src/components/ui/mark-value-renderer/mark-value-renderer.stories.tsx +0 -114
  117. package/dist/frontend-core/template/src/components/ui/mark-value-renderer/mark-value-renderer.tsx +0 -48
  118. package/dist/frontend-core/template/src/components/ui/menubar/menu.stories.tsx +0 -134
  119. package/dist/frontend-core/template/src/components/ui/menubar/menubar.tsx +0 -208
  120. package/dist/frontend-core/template/src/components/ui/modal/modal.stories.tsx +0 -297
  121. package/dist/frontend-core/template/src/components/ui/modal/modal.tsx +0 -80
  122. package/dist/frontend-core/template/src/components/ui/navigation-menu/navigation-menu.stories.tsx +0 -213
  123. package/dist/frontend-core/template/src/components/ui/navigation-menu/navigation-menu.tsx +0 -142
  124. package/dist/frontend-core/template/src/components/ui/pagination/pagination.stories.tsx +0 -49
  125. package/dist/frontend-core/template/src/components/ui/pagination/pagination.tsx +0 -84
  126. package/dist/frontend-core/template/src/components/ui/popover/popover.stories.tsx +0 -82
  127. package/dist/frontend-core/template/src/components/ui/popover/popover.tsx +0 -55
  128. package/dist/frontend-core/template/src/components/ui/progress/progress.stories.tsx +0 -80
  129. package/dist/frontend-core/template/src/components/ui/progress/progress.tsx +0 -17
  130. package/dist/frontend-core/template/src/components/ui/radio-group/radio-group.stories.tsx +0 -154
  131. package/dist/frontend-core/template/src/components/ui/radio-group/radio-group.tsx +0 -68
  132. package/dist/frontend-core/template/src/components/ui/resizable/resizable.stories.tsx +0 -73
  133. package/dist/frontend-core/template/src/components/ui/resizable/resizeable.tsx +0 -38
  134. package/dist/frontend-core/template/src/components/ui/scroll-area/scroll-area.stories.tsx +0 -55
  135. package/dist/frontend-core/template/src/components/ui/scroll-area/scroll-area.tsx +0 -39
  136. package/dist/frontend-core/template/src/components/ui/select/select.stories.tsx +0 -297
  137. package/dist/frontend-core/template/src/components/ui/select/select.tsx +0 -227
  138. package/dist/frontend-core/template/src/components/ui/separator/separator.tsx +0 -21
  139. package/dist/frontend-core/template/src/components/ui/separator/seperator.stories.tsx +0 -25
  140. package/dist/frontend-core/template/src/components/ui/sheet/sheet.stories.tsx +0 -45
  141. package/dist/frontend-core/template/src/components/ui/sheet/sheet.tsx +0 -107
  142. package/dist/frontend-core/template/src/components/ui/skeleton/skeleton.stories.tsx +0 -26
  143. package/dist/frontend-core/template/src/components/ui/skeleton/skeleton.tsx +0 -7
  144. package/dist/frontend-core/template/src/components/ui/slider/slider.stories.tsx +0 -101
  145. package/dist/frontend-core/template/src/components/ui/slider/slider.tsx +0 -98
  146. package/dist/frontend-core/template/src/components/ui/spinner/spinner.stories.tsx +0 -19
  147. package/dist/frontend-core/template/src/components/ui/spinner/spinner.tsx +0 -21
  148. package/dist/frontend-core/template/src/components/ui/switch/switch.stories.tsx +0 -33
  149. package/dist/frontend-core/template/src/components/ui/switch/switch.tsx +0 -28
  150. package/dist/frontend-core/template/src/components/ui/tabs/tabs.stories.tsx +0 -215
  151. package/dist/frontend-core/template/src/components/ui/tabs/tabs.tsx +0 -70
  152. package/dist/frontend-core/template/src/components/ui/textarea/textarea.stories.tsx +0 -138
  153. package/dist/frontend-core/template/src/components/ui/textarea/textarea.tsx +0 -40
  154. package/dist/frontend-core/template/src/components/ui/toast/toast.mdx +0 -31
  155. package/dist/frontend-core/template/src/components/ui/toast/toast.stories.tsx +0 -89
  156. package/dist/frontend-core/template/src/components/ui/toggle/toggle.stories.tsx +0 -65
  157. package/dist/frontend-core/template/src/components/ui/toggle/toggle.tsx +0 -38
  158. package/dist/frontend-core/template/src/components/ui/toggle-group/toggle-group.stories.tsx +0 -85
  159. package/dist/frontend-core/template/src/components/ui/toggle-group/toggle-group.tsx +0 -54
  160. package/dist/frontend-core/template/src/components/ui/tooltip/tooltip.stories.tsx +0 -29
  161. package/dist/frontend-core/template/src/components/ui/tooltip/tooltip.tsx +0 -29
@@ -1,150 +0,0 @@
1
- import type { Meta, StoryObj } from '@storybook/react-vite'
2
-
3
- import { Button } from './button'
4
- import { expect, userEvent, screen } from 'storybook/test'
5
-
6
- const meta = {
7
- title: 'Button',
8
- component: Button,
9
- tags: ['autodocs'],
10
- parameters: {
11
- layout: 'centered',
12
- },
13
- argTypes: {
14
- variant: {
15
- control: 'select',
16
- options: [
17
- 'default',
18
- 'secondary',
19
- 'destructive',
20
- 'outline',
21
- 'ghost',
22
- 'link',
23
- 'outlineInvert',
24
- 'extraGhost',
25
- 'extraGhost2',
26
- 'neutral',
27
- ],
28
- },
29
- size: {
30
- control: 'select',
31
- options: ['default', 'xs', 'sm', 'lg', 'xs2', 'icon', 'iconSm', 'smLow', 'max'],
32
- },
33
- },
34
- } satisfies Meta<typeof Button>
35
- export default meta
36
-
37
- type Story = StoryObj<typeof meta>
38
-
39
- export const Default: Story = {
40
- args: { variant: 'default', size: 'default' },
41
- render: (args) => (
42
- <Button {...args} onClick={() => window.alert('click event fired')} data-testid="default-button">
43
- Button
44
- </Button>
45
- ),
46
- play: async () => {
47
- // Use a simple global spy for window.alert
48
- const originalAlert = window.alert
49
- let alertCalled = false
50
- window.alert = (msg) => {
51
- if (msg === 'click event fired') alertCalled = true
52
- }
53
- const button = screen.getByTestId('default-button')
54
- await userEvent.click(button)
55
- expect(alertCalled).toBe(true)
56
- window.alert = originalAlert
57
- },
58
- }
59
-
60
- export const Variants: Story = {
61
- render: () => (
62
- <div className="flex flex-wrap gap-2">
63
- <Button variant="default">Default</Button>
64
- <Button variant="secondary">Secondary</Button>
65
- <Button variant="destructive">Destructive</Button>
66
- <Button variant="outline">Outline</Button>
67
- <Button variant="ghost">Ghost</Button>
68
- <Button variant="link">Link</Button>
69
- <Button variant="outlineInvert">Outline Invert</Button>
70
- <Button variant="extraGhost">Extra Ghost</Button>
71
- <Button variant="extraGhost2">Extra Ghost 2</Button>
72
- <Button variant="neutral">Neutral</Button>
73
- </div>
74
- ),
75
- }
76
-
77
- export const Sizes: Story = {
78
- render: () => (
79
- <div className="flex flex-wrap gap-2 items-center">
80
- <Button size="xs">Extra Small</Button>
81
- <Button size="sm">Small</Button>
82
- <Button size="default">Default</Button>
83
- <Button size="lg">Large</Button>
84
- <Button size="xs2">Extra Small 2</Button>
85
- <Button size="icon">Icon</Button>
86
- <Button size="iconSm">Icon Small</Button>
87
- <Button size="smLow">Small Low</Button>
88
- <Button size="max">Max</Button>
89
- </div>
90
- ),
91
- }
92
-
93
- export const WithIcon: Story = {
94
- render: () => (
95
- <Button>
96
- <svg
97
- xmlns="http://www.w3.org/2000/svg"
98
- width="24"
99
- height="24"
100
- viewBox="0 0 24 24"
101
- fill="none"
102
- stroke="currentColor"
103
- strokeWidth="2"
104
- strokeLinecap="round"
105
- strokeLinejoin="round"
106
- >
107
- <circle cx="12" cy="12" r="10" />
108
- <path d="m15 9-6 6" />
109
- <path d="m9 9 6 6" />
110
- </svg>
111
- With Icon
112
- </Button>
113
- ),
114
- }
115
-
116
- export const AsChild: Story = {
117
- render: () => (
118
- <Button asChild>
119
- <a href="https://example.com" target="_blank" rel="noreferrer">
120
- Link Button
121
- </a>
122
- </Button>
123
- ),
124
- }
125
-
126
- export const Disabled: Story = {
127
- args: {
128
- children: 'Disabled',
129
- disabled: true,
130
- },
131
- render: (args) => (
132
- <Button {...args} onClick={() => window.alert('click event fired')} data-testid="disabled-button" />
133
- ),
134
- play: async () => {
135
- // Use a simple global spy for window.alert
136
- const originalAlert = window.alert
137
- let alertCalled = false
138
- window.alert = (msg) => {
139
- if (msg === 'click event fired') alertCalled = true
140
- }
141
- const disabledButton = screen.getByTestId('disabled-button')
142
-
143
- await expect(disabledButton).toHaveAttribute('disabled')
144
- await expect(disabledButton).toHaveStyle('pointer-events: none')
145
-
146
- await userEvent.click(disabledButton, { pointerEventsCheck: 0 })
147
- expect(alertCalled).toBe(false)
148
- window.alert = originalAlert
149
- },
150
- }
@@ -1,68 +0,0 @@
1
- import { Slot } from '@radix-ui/react-slot'
2
-
3
- import { cva, type VariantProps } from 'class-variance-authority'
4
- import * as React from 'react'
5
-
6
- import { cn } from '@lib/utils'
7
-
8
- const buttonVariants = cva(
9
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md cursor-pointer text-base font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
10
- {
11
- variants: {
12
- variant: {
13
- default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
14
- destructive:
15
- 'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
16
- outline:
17
- 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
18
- secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
19
- ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
20
- link: 'text-primary underline-offset-4 hover:underline',
21
- outlineInvert:
22
- 'border border-muted-foreground bg-background shadow-xs text-muted-foreground hover:bg-muted-foreground hover:text-background dark:bg-input/30 dark:border-input dark:hover:bg-input/70 dark:hover:text-foreground/80',
23
- extraGhost: 'bg-none hover:text-accent-foreground text-muted-foreground hover:text-foreground',
24
- extraGhost2: 'bg-none text-foreground hover:text-muted-foreground',
25
- neutral: 'bg-background',
26
- },
27
- size: {
28
- default: 'h-9 px-4 py-2 has-[>svg]:px-3',
29
- sm: 'h-8 gap-1.5 px-3 has-[>svg]:px-2.5',
30
- lg: 'h-10 px-6 has-[>svg]:px-4',
31
- xs: 'py-1 px-2',
32
- xs2: 'px-2 py-0.5',
33
- icon: 'size-9',
34
- iconSm: 'size-7.5',
35
- smLow: 'h-7.5 gap-1.5 px-3 has-[>svg]:px-2.5',
36
- max: 'h-full w-full',
37
- },
38
- },
39
- defaultVariants: {
40
- variant: 'default',
41
- size: 'default',
42
- },
43
- },
44
- )
45
-
46
- export type ButtonProps = React.ComponentProps<'button'> &
47
- VariantProps<typeof buttonVariants> & {
48
- asChild?: boolean
49
- /**
50
- * E2E test_id to identify the button.
51
- */
52
- __e2e_test_id__?: string
53
- }
54
-
55
- function Button({ className, variant, size, asChild = false, __e2e_test_id__, ...props }: ButtonProps) {
56
- const Comp = asChild ? Slot : 'button'
57
-
58
- return (
59
- <Comp
60
- data-slot="button"
61
- {...props}
62
- data-test-id={__e2e_test_id__}
63
- className={cn(buttonVariants({ variant, size, className }))}
64
- />
65
- )
66
- }
67
-
68
- export { Button, buttonVariants }
@@ -1,160 +0,0 @@
1
- import type { Meta, StoryObj } from '@storybook/react-vite'
2
-
3
- import { Calendar } from './calendar'
4
- import { expect, userEvent, screen } from 'storybook/test'
5
- import { useState } from 'react'
6
-
7
- const meta = {
8
- title: 'Calendar',
9
- component: Calendar,
10
- tags: ['autodocs'],
11
- parameters: {
12
- layout: 'centered',
13
- },
14
- argTypes: {
15
- buttonVariant: {
16
- control: 'select',
17
- options: [
18
- undefined,
19
- 'default',
20
- 'secondary',
21
- 'destructive',
22
- 'outline',
23
- 'ghost',
24
- 'link',
25
- 'outlineInvert',
26
- 'extraGhost',
27
- 'extraGhost2',
28
- 'neutral',
29
- ],
30
- },
31
- showYearNavigation: {
32
- control: 'boolean',
33
- },
34
- mode: {
35
- control: 'select',
36
- options: ['single', 'multiple', 'range'],
37
- },
38
- showOutsideDays: {
39
- control: 'boolean',
40
- defaultValue: true,
41
- },
42
- captionLayout: {
43
- control: 'select',
44
- options: ['label', 'dropdown', 'dropdown-months', 'dropdown-years'],
45
- defaultValue: 'label',
46
- },
47
- formatters: {
48
- control: 'object',
49
- },
50
- components: {
51
- control: 'object',
52
- },
53
- disabled: {
54
- control: 'object',
55
- },
56
- fromYear: {
57
- control: 'number',
58
- },
59
- toYear: {
60
- control: 'number',
61
- },
62
- },
63
- } satisfies Meta<typeof Calendar>
64
- export default meta
65
-
66
- type Story = StoryObj<typeof meta>
67
-
68
- type DateRange = {
69
- from?: Date
70
- to?: Date
71
- }
72
-
73
- export const Default: Story = {
74
- args: {
75
- mode: 'single',
76
- showOutsideDays: true,
77
- captionLayout: 'label',
78
- formatters: {
79
- // used by this Calendar (and merged with the built-in formatMonthDropdown)
80
- formatMonthDropdown: (date: Date) => date.toLocaleString('en-US', { month: 'long' }), // "January", "February", ...
81
-
82
- // typical DayPicker formatter names you can override:
83
- formatWeekdayName: (date: Date) => date.toLocaleDateString('en-US', { weekday: 'short' }), // "Mon", "Tue", ...
84
-
85
- formatDay: (date: Date) => date.toLocaleDateString('en-US', { day: 'numeric' }), // "01"
86
- },
87
- },
88
- render: (args) => <Calendar {...args} data-testid="default-calendar" />,
89
- play: async () => {
90
- const calendar = screen.getByTestId('default-calendar')
91
- const dayButton = calendar.querySelector('button[aria-label*="13"]') as HTMLElement
92
- await userEvent.click(dayButton)
93
- expect(dayButton).toHaveAttribute('data-selected-single', 'true')
94
- },
95
- }
96
-
97
- export const Multiple: Story = {
98
- args: {
99
- mode: 'multiple',
100
- },
101
- render: (args) => <Calendar {...args} data-testid="default-calendar" />,
102
- }
103
-
104
- function RangeCalendarStory(args: any) {
105
- const [range, setRange] = useState<DateRange | undefined>(undefined)
106
- return (
107
- <Calendar
108
- mode="range"
109
- buttonVariant={args.buttonVariant}
110
- data-testid="default-calendar"
111
- selected={range?.from && range?.to ? { from: range.from, to: range.to } : undefined}
112
- onSelect={(value: { from?: Date; to?: Date } | undefined) => {
113
- if (value?.from == null) {
114
- setRange(undefined)
115
- } else {
116
- const newRange = { from: value.from, to: value.to ?? value.from }
117
- setRange(newRange)
118
- }
119
- }}
120
- />
121
- )
122
- }
123
- export const RangeWithAlternatingSelection: Story = {
124
- render: (args) => <RangeCalendarStory {...args} />,
125
- }
126
-
127
- export const WithDisabledRange: Story = {
128
- render: () => {
129
- return (
130
- <Calendar
131
- disabled={(date) => date > new Date() || date < new Date(new Date().setMonth(new Date().getMonth() - 3))}
132
- data-testid="default-calendar"
133
- />
134
- )
135
- },
136
- play: async () => {
137
- const calendar = screen.getByTestId('default-calendar')
138
- const today = new Date()
139
- const future = new Date(today)
140
- future.setDate(today.getDate() + 2)
141
- const futureDate = future.toLocaleDateString()
142
- // Use data-day attribute to find the specific button for the future date
143
- // This is more reliable than using aria-label with partial string matching
144
- const disabledDayButton = calendar.querySelector(`button[data-day="${futureDate}"]`) as HTMLElement
145
-
146
- // Previous approach had a bug: aria-label*="${dayNumber}" would match any date containing that number
147
- // For example, dayNumber=25 would incorrectly match "September 28th 2025" because it contains "25"
148
- // const dayNumber = future.getDate()
149
- // const disabledDayButton = calendar.querySelector(`button[aria-label*="${dayNumber}"]`) as HTMLElement
150
- await expect(disabledDayButton).toHaveAttribute('disabled')
151
- await userEvent.click(disabledDayButton, { pointerEventsCheck: 0 })
152
- await expect(disabledDayButton).toHaveAttribute('disabled') // still disabled after click
153
- },
154
- }
155
-
156
- export const WithDropdowns: Story = {
157
- render: () => {
158
- return <Calendar data-testid="default-calendar" captionLayout="dropdown" />
159
- },
160
- }
@@ -1,293 +0,0 @@
1
- import {
2
- ChevronDownIcon,
3
- ChevronLeftIcon,
4
- ChevronRightIcon,
5
- DoubleArrowLeftIcon,
6
- DoubleArrowRightIcon,
7
- } from '@radix-ui/react-icons'
8
-
9
- import * as React from 'react'
10
- import { DayButton, DayPicker, getDefaultClassNames } from 'react-day-picker'
11
-
12
- import { Button, buttonVariants } from '@components/ui/button/button'
13
- import { cn } from '@lib/utils'
14
-
15
- function Calendar({
16
- className,
17
- classNames,
18
- showOutsideDays = true,
19
- captionLayout = 'label',
20
- buttonVariant = 'ghost',
21
- showYearNavigation = false,
22
- formatters,
23
- components,
24
- ...props
25
- }: React.ComponentProps<typeof DayPicker> & {
26
- buttonVariant?: React.ComponentProps<typeof Button>['variant']
27
- showYearNavigation?: boolean
28
- }) {
29
- const defaultClassNames = getDefaultClassNames()
30
-
31
- // Track last selected field for range mode
32
- const [rangeSelectionStep, setRangeSelectionStep] = React.useState<'from' | 'to'>('from')
33
- // Custom onDayClick for range mode to make sure 'from' and 'to' are always set alternatingly
34
- const handleDayClick = React.useCallback(
35
- (day: Date, modifiers: any, e: React.MouseEvent<Element>) => {
36
- if (props.mode === 'range') {
37
- const range = props.selected as { from?: Date; to?: Date } | undefined
38
-
39
- // Always start a new range when selecting 'from'
40
- if (rangeSelectionStep === 'from') {
41
- props.onSelect?.({ from: day, to: undefined }, day, modifiers, e)
42
- setRangeSelectionStep('to')
43
- return
44
- }
45
-
46
- // Only set 'to' if 'from' is set and not the same day
47
- if (rangeSelectionStep === 'to') {
48
- if (range?.from && day.getTime() !== range.from.getTime()) {
49
- props.onSelect?.({ from: range.from, to: day }, day, modifiers, e)
50
- setRangeSelectionStep('from')
51
- }
52
- return
53
- }
54
- } else {
55
- props.onDayClick?.(day, modifiers, e)
56
- }
57
- },
58
- [props, rangeSelectionStep],
59
- )
60
-
61
- // default year range if caption layout has year dropdown: 100 years past and 100 years future unless user overrides
62
- // with the dropdown a range is required. if we don't set it manually it will be -100 years until now which is impractical for future events
63
- let fromMonth: Date | undefined
64
- let toMonth: Date | undefined
65
- if (captionLayout === 'dropdown-years' || captionLayout === 'dropdown') {
66
- const nowYear = new Date().getFullYear()
67
- fromMonth = props.startMonth ?? new Date(nowYear - 100, 0)
68
- toMonth = props.endMonth ?? new Date(nowYear + 100, 11)
69
- }
70
-
71
- // controlled month state so we can programmatically change months (and years)
72
- const [currentMonth, setCurrentMonth] = React.useState<Date>(props.defaultMonth ?? props.month ?? new Date())
73
- React.useEffect(() => {
74
- if (props.month) {
75
- setCurrentMonth(props.month)
76
- }
77
- }, [props.month])
78
-
79
- const addMonths = (date: Date, months: number) => {
80
- const d = new Date(date)
81
- d.setMonth(d.getMonth() + months)
82
- if (fromMonth && d < fromMonth) {
83
- return fromMonth
84
- }
85
- if (toMonth && d > toMonth) {
86
- return toMonth
87
- }
88
- return d
89
- }
90
-
91
- return (
92
- <DayPicker
93
- month={currentMonth}
94
- onMonthChange={(m) => {
95
- setCurrentMonth(m)
96
- props.onMonthChange?.(m)
97
- }}
98
- showOutsideDays={showOutsideDays}
99
- className={cn(
100
- 'bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent',
101
- String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
102
- String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
103
- className,
104
- )}
105
- captionLayout={captionLayout}
106
- formatters={{
107
- formatMonthDropdown: (date) => date.toLocaleString('default', { month: 'short' }),
108
- ...formatters,
109
- }}
110
- classNames={{
111
- root: cn('w-fit', defaultClassNames.root),
112
- months: cn('flex gap-4 flex-col md:flex-row relative', defaultClassNames.months),
113
- month: cn('flex flex-col w-full gap-4', defaultClassNames.month),
114
- nav: cn('flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between', defaultClassNames.nav),
115
- button_previous: cn(
116
- buttonVariants({ variant: buttonVariant }),
117
- 'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none',
118
- defaultClassNames.button_previous,
119
- ),
120
- button_next: cn(
121
- buttonVariants({ variant: buttonVariant }),
122
- 'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none',
123
- defaultClassNames.button_next,
124
- ),
125
- month_caption: cn(
126
- 'flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)',
127
- defaultClassNames.month_caption,
128
- ),
129
- dropdowns: cn(
130
- 'w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5',
131
- defaultClassNames.dropdowns,
132
- ),
133
- dropdown_root: cn(
134
- 'relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md',
135
- defaultClassNames.dropdown_root,
136
- ),
137
- dropdown: cn('absolute bg-popover inset-0 opacity-0', defaultClassNames.dropdown),
138
- caption_label: cn(
139
- 'select-none font-medium',
140
- captionLayout === 'label'
141
- ? 'text-sm'
142
- : 'rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5',
143
- defaultClassNames.caption_label,
144
- ),
145
- table: 'w-full border-collapse',
146
- weekdays: cn('flex', defaultClassNames.weekdays),
147
- weekday: cn(
148
- 'text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none',
149
- defaultClassNames.weekday,
150
- ),
151
- week: cn('flex w-full mt-2', defaultClassNames.week),
152
- week_number_header: cn('select-none w-(--cell-size)', defaultClassNames.week_number_header),
153
- week_number: cn('text-[0.8rem] select-none text-muted-foreground', defaultClassNames.week_number),
154
- day: cn(
155
- 'relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none',
156
- defaultClassNames.day,
157
- ),
158
- range_start: cn('rounded-l-md bg-accent', defaultClassNames.range_start),
159
- range_middle: cn('rounded-none', defaultClassNames.range_middle),
160
- range_end: cn('rounded-r-md bg-accent', defaultClassNames.range_end),
161
- today: cn(
162
- 'bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none',
163
- defaultClassNames.today,
164
- ),
165
- outside: cn('text-muted-foreground aria-selected:text-muted-foreground', defaultClassNames.outside),
166
- disabled: cn('text-muted-foreground opacity-50', defaultClassNames.disabled),
167
- hidden: cn('invisible', defaultClassNames.hidden),
168
- ...classNames,
169
- }}
170
- // prefer startMonth/toMonth instead of deprecated fromYear/toYear
171
- startMonth={fromMonth}
172
- endMonth={toMonth}
173
- components={{
174
- // custom navigation: to add double-chevron year skip buttons
175
- Nav: ({ className }) => (
176
- <div className={cn('mt-0.5', className)}>
177
- <div className="flex items-center">
178
- {showYearNavigation && (
179
- <Button
180
- aria-label="previous year"
181
- variant={buttonVariant}
182
- size="iconSm"
183
- disabled={fromMonth && currentMonth <= fromMonth}
184
- className={captionLayout === 'dropdown' ? 'size-6.5' : ''}
185
- onClick={() => setCurrentMonth((m) => addMonths(m ?? new Date(), -12))}
186
- >
187
- <DoubleArrowLeftIcon className="" />
188
- </Button>
189
- )}
190
- <Button
191
- aria-label="previous month"
192
- variant={buttonVariant}
193
- size="iconSm"
194
- disabled={fromMonth && currentMonth <= fromMonth}
195
- className={captionLayout === 'dropdown' ? 'size-6.5' : ''}
196
- onClick={() => setCurrentMonth((m) => addMonths(m ?? new Date(), -1))}
197
- >
198
- <ChevronLeftIcon />
199
- </Button>
200
- </div>
201
-
202
- <div className="flex items-center">
203
- <Button
204
- aria-label="next month"
205
- variant={buttonVariant}
206
- size="iconSm"
207
- disabled={toMonth && currentMonth >= toMonth}
208
- className={captionLayout === 'dropdown' ? 'size-6.5' : ''}
209
- onClick={() => setCurrentMonth((m) => addMonths(m ?? new Date(), 1))}
210
- >
211
- <ChevronRightIcon />
212
- </Button>
213
- {showYearNavigation && (
214
- <Button
215
- aria-label="next year"
216
- variant={buttonVariant}
217
- size="iconSm"
218
- disabled={toMonth && currentMonth >= toMonth}
219
- className={captionLayout === 'dropdown' ? 'size-6.5' : ''}
220
- onClick={() => setCurrentMonth((m) => addMonths(m ?? new Date(), 12))}
221
- >
222
- <DoubleArrowRightIcon />
223
- </Button>
224
- )}
225
- </div>
226
- </div>
227
- ),
228
- // prettier-ignore
229
- Root: ({ className, rootRef, ...props }) => { //NOSONAR - default shadeCn
230
- return <div data-slot="calendar" ref={rootRef} className={cn(className)} {...props} />
231
- },
232
- // prettier-ignore
233
- Chevron: ({ className, orientation, ...props }) => { //NOSONAR - default shadeCn
234
- if (orientation === 'left') {
235
- return <ChevronLeftIcon className={cn('size-4', className)} {...props} />
236
- }
237
-
238
- if (orientation === 'right') {
239
- return <ChevronRightIcon className={cn('size-4', className)} {...props} />
240
- }
241
-
242
- return <ChevronDownIcon className={cn('size-4', className)} {...props} />
243
- },
244
- DayButton: CalendarDayButton,
245
- // prettier-ignore
246
- WeekNumber: ({ children, ...props }) => { //NOSONAR - default shadeCn
247
- return (
248
- <td {...props}>
249
- <div className="flex size-(--cell-size) items-center justify-center text-center">{children}</div>
250
- </td>
251
- )
252
- },
253
- ...components,
254
- }}
255
- onDayClick={handleDayClick}
256
- {...props}
257
- />
258
- )
259
- }
260
-
261
- function CalendarDayButton({ className, day, modifiers, ...props }: React.ComponentProps<typeof DayButton>) {
262
- const defaultClassNames = getDefaultClassNames()
263
-
264
- const ref = React.useRef<HTMLButtonElement>(null)
265
- React.useEffect(() => {
266
- if (modifiers.focused) {
267
- ref.current?.focus()
268
- }
269
- }, [modifiers.focused])
270
-
271
- return (
272
- <Button
273
- ref={ref}
274
- variant="ghost"
275
- size="icon"
276
- data-day={day.date.toLocaleDateString()}
277
- data-selected-single={
278
- modifiers.selected && !modifiers.range_start && !modifiers.range_end && !modifiers.range_middle
279
- }
280
- data-range-start={modifiers.range_start}
281
- data-range-end={modifiers.range_end}
282
- data-range-middle={modifiers.range_middle}
283
- className={cn(
284
- 'data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70',
285
- defaultClassNames.day,
286
- className,
287
- )}
288
- {...props}
289
- />
290
- )
291
- }
292
-
293
- export { Calendar, CalendarDayButton }