@native-mate/core 0.1.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 (111) hide show
  1. package/dist/index.d.ts +17 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +15 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/primitives/Icon/Icon.d.ts +4 -0
  6. package/dist/primitives/Icon/Icon.d.ts.map +1 -0
  7. package/dist/primitives/Icon/Icon.js +10 -0
  8. package/dist/primitives/Icon/Icon.js.map +1 -0
  9. package/dist/primitives/Icon/Icon.types.d.ts +8 -0
  10. package/dist/primitives/Icon/Icon.types.d.ts.map +1 -0
  11. package/dist/primitives/Icon/Icon.types.js +2 -0
  12. package/dist/primitives/Icon/Icon.types.js.map +1 -0
  13. package/dist/primitives/Separator/Separator.d.ts +4 -0
  14. package/dist/primitives/Separator/Separator.d.ts.map +1 -0
  15. package/dist/primitives/Separator/Separator.js +16 -0
  16. package/dist/primitives/Separator/Separator.js.map +1 -0
  17. package/dist/primitives/Separator/Separator.types.d.ts +6 -0
  18. package/dist/primitives/Separator/Separator.types.d.ts.map +1 -0
  19. package/dist/primitives/Separator/Separator.types.js +2 -0
  20. package/dist/primitives/Separator/Separator.types.js.map +1 -0
  21. package/dist/primitives/Spinner/Spinner.d.ts +4 -0
  22. package/dist/primitives/Spinner/Spinner.d.ts.map +1 -0
  23. package/dist/primitives/Spinner/Spinner.js +31 -0
  24. package/dist/primitives/Spinner/Spinner.js.map +1 -0
  25. package/dist/primitives/Spinner/Spinner.types.d.ts +5 -0
  26. package/dist/primitives/Spinner/Spinner.types.d.ts.map +1 -0
  27. package/dist/primitives/Spinner/Spinner.types.js +2 -0
  28. package/dist/primitives/Spinner/Spinner.types.js.map +1 -0
  29. package/dist/primitives/Text/Text.d.ts +4 -0
  30. package/dist/primitives/Text/Text.d.ts.map +1 -0
  31. package/dist/primitives/Text/Text.js +27 -0
  32. package/dist/primitives/Text/Text.js.map +1 -0
  33. package/dist/primitives/Text/Text.types.d.ts +14 -0
  34. package/dist/primitives/Text/Text.types.d.ts.map +1 -0
  35. package/dist/primitives/Text/Text.types.js +2 -0
  36. package/dist/primitives/Text/Text.types.js.map +1 -0
  37. package/dist/theme/ThemeContext.d.ts +4 -0
  38. package/dist/theme/ThemeContext.d.ts.map +1 -0
  39. package/dist/theme/ThemeContext.js +5 -0
  40. package/dist/theme/ThemeContext.js.map +1 -0
  41. package/dist/theme/ThemeProvider.d.ts +14 -0
  42. package/dist/theme/ThemeProvider.d.ts.map +1 -0
  43. package/dist/theme/ThemeProvider.js +11 -0
  44. package/dist/theme/ThemeProvider.js.map +1 -0
  45. package/dist/theme/makeStyles.d.ts +6 -0
  46. package/dist/theme/makeStyles.d.ts.map +1 -0
  47. package/dist/theme/makeStyles.js +10 -0
  48. package/dist/theme/makeStyles.js.map +1 -0
  49. package/dist/theme/useTheme.d.ts +3 -0
  50. package/dist/theme/useTheme.d.ts.map +1 -0
  51. package/dist/theme/useTheme.js +6 -0
  52. package/dist/theme/useTheme.js.map +1 -0
  53. package/dist/tokens/index.d.ts +15 -0
  54. package/dist/tokens/index.d.ts.map +1 -0
  55. package/dist/tokens/index.js +18 -0
  56. package/dist/tokens/index.js.map +1 -0
  57. package/dist/tokens/presets/midnight.d.ts +3 -0
  58. package/dist/tokens/presets/midnight.d.ts.map +1 -0
  59. package/dist/tokens/presets/midnight.js +23 -0
  60. package/dist/tokens/presets/midnight.js.map +1 -0
  61. package/dist/tokens/presets/rose.d.ts +3 -0
  62. package/dist/tokens/presets/rose.d.ts.map +1 -0
  63. package/dist/tokens/presets/rose.js +23 -0
  64. package/dist/tokens/presets/rose.js.map +1 -0
  65. package/dist/tokens/presets/slate.d.ts +3 -0
  66. package/dist/tokens/presets/slate.d.ts.map +1 -0
  67. package/dist/tokens/presets/slate.js +23 -0
  68. package/dist/tokens/presets/slate.js.map +1 -0
  69. package/dist/tokens/presets/zinc.d.ts +3 -0
  70. package/dist/tokens/presets/zinc.d.ts.map +1 -0
  71. package/dist/tokens/presets/zinc.js +36 -0
  72. package/dist/tokens/presets/zinc.js.map +1 -0
  73. package/dist/tokens/types.d.ts +109 -0
  74. package/dist/tokens/types.d.ts.map +1 -0
  75. package/dist/tokens/types.js +2 -0
  76. package/dist/tokens/types.js.map +1 -0
  77. package/dist/utils/platform.d.ts +17 -0
  78. package/dist/utils/platform.d.ts.map +1 -0
  79. package/dist/utils/platform.js +19 -0
  80. package/dist/utils/platform.js.map +1 -0
  81. package/dist/utils/useBreakpoint.d.ts +3 -0
  82. package/dist/utils/useBreakpoint.d.ts.map +1 -0
  83. package/dist/utils/useBreakpoint.js +10 -0
  84. package/dist/utils/useBreakpoint.js.map +1 -0
  85. package/package.json +31 -0
  86. package/src/__tests__/makeStyles.test.ts +56 -0
  87. package/src/__tests__/perf.test.ts +46 -0
  88. package/src/__tests__/platform.test.ts +34 -0
  89. package/src/__tests__/theme.test.ts +58 -0
  90. package/src/__tests__/tokens.test.ts +105 -0
  91. package/src/index.ts +27 -0
  92. package/src/primitives/Icon/Icon.tsx +11 -0
  93. package/src/primitives/Icon/Icon.types.ts +7 -0
  94. package/src/primitives/Separator/Separator.tsx +22 -0
  95. package/src/primitives/Separator/Separator.types.ts +6 -0
  96. package/src/primitives/Spinner/Spinner.tsx +50 -0
  97. package/src/primitives/Spinner/Spinner.types.ts +4 -0
  98. package/src/primitives/Text/Text.tsx +45 -0
  99. package/src/primitives/Text/Text.types.ts +15 -0
  100. package/src/theme/ThemeContext.ts +6 -0
  101. package/src/theme/ThemeProvider.tsx +27 -0
  102. package/src/theme/makeStyles.ts +15 -0
  103. package/src/theme/useTheme.ts +7 -0
  104. package/src/tokens/index.ts +29 -0
  105. package/src/tokens/presets/midnight.ts +24 -0
  106. package/src/tokens/presets/rose.ts +24 -0
  107. package/src/tokens/presets/slate.ts +24 -0
  108. package/src/tokens/presets/zinc.ts +37 -0
  109. package/src/tokens/types.ts +72 -0
  110. package/src/utils/platform.ts +20 -0
  111. package/src/utils/useBreakpoint.ts +10 -0
