@umituz/web-design-system 2.6.3 → 2.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/web-design-system",
3
- "version": "2.6.3",
3
+ "version": "2.7.0",
4
4
  "private": false,
5
5
  "description": "Web Design System - Atomic Design components (Atoms, Molecules, Organisms, Templates) for React applications",
6
6
  "main": "./src/index.ts",
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Responsive System Type Definitions
3
+ * @description Centralized breakpoint types for responsive design
4
+ */
5
+
6
+ export type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
7
+
8
+ export type ScreenSize = Breakpoint;
9
+
10
+ export interface BreakpointValue {
11
+ min: number;
12
+ max?: number;
13
+ }
14
+
15
+ export interface ResponsiveProps {
16
+ xs?: boolean;
17
+ sm?: boolean;
18
+ md?: boolean;
19
+ lg?: boolean;
20
+ xl?: boolean;
21
+ '2xl'?: boolean;
22
+ }
23
+
24
+ export interface ShowProps {
25
+ above?: Breakpoint; // Show on larger screens (min-width)
26
+ below?: Breakpoint; // Show on smaller screens (max-width)
27
+ at?: Breakpoint; // Show only at this breakpoint
28
+ }
29
+
30
+ export interface HideProps {
31
+ above?: Breakpoint; // Hide on larger screens
32
+ below?: Breakpoint; // Hide on smaller screens
33
+ at?: Breakpoint; // Hide only at this breakpoint
34
+ }
35
+
36
+ export interface BreakpointState {
37
+ currentBreakpoint: Breakpoint;
38
+ isXs: boolean;
39
+ isSm: boolean;
40
+ isMd: boolean;
41
+ isLg: boolean;
42
+ isXl: boolean;
43
+ is2Xl: boolean;
44
+ isMobile: boolean; // xs, sm
45
+ isTablet: boolean; // md, lg
46
+ isDesktop: boolean; // xl, 2xl
47
+ }
48
+
49
+ export interface UseBreakpointReturn extends BreakpointState {
50
+ matches: (breakpoint: Breakpoint | Breakpoint[]) => boolean;
51
+ isGreaterThan: (breakpoint: Breakpoint) => boolean;
52
+ isLessThan: (breakpoint: Breakpoint) => boolean;
53
+ }
@@ -12,3 +12,14 @@ export type {
12
12
  ChildrenProps,
13
13
  PolymorphicProps,
14
14
  } from './component.types';
15
+
16
+ export type {
17
+ Breakpoint,
18
+ ScreenSize,
19
+ BreakpointValue,
20
+ ResponsiveProps,
21
+ ShowProps,
22
+ HideProps,
23
+ BreakpointState,
24
+ UseBreakpointReturn,
25
+ } from './breakpoint.types';
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Responsive System Constants
3
+ * @description Centralized breakpoint values matching Tailwind CSS defaults
4
+ */
5
+
6
+ import type { Breakpoint, BreakpointValue } from '../../domain/types/breakpoint.types';
7
+
8
+ /**
9
+ * Breakpoint values matching Tailwind CSS default breakpoints
10
+ * https://tailwindcss.com/docs/screens
11
+ */
12
+ export const BREAKPOINTS: Record<Breakpoint, BreakpointValue> = {
13
+ xs: { min: 0 },
14
+ sm: { min: 640 },
15
+ md: { min: 768 },
16
+ lg: { min: 1024 },
17
+ xl: { min: 1280 },
18
+ '2xl': { min: 1536 },
19
+ };
20
+
21
+ /**
22
+ * Create a media query string for a given breakpoint
23
+ * @param breakpoint - The breakpoint to create a query for
24
+ * @returns CSS media query string (e.g., "(min-width: 640px)")
25
+ */
26
+ export const createMediaQuery = (breakpoint: Breakpoint): string => {
27
+ const bp = BREAKPOINTS[breakpoint];
28
+ if (bp.max) {
29
+ return `(min-width: ${bp.min}px) and (max-width: ${bp.max}px)`;
30
+ }
31
+ return `(min-width: ${bp.min}px)`;
32
+ };
33
+
34
+ /**
35
+ * Create a max-width media query
36
+ * @param breakpoint - The breakpoint to create a max query for
37
+ * @returns CSS media query string (e.g., "(max-width: 639px)")
38
+ */
39
+ export const createMaxMediaQuery = (breakpoint: Breakpoint): string => {
40
+ const bp = BREAKPOINTS[breakpoint];
41
+ const maxValue = bp.min - 1;
42
+ return `(max-width: ${maxValue}px)`;
43
+ };
44
+
45
+ /**
46
+ * Breakpoint order for comparison operations
47
+ */
48
+ export const BREAKPOINT_ORDER: Breakpoint[] = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'];
49
+
50
+ /**
51
+ * Compare two breakpoints
52
+ * @returns -1 if a < b, 0 if a === b, 1 if a > b
53
+ */
54
+ export const compareBreakpoints = (a: Breakpoint, b: Breakpoint): number => {
55
+ const indexA = BREAKPOINT_ORDER.indexOf(a);
56
+ const indexB = BREAKPOINT_ORDER.indexOf(b);
57
+ return indexA - indexB;
58
+ };
59
+
60
+ /**
61
+ * Check if breakpoint A is greater than breakpoint B
62
+ */
63
+ export const isBreakpointGreaterThan = (a: Breakpoint, b: Breakpoint): boolean => {
64
+ return compareBreakpoints(a, b) > 0;
65
+ };
66
+
67
+ /**
68
+ * Check if breakpoint A is less than breakpoint B
69
+ */
70
+ export const isBreakpointLessThan = (a: Breakpoint, b: Breakpoint): boolean => {
71
+ return compareBreakpoints(a, b) < 0;
72
+ };
@@ -11,3 +11,13 @@ export {
11
11
  SIZE_MAP,
12
12
  COLOR_MAP,
13
13
  } from './component.constants';
