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.
- package/.github/workflows/build-and-deploy.yml +41 -0
- package/.gitlab-ci.yml +108 -0
- package/.releaserc.json +18 -0
- package/.storybook/main.ts +28 -0
- package/.storybook/preview.ts +16 -0
- package/.storybook/vitest.setup.ts +9 -0
- package/.vite/deps/@radix-ui_react-avatar.js +230 -0
- package/.vite/deps/@radix-ui_react-avatar.js.map +7 -0
- package/.vite/deps/@radix-ui_react-slot.js +12 -0
- package/.vite/deps/@radix-ui_react-slot.js.map +7 -0
- package/.vite/deps/_metadata.json +79 -0
- package/.vite/deps/chunk-5VGQBUCU.js +597 -0
- package/.vite/deps/chunk-5VGQBUCU.js.map +7 -0
- package/.vite/deps/chunk-DC5AMYBS.js +38 -0
- package/.vite/deps/chunk-DC5AMYBS.js.map +7 -0
- package/.vite/deps/chunk-HUIEPYH7.js +11265 -0
- package/.vite/deps/chunk-HUIEPYH7.js.map +7 -0
- package/.vite/deps/chunk-TKHB4QMX.js +281 -0
- package/.vite/deps/chunk-TKHB4QMX.js.map +7 -0
- package/.vite/deps/chunk-YLDSBLSF.js +1139 -0
- package/.vite/deps/chunk-YLDSBLSF.js.map +7 -0
- package/.vite/deps/class-variance-authority.js +63 -0
- package/.vite/deps/class-variance-authority.js.map +7 -0
- package/.vite/deps/lucide-react.js +36984 -0
- package/.vite/deps/lucide-react.js.map +7 -0
- package/.vite/deps/package.json +3 -0
- package/.vite/deps/react-dom_client.js +17917 -0
- package/.vite/deps/react-dom_client.js.map +7 -0
- package/.vite/deps/react-router-dom.js +452 -0
- package/.vite/deps/react-router-dom.js.map +7 -0
- package/.vite/deps/react-router.js +234 -0
- package/.vite/deps/react-router.js.map +7 -0
- package/.vite/deps/react.js +5 -0
- package/.vite/deps/react.js.map +7 -0
- package/.vite/deps/react_jsx-dev-runtime.js +470 -0
- package/.vite/deps/react_jsx-dev-runtime.js.map +7 -0
- package/CHANGELOG.md +420 -0
- package/LICENSE +21 -0
- package/README.md +129 -0
- package/RELEASE_CHEATSHEET.md +93 -0
- package/RELEASE_NOTES.md +120 -0
- package/components.json +21 -0
- package/docs/DEPLOYMENT_WORKFLOW.md +262 -0
- package/docs/RELEASE_GUIDE.md +591 -0
- package/docs/architecture.md +432 -0
- package/docs/components.md +199 -0
- package/docs/index.md +69 -0
- package/docs/local-release-workflow.md +234 -0
- package/docs/routes.md +118 -0
- package/docs/sdk-integration.md +325 -0
- package/docs/semantic-release.md +124 -0
- package/docs/user-flow.md +206 -0
- package/eslint.config.js +28 -0
- package/index.html +19 -0
- package/install.sh +198 -0
- package/package.json +115 -0
- package/public/images/bank-logo.png +0 -0
- package/public/saafe-icon.svg +9 -0
- package/src/App.tsx +171 -0
- package/src/__tests__/url-parameters.test.ts +82 -0
- package/src/assets/brand/applestore.svg +13 -0
- package/src/assets/brand/playstore.svg +23 -0
- package/src/assets/brand/saafe-color-white-logo.svg +14 -0
- package/src/assets/brand/saafe-icon.svg +9 -0
- package/src/assets/brand/saafe-logo.svg +18 -0
- package/src/assets/icons/check-icon-dark.svg +27 -0
- package/src/assets/icons/check-icon.svg +23 -0
- package/src/components/ErrorBoundary.tsx +132 -0
- package/src/components/alert/alert.tsx +27 -0
- package/src/components/auth/AuthGuard.tsx +76 -0
- package/src/components/cards/BankCard.stories.tsx +69 -0
- package/src/components/cards/BankCard.tsx +227 -0
- package/src/components/cards/OuterCard.tsx +109 -0
- package/src/components/cards/WrapperCard.tsx +64 -0
- package/src/components/documents/PrivacyContent.tsx +1 -0
- package/src/components/dummyFooter.tsx +29 -0
- package/src/components/icons/github.tsx +12 -0
- package/src/components/language/LanguageSwitcher.tsx +44 -0
- package/src/components/layouts/FrostedLayout.stories.tsx +42 -0
- package/src/components/layouts/FrostedLayout.tsx +333 -0
- package/src/components/layouts/MobileLayout.tsx +403 -0
- package/src/components/mobile-background.tsx +136 -0
- package/src/components/mobileAppDownload.tsx +30 -0
- package/src/components/modal/ModalComp.tsx +27 -0
- package/src/components/mode-toggle.tsx +36 -0
- package/src/components/page-header.tsx +50 -0
- package/src/components/session/SessionTimeoutScreen.tsx +134 -0
- package/src/components/session/SessionTimer.tsx +173 -0
- package/src/components/step-navigation.tsx +87 -0
- package/src/components/title/AppBar.stories.tsx +50 -0
- package/src/components/title/AppBar.tsx +150 -0
- package/src/components/title/SectionTitle.tsx +31 -0
- package/src/components/ui/AnimatedButton.module.css +13 -0
- package/src/components/ui/alert.tsx +66 -0
- package/src/components/ui/animatedButton.tsx +111 -0
- package/src/components/ui/avatar.tsx +51 -0
- package/src/components/ui/badge.tsx +36 -0
- package/src/components/ui/bottom-sheet.tsx +122 -0
- package/src/components/ui/button.tsx +59 -0
- package/src/components/ui/calendar.tsx +86 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/checkbox.stories.tsx +49 -0
- package/src/components/ui/checkbox.tsx +67 -0
- package/src/components/ui/collapsible.tsx +45 -0
- package/src/components/ui/dialog.tsx +134 -0
- package/src/components/ui/document-link.tsx +26 -0
- package/src/components/ui/dot-stepper.tsx +57 -0
- package/src/components/ui/dropdown-menu.tsx +255 -0
- package/src/components/ui/form.tsx +165 -0
- package/src/components/ui/frosted-panel.stories.tsx +86 -0
- package/src/components/ui/frosted-panel.tsx +276 -0
- package/src/components/ui/input.tsx +39 -0
- package/src/components/ui/label.stories.tsx +67 -0
- package/src/components/ui/label.tsx +23 -0
- package/src/components/ui/mobile-footer.tsx +54 -0
- package/src/components/ui/modal.tsx +90 -0
- package/src/components/ui/otp-input.stories.tsx +62 -0
- package/src/components/ui/otp-input.tsx +221 -0
- package/src/components/ui/platform-specific-behavior.tsx +28 -0
- package/src/components/ui/popover.tsx +46 -0
- package/src/components/ui/progress.tsx +103 -0
- package/src/components/ui/radio-group.tsx +45 -0
- package/src/components/ui/scroll-area.tsx +56 -0
- package/src/components/ui/sdk-params-docs.tsx +53 -0
- package/src/components/ui/select.tsx +159 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +137 -0
- package/src/components/ui/sidebar.tsx +724 -0
- package/src/components/ui/skeleton.stories.tsx +50 -0
- package/src/components/ui/skeleton.tsx +15 -0
- package/src/components/ui/sonner.tsx +23 -0
- package/src/components/ui/step.stories.tsx +132 -0
- package/src/components/ui/step.tsx +234 -0
- package/src/components/ui/stepper-progress.tsx +136 -0
- package/src/components/ui/stepper.tsx +259 -0
- package/src/components/ui/tabs.tsx +55 -0
- package/src/components/ui/tooltip.tsx +61 -0
- package/src/components/ui/url-decode-loader.tsx +36 -0
- package/src/components/ui/version-display.tsx +104 -0
- package/src/components/ui/web-footer.tsx +36 -0
- package/src/config/environments.ts +99 -0
- package/src/config/urls.ts +53 -0
- package/src/const/fiTypeCategoryMap.ts +19 -0
- package/src/contexts/LanguageContext.tsx +41 -0
- package/src/contexts/RTLContext.tsx +42 -0
- package/src/contexts/ThemeContext.tsx +93 -0
- package/src/hooks/use-account-discovery.ts +205 -0
- package/src/hooks/use-auth-query.ts +141 -0
- package/src/hooks/use-fip-query.ts +72 -0
- package/src/hooks/use-media-query.ts +32 -0
- package/src/hooks/use-mobile.ts +24 -0
- package/src/hooks/use-page-title.tsx +48 -0
- package/src/hooks/use-platform.ts +52 -0
- package/src/hooks/use-trusted-count.ts +21 -0
- package/src/hooks/use-url-decode.ts +90 -0
- package/src/hooks/useStep.ts +170 -0
- package/src/index.css +154 -0
- package/src/interfaces/app.interfaces.ts +39 -0
- package/src/interfaces/services.interfaces.ts +65 -0
- package/src/lib/i18n.ts +68 -0
- package/src/lib/utils.ts +6 -0
- package/src/locales/en/common.json +167 -0
- package/src/locales/hi/common.json +137 -0
- package/src/locales/kn/common.json +137 -0
- package/src/locales/ml/common.json +137 -0
- package/src/locales/ta/common.json +137 -0
- package/src/locales/te/common.json +137 -0
- package/src/locales/ur/common.json +138 -0
- package/src/main.tsx +46 -0
- package/src/pages/Login.tsx +363 -0
- package/src/pages/accounts/AccountsToProceed.tsx +396 -0
- package/src/pages/accounts/Discover.tsx +76 -0
- package/src/pages/accounts/DiscoverAccount.tsx +751 -0
- package/src/pages/accounts/LinkSelectedAccounts.tsx +638 -0
- package/src/pages/accounts/OldUser.tsx +329 -0
- package/src/pages/accounts/link-accounts.tsx +913 -0
- package/src/pages/consent/ReviewConsent.tsx +836 -0
- package/src/pages/consent/rejected.tsx +253 -0
- package/src/pages/consent/success.tsx +220 -0
- package/src/providers/query-provider.tsx +24 -0
- package/src/providers/toast-provider.tsx +26 -0
- package/src/services/api/account.service.ts +296 -0
- package/src/services/api/auth.service.ts +206 -0
- package/src/services/api/axios.ts +138 -0
- package/src/services/api/consent.service.ts +142 -0
- package/src/services/api/decode.service.ts +53 -0
- package/src/services/api/feedback.service.ts +34 -0
- package/src/services/api/fip.service.ts +187 -0
- package/src/services/api/index.ts +9 -0
- package/src/services/api/public.service.ts +18 -0
- package/src/services/api.ts +2 -0
- package/src/services/postMessage.service.ts +179 -0
- package/src/store/NavigationBlockContext.tsx +34 -0
- package/src/store/auth.store.ts +79 -0
- package/src/store/fip.store.ts +396 -0
- package/src/store/mandatoryConsent.store.ts +24 -0
- package/src/store/redirect.store.ts +73 -0
- package/src/store/step.store.ts +124 -0
- package/src/stories/Button.stories.ts +53 -0
- package/src/stories/Button.tsx +37 -0
- package/src/stories/Configure.mdx +364 -0
- package/src/stories/Header.stories.ts +33 -0
- package/src/stories/Header.tsx +56 -0
- package/src/stories/Page.stories.ts +32 -0
- package/src/stories/Page.tsx +73 -0
- package/src/stories/button.css +30 -0
- package/src/stories/header.css +32 -0
- package/src/stories/page.css +68 -0
- package/src/styles/rtl-utils.css +90 -0
- package/src/styles/rtl.css +105 -0
- package/src/utils/api-error.ts +26 -0
- package/src/utils/cn.ts +10 -0
- package/src/utils/error-callback.ts +116 -0
- package/src/utils/formatAccountNumber.ts +9 -0
- package/src/utils/handleIdentifiers.ts +90 -0
- package/src/utils/posthog.ts +67 -0
- package/src/utils/toast-helpers.ts +61 -0
- package/src/vite-env.d.ts +1 -0
- package/stage-aa-2506251021.zip +0 -0
- package/tsconfig.app.json +33 -0
- package/tsconfig.json +13 -0
- package/tsconfig.node.json +24 -0
- package/vite.config.ts +45 -0
- package/vitest.shims.d.ts +1 -0
- 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
|
+
};
|