@umituz/web-design-system 5.3.0 → 6.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 (150) hide show
  1. package/dist/styles.css +1 -1
  2. package/package.json +1 -1
  3. package/src/__tests__/cn.test.ts +0 -3
  4. package/src/__tests__/component-slots.test.tsx +0 -4
  5. package/src/__tests__/components.smoke.test.tsx +3 -11
  6. package/src/__tests__/money.test.tsx +0 -3
  7. package/src/__tests__/productivity-components.test.tsx +135 -0
  8. package/src/__tests__/styles.contract.test.ts +0 -13
  9. package/src/domain/tokens/index.ts +0 -7
  10. package/src/domain/tokens/responsive-map.tokens.ts +18 -107
  11. package/src/domain/types/breakpoint.types.ts +9 -13
  12. package/src/domain/types/component.types.ts +0 -4
  13. package/src/domain/types/index.ts +0 -5
  14. package/src/global.d.ts +0 -4
  15. package/src/index.ts +82 -29
  16. package/src/infrastructure/constants/breakpoint.constants.ts +0 -31
  17. package/src/infrastructure/constants/component.constants.ts +0 -4
  18. package/src/infrastructure/constants/index.ts +0 -5
  19. package/src/infrastructure/constants/size-variant.constants.ts +0 -33
  20. package/src/infrastructure/constants/spacing.constants.ts +34 -63
  21. package/src/infrastructure/constants/timing.constants.ts +0 -18
  22. package/src/infrastructure/constants/typography.constants.ts +22 -54
  23. package/src/infrastructure/utils/calculation.utils.ts +0 -9
  24. package/src/infrastructure/utils/cn.util.ts +0 -18
  25. package/src/infrastructure/utils/hash.util.ts +0 -5
  26. package/src/infrastructure/utils/index.ts +0 -5
  27. package/src/infrastructure/utils/money.utils.ts +0 -7
  28. package/src/infrastructure/utils/responsive.utils.ts +0 -66
  29. package/src/presentation/atoms/AspectRatio.tsx +0 -10
  30. package/src/presentation/atoms/Badge.tsx +0 -5
  31. package/src/presentation/atoms/BatteryPill.tsx +0 -5
  32. package/src/presentation/atoms/Button.tsx +0 -2
  33. package/src/presentation/atoms/Checkbox.tsx +0 -4
  34. package/src/presentation/atoms/CopyButton.tsx +93 -0
  35. package/src/presentation/atoms/Divider.tsx +0 -4
  36. package/src/presentation/atoms/HeatmapStrip.tsx +0 -10
  37. package/src/presentation/atoms/Hide.tsx +0 -24
  38. package/src/presentation/atoms/Icon.tsx +0 -4
  39. package/src/presentation/atoms/IconButton.tsx +0 -4
  40. package/src/presentation/atoms/Input.tsx +0 -4
  41. package/src/presentation/atoms/Kbd.tsx +0 -4
  42. package/src/presentation/atoms/Label.tsx +0 -4
  43. package/src/presentation/atoms/Link.tsx +0 -4
  44. package/src/presentation/atoms/Money.tsx +0 -6
  45. package/src/presentation/atoms/Progress.tsx +0 -6
  46. package/src/presentation/atoms/Radio.tsx +0 -4
  47. package/src/presentation/atoms/Separator.tsx +0 -4
  48. package/src/presentation/atoms/Show.tsx +0 -24
  49. package/src/presentation/atoms/Skeleton.tsx +0 -4
  50. package/src/presentation/atoms/Slider.tsx +0 -4
  51. package/src/presentation/atoms/Sparkline.tsx +0 -4
  52. package/src/presentation/atoms/Spinner.tsx +0 -4
  53. package/src/presentation/atoms/StatusDot.tsx +79 -0
  54. package/src/presentation/atoms/Switch.tsx +0 -4
  55. package/src/presentation/atoms/Text.tsx +0 -4
  56. package/src/presentation/atoms/Textarea.tsx +0 -4
  57. package/src/presentation/atoms/Toggle.tsx +0 -4
  58. package/src/presentation/atoms/Tooltip.tsx +0 -4
  59. package/src/presentation/atoms/index.ts +9 -9
  60. package/src/presentation/hooks/index.ts +0 -5
  61. package/src/presentation/hooks/useClickOutside.ts +0 -6
  62. package/src/presentation/hooks/useClipboard.ts +0 -4
  63. package/src/presentation/hooks/useDebounce.ts +0 -8
  64. package/src/presentation/hooks/useKeyboard.ts +0 -6
  65. package/src/presentation/hooks/useLocalStorage.ts +0 -4
  66. package/src/presentation/hooks/useMediaQuery.ts +0 -37
  67. package/src/presentation/hooks/useScrollLock.ts +0 -5
  68. package/src/presentation/hooks/useTheme.ts +9 -13
  69. package/src/presentation/hooks/useToggle.ts +0 -4
  70. package/src/presentation/molecules/ActiveFilterTags.tsx +0 -4
  71. package/src/presentation/molecules/Avatar.tsx +0 -5
  72. package/src/presentation/molecules/BookmarkButton.tsx +0 -15
  73. package/src/presentation/molecules/CheckboxGroup.tsx +0 -4
  74. package/src/presentation/molecules/Chip.tsx +0 -5
  75. package/src/presentation/molecules/CodeBlock.tsx +0 -5
  76. package/src/presentation/molecules/ConfirmButton.tsx +125 -0
  77. package/src/presentation/molecules/Field.tsx +0 -7
  78. package/src/presentation/molecules/FormField.tsx +1 -5
  79. package/src/presentation/molecules/InputGroup.tsx +0 -4
  80. package/src/presentation/molecules/ListItem.tsx +0 -4
  81. package/src/presentation/molecules/OptionTile.tsx +0 -10
  82. package/src/presentation/molecules/PasswordInput.tsx +43 -0
  83. package/src/presentation/molecules/RadioGroup.tsx +0 -4
  84. package/src/presentation/molecules/RatingStars.tsx +0 -8
  85. package/src/presentation/molecules/ScrollArea.tsx +0 -4
  86. package/src/presentation/molecules/SearchBox.tsx +0 -4
  87. package/src/presentation/molecules/SearchInput.tsx +110 -0
  88. package/src/presentation/molecules/Select.tsx +0 -4
  89. package/src/presentation/molecules/StatPill.tsx +0 -5
  90. package/src/presentation/molecules/TagInput.tsx +0 -7
  91. package/src/presentation/molecules/Textarea.tsx +0 -4
  92. package/src/presentation/molecules/Toggle.tsx +0 -4
  93. package/src/presentation/molecules/Toolbar.tsx +36 -0
  94. package/src/presentation/molecules/index.ts +25 -8
  95. package/src/presentation/organisms/Accordion.tsx +0 -4
  96. package/src/presentation/organisms/Alert.tsx +0 -4
  97. package/src/presentation/organisms/AvatarStack.tsx +0 -5
  98. package/src/presentation/organisms/Breadcrumb.tsx +0 -7
  99. package/src/presentation/organisms/Card.tsx +0 -6
  100. package/src/presentation/organisms/Checklist.tsx +0 -4
  101. package/src/presentation/organisms/Collapsible.tsx +0 -4
  102. package/src/presentation/organisms/ConfirmDialog.tsx +0 -8
  103. package/src/presentation/organisms/CoverArt.tsx +0 -9
  104. package/src/presentation/organisms/DataTable.tsx +0 -8
  105. package/src/presentation/organisms/DateRangePicker.tsx +0 -9
  106. package/src/presentation/organisms/DescriptionList.tsx +113 -0
  107. package/src/presentation/organisms/Dialog.tsx +0 -4
  108. package/src/presentation/organisms/Drawer.tsx +0 -6
  109. package/src/presentation/organisms/DropdownMenu.tsx +0 -4
  110. package/src/presentation/organisms/ErrorBoundary.tsx +0 -12
  111. package/src/presentation/organisms/FileUploader.tsx +0 -9
  112. package/src/presentation/organisms/FilterPopover.tsx +0 -8
  113. package/src/presentation/organisms/FormModal.tsx +0 -5
  114. package/src/presentation/organisms/HoverCard.tsx +0 -4
  115. package/src/presentation/organisms/KeyValueList.tsx +0 -4
  116. package/src/presentation/organisms/KpiCard.tsx +0 -6
  117. package/src/presentation/organisms/Lightbox.tsx +0 -7
  118. package/src/presentation/organisms/LoadingState.tsx +0 -4
  119. package/src/presentation/organisms/MapPreview.tsx +0 -9
  120. package/src/presentation/organisms/MetricCard.tsx +1 -5
  121. package/src/presentation/organisms/Modal.tsx +0 -4
  122. package/src/presentation/organisms/PhotoGallery.tsx +0 -9
  123. package/src/presentation/organisms/PlanPicker.tsx +0 -5
  124. package/src/presentation/organisms/Popover.tsx +0 -4
  125. package/src/presentation/organisms/Sheet.tsx +0 -4
  126. package/src/presentation/organisms/StatCard.tsx +0 -5
  127. package/src/presentation/organisms/StatsGrid.tsx +110 -0
  128. package/src/presentation/organisms/StatusBadge.tsx +0 -12
  129. package/src/presentation/organisms/Stepper.tsx +0 -5
  130. package/src/presentation/organisms/Table.tsx +0 -4
  131. package/src/presentation/organisms/Tabs.tsx +0 -7
  132. package/src/presentation/organisms/Timeline.tsx +0 -8
  133. package/src/presentation/organisms/Toaster.tsx +0 -9
  134. package/src/presentation/organisms/ToggleGroup.tsx +0 -4
  135. package/src/presentation/organisms/index.ts +10 -11
  136. package/src/presentation/templates/CrudPageTemplate.tsx +69 -0
  137. package/src/presentation/templates/Form.tsx +0 -4
  138. package/src/presentation/templates/List.tsx +0 -4
  139. package/src/presentation/templates/PageHeader.tsx +0 -4
  140. package/src/presentation/templates/PageLayout.tsx +0 -4
  141. package/src/presentation/templates/ProjectSkeleton.tsx +0 -4
  142. package/src/presentation/templates/ResponsiveContainer.tsx +0 -43
  143. package/src/presentation/templates/Section.tsx +0 -4
  144. package/src/presentation/templates/SettingsLayout.tsx +102 -0
  145. package/src/presentation/templates/index.ts +7 -6
  146. package/src/styles/app.css +0 -18
  147. package/src/styles/bridge.css +0 -34
  148. package/src/styles/compile.css +0 -13
  149. package/src/styles/source.css +0 -12
  150. package/src/styles/theme.css +0 -27