14
+
15
+ export {
16
+ BREAKPOINTS,
17
+ createMediaQuery,
18
+ createMaxMediaQuery,
19
+ BREAKPOINT_ORDER,
20
+ compareBreakpoints,
21
+ isBreakpointGreaterThan,
22
+ isBreakpointLessThan,
23
+ } from './breakpoint.constants';
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Hide Component (Atom)
3
+ * @description Conditionally hide content based on screen breakpoint
4
+ */
5
+
6
+ import { useBreakpoint } from '../hooks/useMediaQuery';
7
+ import type { Breakpoint, HideProps } from '../../domain/types/breakpoint.types';
8
+ import type { BaseProps } from '../../domain/types';
9
+
10
+ export interface HideComponentProps extends BaseProps, HideProps {
11
+ children: React.ReactNode;
12
+ }
13
+
14
+ /**
15
+ * Conditionally hide content based on breakpoint
16
+ * @param above - Hide on screens larger than this breakpoint
17
+ * @param below - Hide on screens smaller than this breakpoint
18
+ * @param at - Hide only on this specific breakpoint
19
+ * @param children - Content to hide
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * // Hide on mobile (md and below)
24
+ * <Hide below="md"><DesktopNav /></Hide>
25
+ *
26
+ * // Hide on desktop (lg and above)
27
+ * <Hide above="lg"><MobileNav /></Hide>
28
+ *
29
+ * // Hide only on tablet
30
+ * <Hide at="md"><TabletContent /></Hide>
31
+ * ```
32
+ */
33
+ export const Hide = ({ above, below, at, children, className }: HideComponentProps) => {
34
+ const { matches, isGreaterThan, isLessThan } = useBreakpoint();
35
+
36
+ // Determine if content should be hidden
37
+ let shouldHide = false;
38
+
39
+ if (above) {
40
+ shouldHide = shouldHide || isGreaterThan(above);
41
+ }
42
+
43
+ if (below) {
44
+ shouldHide = shouldHide || isLessThan(below);
45
+ }
46
+
47
+ if (at) {
48
+ shouldHide = shouldHide || matches(at);
49
+ }
50
+
51
+ if (shouldHide) {
52
+ return null;
53
+ }
54
+
55
+ return <div className={className || ''}>{children}</div>;
56
+ };
57
+
58
+ Hide.displayName = 'Hide';
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Show Component (Atom)
3
+ * @description Conditionally render content based on screen breakpoint
4
+ */
5
+
6
+ import { useBreakpoint } from '../hooks/useMediaQuery';
7
+ import type { Breakpoint, ShowProps } from '../../domain/types/breakpoint.types';
8
+ import type { BaseProps } from '../../domain/types';
9
+
10
+ export interface ShowComponentProps extends BaseProps, ShowProps {
11
+ children: React.ReactNode;
12
+ }
13
+
14
+ /**
15
+ * Conditionally show content based on breakpoint
16
+ * @param above - Show on screens larger than this breakpoint
17
+ * @param below - Show on screens smaller than this breakpoint
18
+ * @param at - Show only on this specific breakpoint
19
+ * @param children - Content to show
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * // Show on desktop (lg and above)
24
+ * <Show above="lg"><DesktopNav /></Show>
25
+ *
26
+ * // Show on mobile (sm and below)
27
+ * <Show below="md"><MobileNav /></Show>
28
+ *
29
+ * // Show only on tablet
30
+ * <Show at="md"><TabletContent /></Show>
31
+ * ```
32
+ */
33
+ export const Show = ({ above, below, at, children, className }: ShowComponentProps) => {
34
+ const { matches, isGreaterThan, isLessThan } = useBreakpoint();
35
+
36
+ // Determine if content should be shown
37
+ let shouldShow = true;
38
+
39
+ if (above) {
40
+ shouldShow = shouldShow && isGreaterThan(above);
41
+ }
42
+
43
+ if (below) {
44
+ shouldShow = shouldShow && isLessThan(below);
45
+ }
46
+
47
+ if (at) {
48
+ shouldShow = shouldShow && matches(at);
49
+ }
50
+
51
+ if (!shouldShow) {
52
+ return null;
53
+ }
54
+
55
+ return <div className={className || ''}>{children}</div>;
56
+ };
57
+
58
+ Show.displayName = 'Show';
@@ -57,3 +57,9 @@ export type { SwitchProps } from './Switch';
57
57
  export { Separator } from './Separator';
