saafe-redirection-flow 2.0.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 (225) hide show
  1. package/.github/workflows/build-and-deploy.yml +41 -0
  2. package/.gitlab-ci.yml +108 -0
  3. package/.releaserc.json +18 -0
  4. package/.storybook/main.ts +28 -0
  5. package/.storybook/preview.ts +16 -0
  6. package/.storybook/vitest.setup.ts +9 -0
  7. package/.vite/deps/@radix-ui_react-avatar.js +230 -0
  8. package/.vite/deps/@radix-ui_react-avatar.js.map +7 -0
  9. package/.vite/deps/@radix-ui_react-slot.js +12 -0
  10. package/.vite/deps/@radix-ui_react-slot.js.map +7 -0
  11. package/.vite/deps/_metadata.json +79 -0
  12. package/.vite/deps/chunk-5VGQBUCU.js +597 -0
  13. package/.vite/deps/chunk-5VGQBUCU.js.map +7 -0
  14. package/.vite/deps/chunk-DC5AMYBS.js +38 -0
  15. package/.vite/deps/chunk-DC5AMYBS.js.map +7 -0
  16. package/.vite/deps/chunk-HUIEPYH7.js +11265 -0
  17. package/.vite/deps/chunk-HUIEPYH7.js.map +7 -0
  18. package/.vite/deps/chunk-TKHB4QMX.js +281 -0
  19. package/.vite/deps/chunk-TKHB4QMX.js.map +7 -0
  20. package/.vite/deps/chunk-YLDSBLSF.js +1139 -0
  21. package/.vite/deps/chunk-YLDSBLSF.js.map +7 -0
  22. package/.vite/deps/class-variance-authority.js +63 -0
  23. package/.vite/deps/class-variance-authority.js.map +7 -0
  24. package/.vite/deps/lucide-react.js +36984 -0
  25. package/.vite/deps/lucide-react.js.map +7 -0
  26. package/.vite/deps/package.json +3 -0
  27. package/.vite/deps/react-dom_client.js +17917 -0
  28. package/.vite/deps/react-dom_client.js.map +7 -0
  29. package/.vite/deps/react-router-dom.js +452 -0
  30. package/.vite/deps/react-router-dom.js.map +7 -0
  31. package/.vite/deps/react-router.js +234 -0
  32. package/.vite/deps/react-router.js.map +7 -0
  33. package/.vite/deps/react.js +5 -0
  34. package/.vite/deps/react.js.map +7 -0
  35. package/.vite/deps/react_jsx-dev-runtime.js +470 -0
  36. package/.vite/deps/react_jsx-dev-runtime.js.map +7 -0
  37. package/CHANGELOG.md +420 -0
  38. package/LICENSE +21 -0
  39. package/README.md +129 -0
  40. package/RELEASE_CHEATSHEET.md +93 -0
  41. package/RELEASE_NOTES.md +120 -0
  42. package/components.json +21 -0
  43. package/docs/DEPLOYMENT_WORKFLOW.md +262 -0
  44. package/docs/RELEASE_GUIDE.md +591 -0
  45. package/docs/architecture.md +432 -0
  46. package/docs/components.md +199 -0
  47. package/docs/index.md +69 -0
  48. package/docs/local-release-workflow.md +234 -0
  49. package/docs/routes.md +118 -0
  50. package/docs/sdk-integration.md +325 -0
  51. package/docs/semantic-release.md +124 -0
  52. package/docs/user-flow.md +206 -0
  53. package/eslint.config.js +28 -0
  54. package/index.html +19 -0
  55. package/install.sh +198 -0
  56. package/package.json +115 -0
  57. package/public/images/bank-logo.png +0 -0
  58. package/public/saafe-icon.svg +9 -0
  59. package/src/App.tsx +171 -0
  60. package/src/__tests__/url-parameters.test.ts +82 -0
  61. package/src/assets/brand/applestore.svg +13 -0
  62. package/src/assets/brand/playstore.svg +23 -0
  63. package/src/assets/brand/saafe-color-white-logo.svg +14 -0
  64. package/src/assets/brand/saafe-icon.svg +9 -0
  65. package/src/assets/brand/saafe-logo.svg +18 -0
  66. package/src/assets/icons/check-icon-dark.svg +27 -0
  67. package/src/assets/icons/check-icon.svg +23 -0
  68. package/src/components/ErrorBoundary.tsx +132 -0
  69. package/src/components/alert/alert.tsx +27 -0
  70. package/src/components/auth/AuthGuard.tsx +76 -0
  71. package/src/components/cards/BankCard.stories.tsx +69 -0
  72. package/src/components/cards/BankCard.tsx +227 -0
  73. package/src/components/cards/OuterCard.tsx +109 -0
  74. package/src/components/cards/WrapperCard.tsx +64 -0
  75. package/src/components/documents/PrivacyContent.tsx +1 -0
  76. package/src/components/dummyFooter.tsx +29 -0
  77. package/src/components/icons/github.tsx +12 -0
  78. package/src/components/language/LanguageSwitcher.tsx +44 -0
  79. package/src/components/layouts/FrostedLayout.stories.tsx +42 -0
  80. package/src/components/layouts/FrostedLayout.tsx +333 -0
  81. package/src/components/layouts/MobileLayout.tsx +403 -0
  82. package/src/components/mobile-background.tsx +136 -0
  83. package/src/components/mobileAppDownload.tsx +30 -0
  84. package/src/components/modal/ModalComp.tsx +27 -0
  85. package/src/components/mode-toggle.tsx +36 -0
  86. package/src/components/page-header.tsx +50 -0
  87. package/src/components/session/SessionTimeoutScreen.tsx +134 -0
  88. package/src/components/session/SessionTimer.tsx +173 -0
  89. package/src/components/step-navigation.tsx +87 -0
  90. package/src/components/title/AppBar.stories.tsx +50 -0
  91. package/src/components/title/AppBar.tsx +150 -0
  92. package/src/components/title/SectionTitle.tsx +31 -0
  93. package/src/components/ui/AnimatedButton.module.css +13 -0
  94. package/src/components/ui/alert.tsx +66 -0
  95. package/src/components/ui/animatedButton.tsx +111 -0
  96. package/src/components/ui/avatar.tsx +51 -0
  97. package/src/components/ui/badge.tsx +36 -0
  98. package/src/components/ui/bottom-sheet.tsx +122 -0
  99. package/src/components/ui/button.tsx +59 -0
  100. package/src/components/ui/calendar.tsx +86 -0
  101. package/src/components/ui/card.tsx +92 -0
  102. package/src/components/ui/checkbox.stories.tsx +49 -0
  103. package/src/components/ui/checkbox.tsx +67 -0
  104. package/src/components/ui/collapsible.tsx +45 -0
  105. package/src/components/ui/dialog.tsx +134 -0
  106. package/src/components/ui/document-link.tsx +26 -0
  107. package/src/components/ui/dot-stepper.tsx +57 -0
  108. package/src/components/ui/dropdown-menu.tsx +255 -0
  109. package/src/components/ui/form.tsx +165 -0
  110. package/src/components/ui/frosted-panel.stories.tsx +86 -0
  111. package/src/components/ui/frosted-panel.tsx +276 -0
  112. package/src/components/ui/input.tsx +39 -0
  113. package/src/components/ui/label.stories.tsx +67 -0
  114. package/src/components/ui/label.tsx +23 -0
  115. package/src/components/ui/mobile-footer.tsx +54 -0
  116. package/src/components/ui/modal.tsx +90 -0
  117. package/src/components/ui/otp-input.stories.tsx +62 -0
  118. package/src/components/ui/otp-input.tsx +221 -0
  119. package/src/components/ui/platform-specific-behavior.tsx +28 -0
  120. package/src/components/ui/popover.tsx +46 -0
  121. package/src/components/ui/progress.tsx +103 -0
  122. package/src/components/ui/radio-group.tsx +45 -0
  123. package/src/components/ui/scroll-area.tsx +56 -0
  124. package/src/components/ui/sdk-params-docs.tsx +53 -0
  125. package/src/components/ui/select.tsx +159 -0
  126. package/src/components/ui/separator.tsx +28 -0
  127. package/src/components/ui/sheet.tsx +137 -0
  128. package/src/components/ui/sidebar.tsx +724 -0
  129. package/src/components/ui/skeleton.stories.tsx +50 -0
  130. package/src/components/ui/skeleton.tsx +15 -0
  131. package/src/components/ui/sonner.tsx +23 -0
  132. package/src/components/ui/step.stories.tsx +132 -0
  133. package/src/components/ui/step.tsx +234 -0
  134. package/src/components/ui/stepper-progress.tsx +136 -0
  135. package/src/components/ui/stepper.tsx +259 -0
  136. package/src/components/ui/tabs.tsx +55 -0
  137. package/src/components/ui/tooltip.tsx +61 -0
  138. package/src/components/ui/url-decode-loader.tsx +36 -0
  139. package/src/components/ui/version-display.tsx +104 -0
  140. package/src/components/ui/web-footer.tsx +36 -0
  141. package/src/config/environments.ts +99 -0
  142. package/src/config/urls.ts +53 -0
  143. package/src/const/fiTypeCategoryMap.ts +19 -0
  144. package/src/contexts/LanguageContext.tsx +41 -0
  145. package/src/contexts/RTLContext.tsx +42 -0
  146. package/src/contexts/ThemeContext.tsx +93 -0
  147. package/src/hooks/use-account-discovery.ts +205 -0
  148. package/src/hooks/use-auth-query.ts +141 -0
  149. package/src/hooks/use-fip-query.ts +72 -0
  150. package/src/hooks/use-media-query.ts +32 -0
  151. package/src/hooks/use-mobile.ts +24 -0
  152. package/src/hooks/use-page-title.tsx +48 -0
  153. package/src/hooks/use-platform.ts +52 -0
  154. package/src/hooks/use-trusted-count.ts +21 -0
  155. package/src/hooks/use-url-decode.ts +90 -0
  156. package/src/hooks/useStep.ts +170 -0
  157. package/src/index.css +154 -0
  158. package/src/interfaces/app.interfaces.ts +39 -0
  159. package/src/interfaces/services.interfaces.ts +65 -0
  160. package/src/lib/i18n.ts +68 -0
  161. package/src/lib/utils.ts +6 -0
  162. package/src/locales/en/common.json +167 -0
  163. package/src/locales/hi/common.json +137 -0
  164. package/src/locales/kn/common.json +137 -0
  165. package/src/locales/ml/common.json +137 -0
  166. package/src/locales/ta/common.json +137 -0
  167. package/src/locales/te/common.json +137 -0
  168. package/src/locales/ur/common.json +138 -0
  169. package/src/main.tsx +46 -0
  170. package/src/pages/Login.tsx +363 -0
  171. package/src/pages/accounts/AccountsToProceed.tsx +396 -0
  172. package/src/pages/accounts/Discover.tsx +76 -0
  173. package/src/pages/accounts/DiscoverAccount.tsx +751 -0
  174. package/src/pages/accounts/LinkSelectedAccounts.tsx +638 -0
  175. package/src/pages/accounts/OldUser.tsx +329 -0
  176. package/src/pages/accounts/link-accounts.tsx +913 -0
  177. package/src/pages/consent/ReviewConsent.tsx +836 -0
  178. package/src/pages/consent/rejected.tsx +253 -0
  179. package/src/pages/consent/success.tsx +220 -0
  180. package/src/providers/query-provider.tsx +24 -0
  181. package/src/providers/toast-provider.tsx +26 -0
  182. package/src/services/api/account.service.ts +296 -0
  183. package/src/services/api/auth.service.ts +206 -0
  184. package/src/services/api/axios.ts +138 -0
  185. package/src/services/api/consent.service.ts +142 -0
  186. package/src/services/api/decode.service.ts +53 -0
  187. package/src/services/api/feedback.service.ts +34 -0
  188. package/src/services/api/fip.service.ts +187 -0
  189. package/src/services/api/index.ts +9 -0
  190. package/src/services/api/public.service.ts +18 -0
  191. package/src/services/api.ts +2 -0
  192. package/src/services/postMessage.service.ts +179 -0
  193. package/src/store/NavigationBlockContext.tsx +34 -0
  194. package/src/store/auth.store.ts +79 -0
  195. package/src/store/fip.store.ts +396 -0
  196. package/src/store/mandatoryConsent.store.ts +24 -0
  197. package/src/store/redirect.store.ts +73 -0
  198. package/src/store/step.store.ts +124 -0
  199. package/src/stories/Button.stories.ts +53 -0
  200. package/src/stories/Button.tsx +37 -0
  201. package/src/stories/Configure.mdx +364 -0
  202. package/src/stories/Header.stories.ts +33 -0
  203. package/src/stories/Header.tsx +56 -0
  204. package/src/stories/Page.stories.ts +32 -0
  205. package/src/stories/Page.tsx +73 -0
  206. package/src/stories/button.css +30 -0
  207. package/src/stories/header.css +32 -0
  208. package/src/stories/page.css +68 -0
  209. package/src/styles/rtl-utils.css +90 -0
  210. package/src/styles/rtl.css +105 -0
  211. package/src/utils/api-error.ts +26 -0
  212. package/src/utils/cn.ts +10 -0
  213. package/src/utils/error-callback.ts +116 -0
  214. package/src/utils/formatAccountNumber.ts +9 -0
  215. package/src/utils/handleIdentifiers.ts +90 -0
  216. package/src/utils/posthog.ts +67 -0
  217. package/src/utils/toast-helpers.ts +61 -0
  218. package/src/vite-env.d.ts +1 -0
  219. package/stage-aa-2506251021.zip +0 -0
  220. package/tsconfig.app.json +33 -0
  221. package/tsconfig.json +13 -0
  222. package/tsconfig.node.json +24 -0
  223. package/vite.config.ts +45 -0
  224. package/vitest.shims.d.ts +1 -0
  225. package/vitest.workspace.ts +46 -0