@@ -1,7 +1,3 @@
1
- /**
2
- * useMediaQuery & useBreakpoint Hooks
3
- * @description Enhanced responsive breakpoint detection with helper functions
4
- */
5
1
 
6
2
  import { useEffect, useState, useCallback, useRef, useMemo, useSyncExternalStore } from 'react';
7
3
  import type {
@@ -16,16 +12,6 @@ import {
16
12
  } from '../../infrastructure/constants/breakpoint.constants';
17
13
  import { TIMING } from '../../infrastructure/constants/timing.constants';
18
14
 
19
- /**
20
- * Simple media query hook for a single breakpoint
21
- * @param breakpoint - The breakpoint to check
22
- * @returns Whether the media query matches
23
- *
24
- * @example
25
- * ```tsx
26
- * const isDesktop = useMediaQuery('lg') // true on lg screens and above
27
- * ```
28
- */
29
15
  export function useMediaQuery(breakpoint: Breakpoint): boolean {
30
16
  const query = useMemo(() => createMediaQuery(breakpoint), [breakpoint]);
31
17
 
@@ -49,24 +35,7 @@ export function useMediaQuery(breakpoint: Breakpoint): boolean {
49
35
  return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
50
36
  }
51
37
 
52
- /**
53
- * Enhanced breakpoint hook with helper functions
54
- * @returns Object containing current breakpoint and helper functions
55
- *
56
- * @example
57
- * ```tsx
58
- * const { currentBreakpoint, isMobile, isDesktop } = useBreakpoint()
59
- *
60
- * return (
61
- * <div>
62
- * {isMobile && <MobileNav />}
63
- * {isDesktop && <DesktopNav />}
64
- * </div>
65
- * )
66
- * ```
67
- */
68
38
  export function useBreakpoint(): UseBreakpointReturn {
69
- // FIX: Sort breakpoints once and memoize
70
39
  const sortedBreakpoints = useMemo(() => {
71
40
  return Object.entries(BREAKPOINTS).sort(
72
41
  ([, a], [, b]) => b.min - a.min
@@ -74,7 +43,6 @@ export function useBreakpoint(): UseBreakpointReturn {
74
43
  }, []);
75
44
 
76
45
  const [currentBreakpoint, setCurrentBreakpoint] = useState<Breakpoint>(() => {
77
- // FIX: SSR-safe initialization
78
46
  if (typeof window === 'undefined') return 'lg';
79
47
 
80
48
  const width = window.innerWidth;
@@ -84,7 +52,6 @@ export function useBreakpoint(): UseBreakpointReturn {
84
52
  return 'xs';
85
53
  });
86
54
 
87
- // FIX: Use useRef for resize timer to prevent timer issues
88
55
  const resizeTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
89
56
 
90
57
  useEffect(() => {
@@ -99,10 +66,8 @@ export function useBreakpoint(): UseBreakpointReturn {
99
66
  }
100
67
  };
101
68
 
102
- // Initial check
103
69
  updateBreakpoint();
104
70
 
105
- // Debounced resize listener
106
71
  const handleResize = () => {
107
72
  if (resizeTimerRef.current) {
108
73
  clearTimeout(resizeTimerRef.current);
@@ -120,7 +85,6 @@ export function useBreakpoint(): UseBreakpointReturn {
120
85
  };
121
86
  }, [sortedBreakpoints]);
122
87
 
123
- // Helper functions
124
88
  const matches = useCallback(
125
89
  (breakpoint: Breakpoint | Breakpoint[]): boolean => {
126
90
  if (Array.isArray(breakpoint)) {
@@ -145,7 +109,6 @@ export function useBreakpoint(): UseBreakpointReturn {
145
109
  [currentBreakpoint]
146
110
  );
147
111
 
148
- // Computed values
149
112
  const isXs = currentBreakpoint === 'xs';
150
113
  const isSm = currentBreakpoint === 'sm';
151
114
  const isMd = currentBreakpoint === 'md';
@@ -1,11 +1,6 @@
1
- /**
2
- * useScrollLock Hook
3
- * @description Lock/unlock body scroll while preserving scrollbar gutter
4
- */
5
1
 
6
2
  import { useEffect } from 'react';
7
3
 
8
- /** Width of the vertical scrollbar, so locking scroll doesn't shift layout. */
9
4
  const measureScrollbarWidth = (): number => {
10
5
  if (typeof window === 'undefined' || typeof document === 'undefined') return 0;
11
6
  return Math.max(0, window.innerWidth - document.documentElement.clientWidth);
@@ -1,7 +1,3 @@
1
- /**
2
- * useTheme Hook
3
- * @description Theme toggle functionality with SSR support and system preference detection
4
- */
5
1
 
6
2
  import { useEffect, useState, useCallback } from 'react';
7
3
 
@@ -25,9 +21,16 @@ const getSystemTheme = (): 'light' | 'dark' => {
25
21
  return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
26
22
  };
27
23
 
24
+ const writeThemeStorage = (theme: Theme): void => {
25
+ try {
26
+ localStorage.setItem(THEME_STORAGE_KEY, theme);
27
+ } catch {
28
+ return;
29
+ }
30
+ };
31
+
28
32
  export function useTheme(): UseThemeReturn {
29
33
  const [theme, setThemeState] = useState<Theme>(() => {
30
- // FIX: SSR-safe initialization
31
34
  if (typeof window === 'undefined') return 'system';
32
35
 
33
36
  try {
@@ -47,7 +50,6 @@ export function useTheme(): UseThemeReturn {
47
50
  return theme;
48
51
  });
49
52
 
50
- // Listen for system theme changes
51
53
  useEffect(() => {
52
54
  if (theme !== 'system') return;
53
55
 
@@ -66,15 +68,9 @@ export function useTheme(): UseThemeReturn {
66
68
 
67
69
  root.classList.remove('light', 'dark');
68
70
  root.classList.add(themeToApply);
69
-
70
- try {
71
- localStorage.setItem(THEME_STORAGE_KEY, theme);
72
- } catch {
73
- // Ignore storage errors
74
- }
71
+ writeThemeStorage(theme);
75
72
  }, [theme]);
76
73
 
77
- // FIX: Memoize callbacks to prevent unnecessary re-renders
78
74
  const toggleTheme = useCallback(() => {
79
75
  setThemeState((prev) => {
80
76
  if (prev === 'light') return 'dark';
@@ -1,7 +1,3 @@
1
- /**
2
- * useToggle Hook
3
- * @description Toggle boolean state with explicit setValue
4
- */
5
1
 
6
2
  import { useCallback, useState, useMemo } from 'react';
7
3
 
@@ -1,7 +1,3 @@
1
- /**
2
- * ActiveFilterTags Component (Molecule)
3
- * @description Display active filters with remove buttons
4
- */
5
1
 
6
2
  import { X } from 'lucide-react';
7
3
  import { cn } from '../../infrastructure/utils';
@@ -1,7 +1,3 @@
1
- /**
2
- * Avatar Component (Molecule)
3
- * @description User avatar with image and fallback (shadcn/ui compatible)
4
- */
5
1
 
6
2
  import { forwardRef, type HTMLAttributes, type ComponentPropsWithoutRef } from 'react';
7
3
  import { cn } from '../../infrastructure/utils';
@@ -11,7 +7,6 @@ import type { ExtendedSizes } from '../../infrastructure/constants';
11
7
  export interface AvatarProps extends HTMLAttributes<HTMLDivElement>, BaseProps {
12
8
  name?: string;
13
9
  size?: ExtendedSizes;
14
- /** Accent hue (0-360) for the initials fallback, overriding the name-hash tone. */
15
10
  hue?: number;
16
11
  }
17
12
 
@@ -1,8 +1,3 @@
1
- /**
2
- * BookmarkButton Component (Molecule)
3
- * @description Accessible toggleable button for saving, bookmarking, or favoriting items.
4
- * Supports pill (icon + label) and icon-only variants, multiple sizes, semantic tones, and custom icons.
5
- */
6
1
 
7
2
  import React, { forwardRef } from 'react';
8
3
  import { Star } from 'lucide-react';
@@ -15,25 +10,15 @@ export type BookmarkTone = 'warning' | 'primary' | 'success' | 'default';
15
10
  export interface BookmarkButtonProps
16
11
  extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children' | 'onChange' | 'onToggle'>,
17
12
  BaseProps {
18
- /** Whether the item is currently saved / bookmarked / active. */
19
13
  active?: boolean;
20
- /** Callback fired when clicked or toggled. Passes the inverted active state. */
21
14
  onToggle?: (nextActive: boolean) => void;
22
- /** Display variant: 'pill' shows icon + label, 'icon' renders a square/round icon-only button. */
23
15
  variant?: 'pill' | 'icon';
24
- /** Size variant */
25
16
  size?: 'xs' | 'sm' | 'md' | 'lg';
26
- /** Inactive label (default: "Save"). Used as text in pill mode and aria-label in icon mode. */
27
17
  label?: string;
28
- /** Active label (default: "Saved"). */
29
18
  activeLabel?: string;
30
- /** Custom icon when inactive. Defaults to a Star outline. */
31
19
  icon?: React.ReactNode;
32
- /** Custom icon when active. Defaults to a filled Star. */
33
20
  activeIcon?: React.ReactNode;
34
- /** Color theme tone when active (default: 'warning' for warm bookmark gold). */
35
21
  tone?: BookmarkTone;
36
- /** Loading indicator */
37
22
  loading?: boolean;
38
23
  }
39
24
 
@@ -1,7 +1,3 @@
1
- /**
2
- * CheckboxGroup Component (Molecule)
3
- * @description Group of checkboxes
4
- */
5
1
 
6
2
  import { forwardRef, type HTMLAttributes } from 'react';
7
3
  import { cn } from '../../infrastructure/utils';
@@ -1,7 +1,3 @@
1
- /**
2
- * Chip Component (Molecule)
3
- * @description Selectable/removable tag
4
- */
5
1
 
6
2
  import { forwardRef } from 'react';
7
3
  import { cn } from '../../infrastructure/utils';
@@ -15,7 +11,6 @@ export interface ChipProps extends BaseProps {
15
11
  onRemove?: () => void;
16
12
  onClick?: () => void;
17
13
  removable?: boolean;
18
- /** Toggle state for filter chips — renders as a pressed button when set. */
19
14
  selected?: boolean;
20
15
  }
21
16
 
@@ -1,7 +1,3 @@
1
- /**
2
- * CodeBlock Component (Molecule)
3
- * @description Syntax-highlighted code block with copy button and expand/collapse
4
- */
5
1
 
6
2
  import { useState, useEffect } from 'react';
7
3
  import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
@@ -15,7 +11,6 @@ export interface CodeBlockProps extends BaseProps {
15
11
  language?: string;
16
12
  filename?: string;
17
13
  showLineNumbers?: boolean;
18
- /** Lines threshold to enable collapse/expand. */
19
14
  collapseThreshold?: number;
20
15
  }
21
16
 
@@ -0,0 +1,125 @@
1
+ import * as React from 'react';
2
+ import { Button, type ButtonProps } from '../atoms/Button';
3
+ import { Check, X } from 'lucide-react';
4
+ import { cn } from '../../infrastructure/utils';
5
+
6
+ export interface ConfirmButtonProps extends Omit<ButtonProps, 'onConfirm'> {
7
+ onConfirm: () => void | Promise<void>;
8
+ confirmText?: string;
9
+ confirmLabel?: string;
10
+ cancelLabel?: string;
11
+ timeoutMs?: number;
12
+ }
13
+
14
+ export const ConfirmButton = React.forwardRef<HTMLButtonElement, ConfirmButtonProps>(
15
+ (
16
+ {
17
+ onConfirm,
18
+ confirmText = 'Emin misiniz?',
19
+ confirmLabel = 'Evet',
20
+ cancelLabel = 'İptal',
21
+ timeoutMs = 4000,
22
+ children,
23
+ variant = 'destructive',
24
+ size = 'default',
25
+ className,
26
+ disabled,
27
+ ...props
28
+ },
29
+ ref
30
+ ) => {
31
+ const [isConfirming, setIsConfirming] = React.useState(false);
32
+ const [isLoading, setIsLoading] = React.useState(false);
33
+ const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
34
+
35
+ React.useEffect(() => {
36
+ return () => {
37
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
38
+ };
39
+ }, []);
40
+
41
+ const resetConfirmation = () => {
42
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
43
+ setIsConfirming(false);
44
+ };
45
+
46
+ const handleInitialClick = (e: React.MouseEvent<HTMLButtonElement>) => {
47
+ e.preventDefault();
48
+ e.stopPropagation();
49
+ setIsConfirming(true);
50
+
51
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
52
+ timeoutRef.current = setTimeout(() => {
53
+ setIsConfirming(false);
54
+ }, timeoutMs);
55
+ };
56
+
57
+ const handleConfirm = async (e: React.MouseEvent) => {
58
+ e.preventDefault();
59
+ e.stopPropagation();
60
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
61
+
62
+ try {
63
+ setIsLoading(true);
64
+ await onConfirm();
65
+ } finally {
66
+ setIsLoading(false);
67
+ setIsConfirming(false);
68
+ }
69
+ };
70
+
71
+ const handleCancel = (e: React.MouseEvent) => {
72
+ e.preventDefault();
73
+ e.stopPropagation();
74
+ resetConfirmation();
75
+ };
76
+
77
+ if (isConfirming) {
78
+ return (
79
+ <div className={cn('inline-flex items-center gap-1.5 animate-in fade-in zoom-in-95 duration-150', className)}>
80
+ <span className="text-xs font-semibold text-destructive px-1 select-none">
81
+ {confirmText}
82
+ </span>
83
+ <Button
84
+ type="button"
85
+ size="xs"
86
+ variant="destructive"
87
+ loading={isLoading}
88
+ onClick={handleConfirm}
89
+ className="gap-1 h-7 px-2"
90
+ >
91
+ <Check className="size-3" />
92
+ <span>{confirmLabel}</span>
93
+ </Button>
94
+ <Button
95
+ type="button"
96
+ size="xs"
97
+ variant="ghost"
98
+ disabled={isLoading}
99
+ onClick={handleCancel}
100
+ className="h-7 px-1.5 text-muted-foreground hover:text-foreground"
101
+ >
102
+ <X className="size-3" />
103
+ <span className="sr-only">{cancelLabel}</span>
104
+ </Button>
105
+ </div>
106
+ );
107
+ }
108
+
109
+ return (
110
+ <Button
111
+ ref={ref}
112
+ variant={variant}
113
+ size={size}
114
+ disabled={disabled || isLoading}
115
+ onClick={handleInitialClick}
116
+ className={className}
117
+ {...props}
118
+ >
119
+ {children}
120
+ </Button>
121
+ );
122
+ }
123
+ );
124
+
125
+ ConfirmButton.displayName = 'ConfirmButton';
@@ -13,18 +13,11 @@ export interface FieldProps {
13
13
  error?: ReactNode;
14
14
  required?: boolean;
15
15
  htmlFor?: string;
16
- /** Keep the label in the accessibility tree while hiding it visually. */
17
16
  hideLabel?: boolean;
18
17
  className?: string;
19
18
  children: ReactNode;
20
19
  }
21
20
 
22
- /**
23
- * Label + hint/error wrapper for a single form control. When the child is a
24
- * valid element, the control id (existing id wins, then htmlFor, then a
25
- * generated one) and the aria wiring for hint/error are injected into it so
26
- * the label association is automatic.
27
- */
28
21
  export function Field({ label, hint, error, required, htmlFor, hideLabel = false, className, children }: FieldProps) {
29
22
  const autoId = useId();
30
23
  const childId = isValidElement(children) ? (children as ReactElement<FieldControlProps>).props.id : undefined;
@@ -1,7 +1,3 @@
1
- /**
2
- * FormField Component (Molecule)
3
- * @description Label + Input combination (Responsive)
4
- */
5
1
 
6
2
  import { forwardRef, useId } from 'react';
7
3
  import { cn, getSpaceY } from '../../infrastructure/utils';
@@ -16,7 +12,7 @@ export interface FormFieldProps extends BaseProps {
16
12
  required?: boolean;
17
13
  id?: string;
18
14
  inputProps?: React.ComponentProps<typeof Input>;
19
- fullWidth?: boolean; // Enable responsive full width
15
+ fullWidth?: boolean;
20
16
  }
21
17
 
22
18
  export const FormField = forwardRef<HTMLInputElement, FormFieldProps>(
@@ -1,7 +1,3 @@
1
- /**
2
- * InputGroup Component (Molecule)
3
- * @description Input with prefix/suffix elements
4
- */
5
1
 
6
2
  import { forwardRef, type ReactNode, type HTMLAttributes } from 'react';
7
3
  import { cn } from '../../infrastructure/utils';
@@ -1,7 +1,3 @@
1
- /**
2
- * ListItem Component (Molecule)
3
- * @description Reusable list item with icon, content, and actions
4
- */
5
1
 
6
2
  import { forwardRef, type ReactNode, type MouseEvent } from 'react';
7
3
  import { cn } from '../../infrastructure/utils';
@@ -1,9 +1,3 @@
1
- /**
2
- * OptionTile Component (Molecule)
3
- * @description Graded answer tile for exercises — a native input (radio or
4
- * checkbox) wrapped in its label, with an optional A/B/C/D key badge and
5
- * reveal states (correct / wrong / missed) for post-submission feedback.
6
- */
7
1
 
8
2
  import { forwardRef } from 'react';
9
3
  import { Check, X } from 'lucide-react';
@@ -14,15 +8,11 @@ export type OptionTileState = 'correct' | 'wrong' | 'missed';
14
8
 
15
9
  export interface OptionTileProps extends BaseProps {
16
10
  label: React.ReactNode;
17
- /** Key badge rendered before the label (A, B, C…). */
18
11
  keyBadge?: string;
19
- /** Native input type — radio (single) or checkbox (multi-select). */
20
12
  type?: 'radio' | 'checkbox';
21
13
  checked?: boolean;
22
- /** Reveal semantics — overrides selection styling after grading. */
23
14
  state?: OptionTileState;
24
15
  disabled?: boolean;
25
- /** Group name for radio inputs. */
26
16
  name?: string;
27
17
  onChange: (checked: boolean) => void;
28
18
  }
@@ -0,0 +1,43 @@
1
+ import * as React from 'react';
2
+ import { Eye, EyeOff } from 'lucide-react';
3
+ import { Input, type InputProps } from '../atoms/Input';
4
+ import { cn } from '../../infrastructure/utils';
5
+
6
+ export interface PasswordInputProps extends Omit<InputProps, 'type'> {
7
+ showToggle?: boolean;
8
+ }
9
+
10
+ export const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(
11
+ ({ showToggle = true, className, ...props }, ref) => {
12
+ const [visible, setVisible] = React.useState(false);
13
+
14
+ const toggleVisibility = (e: React.MouseEvent) => {
15
+ e.preventDefault();
16
+ e.stopPropagation();
17
+ setVisible((prev) => !prev);
18
+ };
19
+
20
+ return (
21
+ <div className={cn('relative flex items-center w-full', className)}>
22
+ <Input
23
+ ref={ref}
24
+ type={visible ? 'text' : 'password'}
25
+ className="pr-10"
26
+ {...props}
27
+ />
28
+ {showToggle && (
29
+ <button
30
+ type="button"
31
+ onClick={toggleVisibility}
32
+ aria-label={visible ? 'Şifreyi gizle' : 'Şifreyi göster'}
33
+ className="absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground focus:outline-none transition-colors p-1"
34
+ >
35
+ {visible ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
36
+ </button>
37
+ )}
38
+ </div>
39
+ );
40
+ }
41
+ );
42
+
43
+ PasswordInput.displayName = 'PasswordInput';
@@ -1,7 +1,3 @@
1
- /**
2
- * RadioGroup Component (Molecule)
3
- * @description Group of radio buttons
4
- */
5
1
 
6
2
  import { forwardRef, type HTMLAttributes } from 'react';
7
3
  import { cn } from '../../infrastructure/utils';
@@ -1,18 +1,10 @@
1
- /**
2
- * RatingStars Component (Molecule)
3
- * @description 0–5 rating with visual half-star precision, numeric value and
4
- * optional review count
5
- */
6
1
 
7
2
  import { cn } from '../../infrastructure/utils';
8
3
  import type { BaseProps } from '../../domain/types';
9
4
 
10
5
  export interface RatingStarsProps extends BaseProps {
11
- /** 0–5 rating; halves render as partial star fills. */
12
6
  value: number;
13
- /** Optional review count rendered in parentheses. */
14
7
  count?: number;
15
- /** Locale-formatted count override (e.g. 1,204). */
16
8
  formatCount?: (count: number) => string;
17
9
  size?: 'sm' | 'md';
18
10
  }
@@ -1,7 +1,3 @@
1
- /**
2
- * ScrollArea Component (Molecule)
3
- * @description Custom scrollable area (Shadcn/ui compatible)
4
- */
5
1
 
6
2
  import * as React from 'react';
7
3
  import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
@@ -1,7 +1,3 @@
1
- /**
2
- * SearchBox Component (Molecule)
3
- * @description Input with search icon
4
- */
5
1
 
6
2
  import { forwardRef } from 'react';
7
3
  import { X } from 'lucide-react';
@@ -0,0 +1,110 @@
1
+ import * as React from 'react';
2
+ import { Search, X } from 'lucide-react';
3
+ import { Input, type InputProps } from '../atoms/Input';
4
+ import { Spinner } from '../atoms/Spinner';
5
+ import { cn } from '../../infrastructure/utils';
6
+
7
+ export interface SearchInputProps extends Omit<InputProps, 'onChange' | 'value' | 'defaultValue'> {
8
+ value?: string;
9
+ defaultValue?: string;
10
+ onChange?: (value: string) => void;
11
+ onSearch?: (value: string) => void;
12
+ onClear?: () => void;
13
+ debounceMs?: number;
14
+ loading?: boolean;
15
+ clearable?: boolean;
16
+ }
17
+
18
+ export const SearchInput = React.forwardRef<HTMLInputElement, SearchInputProps>(
19
+ (
20
+ {
21
+ value: controlledValue,
22
+ defaultValue = '',
23
+ onChange,
24
+ onSearch,
25
+ onClear,
26
+ debounceMs = 0,
27
+ loading = false,
28
+ clearable = true,
29
+ placeholder = 'Ara...',
30
+ className,
31
+ ...props
32
+ },
33
+ ref
34
+ ) => {
35
+ const isControlled = controlledValue !== undefined;
36
+ const [internalValue, setInternalValue] = React.useState(defaultValue);
37
+ const currentValue = isControlled ? controlledValue : internalValue;
38
+
39
+ const debounceTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
40
+
41
+ React.useEffect(() => {
42
+ return () => {
43
+ if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
44
+ };
45
+ }, []);
46
+
47
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
48
+ const nextVal = e.target.value;
49
+ if (!isControlled) {
50
+ setInternalValue(nextVal);
51
+ }
52
+ onChange?.(nextVal);
53
+
54
+ if (debounceMs > 0 && onSearch) {
55
+ if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
56
+ debounceTimerRef.current = setTimeout(() => {
57
+ onSearch(nextVal);
58
+ }, debounceMs);
59
+ }
60
+ };
61
+
62
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
63
+ if (e.key === 'Enter') {
64
+ if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
65
+ onSearch?.(currentValue);
66
+ }
67
+ };
68
+
69
+ const handleClear = () => {
70
+ if (!isControlled) {
71
+ setInternalValue('');
72
+ }
73
+ onChange?.('');
74
+ onClear?.();
75
+ onSearch?.('');
76
+ };
77
+
78
+ return (
79
+ <div className={cn('relative flex items-center w-full', className)}>
80
+ <Search className="absolute left-3 size-4 text-muted-foreground pointer-events-none shrink-0" />
81
+ <Input
82
+ ref={ref}
83
+ type="text"
84
+ value={currentValue}
85
+ onChange={handleChange}
86
+ onKeyDown={handleKeyDown}
87
+ placeholder={placeholder}
88
+ className="pl-9 pr-9"
89
+ {...props}
90
+ />
91
+ <div className="absolute right-2.5 flex items-center">
92
+ {loading ? (
93
+ <Spinner size="sm" />
94
+ ) : clearable && currentValue ? (
95
+ <button
96
+ type="button"
97
+ aria-label="Aramayı temizle"
98
+ onClick={handleClear}
99
+ className="text-muted-foreground hover:text-foreground transition-colors p-1"
100
+ >
101
+ <X className="size-3.5" />
102
+ </button>
103
+ ) : null}
104
+ </div>
105
+ </div>
106
+ );
107
+ }
108
+ );
109
+
110
+ SearchInput.displayName = 'SearchInput';
@@ -1,7 +1,3 @@
1
- /**
2
- * Select Component (Molecule)
3
- * @description Dropdown select (Shadcn/Radix UI compatible)
4
- */
5
1
 
6
2
  import * as React from 'react';
7
3
  import * as SelectPrimitive from '@radix-ui/react-select';