@umituz/web-design-system 2.6.4 → 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.4",
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
  }
@@ -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 => {