@umituz/web-dashboard 6.1.0 → 6.2.1

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 (135) hide show
  1. package/README.md +47 -73
  2. package/package.json +5 -9
  3. package/src/config/DashboardConfig.ts +19 -68
  4. package/src/domains/analytics/components/index.ts +0 -6
  5. package/src/domains/analytics/hooks/index.ts +0 -6
  6. package/src/domains/analytics/hooks/useAnalytics.ts +2 -20
  7. package/src/domains/analytics/index.ts +0 -10
  8. package/src/domains/analytics/services/AnalyticsEngineService.ts +7 -74
  9. package/src/domains/analytics/services/PerformanceService.ts +27 -138
  10. package/src/domains/analytics/services/index.ts +0 -4
  11. package/src/domains/analytics/types/analytics.ts +100 -270
  12. package/src/domains/analytics/types/index.ts +0 -6
  13. package/src/domains/analytics/utils/analytics.test.ts +2 -9
  14. package/src/domains/analytics/utils/analytics.ts +1 -22
  15. package/src/domains/analytics/utils/index.ts +0 -6
  16. package/src/domains/calendar/config.ts +0 -7
  17. package/src/domains/calendar/hooks/index.ts +0 -4
  18. package/src/domains/calendar/hooks/useCalendar.ts +7 -27
  19. package/src/domains/calendar/index.ts +0 -4
  20. package/src/domains/calendar/services/CalendarService.ts +0 -21
  21. package/src/domains/calendar/services/index.ts +0 -4
  22. package/src/domains/calendar/types/calendar.types.ts +9 -53
  23. package/src/domains/calendar/utils/contentItemMapper.test.ts +0 -4
  24. package/src/domains/calendar/utils/contentItemMapper.ts +0 -31
  25. package/src/domains/calendar/utils/index.ts +0 -26
  26. package/src/domains/layouts/components/index.ts +0 -4
  27. package/src/domains/layouts/hooks/dashboard.ts +0 -21
  28. package/src/domains/layouts/hooks/index.ts +0 -4
  29. package/src/domains/layouts/index.ts +0 -6
  30. package/src/domains/layouts/theme/default.ts +0 -12
  31. package/src/domains/layouts/theme/index.ts +0 -4
  32. package/src/domains/layouts/theme/presets.ts +0 -9
  33. package/src/domains/layouts/theme/utils.ts +0 -15
  34. package/src/domains/layouts/types/index.ts +0 -4
  35. package/src/domains/layouts/types/layout.ts +18 -56
  36. package/src/domains/layouts/types/notification.ts +4 -17
  37. package/src/domains/layouts/types/sidebar.ts +8 -28
  38. package/src/domains/layouts/types/theme.ts +23 -59
  39. package/src/domains/layouts/types/user.ts +9 -30
  40. package/src/domains/layouts/utils/dashboard.ts +4 -48
  41. package/src/domains/layouts/utils/index.ts +0 -4
  42. package/src/domains/modules/build.tsx +2 -16
  43. package/src/domains/modules/types.ts +8 -23
  44. package/src/domains/onboarding/calc/calc.test.ts +1 -9
  45. package/src/domains/onboarding/calc/card-stack-motion-curve.ts +0 -6
  46. package/src/domains/onboarding/calc/count-with-locale.ts +0 -8
  47. package/src/domains/onboarding/calc/duration-equal-split.ts +0 -12
  48. package/src/domains/onboarding/calc/elapsed-millis.ts +0 -6
  49. package/src/domains/onboarding/calc/progress-percentage.ts +1 -11
  50. package/src/domains/onboarding/calc/slider-value-bounds.ts +1 -11
  51. package/src/domains/onboarding/calc/step-index-clamp.ts +0 -9
  52. package/src/domains/onboarding/calc/swipe-gesture-curve.ts +0 -13
  53. package/src/domains/onboarding/components/{shared/AnimationWrapper.tsx → AnimationWrapper.tsx} +0 -4
  54. package/src/domains/onboarding/components/{shared/OnboardingErrorBoundary.tsx → OnboardingErrorBoundary.tsx} +7 -27
  55. package/src/domains/onboarding/components/OnboardingFlow.test.tsx +8 -30
  56. package/src/domains/onboarding/components/OnboardingFlow.tsx +13 -61
  57. package/src/domains/onboarding/components/OnboardingIntl.tsx +1 -11
  58. package/src/domains/onboarding/components/PrimaryButton.tsx +40 -0
  59. package/src/domains/onboarding/components/ProgressBar.tsx +32 -0
  60. package/src/domains/onboarding/components/QuestionWrapper.tsx +28 -0
  61. package/src/domains/onboarding/components/ScreenContainer.tsx +19 -0
  62. package/src/domains/onboarding/components/{shared/SwipeCard.test.tsx → SwipeCard.test.tsx} +1 -3
  63. package/src/domains/onboarding/components/{shared/SwipeCard.tsx → SwipeCard.tsx} +10 -28
  64. package/src/domains/onboarding/components/renderBundledIcon.tsx +7 -0
  65. package/src/domains/onboarding/components/screens/AccountGateScreen.tsx +7 -17
  66. package/src/domains/onboarding/components/screens/AppDemoScreen.tsx +4 -10
  67. package/src/domains/onboarding/components/screens/ComparisonScreen.tsx +12 -22
  68. package/src/domains/onboarding/components/screens/GoalQuestionScreen.tsx +9 -6
  69. package/src/domains/onboarding/components/screens/PainPointsScreen.tsx +10 -10
  70. package/src/domains/onboarding/components/screens/PaywallScreen.tsx +22 -38
  71. package/src/domains/onboarding/components/screens/PermissionPrimingScreen.tsx +14 -29
  72. package/src/domains/onboarding/components/screens/PersonalizedSolutionScreen.tsx +10 -23
  73. package/src/domains/onboarding/components/screens/PreferencesScreen.tsx +13 -27
  74. package/src/domains/onboarding/components/screens/ProcessingScreen.tsx +11 -27
  75. package/src/domains/onboarding/components/screens/SocialProofScreen.tsx +22 -51
  76. package/src/domains/onboarding/components/screens/TinderCardsScreen.tsx +13 -30
  77. package/src/domains/onboarding/components/screens/ValueDeliveryScreen.tsx +13 -29
  78. package/src/domains/onboarding/components/screens/WelcomeScreen.tsx +12 -32
  79. package/src/domains/onboarding/{components/hooks → hooks}/useOnboardingAnalytics.test.tsx +1 -1
  80. package/src/domains/onboarding/{components/hooks → hooks}/useOnboardingAnalytics.ts +3 -16
  81. package/src/domains/onboarding/{components/hooks → hooks}/useOnboardingProgress.test.tsx +2 -2
  82. package/src/domains/onboarding/{components/hooks → hooks}/useOnboardingProgress.ts +10 -21
  83. package/src/domains/onboarding/{components/hooks → hooks}/usePermissionPriming.ts +3 -10
  84. package/src/domains/onboarding/{components/hooks → hooks}/useReducedMotion.ts +0 -5
  85. package/src/domains/onboarding/index.ts +16 -45
  86. package/src/domains/onboarding/predicates/has-handler.ts +0 -4
  87. package/src/domains/onboarding/predicates/icon-name-resolves.ts +1 -17
  88. package/src/domains/onboarding/predicates/permission-granted.ts +0 -5
  89. package/src/domains/onboarding/predicates/permission-supports.ts +1 -11
  90. package/src/domains/onboarding/predicates/predicates.test.ts +6 -20
  91. package/src/domains/onboarding/predicates/screen-emits-data.ts +0 -4
  92. package/src/domains/onboarding/predicates/storage-available.ts +0 -8
  93. package/src/domains/onboarding/templates/TemplateDemoFallback.tsx +19 -39
  94. package/src/domains/onboarding/templates/art-onboarding.ts +1 -11
  95. package/src/domains/onboarding/templates/health-onboarding.ts +0 -5
  96. package/src/domains/onboarding/templates/platform-onboarding.ts +0 -9
  97. package/src/domains/onboarding/templates/video-onboarding.ts +0 -5
  98. package/src/domains/onboarding/test/setup.ts +1 -8
  99. package/src/domains/onboarding/types/config.ts +7 -18
  100. package/src/domains/onboarding/types/labels.ts +25 -59
  101. package/src/domains/onboarding/types/screens.ts +3 -11
  102. package/src/domains/onboarding/utils/analytics-dispatch.ts +2 -21
  103. package/src/domains/onboarding/utils/default-storage-key.ts +0 -1
  104. package/src/domains/onboarding/utils/index.ts +0 -1
  105. package/src/domains/onboarding/utils/local-storage-bridge.ts +1 -21
  106. package/src/domains/onboarding/utils/permission-bridge.ts +0 -8
  107. package/src/domains/onboarding/validation/flow-structure-check.ts +0 -6
  108. package/src/domains/onboarding/validation/permission-status-check.ts +0 -5
  109. package/src/domains/onboarding/validation/screen-config-check.ts +0 -5
  110. package/src/domains/onboarding/validation/stored-progress-check.ts +1 -10
  111. package/src/domains/onboarding/validation/validation.test.ts +1 -6
  112. package/src/domains/permissions/hasPermission.ts +0 -8
  113. package/src/domains/permissions/types.ts +0 -20
  114. package/src/domains/settings/components/index.ts +0 -4
  115. package/src/domains/settings/hooks/index.ts +0 -4
  116. package/src/domains/settings/hooks/useSettings.ts +1 -26
  117. package/src/domains/settings/index.ts +0 -6
  118. package/src/domains/settings/types/index.ts +0 -4
  119. package/src/domains/settings/types/settings.ts +23 -67
  120. package/src/domains/settings/utils/index.ts +0 -4
  121. package/src/domains/settings/utils/settings.ts +0 -45
  122. package/src/styles/dashboard.css +818 -2
  123. package/src/domains/onboarding/components/shared/OptionCard.tsx +0 -73
  124. package/src/domains/onboarding/components/shared/PrimaryButton.tsx +0 -58
  125. package/src/domains/onboarding/components/shared/ProgressBar.tsx +0 -51
  126. package/src/domains/onboarding/components/shared/QuestionWrapper.tsx +0 -36
  127. package/src/domains/onboarding/components/shared/ScreenContainer.tsx +0 -29
  128. package/src/domains/onboarding/components/shared/index.ts +0 -16
  129. package/src/domains/onboarding/components/shared/renderBundledIcon.tsx +0 -20
  130. package/src/domains/onboarding/onboarding.css +0 -1
  131. package/src/domains/onboarding/theme/cn.ts +0 -10
  132. package/src/domains/onboarding/theme/index.css +0 -131
  133. package/src/domains/onboarding/theme/index.ts +0 -12
  134. package/src/domains/onboarding/theme/tokens.ts +0 -122
  135. /package/src/domains/onboarding/{components/hooks → hooks}/index.ts +0 -0