@@ -0,0 +1,66 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const alertVariants = cva(
7
+ "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-card text-card-foreground",
12
+ destructive:
13
+ "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: "default",
18
+ },
19
+ }
20
+ )
21
+
22
+ function Alert({
23
+ className,
24
+ variant,
25
+ ...props
26
+ }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
27
+ return (
28
+ <div
29
+ data-slot="alert"
30
+ role="alert"
31
+ className={cn(alertVariants({ variant }), className)}
32
+ {...props}
33
+ />
34
+ )
35
+ }
36
+
37
+ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
38
+ return (
39
+ <div
40
+ data-slot="alert-title"
41
+ className={cn(
42
+ "col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight w-full",
43
+ className
44
+ )}
45
+ {...props}
46
+ />
47
+ )
48
+ }
49
+
50
+ function AlertDescription({
51
+ className,
52
+ ...props
53
+ }: React.ComponentProps<"div">) {
54
+ return (
55
+ <div
56
+ data-slot="alert-description"
57
+ className={cn(
58
+ "text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed w-full",
59
+ className
60
+ )}
61
+ {...props}
62
+ />
63
+ )
64
+ }
65
+
66
+ export { Alert, AlertTitle, AlertDescription }
@@ -0,0 +1,111 @@
1
+ 'use client'
2
+
3
+ import React from 'react'
4
+ import { motion, MotionProps } from 'framer-motion'
5
+ import { cva, type VariantProps } from 'class-variance-authority'
6
+ import { cn } from '@/lib/utils'
7
+ import styles from './AnimatedButton.module.css'
8
+
9
+ const buttonVariants = cva(
10
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm 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 cursor-pointer",
11
+ {
12
+ variants: {
13
+ variant: {
14
+ default:
15
+ 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
16
+ destructive:
17
+ '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',
18
+ outline:
19
+ 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
20
+ secondary:
21
+ 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
22
+ ghost:
23
+ 'hover:bg-accent hover:text-primary/80 dark:hover:bg-accent/50',
24
+ text: 'text-primary text-sm md:text-lg underline-offset-4 p-0 m-0',
25
+ link: 'text-primary underline-offset-4 hover:underline'
26
+ },
27
+ size: {
28
+ default: 'h-9 px-4 py-2 has-[>svg]:px-3',
29
+ sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
30
+ lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
31
+ icon: 'size-9',
32
+ text: 'text-primary p-0 m-0 text-sm md:text-lg font-medium'
33
+ }
34
+ },
35
+ defaultVariants: {
36
+ variant: 'default',
37
+ size: 'default'
38
+ }
39
+ }
40
+ )
41
+
42
+ type MotionButtonProps = MotionProps & {
43
+ asChild?: boolean
44
+ children: React.ReactNode
45
+ className?: string
46
+ loading?: boolean
47
+ }
48
+
49
+ type HTMLButtonProps = Omit<
50
+ React.ButtonHTMLAttributes<HTMLButtonElement>,
51
+ 'ref'
52
+ >
53
+
54
+ const AnimatedButton = React.forwardRef<
55
+ HTMLButtonElement,
56
+ MotionButtonProps & HTMLButtonProps & VariantProps<typeof buttonVariants>
57
+ >(
58
+ (
59
+ {
60
+ asChild = false,
61
+ children,
62
+ className,
63
+ variant = 'default',
64
+ size = 'default',
65
+ loading = false,
66
+ ...props
67
+ },
68
+ ref
69
+ ) => {
70
+ const classNames = cn(
71
+ buttonVariants({ variant, size }),
72
+ 'cursor-pointer',
73
+ className
74
+ )
75
+
76
+ if (asChild && React.isValidElement(children)) {
77
+ const child = children as React.ReactElement
78
+ return (
79
+ <motion.span whileTap={{ scale: 0.93 }}>
80
+ {React.cloneElement(child, {
81
+ className: cn(child.props.className, classNames),
82
+ ref,
83
+ ...props
84
+ })}
85
+ </motion.span>
86
+ )
87
+ }
88
+
89
+ return (
90
+ <motion.button
91
+ whileTap={{ scale: 0.98 }}
92
+ className={classNames}
93
+ {...props}
94
+ ref={ref}
95
+ disabled={loading}
96
+ >
97
+ {loading ? (
98
+ <div className={styles.loader}></div>
99
+ ) : (
100
+ <div className='flex items-center justify-center gap-2'>
101
+ {children}
102
+ </div>
103
+ )}
104
+ </motion.button>
105
+ )
106
+ }
107
+ )
108
+
109
+ AnimatedButton.displayName = 'AnimatedButton'
110
+
111
+ export { AnimatedButton }
@@ -0,0 +1,51 @@
1
+ import * as React from "react"
2
+ import * as AvatarPrimitive from "@radix-ui/react-avatar"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ function Avatar({
7
+ className,
8
+ ...props
9
+ }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
10
+ return (
11
+ <AvatarPrimitive.Root
12
+ data-slot="avatar"
13
+ className={cn(
14
+ "relative flex size-8 shrink-0 overflow-hidden rounded-full",
15
+ className
16
+ )}
17
+ {...props}
18
+ />
19
+ )
20
+ }
21
+
22
+ function AvatarImage({
23
+ className,
24
+ ...props
25
+ }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
26
+ return (
27
+ <AvatarPrimitive.Image
28
+ data-slot="avatar-image"
29
+ className={cn("aspect-square size-full", className)}
30
+ {...props}
31
+ />
32
+ )
33
+ }
34
+
35
+ function AvatarFallback({
36
+ className,
37
+ ...props
38
+ }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
39
+ return (
40
+ <AvatarPrimitive.Fallback
41
+ data-slot="avatar-fallback"
42
+ className={cn(
43
+ "bg-muted flex size-full items-center justify-center rounded-full",
44
+ className
45
+ )}
46
+ {...props}
47
+ />
48
+ )
49
+ }
50
+
51
+ export { Avatar, AvatarImage, AvatarFallback }
@@ -0,0 +1,36 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const badgeVariants = cva(
7
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default:
12
+ "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13
+ secondary:
14
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15
+ destructive:
16
+ "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17
+ outline: "text-foreground",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: "default",
22
+ },
23
+ }
24
+ )
25
+
26
+ export interface BadgeProps
27
+ extends React.HTMLAttributes<HTMLDivElement>,
28
+ VariantProps<typeof badgeVariants> {}
29
+
30
+ function Badge({ className, variant, ...props }: BadgeProps) {
31
+ return (
32
+ <div className={cn(badgeVariants({ variant }), className)} {...props} />
33
+ )
34
+ }
35
+
36
+ export { Badge, badgeVariants }
@@ -0,0 +1,122 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { cn } from "@/lib/utils";
5
+ import { motion, AnimatePresence } from "framer-motion";
6
+ import { X } from "lucide-react";
7
+
8
+ interface BottomSheetProps {
9
+ isOpen: boolean;
10
+ onClose: () => void;
11
+ children: React.ReactNode;
12
+ title?: string;
13
+ height?: string;
14
+ showHandle?: boolean;
15
+ showCloseButton?: boolean;
16
+ className?: string;
17
+ overlayClassName?: string;
18
+ contentClassName?: string;
19
+ bodyClassName?: string;
20
+ }
21
+
22
+ export function BottomSheet({
23
+ isOpen,
24
+ onClose,
25
+ children,
26
+ title,
27
+ height = "50vh",
28
+ showHandle = true,
29
+ showCloseButton = true,
30
+ className,
31
+ overlayClassName,
32
+ contentClassName,
33
+ bodyClassName,
34
+ }: BottomSheetProps) {
35
+ // Handle click outside to close
36
+ const contentRef = React.useRef<HTMLDivElement>(null);
37
+
38
+ // Animation variants
39
+ const overlayVariants = {
40
+ hidden: { opacity: 0 },
41
+ visible: { opacity: 1 },
42
+ };
43
+
44
+ const sheetVariants = {
45
+ hidden: { y: "100%" },
46
+ visible: { y: 0 },
47
+ };
48
+
49
+ return (
50
+ <AnimatePresence>
51
+ {isOpen && (
52
+ <div className={cn("fixed inset-0 z-50", className)}>
53
+ {/* Backdrop/Overlay */}
54
+ <motion.div
55
+ className={cn(
56
+ "absolute inset-0 bg-black/50 backdrop-blur-sm",
57
+ overlayClassName
58
+ )}
59
+ initial="hidden"
60
+ animate="visible"
61
+ exit="hidden"
62
+ variants={overlayVariants}
63
+ transition={{ duration: 0.2 }}
64
+ onClick={onClose}
65
+ />
66
+
67
+ {/* Sheet Content */}
68
+ <motion.div
69
+ ref={contentRef}
70
+ className={cn(
71
+ "absolute bottom-0 left-0 right-0 rounded-t-xl bg-white dark:bg-card overflow-hidden",
72
+ contentClassName
73
+ )}
74
+ style={{ maxHeight: height }}
75
+ initial="hidden"
76
+ animate="visible"
77
+ exit="hidden"
78
+ variants={sheetVariants}
79
+ transition={{
80
+ type: "spring",
81
+ damping: 25,
82
+ stiffness: 300
83
+ }}
84
+ >
85
+ {/* Handle for dragging */}
86
+ {showHandle && (
87
+ <div className="flex justify-center p-2">
88
+ <div className="w-24 h-1.5 rounded-full bg-gray-300" />
89
+ </div>
90
+ )}
91
+
92
+ {/* Header with title and close button */}
93
+ {(title || showCloseButton) && (
94
+ <div className="flex items-center justify-between px-4 py-3">
95
+ {title && <h3 className="font-medium text-lg">{title}</h3>}
96
+ {showCloseButton && (
97
+ <button
98
+ onClick={onClose}
99
+ className="p-1 rounded-full hover:bg-gray-100 transition-colors"
100
+ >
101
+ <X size={18} />
102
+ </button>
103
+ )}
104
+ </div>
105
+ )}
106
+
107
+ {/* Body content */}
108
+ <div
109
+ className={cn(
110
+ "overflow-auto px-4 pb-4",
111
+ height ? { maxHeight: `calc(${height} - 2rem)` } : {},
112
+ bodyClassName
113
+ )}
114
+ >
115
+ {children}
116
+ </div>
117
+ </motion.div>
118
+ </div>
119
+ )}
120
+ </AnimatePresence>
121
+ );
122
+ }
@@ -0,0 +1,59 @@
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm 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 cursor-pointer",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ "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:
19
+ "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80 text-xs md:text-md",
20
+ ghost:
21
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
22
+ link: "text-primary underline-offset-4 hover:underline",
23
+ },
24
+ size: {
25
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
26
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
27
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
28
+ icon: "size-9",
29
+ },
30
+ },
31
+ defaultVariants: {
32
+ variant: "default",
33
+ size: "default",
34
+ },
35
+ }
36
+ )
37
+
38
+ function Button({
39
+ className,
40
+ variant,
41
+ size,
42
+ asChild = false,
43
+ ...props
44
+ }: React.ComponentProps<"button"> &
45
+ VariantProps<typeof buttonVariants> & {
46
+ asChild?: boolean
47
+ }) {
48
+ const Comp = asChild ? Slot : "button"
49
+
50
+ return (
51
+ <Comp
52
+ data-slot="button"
53
+ className={cn(buttonVariants({ variant, size, className }))}
54
+ {...props}
55
+ />
56
+ )
57
+ }
58
+
59
+ export { Button, buttonVariants }
@@ -0,0 +1,86 @@
1
+ "use client";
2
+
3
+ import { ChevronLeft, ChevronRight } from "lucide-react";
4
+ import * as React from "react";
5
+ import { DayPicker } from "react-day-picker";
6
+
7
+ import { cn } from "@/lib/utils";
8
+ import { buttonVariants } from "@/components/ui/button";
9
+
10
+ export type CalendarProps = React.ComponentProps<typeof DayPicker>;
11
+
12
+ function Calendar({
13
+ className,
14
+ classNames,
15
+ showOutsideDays = true,
16
+ components: userComponents,
17
+ ...props
18
+ }: CalendarProps) {
19
+ const defaultClassNames = {
20
+ months: "relative flex flex-col sm:flex-row gap-4",
21
+ month: "w-full",
22
+ month_caption: "relative mx-10 mb-1 flex h-9 items-center justify-center z-20",
23
+ caption_label: "text-sm font-medium",
24
+ nav: "absolute top-0 flex w-full justify-between z-10",
25
+ button_previous: cn(
26
+ buttonVariants({ variant: "ghost" }),
27
+ "size-9 text-muted-foreground/80 hover:text-foreground p-0",
28
+ ),
29
+ button_next: cn(
30
+ buttonVariants({ variant: "ghost" }),
31
+ "size-9 text-muted-foreground/80 hover:text-foreground p-0",
32
+ ),
33
+ weekday: "size-9 p-0 text-xs font-medium text-muted-foreground/80",
34
+ day_button:
35
+ "relative flex size-9 items-center justify-center whitespace-nowrap rounded-lg p-0 text-foreground outline-offset-2 group-[[data-selected]:not(.range-middle)]:[transition-property:color,background-color,border-radius,box-shadow] group-[[data-selected]:not(.range-middle)]:duration-150 focus:outline-none group-data-[disabled]:pointer-events-none focus-visible:z-10 hover:bg-accent group-data-[selected]:bg-primary hover:text-foreground group-data-[selected]:text-primary-foreground group-data-[disabled]:text-foreground/30 group-data-[disabled]:line-through group-data-[outside]:text-foreground/30 group-data-[outside]:group-data-[selected]:text-primary-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring/70 group-[.range-start:not(.range-end)]:rounded-e-none group-[.range-end:not(.range-start)]:rounded-s-none group-[.range-middle]:rounded-none group-data-[selected]:group-[.range-middle]:bg-accent group-data-[selected]:group-[.range-middle]:text-foreground",
36
+ day: "group size-9 px-0 text-sm",
37
+ range_start: "range-start",
38
+ range_end: "range-end",
39
+ range_middle: "range-middle",
40
+ today:
41
+ "*:after:pointer-events-none *:after:absolute *:after:bottom-1 *:after:start-1/2 *:after:z-10 *:after:size-[3px] *:after:-translate-x-1/2 *:after:rounded-full *:after:bg-primary [&[data-selected]:not(.range-middle)>*]:after:bg-background [&[data-disabled]>*]:after:bg-foreground/30 *:after:transition-colors",
42
+ outside: "text-muted-foreground data-selected:bg-accent/50 data-selected:text-muted-foreground",
43
+ hidden: "invisible",
44
+ week_number: "size-9 p-0 text-xs font-medium text-muted-foreground/80",
45
+ };
46
+
47
+ const mergedClassNames: typeof defaultClassNames = Object.keys(defaultClassNames).reduce(
48
+ (acc, key) => ({
49
+ ...acc,
50
+ [key]: classNames?.[key as keyof typeof classNames]
51
+ ? cn(
52
+ defaultClassNames[key as keyof typeof defaultClassNames],
53
+ classNames[key as keyof typeof classNames],
54
+ )
55
+ : defaultClassNames[key as keyof typeof defaultClassNames],
56
+ }),
57
+ {} as typeof defaultClassNames,
58
+ );
59
+
60
+ const defaultComponents = {
61
+ Chevron: (props: any) => {
62
+ if (props.orientation === "left") {
63
+ return <ChevronLeft size={16} strokeWidth={2} {...props} aria-hidden="true" />;
64
+ }
65
+ return <ChevronRight size={16} strokeWidth={2} {...props} aria-hidden="true" />;
66
+ },
67
+ };
68
+
69
+ const mergedComponents = {
70
+ ...defaultComponents,
71
+ ...userComponents,
72
+ };
73
+
74
+ return (
75
+ <DayPicker
76
+ showOutsideDays={showOutsideDays}
77
+ className={cn("w-fit", className)}
78
+ classNames={mergedClassNames}
79
+ components={mergedComponents}
80
+ {...props}
81
+ />
82
+ );
83
+ }
84
+ Calendar.displayName = "Calendar";
85
+
86
+ export { Calendar };
@@ -0,0 +1,92 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ function Card({ className, ...props }: React.ComponentProps<"div">) {
6
+ return (
7
+ <div
8
+ data-slot="card"
9
+ className={cn(
10
+ "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
11
+ className
12
+ )}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
19
+ return (
20
+ <div
21
+ data-slot="card-header"
22
+ className={cn(
23
+ "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-[data-slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
24
+ className
25
+ )}
26
+ {...props}
27
+ />
28
+ )
29
+ }
30
+
31
+ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
32
+ return (
33
+ <div
34
+ data-slot="card-title"
35
+ className={cn("leading-none font-semibold", className)}
36
+ {...props}
37
+ />
38
+ )
39
+ }
40
+
41
+ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
42
+ return (
43
+ <div
44
+ data-slot="card-description"
45
+ className={cn("text-muted-foreground text-sm", className)}
46
+ {...props}
47
+ />
48
+ )
49
+ }
50
+
51
+ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
52
+ return (
53
+ <div
54
+ data-slot="card-action"
55
+ className={cn(
56
+ "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
57
+ className
58
+ )}
59
+ {...props}
60
+ />
61
+ )
62
+ }
63
+
64
+ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
65
+ return (
66
+ <div
67
+ data-slot="card-content"
68
+ className={cn("px-6", className)}
69
+ {...props}
70
+ />
71
+ )
72
+ }
73
+
74
+ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
75
+ return (
76
+ <div
77
+ data-slot="card-footer"
78
+ className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
79
+ {...props}
80
+ />
81
+ )
82
+ }
83
+
84
+ export {
85
+ Card,
86
+ CardHeader,
87
+ CardFooter,
88
+ CardTitle,
89
+ CardAction,
90
+ CardDescription,
91
+ CardContent,
92
+ }
@@ -0,0 +1,49 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Checkbox } from "./checkbox";
3
+
4
+ const meta: Meta<typeof Checkbox> = {
5
+ title: "UI/Checkbox",
6
+ component: Checkbox,
7
+ parameters: {
8
+ layout: "centered",
9
+ },
10
+ tags: ["autodocs"],
11
+ };
12
+
13
+ export default meta;
14
+ type Story = StoryObj<typeof Checkbox>;
15
+
16
+ export const Default: Story = {
17
+ args: {},
18
+ };
19
+
20
+ export const Checked: Story = {
21
+ args: {
22
+ checked: true,
23
+ },
24
+ };
25
+
26
+ export const Indeterminate: Story = {
27
+ args: {
28
+ checked: "indeterminate",
29
+ },
30
+ };
31
+
32
+ export const Disabled: Story = {
33
+ args: {
34
+ disabled: true,
35
+ },
36
+ };
37
+
38
+ export const DisabledChecked: Story = {
39
+ args: {
40
+ disabled: true,
41
+ checked: true,
42
+ },
43
+ };
44
+
45
+ export const CustomClassName: Story = {
46
+ args: {
47
+ className: "size-6",
48
+ },
49
+ };