58
58
 
59
59
  export { Toggle, toggleVariants } from './Toggle';
60
+
61
+ export { Show } from './Show';
62
+ export type { ShowComponentProps } from './Show';
63
+
64
+ export { Hide } from './Hide';
65
+ export type { HideComponentProps } from './Hide';
@@ -8,7 +8,7 @@ export { useTheme } from './useTheme';
8
8
  export type { Theme, UseThemeReturn } from './useTheme';
9
9
 
10
10
  export { useMediaQuery, useBreakpoint } from './useMediaQuery';
11
- export type { Breakpoint } from './useMediaQuery';
11
+ export type { Breakpoint, UseBreakpointReturn } from '../../domain/types/breakpoint.types';
12
12
 
13
13
  export { useLocalStorage } from './useLocalStorage';
14
14
 
@@ -1,28 +1,39 @@
1
1
  /**
2
- * useMediaQuery Hook
3
- * @description Responsive breakpoint detection
2
+ * useMediaQuery & useBreakpoint Hooks
3
+ * @description Enhanced responsive breakpoint detection with helper functions
4
4
  */
5
5
 
6
- import { useEffect, useState } from 'react';
7
-
8
- export type Breakpoint = 'sm' | 'md' | 'lg' | 'xl' | '2xl';
9
-
10
- const breakpointValues: Record<Breakpoint, number> = {
11
- sm: 640,
12
- md: 768,
13
- lg: 1024,
14
- xl: 1280,
15
- '2xl': 1536,
16
- };
6
+ import { useEffect, useState, useCallback } from 'react';
7
+ import type {
8
+ Breakpoint,
9
+ UseBreakpointReturn,
10
+ } from '../../domain/types/breakpoint.types';
11
+ import {
12
+ BREAKPOINTS,
13
+ createMediaQuery,
14
+ isBreakpointGreaterThan,
15
+ isBreakpointLessThan,
16
+ } from '../../infrastructure/constants/breakpoint.constants';
17
17
 