@@ -1,73 +0,0 @@
1
- import { motion } from 'framer-motion';
2
- import { cn } from '../../theme/cn';
3
- import { renderBundledIcon } from './renderBundledIcon';
4
- import { doesIconNameResolve } from '../../predicates/icon-name-resolves';
5
- import type { OnboardingOption } from '../../types/screens';
6
-
7
- export interface OptionCardProps {
8
- option: OnboardingOption;
9
- selected: boolean;
10
- onToggle: (id: string) => void;
11
- multiSelect?: boolean;
12
- className?: string;
13
- }
14
-
15
- /**
16
- * Selectable option card with optional icon.
17
- * Single responsibility: render one option and emit toggle events.
18
- */
19
- export function OptionCard({ option, selected, onToggle, multiSelect = false, className }: OptionCardProps) {
20
- return (
21
- <motion.button
22
- type="button"
23
- role={multiSelect ? 'checkbox' : 'radio'}
24
- aria-checked={selected}
25
- onClick={() => onToggle(option.id)}
26
- whileTap={{ scale: 0.98 }}
27
- className={cn(
28
- 'flex w-full items-center gap-3 rounded-xl border p-4 text-left transition-colors',
29
- 'focus:outline-none focus:ring-2 focus:ring-[rgb(var(--uob-primary-500))] focus:ring-offset-2',
30
- selected
31
- ? 'border-[rgb(var(--uob-primary-500))] bg-[rgb(var(--uob-primary-50))]'
32
- : 'border-[rgb(var(--uob-border))] bg-[rgb(var(--uob-surface))] hover:border-[rgb(var(--uob-primary-100))]',
33
- className,
34
- )}
35
- >
36
- {doesIconNameResolve(option.iconName) && (
37
- <span
38
- className={cn(
39
- 'flex h-10 w-10 shrink-0 items-center justify-center rounded-lg',
40
- selected
41
- ? 'bg-[rgb(var(--uob-primary-500))] text-white'
42
- : 'bg-[rgb(var(--uob-surface-muted))] text-[rgb(var(--uob-text-muted))]',
43
- )}
44
- >
45
- {renderBundledIcon(option.iconName, 'h-5 w-5')}
46
- </span>
47
- )}
48
- <span className="flex flex-1 flex-col">
49
- <span className="text-base font-medium text-[rgb(var(--uob-text-primary))]">
50
- {option.label}
51
- </span>
52
- {option.description && (
53
- <span className="text-sm text-[rgb(var(--uob-text-muted))]">{option.description}</span>
54
- )}
55
- </span>
56
- <span
57
- className={cn(
58
- 'flex h-5 w-5 shrink-0 items-center justify-center rounded-full border-2',
59
- selected
60
- ? 'border-[rgb(var(--uob-primary-500))] bg-[rgb(var(--uob-primary-500))] text-white'
61
- : 'border-[rgb(var(--uob-border))]',
62
- )}
63
- aria-hidden="true"
64
- >
65
- {selected && (
66
- <svg viewBox="0 0 12 12" className="h-3 w-3" fill="none" stroke="currentColor" strokeWidth="2">
67
- <path d="M2 6 L5 9 L10 3" />
68
- </svg>
69
- )}
70
- </span>
71
- </motion.button>
72
- );
73
- }
@@ -1,58 +0,0 @@
1
- import type { ButtonHTMLAttributes, ReactNode } from 'react';
2
- import { motion, type HTMLMotionProps } from 'framer-motion';
3
- import { cn } from '../../theme/cn';
4
- import { useOnboardingIntl } from '../OnboardingIntl';
5
-
6
- export interface PrimaryButtonProps
7
- extends Omit<HTMLMotionProps<'button'>, 'children'> {
8
- children: ReactNode;
9
- loading?: boolean;
10
- fullWidth?: boolean;
11
- }
12
-
13
- /**
14
- * Primary CTA button used across the onboarding flow.
15
- * Single responsibility: render a tappable, accessible, animated button.
16
- */
17
- export function PrimaryButton({
18
- children,
19
- loading = false,
20
- fullWidth = true,
21
- className,
22
- disabled,
23
- type,
24
- ...rest
25
- }: PrimaryButtonProps) {
26
- const { labels } = useOnboardingIntl();
27
- return (
28
- <motion.button
29
- {...rest}
30
- type={type ?? 'button'}
31
- disabled={disabled || loading}
32
- whileTap={{ scale: disabled || loading ? 1 : 0.98 }}
33
- className={cn(
34
- 'inline-flex items-center justify-center rounded-xl px-5 py-3 text-base font-semibold',
35
- 'bg-[rgb(var(--uob-primary-500))] text-white shadow-sm transition-colors',
36
- 'hover:bg-[rgb(var(--uob-primary-600))] focus:outline-none focus:ring-2 focus:ring-[rgb(var(--uob-primary-500))] focus:ring-offset-2',
37
- 'disabled:cursor-not-allowed disabled:opacity-60',
38
- fullWidth ? 'w-full' : '',
39
- className,
40
- )}
41
- >
42
- {loading ? (
43
- <span className="flex items-center gap-2">
44
- <span
45
- className="h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent"
46
- aria-hidden="true"
47
- />
48
- <span>{labels.loading}</span>
49
- </span>
50
- ) : (
51
- children
52
- )}
53
- </motion.button>
54
- );
55
- }
56
-
57
- // Re-export the HTML button type for consumers that need to wrap a non-motion button.
58
- export type { ButtonHTMLAttributes };
@@ -1,51 +0,0 @@
1
- import * as Progress from '@radix-ui/react-progress';
2
- import { motion } from 'framer-motion';
3
- import { cn } from '../../theme/cn';
4
- import { computeProgressPercentage } from '../../calc/progress-percentage';
5
- import { formatLabel, useOnboardingIntl } from '../OnboardingIntl';
6
-
7
- export interface ProgressBarProps {
8
- current: number;
9
- total: number;
10
- className?: string;
11
- showLabel?: boolean;
12
- }
13
-
14
- /**
15
- * Accessible progress bar built on Radix UI primitive.
16
- * Progress math lives in `calc/progress-percentage`.
17
- */
18
- export function ProgressBar({ current, total, className, showLabel = false }: ProgressBarProps) {
19
- const { labels } = useOnboardingIntl();
20
- const safeTotal = Math.max(1, total);
21
- const safeCurrent = Math.max(0, Math.min(current, safeTotal));
22
- const percentage = computeProgressPercentage(safeCurrent, safeTotal);
23
-
24
- return (
25
- <div className={cn('flex w-full flex-col gap-2', className)}>
26
- {showLabel && (
27
- <div className="flex items-center justify-between text-sm text-[rgb(var(--uob-text-muted))]">
28
- <span aria-live="polite">
29
- {formatLabel(labels.stepOfPattern, { current: safeCurrent, total: safeTotal })}
30
- </span>
31
- <span aria-hidden="true">{Math.round(percentage)}%</span>
32
- </div>
33
- )}
34
- <Progress.Root
35
- value={percentage}
36
- aria-label={`Onboarding progress: ${Math.round(percentage)} percent`}
37
- aria-valuemin={0}
38
- aria-valuemax={100}
39
- aria-valuenow={Math.round(percentage)}
40
- className="relative h-2 w-full overflow-hidden rounded-full bg-[rgb(var(--uob-border))]"
41
- >
42
- <motion.div
43
- initial={{ width: 0 }}
44
- animate={{ width: `${percentage}%` }}
45
- transition={{ duration: 0.4, ease: 'easeOut' }}
46
- className="h-full rounded-full bg-[rgb(var(--uob-primary-500))]"
47
- />
48
- </Progress.Root>
49
- </div>
50
- );
51
- }
@@ -1,36 +0,0 @@
1
- import type { ReactNode } from 'react';
2
- import { motion } from 'framer-motion';
3
- import { cn } from '../../theme/cn';
4
-
5
- export interface QuestionWrapperProps {
6
- title: string;
7
- subtitle?: string;
8
- children: ReactNode;
9
- className?: string;
10
- }
11
-
12
- /**
13
- * Consistent framing for question-style screens.
14
- * Single responsibility: layout the question header + content area.
15
- */
16
- export function QuestionWrapper({ title, subtitle, children, className }: QuestionWrapperProps) {
17
- return (
18
- <motion.div
19
- initial={{ opacity: 0, y: 8 }}
20
- animate={{ opacity: 1, y: 0 }}
21
- exit={{ opacity: 0, y: -8 }}
22
- transition={{ duration: 0.25, ease: 'easeOut' }}
23
- className={cn('flex w-full flex-col gap-6', className)}
24
- >
25
- <header className="flex flex-col gap-2">
26
- <h2 className="text-2xl font-semibold text-[rgb(var(--uob-text-primary))] sm:text-3xl">
27
- {title}
28
- </h2>
29
- {subtitle && (
30
- <p className="text-base text-[rgb(var(--uob-text-muted))]">{subtitle}</p>
31
- )}
32
- </header>
33
- <div className="flex flex-col gap-3">{children}</div>
34
- </motion.div>
35
- );
36
- }
@@ -1,29 +0,0 @@
1
- import type { ReactNode } from 'react';
2
- import { cn } from '../../theme/cn';
3
-
4
- export interface ScreenContainerProps {
5
- children: ReactNode;
6
- header?: ReactNode;
7
- footer?: ReactNode;
8
- className?: string;
9
- }
10
-
11
- /**
12
- * Standard layout for an onboarding screen.
13
- * Owns safe-area, padding, header/footer slots. Per project rules this is the SOLE
14
- * layout for authenticated onboarding screens — no per-page layout duplication.
15
- */
16
- export function ScreenContainer({ children, header, footer, className }: ScreenContainerProps) {
17
- return (
18
- <div
19
- className={cn(
20
- 'mx-auto flex min-h-screen w-full max-w-2xl flex-col gap-6 px-4 pb-8 pt-6 sm:px-6',
21
- className,
22
- )}
23
- >
24
- {header && <div className="flex flex-col gap-3">{header}</div>}
25
- <main className="flex flex-1 flex-col gap-6">{children}</main>
26
- {footer && <div className="flex flex-col gap-3">{footer}</div>}
27
- </div>
28
- );
29
- }
@@ -1,16 +0,0 @@
1
- export { ProgressBar } from './ProgressBar';
2
- export type { ProgressBarProps } from './ProgressBar';
3
- export { QuestionWrapper } from './QuestionWrapper';
4
- export type { QuestionWrapperProps } from './QuestionWrapper';
5
- export { OptionCard } from './OptionCard';
6
- export type { OptionCardProps } from './OptionCard';
7
- export { SwipeCard } from './SwipeCard';
8
- export type { SwipeCardProps } from './SwipeCard';
9
- export { AnimationWrapper } from './AnimationWrapper';
10
- export type { AnimationWrapperProps, ScreenTransition } from './AnimationWrapper';
11
- export { ScreenContainer } from './ScreenContainer';
12
- export type { ScreenContainerProps } from './ScreenContainer';
13
- export { PrimaryButton } from './PrimaryButton';
14
- export type { PrimaryButtonProps } from './PrimaryButton';
15
- export { OnboardingErrorBoundary } from './OnboardingErrorBoundary';
16
- export type { OnboardingErrorBoundaryProps } from './OnboardingErrorBoundary';
@@ -1,20 +0,0 @@
1
- import { resolveIconByName } from '../../predicates/icon-name-resolves';
2
-
3
- /**
4
- * Render a bundled icon by registry name.
5
- *
6
- * Returns null when the name is not in the registry — callers combine it with
7
- * `doesIconNameResolve` when the surrounding markup must disappear with the icon.
8
- *
9
- * Why a helper instead of `const Icon = resolveIconByName(name)` inside the
10
- * component body? The registry lookup always yields a stable module-level
11
- * component (nothing is created per render), but assigning it to a variable
12
- * during render trips eslint-plugin-react-hooks' `static-components` rule at
13
- * the JSX usage site. A module-scope function returning the element keeps the
14
- * lint signal meaningful without suppression comments.
15
- */
16
- export function renderBundledIcon(name: string | undefined, className?: string) {
17
- const Icon = resolveIconByName(name);
18
- if (!Icon) return null;
19
- return <Icon className={className} aria-hidden="true" />;
20
- }
@@ -1 +0,0 @@
1
- .uob-flow{--uob-font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--uob-primary-50: 238 242 255;--uob-primary-100: 224 231 255;--uob-primary-500: 99 102 241;--uob-primary-600: 79 70 229;--uob-primary-700: 67 56 202;--uob-surface: 255 255 255;--uob-surface-muted: 249 250 251;--uob-text-primary: 17 24 39;--uob-text-muted: 107 114 128;--uob-border: 229 231 235;--uob-success: 16 185 129;--uob-warning: 245 158 11;--uob-error: 239 68 68}@media (prefers-color-scheme: dark){.uob-flow{--uob-surface: 17 24 39;--uob-surface-muted: 31 41 55;--uob-text-primary: 243 244 246;--uob-text-muted: 156 163 175;--uob-border: 55 65 81}}.uob-flow,.uob-flow *,.uob-flow *:before,.uob-flow *:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:rgb(var(--uob-border))}.uob-flow h1,.uob-flow h2,.uob-flow h3,.uob-flow h4,.uob-flow h5,.uob-flow h6,.uob-flow p,.uob-flow figure,.uob-flow blockquote,.uob-flow pre{margin:0}.uob-flow ol,.uob-flow ul{list-style:none;margin:0;padding:0}.uob-flow button,.uob-flow input,.uob-flow optgroup,.uob-flow select,.uob-flow textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}.uob-flow button,.uob-flow [type=button],.uob-flow [type=reset],.uob-flow [type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}.uob-flow progress{-webkit-appearance:none;-moz-appearance:none;appearance:none}.uob-flow img,.uob-flow svg,.uob-flow video,.uob-flow canvas{display:block;vertical-align:middle}.uob-flow img,.uob-flow video{max-width:100%;height:auto}.pointer-events-none{pointer-events:none}.\!visible{visibility:visible!important}.visible{visibility:visible}.static{position:static}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.left-4{left:1rem}.right-4{right:1rem}.top-4{top:1rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-4{margin-bottom:1rem}.ml-auto{margin-left:auto}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-3{margin-top:.75rem}.mt-6{margin-top:1.5rem}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-12{height:3rem}.h-16{height:4rem}.h-2{height:.5rem}.h-20{height:5rem}.h-24{height:6rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-32{height:8rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-72{height:18rem}.h-8{height:2rem}.h-full{height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-11{width:2.75rem}.w-12{width:3rem}.w-16{width:4rem}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.max-w-2xl{max-width:42rem}.max-w-md{max-width:28rem}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-5{--tw-translate-x: 1.25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-grab{cursor:grab}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-5{gap:1.25rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-3xl{border-radius:1.5rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-\[rgb\(var\(--uob-border\)\)\]{border-color:rgb(var(--uob-border))}.border-\[rgb\(var\(--uob-error\)\)\]{border-color:rgb(var(--uob-error))}.border-\[rgb\(var\(--uob-primary-500\)\)\]{border-color:rgb(var(--uob-primary-500))}.border-\[rgb\(var\(--uob-success\)\)\]{border-color:rgb(var(--uob-success))}.border-transparent{border-color:transparent}.border-white{--tw-border-opacity: 1;border-color:rgb(255 255 255 / var(--tw-border-opacity, 1))}.border-t-transparent{border-top-color:transparent}.bg-\[rgb\(var\(--uob-border\)\)\]{background-color:rgb(var(--uob-border))}.bg-\[rgb\(var\(--uob-error\)\)\]\/10{background-color:rgb(var(--uob-error) / .1)}.bg-\[rgb\(var\(--uob-primary-50\)\)\]{background-color:rgb(var(--uob-primary-50))}.bg-\[rgb\(var\(--uob-primary-500\)\)\]{background-color:rgb(var(--uob-primary-500))}.bg-\[rgb\(var\(--uob-success\)\)\]{background-color:rgb(var(--uob-success))}.bg-\[rgb\(var\(--uob-success\)\)\]\/10{background-color:rgb(var(--uob-success) / .1)}.bg-\[rgb\(var\(--uob-surface\)\)\]{background-color:rgb(var(--uob-surface))}.bg-\[rgb\(var\(--uob-surface-muted\)\)\]{background-color:rgb(var(--uob-surface-muted))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.fill-current{fill:currentColor}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0\.5{padding:.125rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.pb-8{padding-bottom:2rem}.pt-6{padding-top:1.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.not-italic{font-style:normal}.tracking-wide{letter-spacing:.025em}.text-\[rgb\(var\(--uob-border\)\)\]{color:rgb(var(--uob-border))}.text-\[rgb\(var\(--uob-error\)\)\]{color:rgb(var(--uob-error))}.text-\[rgb\(var\(--uob-primary-500\)\)\]{color:rgb(var(--uob-primary-500))}.text-\[rgb\(var\(--uob-primary-700\)\)\]{color:rgb(var(--uob-primary-700))}.text-\[rgb\(var\(--uob-success\)\)\]{color:rgb(var(--uob-success))}.text-\[rgb\(var\(--uob-text-muted\)\)\]{color:rgb(var(--uob-text-muted))}.text-\[rgb\(var\(--uob-text-primary\)\)\]{color:rgb(var(--uob-text-primary))}.text-\[rgb\(var\(--uob-warning\)\)\]{color:rgb(var(--uob-warning))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.underline-offset-4{text-underline-offset:4px}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.hover\:border-\[rgb\(var\(--uob-border\)\)\]:hover{border-color:rgb(var(--uob-border))}.hover\:border-\[rgb\(var\(--uob-primary-100\)\)\]:hover{border-color:rgb(var(--uob-primary-100))}.hover\:border-\[rgb\(var\(--uob-primary-500\)\)\]:hover{border-color:rgb(var(--uob-primary-500))}.hover\:bg-\[rgb\(var\(--uob-error\)\)\]:hover{background-color:rgb(var(--uob-error))}.hover\:bg-\[rgb\(var\(--uob-primary-600\)\)\]:hover{background-color:rgb(var(--uob-primary-600))}.hover\:bg-\[rgb\(var\(--uob-success\)\)\]:hover{background-color:rgb(var(--uob-success))}.hover\:bg-\[rgb\(var\(--uob-surface-muted\)\)\]:hover{background-color:rgb(var(--uob-surface-muted))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-\[rgb\(var\(--uob-primary-500\)\)\]:focus{--tw-ring-color: rgb(var(--uob-primary-500))}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.active\:cursor-grabbing:active{cursor:grabbing}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.disabled\:opacity-60:disabled{opacity:.6}@media (min-width: 640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:text-3xl{font-size:1.875rem;line-height:2.25rem}.sm\:text-4xl{font-size:2.25rem;line-height:2.5rem}.sm\:text-5xl{font-size:3rem;line-height:1}}
@@ -1,10 +0,0 @@
1
- import { clsx, type ClassValue } from 'clsx';
2
- import { twMerge } from 'tailwind-merge';
3
-
4
- /**
5
- * Class name composer — merges Tailwind utility classes safely.
6
- * Single responsibility: build a className string without conflicts.
7
- */
8
- export function cn(...inputs: ClassValue[]): string {
9
- return twMerge(clsx(inputs));
10
- }
@@ -1,131 +0,0 @@
1
- /*
2
- * web-onboarding styles.
3
- *
4
- * Isolation contract (see README "Styling"):
5
- * - NO Tailwind preflight. Host applications keep their own global reset;
6
- * this stylesheet must never change styles outside `.uob-flow`.
7
- * - A scoped reset restores the preflight invariants the library's utility
8
- * classes rely on (box-sizing, border-style, form font inheritance)
9
- * inside `.uob-flow` only.
10
- * - All design tokens are namespaced `--uob-*`. Rebrand by overriding them:
11
- * .uob-flow { --uob-primary-500: 34 197 94; }
12
- * RGB triplets (e.g. "99 102 241"), not hex — they compose with
13
- * `rgb(var(--uob-primary-500) / <alpha>)` and Tailwind `<alpha-value>`.
14
- * - OnboardingFlow renders the `.uob-flow` wrapper automatically.
15
- */
16
-
17
- /* ------------------------------------------------------------------ */
18
- /* Design tokens (light defaults) */
19
- /* ------------------------------------------------------------------ */
20
-
21
- .uob-flow {
22
- --uob-font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
23
- 'Segoe UI', Roboto, sans-serif;
24
-
25
- --uob-primary-50: 238 242 255;
26
- --uob-primary-100: 224 231 255;
27
- --uob-primary-500: 99 102 241;
28
- --uob-primary-600: 79 70 229;
29
- --uob-primary-700: 67 56 202;
30
-
31
- --uob-surface: 255 255 255;
32
- --uob-surface-muted: 249 250 251;
33
- --uob-text-primary: 17 24 39;
34
- --uob-text-muted: 107 114 128;
35
- --uob-border: 229 231 235;
36
-
37
- --uob-success: 16 185 129;
38
- --uob-warning: 245 158 11;
39
- --uob-error: 239 68 68;
40
- }
41
-
42
- @media (prefers-color-scheme: dark) {
43
- .uob-flow {
44
- --uob-surface: 17 24 39;
45
- --uob-surface-muted: 31 41 55;
46
- --uob-text-primary: 243 244 246;
47
- --uob-text-muted: 156 163 175;
48
- --uob-border: 55 65 81;
49
- }
50
- }
51
-
52
- /* ------------------------------------------------------------------ */
53
- /* Scoped reset — restores preflight invariants inside `.uob-flow` */
54
- /* only. Deliberately emitted BEFORE the utilities layer so utility */
55
- /* classes always win on equal specificity. */
56
- /* ------------------------------------------------------------------ */
57
-
58
- .uob-flow,
59
- .uob-flow *,
60
- .uob-flow *::before,
61
- .uob-flow *::after {
62
- box-sizing: border-box;
63
- border-width: 0;
64
- border-style: solid;
65
- border-color: rgb(var(--uob-border));
66
- }
67
-
68
- .uob-flow h1,
69
- .uob-flow h2,
70
- .uob-flow h3,
71
- .uob-flow h4,
72
- .uob-flow h5,
73
- .uob-flow h6,
74
- .uob-flow p,
75
- .uob-flow figure,
76
- .uob-flow blockquote,
77
- .uob-flow pre {
78
- margin: 0;
79
- }
80
-
81
- .uob-flow ol,
82
- .uob-flow ul {
83
- list-style: none;
84
- margin: 0;
85
- padding: 0;
86
- }
87
-
88
- .uob-flow button,
89
- .uob-flow input,
90
- .uob-flow optgroup,
91
- .uob-flow select,
92
- .uob-flow textarea {
93
- font-family: inherit;
94
- font-size: 100%;
95
- font-weight: inherit;
96
- line-height: inherit;
97
- color: inherit;
98
- margin: 0;
99
- padding: 0;
100
- }
101
-
102
- .uob-flow button,
103
- .uob-flow [type='button'],
104
- .uob-flow [type='reset'],
105
- .uob-flow [type='submit'] {
106
- -webkit-appearance: button;
107
- background-color: transparent;
108
- background-image: none;
109
- }
110
-
111
- .uob-flow progress {
112
- -webkit-appearance: none;
113
- appearance: none;
114
- }
115
-
116
- .uob-flow img,
117
- .uob-flow svg,
118
- .uob-flow video,
119
- .uob-flow canvas {
120
- display: block;
121
- vertical-align: middle;
122
- }
123
-
124
- .uob-flow img,
125
- .uob-flow video {
126
- max-width: 100%;
127
- height: auto;
128
- }
129
-
130
- /* Utilities LAST — emitted in place, after the scoped reset. */
131
- @tailwind utilities;
@@ -1,12 +0,0 @@
1
- export {
2
- spacing,
3
- radius,
4
- typography,
5
- zIndex,
6
- motion,
7
- breakpoint,
8
- colorCssVar,
9
- } from './tokens';
10
- export { cn } from './cn';
11
-
12
- export type { SpacingToken, RadiusToken, FontSizeToken } from './tokens';
@@ -1,122 +0,0 @@
1
- /**
2
- * Design Tokens - Single Source of Truth
3
- * All visual values MUST be sourced from here.
4
- * Per project rules: no hardcoded colors, spacing, or font sizes anywhere else.
5
- */
6
-
7
- export const spacing = {
8
- px: '1px',
9
- 0: '0',
10
- 0.5: '0.125rem',
11
- 1: '0.25rem',
12
- 1.5: '0.375rem',
13
- 2: '0.5rem',
14
- 2.5: '0.625rem',
15
- 3: '0.75rem',
16
- 3.5: '0.875rem',
17
- 4: '1rem',
18
- 5: '1.25rem',
19
- 6: '1.5rem',
20
- 7: '1.75rem',
21
- 8: '2rem',
22
- 10: '2.5rem',
23
- 12: '3rem',
24
- 16: '4rem',
25
- 20: '5rem',
26
- 24: '6rem',
27
- 32: '8rem',
28
- } as const;
29
-
30
- export const radius = {
31
- none: '0',
32
- sm: '0.25rem',
33
- md: '0.5rem',
34
- lg: '0.75rem',
35
- xl: '1rem',
36
- '2xl': '1.5rem',
37
- '3xl': '2rem',
38
- full: '9999px',
39
- } as const;
40
-
41
- export const typography = {
42
- fontFamily: {
43
- sans: 'var(--uob-font-sans, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif)',
44
- },
45
- fontSize: {
46
- xs: ['0.75rem', { lineHeight: '1rem' }],
47
- sm: ['0.875rem', { lineHeight: '1.25rem' }],
48
- base: ['1rem', { lineHeight: '1.5rem' }],
49
- lg: ['1.125rem', { lineHeight: '1.75rem' }],
50
- xl: ['1.25rem', { lineHeight: '1.75rem' }],
51
- '2xl': ['1.5rem', { lineHeight: '2rem' }],
52
- '3xl': ['1.875rem', { lineHeight: '2.25rem' }],
53
- '4xl': ['2.25rem', { lineHeight: '2.5rem' }],
54
- '5xl': ['3rem', { lineHeight: '1.15' }],
55
- '6xl': ['3.75rem', { lineHeight: '1.1' }],
56
- },
57
- fontWeight: {
58
- normal: '400',
59
- medium: '500',
60
- semibold: '600',
61
- bold: '700',
62
- },
63
- } as const;
64
-
65
- export const zIndex = {
66
- hide: -1,
67
- base: 0,
68
- raised: 10,
69
- sticky: 20,
70
- overlay: 30,
71
- modal: 40,
72
- toast: 50,
73
- } as const;
74
-
75
- export const motion = {
76
- duration: {
77
- fast: '150ms',
78
- normal: '250ms',
79
- slow: '400ms',
80
- slower: '600ms',
81
- },
82
- easing: {
83
- linear: 'linear',
84
- in: 'cubic-bezier(0.4, 0, 1, 1)',
85
- out: 'cubic-bezier(0, 0, 0.2, 1)',
86
- inOut: 'cubic-bezier(0.4, 0, 0.2, 1)',
87
- },
88
- } as const;
89
-
90
- export const breakpoint = {
91
- sm: '640px',
92
- md: '768px',
93
- lg: '1024px',
94
- xl: '1280px',
95
- '2xl': '1536px',
96
- } as const;
97
-
98
- /**
99
- * Semantic color tokens — referenced via CSS custom properties.
100
- * Consumers override --uob-primary-500 etc. on `.uob-flow` to rebrand
101
- * without code changes. Values are space-separated RGB triplets so they
102
- * compose with `rgb(var(--x) / <alpha>)`.
103
- */
104
- export const colorCssVar = {
105
- primary50: 'var(--uob-primary-50, 238 242 255)',
106
- primary100: 'var(--uob-primary-100, 224 231 255)',
107
- primary500: 'var(--uob-primary-500, 99 102 241)',
108
- primary600: 'var(--uob-primary-600, 79 70 229)',
109
- primary700: 'var(--uob-primary-700, 67 56 202)',
110
- surface: 'var(--uob-surface, 255 255 255)',
111
- surfaceMuted: 'var(--uob-surface-muted, 249 250 251)',
112
- textPrimary: 'var(--uob-text-primary, 17 24 39)',
113
- textMuted: 'var(--uob-text-muted, 107 114 128)',
114
- border: 'var(--uob-border, 229 231 235)',
115
- success: 'var(--uob-success, 16 185 129)',
116
- warning: 'var(--uob-warning, 245 158 11)',
117
- error: 'var(--uob-error, 239 68 68)',
118
- } as const;
119
-
120
- export type SpacingToken = keyof typeof spacing;
121
- export type RadiusToken = keyof typeof radius;
122
- export type FontSizeToken = keyof typeof typography.fontSize;