@@ -0,0 +1,4 @@
1
+ export interface SpinnerProps {
2
+ size?: 'sm' | 'md' | 'lg'
3
+ color?: string
4
+ }
@@ -0,0 +1,45 @@
1
+ import React from 'react'
2
+ import { Text as RNText } from 'react-native'
3
+ import type { TextStyle } from 'react-native'
4
+ import { useTheme } from '../../theme/useTheme'
5
+ import type { TextProps, TextVariant, TextSize, TextWeight } from './Text.types'
6
+
7
+ const variantMap: Record<TextVariant, { sizeKey: string; weightKey: string }> = {
8
+ body: { sizeKey: 'md', weightKey: 'regular' },
9
+ label: { sizeKey: 'sm', weightKey: 'medium' },
10
+ caption: { sizeKey: 'xs', weightKey: 'regular' },
11
+ heading: { sizeKey: 'xl', weightKey: 'bold' },
12
+ title: { sizeKey: '2xl', weightKey: 'bold' },
13
+ display: { sizeKey: '3xl', weightKey: 'bold' },
14
+ }
15
+
16
+ export const Text: React.FC<TextProps> = ({
17
+ variant = 'body',
18
+ size,
19
+ weight,
20
+ color,
21
+ muted = false,
22
+ style,
23
+ children,
24
+ ...rest
25
+ }) => {
26
+ const theme = useTheme()
27
+ const { sizeKey, weightKey } = variantMap[variant]
28
+
29
+ return (
30
+ <RNText
31
+ style={[
32
+ {
33
+ color: color ?? (muted ? theme.colors.muted : theme.colors.foreground),
34
+ fontSize: theme.typography.size[size ?? sizeKey as TextSize],
35
+ fontWeight: theme.typography.weight[weight ?? weightKey as TextWeight] as TextStyle['fontWeight'],
36
+ lineHeight: theme.typography.lineHeight.normal,
37
+ },
38
+ style,
39
+ ]}
40
+ {...rest}
41
+ >
42
+ {children}
43
+ </RNText>
44
+ )
45
+ }
@@ -0,0 +1,15 @@
1
+ import type { TextProps as RNTextProps } from 'react-native'
2
+
3
+ export type TextVariant = 'body' | 'label' | 'caption' | 'heading' | 'title' | 'display'
4
+ export type TextSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl'
5
+ export type TextWeight = 'regular' | 'medium' | 'semibold' | 'bold'
6
+
7
+ export interface TextProps extends Omit<RNTextProps, 'style'> {
8
+ variant?: TextVariant
9
+ size?: TextSize
10
+ weight?: TextWeight
11
+ color?: string
12
+ muted?: boolean
13
+ children: React.ReactNode
14
+ style?: RNTextProps['style']
15
+ }
@@ -0,0 +1,6 @@
1
+ import { createContext } from 'react'
2
+ import type { ResolvedTheme } from '../tokens/types'
3
+ import { resolveTokens, zinc } from '../tokens'
4
+
5
+ export const defaultTheme: ResolvedTheme = resolveTokens(zinc, 'light')
6
+ export const ThemeContext = createContext<ResolvedTheme>(defaultTheme)
@@ -0,0 +1,27 @@
1
+ import React, { useMemo } from 'react'
2
+ import { useColorScheme } from 'react-native'
3
+ import { ThemeContext } from './ThemeContext'
4
+ import { presets, resolveTokens } from '../tokens'
5
+ import type { ThemePreset, NativeMateTokenOverrides } from '../tokens/types'
6
+
7
+ interface ThemeProviderProps {
8
+ preset?: ThemePreset
9
+ forcedColorScheme?: 'light' | 'dark'
10
+ overrides?: { light?: NativeMateTokenOverrides; dark?: NativeMateTokenOverrides }
11
+ children: React.ReactNode
12
+ }
13
+
14
+ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
15
+ preset = 'zinc',
16
+ forcedColorScheme,
17
+ overrides,
18
+ children,
19
+ }) => {
20
+ const systemColorScheme = useColorScheme()
21
+ const mode = forcedColorScheme ?? systemColorScheme ?? 'light'
22
+ const theme = useMemo(
23
+ () => resolveTokens(presets[preset], mode, overrides?.[mode]),
24
+ [preset, mode, overrides],
25
+ )
26
+ return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>
27
+ }
@@ -0,0 +1,15 @@
1
+ import { useMemo } from 'react'
2
+ import { StyleSheet } from 'react-native'
3
+ import { useTheme } from './useTheme'
4
+ import type { ResolvedTheme } from '../tokens/types'
5
+
6
+ type StyleFactory<T extends StyleSheet.NamedStyles<T>> = (theme: ResolvedTheme) => T
7
+
8
+ export function makeStyles<T extends StyleSheet.NamedStyles<T>>(
9
+ factory: StyleFactory<T>,
10
+ ): () => T {
11
+ return function useStyles(): T {
12
+ const theme = useTheme()
13
+ return useMemo(() => StyleSheet.create(factory(theme)), [theme])
14
+ }
15
+ }
@@ -0,0 +1,7 @@
1
+ import { useContext } from 'react'
2
+ import { ThemeContext } from './ThemeContext'
3
+ import type { ResolvedTheme } from '../tokens/types'
4
+
5
+ export function useTheme(): ResolvedTheme {
6
+ return useContext(ThemeContext)
7
+ }
@@ -0,0 +1,29 @@
1
+ import type { TokenSet, ResolvedTheme, NativeMateTokenOverrides } from './types'
2
+ import { zinc } from './presets/zinc'
3
+ import { slate } from './presets/slate'
4
+ import { rose } from './presets/rose'
5
+ import { midnight } from './presets/midnight'
6
+
7
+ export { zinc, slate, rose, midnight }
8
+ export * from './types'
9
+
10
+ export const presets = { zinc, slate, rose, midnight } as const
11
+
12
+ export function resolveTokens(
13
+ preset: TokenSet,
14
+ mode: 'light' | 'dark',
15
+ overrides?: NativeMateTokenOverrides,
16
+ ): ResolvedTheme {
17
+ const resolvedColors = Object.fromEntries(
18
+ Object.entries(preset.colors).map(([key, token]) => [key, token[mode]])
19
+ ) as ResolvedTheme['colors']
20
+
21
+ const colors = overrides?.colors ? { ...resolvedColors, ...overrides.colors } : resolvedColors
22
+ const spacing = overrides?.spacing ? { ...preset.spacing, ...overrides.spacing } : preset.spacing
23
+ const radius = overrides?.radius ? { ...preset.radius, ...overrides.radius } : preset.radius
24
+ const animation = overrides?.animation?.speed
25
+ ? { ...preset.animation, speed: { ...preset.animation.speed, ...overrides.animation.speed } }
26
+ : preset.animation
27
+
28
+ return { colors, spacing, radius, typography: preset.typography, animation, colorScheme: mode }
29
+ }
@@ -0,0 +1,24 @@
1
+ import type { TokenSet } from '../types'
2
+ import { zinc } from './zinc'
3
+
4
+ export const midnight: TokenSet = {
5
+ ...zinc,
6
+ colors: {
7
+ background: { light: '#f8fafc', dark: '#000000' },
8
+ surface: { light: '#f1f5f9', dark: '#111111' },
9
+ surfaceRaised: { light: '#ffffff', dark: '#1a1a1a' },
10
+ border: { light: '#e2e8f0', dark: '#2a2a2a' },
11
+ primary: { light: '#6366f1', dark: '#818cf8' },
12
+ onPrimary: { light: '#ffffff', dark: '#000000' },
13
+ foreground: { light: '#0f172a', dark: '#f8fafc' },
14
+ onBackground: { light: '#0f172a', dark: '#f8fafc' },
15
+ onSurface: { light: '#1e293b', dark: '#e2e8f0' },
16
+ muted: { light: '#64748b', dark: '#6b7280' },
17
+ destructive: { light: '#ef4444', dark: '#f87171' },
18
+ onDestructive: { light: '#ffffff', dark: '#ffffff' },
19
+ success: { light: '#22c55e', dark: '#4ade80' },
20
+ onSuccess: { light: '#ffffff', dark: '#000000' },
21
+ warning: { light: '#f59e0b', dark: '#fbbf24' },
22
+ onWarning: { light: '#ffffff', dark: '#000000' },
23
+ },
24
+ }
@@ -0,0 +1,24 @@
1
+ import type { TokenSet } from '../types'
2
+ import { zinc } from './zinc'
3
+
4
+ export const rose: TokenSet = {
5
+ ...zinc,
6
+ colors: {
7
+ background: { light: '#ffffff', dark: '#0c0a0b' },
8
+ surface: { light: '#fff1f2', dark: '#1c1115' },
9
+ surfaceRaised: { light: '#ffffff', dark: '#2d1a1f' },
10
+ border: { light: '#fecdd3', dark: '#4c2030' },
11
+ primary: { light: '#e11d48', dark: '#fb7185' },
12
+ onPrimary: { light: '#ffffff', dark: '#1c0a0f' },
13
+ foreground: { light: '#0f0a0b', dark: '#fef2f4' },
14
+ onBackground: { light: '#0f0a0b', dark: '#fef2f4' },
15
+ onSurface: { light: '#881337', dark: '#fecdd3' },
16
+ muted: { light: '#9f4258', dark: '#be738a' },
17
+ destructive: { light: '#dc2626', dark: '#f87171' },
18
+ onDestructive: { light: '#ffffff', dark: '#ffffff' },
19
+ success: { light: '#22c55e', dark: '#4ade80' },
20
+ onSuccess: { light: '#ffffff', dark: '#000000' },
21
+ warning: { light: '#f59e0b', dark: '#fbbf24' },
22
+ onWarning: { light: '#ffffff', dark: '#000000' },
23
+ },
24
+ }
@@ -0,0 +1,24 @@
1
+ import type { TokenSet } from '../types'
2
+ import { zinc } from './zinc'
3
+
4
+ export const slate: TokenSet = {
5
+ ...zinc,
6
+ colors: {
7
+ background: { light: '#ffffff', dark: '#0f172a' },
8
+ surface: { light: '#f1f5f9', dark: '#1e293b' },
9
+ surfaceRaised: { light: '#ffffff', dark: '#334155' },
10
+ border: { light: '#cbd5e1', dark: '#475569' },
11
+ primary: { light: '#0f172a', dark: '#f8fafc' },
12
+ onPrimary: { light: '#f8fafc', dark: '#0f172a' },
13
+ foreground: { light: '#020617', dark: '#f8fafc' },
14
+ onBackground: { light: '#020617', dark: '#f8fafc' },
15
+ onSurface: { light: '#0f172a', dark: '#e2e8f0' },
16
+ muted: { light: '#64748b', dark: '#94a3b8' },
17
+ destructive: { light: '#ef4444', dark: '#f87171' },
18
+ onDestructive: { light: '#ffffff', dark: '#ffffff' },
19
+ success: { light: '#22c55e', dark: '#4ade80' },
20
+ onSuccess: { light: '#ffffff', dark: '#000000' },
21
+ warning: { light: '#f59e0b', dark: '#fbbf24' },
22
+ onWarning: { light: '#ffffff', dark: '#000000' },
23
+ },
24
+ }
@@ -0,0 +1,37 @@
1
+ import type { TokenSet } from '../types'
2
+
3
+ export const zinc: TokenSet = {
4
+ colors: {
5
+ background: { light: '#ffffff', dark: '#070709' },
6
+ surface: { light: '#e4e4e7', dark: '#0f0f11' },
7
+ surfaceRaised: { light: '#f4f4f5', dark: '#161619' },
8
+ border: { light: '#d4d4d8', dark: '#252529' },
9
+ primary: { light: '#18181b', dark: '#fafafa' },
10
+ onPrimary: { light: '#fafafa', dark: '#18181b' },
11
+ foreground: { light: '#09090b', dark: '#fafafa' },
12
+ onBackground: { light: '#09090b', dark: '#fafafa' },
13
+ onSurface: { light: '#18181b', dark: '#e4e4e7' },
14
+ muted: { light: '#71717a', dark: '#71717a' },
15
+ destructive: { light: '#ef4444', dark: '#f87171' },
16
+ onDestructive: { light: '#ffffff', dark: '#ffffff' },
17
+ success: { light: '#22c55e', dark: '#4ade80' },
18
+ onSuccess: { light: '#ffffff', dark: '#000000' },
19
+ warning: { light: '#f59e0b', dark: '#fbbf24' },
20
+ onWarning: { light: '#ffffff', dark: '#000000' },
21
+ },
22
+ spacing: { xs: 4, sm: 8, md: 12, lg: 16, xl: 24, '2xl': 32, '3xl': 48 },
23
+ radius: { sm: 6, md: 10, lg: 16, xl: 24, full: 9999 },
24
+ typography: {
25
+ size: { xs: 11, sm: 13, md: 15, lg: 17, xl: 20, '2xl': 24, '3xl': 30 },
26
+ weight: { regular: '400', medium: '500', semibold: '600', bold: '700' },
27
+ lineHeight: { tight: 18, normal: 22, relaxed: 28 },
28
+ },
29
+ animation: {
30
+ speed: { fast: 150, normal: 250, slow: 400 },
31
+ easing: {
32
+ standard: [0.4, 0.0, 0.2, 1],
33
+ decelerate: [0.0, 0.0, 0.2, 1],
34
+ spring: { damping: 15, stiffness: 200, mass: 1 },
35
+ },
36
+ },
37
+ }
@@ -0,0 +1,72 @@
1
+ export interface ColorToken {
2
+ light: string
3
+ dark: string
4
+ }
5
+
6
+ export interface TokenColors {
7
+ background: ColorToken
8
+ surface: ColorToken
9
+ surfaceRaised: ColorToken
10
+ border: ColorToken
11
+ primary: ColorToken
12
+ onPrimary: ColorToken
13
+ foreground: ColorToken
14
+ onBackground: ColorToken
15
+ onSurface: ColorToken
16
+ muted: ColorToken
17
+ destructive: ColorToken
18
+ onDestructive: ColorToken
19
+ success: ColorToken
20
+ onSuccess: ColorToken
21
+ warning: ColorToken
22
+ onWarning: ColorToken
23
+ }
24
+
25
+ export interface TokenSet {
26
+ colors: TokenColors
27
+ spacing: { xs: number; sm: number; md: number; lg: number; xl: number; '2xl': number; '3xl': number }
28
+ radius: { sm: number; md: number; lg: number; xl: number; full: number }
29
+ typography: {
30
+ size: { xs: number; sm: number; md: number; lg: number; xl: number; '2xl': number; '3xl': number }
31
+ weight: { regular: string; medium: string; semibold: string; bold: string }
32
+ lineHeight: { tight: number; normal: number; relaxed: number }
33
+ }
34
+ animation: {
35
+ speed: { fast: number; normal: number; slow: number }
36
+ easing: {
37
+ standard: readonly [number, number, number, number]
38
+ decelerate: readonly [number, number, number, number]
39
+ spring: { damping: number; stiffness: number; mass: number }
40
+ }
41
+ }
42
+ }
43
+
44
+ export type ResolvedColors = { [K in keyof TokenColors]: string }
45
+
46
+ export interface ResolvedTheme {
47
+ colors: ResolvedColors
48
+ spacing: TokenSet['spacing']
49
+ radius: TokenSet['radius']
50
+ typography: TokenSet['typography']
51
+ animation: TokenSet['animation']
52
+ colorScheme: 'light' | 'dark'
53
+ }
54
+
55
+ export type ThemePreset = 'zinc' | 'slate' | 'rose' | 'midnight'
56
+
57
+ export interface NativeMateTokenOverrides {
58
+ colors?: Partial<ResolvedColors>
59
+ spacing?: Partial<TokenSet['spacing']>
60
+ radius?: Partial<TokenSet['radius']>
61
+ animation?: { speed?: Partial<TokenSet['animation']['speed']> }
62
+ }
63
+
64
+ export interface NativeMateConfig {
65
+ theme: ThemePreset
66
+ componentsDir: string
67
+ registry: string
68
+ tokens?: {
69
+ light?: NativeMateTokenOverrides
70
+ dark?: NativeMateTokenOverrides
71
+ }
72
+ }
@@ -0,0 +1,20 @@
1
+ import { Platform } from 'react-native'
2
+
3
+ export function shadow(level: 1 | 2 | 3 | 4 = 1) {
4
+ const config = {
5
+ 1: { opacity: 0.06, radius: 4, offsetY: 1, elevation: 2 },
6
+ 2: { opacity: 0.10, radius: 8, offsetY: 2, elevation: 4 },
7
+ 3: { opacity: 0.14, radius: 16, offsetY: 4, elevation: 8 },
8
+ 4: { opacity: 0.18, radius: 24, offsetY: 8, elevation: 12 },
9
+ }[level]
10
+
11
+ if (Platform.OS === 'ios') {
12
+ return {
13
+ shadowColor: '#000',
14
+ shadowOpacity: config.opacity,
15
+ shadowRadius: config.radius,
16
+ shadowOffset: { width: 0, height: config.offsetY },
17
+ }
18
+ }
19
+ return { elevation: config.elevation }
20
+ }
@@ -0,0 +1,10 @@
1
+ import { useWindowDimensions } from 'react-native'
2
+
3
+ export type Breakpoint = 'sm' | 'md' | 'lg'
4
+
5
+ export function useBreakpoint(): Breakpoint {
6
+ const { width } = useWindowDimensions()
7
+ if (width >= 1024) return 'lg'
8
+ if (width >= 768) return 'md'
9
+ return 'sm'
10
+ }