18
+ /**
19
+ * Simple media query hook for a single breakpoint
20
+ * @param breakpoint - The breakpoint to check
21
+ * @returns Whether the media query matches
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * const isDesktop = useMediaQuery('lg') // true on lg screens and above
26
+ * ```
27
+ */
18
28
  export function useMediaQuery(breakpoint: Breakpoint): boolean {
19
29
  const [matches, setMatches] = useState(false);
20
30
 
21
31
  useEffect(() => {
22
- const media = window.matchMedia(`(min-width: ${breakpointValues[breakpoint]}px)`);
32
+ const query = createMediaQuery(breakpoint);
33
+ const media = window.matchMedia(query);
23
34
  setMatches(media.matches);
24
35
 
25
- const listener = () => setMatches(media.matches);
36
+ const listener = (e: MediaQueryListEvent) => setMatches(e.matches);
26
37
  media.addEventListener('change', listener);
27
38
  return () => media.removeEventListener('change', listener);
28
39
  }, [breakpoint]);
@@ -30,17 +41,118 @@ export function useMediaQuery(breakpoint: Breakpoint): boolean {
30
41
  return matches;
31
42
  }
32
43
 
33
- export function useBreakpoint(): Breakpoint | null {
34
- const isSm = useMediaQuery('sm');
35
- const isMd = useMediaQuery('md');
36
- const isLg = useMediaQuery('lg');
37
- const isXl = useMediaQuery('xl');
38
- const is2xl = useMediaQuery('2xl');
39
-
40
- if (is2xl) return '2xl';
41
- if (isXl) return 'xl';
42
- if (isLg) return 'lg';
43
- if (isMd) return 'md';
44
- if (isSm) return 'sm';
45
- return null;
44
+ /**
45
+ * Enhanced breakpoint hook with helper functions
46
+ * @returns Object containing current breakpoint and helper functions
47
+ *
48
+ * @example
49
+ * ```tsx
50
+ * const { currentBreakpoint, isMobile, isDesktop } = useBreakpoint()
51
+ *
52
+ * return (
53
+ * <div>
54
+ * {isMobile && <MobileNav />}
55
+ * {isDesktop && <DesktopNav />}
56
+ * </div>
57
+ * )
58
+ * ```
59
+ */
60
+ export function useBreakpoint(): UseBreakpointReturn {
61
+ const [currentBreakpoint, setCurrentBreakpoint] = useState<Breakpoint>(() => {
62
+ // Initialize with current window size
63
+ if (typeof window === 'undefined') return 'lg';
64
+
65
+ const width = window.innerWidth;
66
+ for (const [bp, value] of Object.entries(BREAKPOINTS).reverse()) {
67
+ if (width >= value.min) return bp as Breakpoint;
68
+ }
69
+ return 'xs';
70
+ });
71
+
72
+ useEffect(() => {
73
+ // More efficient: track window resize with debounce
74
+ const updateBreakpoint = () => {
75
+ const width = window.innerWidth;
76
+ const sortedBreakpoints = Object.entries(BREAKPOINTS).sort(
77
+ ([, a], [, b]) => b.min - a.min
78
+ );
79
+
80
+ for (const [bp, value] of sortedBreakpoints) {
81
+ if (width >= value.min) {
82
+ setCurrentBreakpoint(bp as Breakpoint);
83
+ break;
84
+ }
85
+ }
86
+ };
87
+
88
+ // Initial check
89
+ updateBreakpoint();
90
+
91
+ // Debounced resize listener
92
+ let resizeTimer: ReturnType<typeof setTimeout>;
93
+ const handleResize = () => {
94
+ clearTimeout(resizeTimer);
95
+ resizeTimer = setTimeout(updateBreakpoint, 100);
96
+ };
97
+
98
+ window.addEventListener('resize', handleResize);
99
+
100
+ return () => {
101
+ window.removeEventListener('resize', handleResize);
102
+ clearTimeout(resizeTimer);
103
+ };
104
+ }, []);
105
+
106
+ // Helper functions
107
+ const matches = useCallback(
108
+ (breakpoint: Breakpoint | Breakpoint[]): boolean => {
109
+ if (Array.isArray(breakpoint)) {
110
+ return breakpoint.includes(currentBreakpoint);
111
+ }
112
+ return currentBreakpoint === breakpoint;
113
+ },
114
+ [currentBreakpoint]
115
+ );
116
+
117
+ const isGreaterThan = useCallback(
118
+ (breakpoint: Breakpoint): boolean => {
119
+ return isBreakpointGreaterThan(currentBreakpoint, breakpoint);
120
+ },
121
+ [currentBreakpoint]
122
+ );
123
+
124
+ const isLessThan = useCallback(
125
+ (breakpoint: Breakpoint): boolean => {
126
+ return isBreakpointLessThan(currentBreakpoint, breakpoint);
127
+ },
128
+ [currentBreakpoint]
129
+ );
130
+
131
+ // Computed values
132
+ const isXs = currentBreakpoint === 'xs';
133
+ const isSm = currentBreakpoint === 'sm';
134
+ const isMd = currentBreakpoint === 'md';
135
+ const isLg = currentBreakpoint === 'lg';
136
+ const isXl = currentBreakpoint === 'xl';
137
+ const is2Xl = currentBreakpoint === '2xl';
138
+
139
+ const isMobile = isXs || isSm;
140
+ const isTablet = isMd || isLg;
141
+ const isDesktop = isXl || is2Xl;
142
+
143
+ return {
144
+ currentBreakpoint,
145
+ isXs,
146
+ isSm,
147
+ isMd,
148
+ isLg,
149
+ isXl,
150
+ is2Xl,
151
+ isMobile,
152
+ isTablet,
153
+ isDesktop,
154
+ matches,
155
+ isGreaterThan,
156
+ isLessThan,
157
+ };
46
158
  }
@@ -92,7 +92,7 @@ export const MainNavbar = ({
92
92
  </Link>
93
93
 
94
94
  {/* Desktop Menu */}
95
- <div className="hidden md:flex items-center space-x-6">
95
+ <div className="hidden lg:flex items-center space-x-6">
96
96
  {navItemsMemo.map((item) => {
97
97
  const isActive = location.pathname === item.path;
98
98
  return (
@@ -115,7 +115,7 @@ export const MainNavbar = ({
115
115
  <div className="relative" ref={langDropdownRef}>
116
116
  <button
117
117
  onClick={() => setIsLangOpen(!isLangOpen)}
118
- className="p-2 rounded-lg bg-bg-secondary text-text-secondary hover:text-primary-light border border-border hover:border-primary-light transition-all transition-theme hidden md:block"
118
+ className="p-2 rounded-lg bg-bg-secondary text-text-secondary hover:text-primary-light border border-border hover:border-primary-light transition-all transition-theme hidden lg:block"
119
119
  title={translations.language}
120
120
  type="button"
121
121
  >
@@ -187,7 +187,7 @@ export const MainNavbar = ({
187
187
  href={githubUrl}
188
188
  target="_blank"
189
189
  rel="noopener noreferrer"
190
- className="hidden md:flex items-center gap-2 px-4 py-2 bg-bg-secondary text-text-secondary rounded-lg border border-border hover:border-primary-light hover:text-text-primary transition-all transition-theme"
190
+ className="hidden lg:flex items-center gap-2 px-4 py-2 bg-bg-secondary text-text-secondary rounded-lg border border-border hover:border-primary-light hover:text-text-primary transition-all transition-theme"
191
191
  >
192
192
  <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
193
193
  <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
@@ -199,7 +199,7 @@ export const MainNavbar = ({
199
199
  {/* Mobile Button */}
200
200
  <button
201
201
  onClick={() => setIsOpen(!isOpen)}
202
- className="max-md:flex text-text-secondary hidden"
202
+ className="lg:hidden text-text-secondary flex"
203
203
  type="button"
204
204
  aria-label="Toggle menu"
205
205
  >
@@ -221,7 +221,7 @@ export const MainNavbar = ({
221
221
 
222
222
  {/* Mobile Menu */}
223
223
  {isOpen && (
224
- <div className="md:hidden bg-bg-secondary border-t border-border transition-theme">
224
+ <div className="lg:hidden bg-bg-secondary border-t border-border transition-theme">
225
225
  <div className="px-4 py-4 space-y-2">
226
226
  {/* Theme Toggle Mobile */}
227
227
  <button
@@ -98,7 +98,7 @@ export const ResponsiveContainer = forwardRef<
98
98
  },
99
99
  ref
100
100
  ) => {
101
- const breakpoint = useBreakpoint();
101
+ const { currentBreakpoint: breakpoint } = useBreakpoint();
102
102
 
103
103
  // Determine current max width based on breakpoint
104
104
  const getCurrentMaxWidth = (): string => {