@datametria/vue-components 1.2.0 → 2.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 (103) hide show
  1. package/README.md +554 -657
  2. package/dist/index.es.js +2570 -1433
  3. package/dist/index.umd.js +10 -10
  4. package/dist/vue-components.css +1 -1
  5. package/package.json +102 -98
  6. package/src/components/DatametriaAlert.vue +137 -137
  7. package/src/components/DatametriaAutocomplete.vue +184 -138
  8. package/src/components/DatametriaAvatar.vue +177 -33
  9. package/src/components/DatametriaBadge.vue +98 -98
  10. package/src/components/DatametriaBreadcrumb.vue +21 -21
  11. package/src/components/DatametriaButton.vue +177 -165
  12. package/src/components/DatametriaCard.vue +12 -12
  13. package/src/components/DatametriaCheckbox.vue +8 -8
  14. package/src/components/DatametriaChip.vue +145 -149
  15. package/src/components/DatametriaContainer.vue +4 -4
  16. package/src/components/DatametriaDatePicker.vue +686 -68
  17. package/src/components/DatametriaDivider.vue +13 -13
  18. package/src/components/DatametriaFileUpload.vue +272 -140
  19. package/src/components/DatametriaFloatingBar.vue +126 -0
  20. package/src/components/DatametriaGrid.vue +3 -3
  21. package/src/components/DatametriaInput.vue +15 -15
  22. package/src/components/DatametriaMenu.vue +604 -619
  23. package/src/components/DatametriaModal.vue +16 -16
  24. package/src/components/DatametriaNavbar.vue +230 -252
  25. package/src/components/DatametriaPasswordInput.vue +430 -0
  26. package/src/components/DatametriaProgress.vue +18 -18
  27. package/src/components/DatametriaRadio.vue +20 -20
  28. package/src/components/DatametriaSelect.vue +15 -15
  29. package/src/components/DatametriaSidebar.vue +230 -0
  30. package/src/components/DatametriaSkeleton.vue +243 -239
  31. package/src/components/DatametriaSlider.vue +395 -407
  32. package/src/components/DatametriaSortableTable.vue +585 -0
  33. package/src/components/DatametriaSpinner.vue +7 -7
  34. package/src/components/DatametriaSwitch.vue +16 -16
  35. package/src/components/DatametriaTable.vue +14 -14
  36. package/src/components/DatametriaTabs.vue +150 -29
  37. package/src/components/DatametriaTextarea.vue +28 -28
  38. package/src/components/DatametriaTimePicker.vue +285 -285
  39. package/src/components/DatametriaToast.vue +176 -176
  40. package/src/components/DatametriaTooltip.vue +408 -408
  41. package/src/components/__tests__/DatametriaAlert.test.js +35 -35
  42. package/src/components/__tests__/DatametriaAlert.test.ts +190 -0
  43. package/src/components/__tests__/DatametriaAutocomplete.test.ts +180 -0
  44. package/src/components/__tests__/DatametriaAvatar.test.ts +152 -0
  45. package/src/components/__tests__/DatametriaBadge.test.js +29 -29
  46. package/src/components/__tests__/DatametriaBadge.test.ts +167 -0
  47. package/src/components/__tests__/DatametriaBreadcrumb.test.ts +75 -0
  48. package/src/components/__tests__/DatametriaButton.test.js +30 -30
  49. package/src/components/__tests__/DatametriaButton.test.ts +283 -0
  50. package/src/components/__tests__/DatametriaCard.test.ts +201 -0
  51. package/src/components/__tests__/DatametriaCheckbox.test.ts +47 -0
  52. package/src/components/__tests__/DatametriaChip.test.js +38 -38
  53. package/src/components/__tests__/DatametriaContainer.test.ts +52 -0
  54. package/src/components/__tests__/DatametriaDatePicker.test.ts +234 -0
  55. package/src/components/__tests__/DatametriaDivider.test.ts +54 -0
  56. package/src/components/__tests__/DatametriaFileUpload.test.ts +291 -0
  57. package/src/components/__tests__/DatametriaFloatingBar.test.ts +137 -0
  58. package/src/components/__tests__/DatametriaGrid.test.ts +31 -0
  59. package/src/components/__tests__/DatametriaInput.test.ts +72 -0
  60. package/src/components/__tests__/DatametriaMenu.test.ts +366 -0
  61. package/src/components/__tests__/DatametriaModal.test.ts +86 -0
  62. package/src/components/__tests__/DatametriaNavbar.test.js +48 -48
  63. package/src/components/__tests__/DatametriaNavbar.test.ts +203 -0
  64. package/src/components/__tests__/DatametriaPasswordInput.test.js +305 -0
  65. package/src/components/__tests__/DatametriaProgress.test.ts +90 -0
  66. package/src/components/__tests__/DatametriaRadio.test.ts +77 -0
  67. package/src/components/__tests__/DatametriaSelect.test.ts +77 -0
  68. package/src/components/__tests__/DatametriaSidebar.test.ts +169 -0
  69. package/src/components/__tests__/DatametriaSlider.test.ts +261 -0
  70. package/src/components/__tests__/DatametriaSortableTable.test.js +168 -0
  71. package/src/components/__tests__/DatametriaSpinner.test.ts +156 -0
  72. package/src/components/__tests__/DatametriaSwitch.test.ts +64 -0
  73. package/src/components/__tests__/DatametriaTable.test.ts +97 -0
  74. package/src/components/__tests__/DatametriaTabs.test.ts +232 -0
  75. package/src/components/__tests__/DatametriaTextarea.test.ts +66 -0
  76. package/src/components/__tests__/DatametriaToast.test.js +48 -48
  77. package/src/components/__tests__/DatametriaToast.test.ts +99 -0
  78. package/src/composables/useAccessibilityScale.ts +94 -94
  79. package/src/composables/useBreakpoints.ts +82 -82
  80. package/src/composables/useHapticFeedback.ts +439 -439
  81. package/src/composables/useRipple.ts +218 -218
  82. package/src/index.ts +70 -61
  83. package/src/stories/Variants.stories.js +95 -95
  84. package/src/styles/design-tokens.css +623 -623
  85. package/src/theme/ThemeProvider.vue +96 -0
  86. package/src/theme/__tests__/ThemeProvider.test.ts +208 -0
  87. package/src/theme/__tests__/constants.test.ts +31 -0
  88. package/src/theme/__tests__/presets.test.ts +166 -0
  89. package/src/theme/__tests__/tokens.test.ts +155 -0
  90. package/src/theme/__tests__/types.test.ts +153 -0
  91. package/src/theme/__tests__/useTheme.test.ts +146 -0
  92. package/src/theme/constants.ts +14 -0
  93. package/src/theme/index.ts +12 -0
  94. package/src/theme/presets/datametria.ts +94 -0
  95. package/src/theme/presets/default.ts +94 -0
  96. package/src/theme/presets/index.ts +8 -0
  97. package/src/theme/tokens/colors.ts +28 -0
  98. package/src/theme/tokens/index.ts +47 -0
  99. package/src/theme/tokens/spacing.ts +21 -0
  100. package/src/theme/tokens/typography.ts +35 -0
  101. package/src/theme/types.ts +111 -0
  102. package/src/theme/useTheme.ts +28 -0
  103. package/src/types/index.ts +19 -0
@@ -1,49 +1,49 @@
1
- import { describe, it, expect, vi } from 'vitest'
2
- import { mount } from '@vue/test-utils'
3
- import DatametriaToast from '../DatametriaToast.vue'
4
-
5
- describe('DatametriaToast', () => {
6
- it('renders with primary variant', () => {
7
- // Create body element for Teleport
8
- const body = document.createElement('div')
9
- document.body.appendChild(body)
10
-
11
- const wrapper = mount(DatametriaToast, {
12
- props: {
13
- message: 'Test message',
14
- variant: 'primary',
15
- modelValue: true
16
- },
17
- attachTo: body
18
- })
19
-
20
- // Check if toast exists in document body
21
- const toast = document.querySelector('.dm-toast')
22
- expect(toast).toBeTruthy()
23
- expect(toast.classList.contains('dm-toast--primary')).toBe(true)
24
-
25
- wrapper.unmount()
26
- document.body.removeChild(body)
27
- })
28
-
29
- it('validates invalid variant in development', () => {
30
- const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
31
- const originalEnv = process.env.NODE_ENV
32
- process.env.NODE_ENV = 'development'
33
-
34
- mount(DatametriaToast, {
35
- props: {
36
- message: 'Test',
37
- variant: 'invalid',
38
- modelValue: true
39
- }
40
- })
41
-
42
- expect(consoleSpy).toHaveBeenCalledWith(
43
- expect.stringContaining('[DatametriaToast] Invalid variant "invalid"')
44
- )
45
-
46
- process.env.NODE_ENV = originalEnv
47
- consoleSpy.mockRestore()
48
- })
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import DatametriaToast from '../DatametriaToast.vue'
4
+
5
+ describe('DatametriaToast', () => {
6
+ it('renders with primary variant', () => {
7
+ // Create body element for Teleport
8
+ const body = document.createElement('div')
9
+ document.body.appendChild(body)
10
+
11
+ const wrapper = mount(DatametriaToast, {
12
+ props: {
13
+ message: 'Test message',
14
+ variant: 'primary',
15
+ modelValue: true
16
+ },
17
+ attachTo: body
18
+ })
19
+
20
+ // Check if toast exists in document body
21
+ const toast = document.querySelector('.dm-toast')
22
+ expect(toast).toBeTruthy()
23
+ expect(toast.classList.contains('dm-toast--primary')).toBe(true)
24
+
25
+ wrapper.unmount()
26
+ document.body.removeChild(body)
27
+ })
28
+
29
+ it('validates invalid variant in development', () => {
30
+ const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
31
+ const originalEnv = process.env.NODE_ENV
32
+ process.env.NODE_ENV = 'development'
33
+
34
+ mount(DatametriaToast, {
35
+ props: {
36
+ message: 'Test',
37
+ variant: 'invalid',
38
+ modelValue: true
39
+ }
40
+ })
41
+
42
+ expect(consoleSpy).toHaveBeenCalledWith(
43
+ expect.stringContaining('[DatametriaToast] Invalid variant "invalid"')
44
+ )
45
+
46
+ process.env.NODE_ENV = originalEnv
47
+ consoleSpy.mockRestore()
48
+ })
49
49
  })
@@ -0,0 +1,99 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import DatametriaToast from '../DatametriaToast.vue'
4
+
5
+ describe.skip('DatametriaToast', () => {
6
+ beforeEach(() => {
7
+ vi.useFakeTimers()
8
+ })
9
+
10
+ afterEach(() => {
11
+ vi.restoreAllMocks()
12
+ })
13
+
14
+ it('renders correctly when visible', () => {
15
+ const wrapper = mount(DatametriaToast, {
16
+ props: { message: 'Test message', modelValue: true }
17
+ })
18
+ expect(wrapper.html()).toContain('dm-toast')
19
+ })
20
+
21
+ it('displays message correctly', () => {
22
+ const wrapper = mount(DatametriaToast, {
23
+ props: { message: 'Test message', modelValue: true }
24
+ })
25
+ expect(wrapper.html()).toContain('Test message')
26
+ })
27
+
28
+ it('applies success variant class', () => {
29
+ const wrapper = mount(DatametriaToast, {
30
+ props: { message: 'Success', variant: 'success', modelValue: true }
31
+ })
32
+ expect(wrapper.html()).toContain('dm-toast--success')
33
+ })
34
+
35
+ it('applies error variant class', () => {
36
+ const wrapper = mount(DatametriaToast, {
37
+ props: { message: 'Error', variant: 'error', modelValue: true }
38
+ })
39
+ expect(wrapper.html()).toContain('dm-toast--error')
40
+ })
41
+
42
+ it('applies warning variant class', () => {
43
+ const wrapper = mount(DatametriaToast, {
44
+ props: { message: 'Warning', variant: 'warning', modelValue: true }
45
+ })
46
+ expect(wrapper.html()).toContain('dm-toast--warning')
47
+ })
48
+
49
+ it('applies primary variant class', () => {
50
+ const wrapper = mount(DatametriaToast, {
51
+ props: { message: 'Primary', variant: 'primary', modelValue: true }
52
+ })
53
+ expect(wrapper.html()).toContain('dm-toast--primary')
54
+ })
55
+
56
+ it('applies info variant class', () => {
57
+ const wrapper = mount(DatametriaToast, {
58
+ props: { message: 'Info', variant: 'info', modelValue: true }
59
+ })
60
+ expect(wrapper.html()).toContain('dm-toast--info')
61
+ })
62
+
63
+ it('renders close button when closable', () => {
64
+ const wrapper = mount(DatametriaToast, {
65
+ props: { message: 'Test', closable: true, modelValue: true }
66
+ })
67
+ expect(wrapper.html()).toContain('dm-toast__close')
68
+ })
69
+
70
+ it('does not render close button when not closable', () => {
71
+ const wrapper = mount(DatametriaToast, {
72
+ props: { message: 'Test', closable: false, modelValue: true }
73
+ })
74
+ expect(wrapper.html()).not.toContain('dm-toast__close')
75
+ })
76
+
77
+ it('emits update:modelValue when close called', async () => {
78
+ const wrapper = mount(DatametriaToast, {
79
+ props: { message: 'Test', closable: true, modelValue: true }
80
+ })
81
+ await (wrapper.vm as any).close()
82
+ expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([false])
83
+ })
84
+
85
+ it('emits close event when close called', async () => {
86
+ const wrapper = mount(DatametriaToast, {
87
+ props: { message: 'Test', closable: true, modelValue: true }
88
+ })
89
+ await (wrapper.vm as any).close()
90
+ expect(wrapper.emitted('close')).toBeTruthy()
91
+ })
92
+
93
+ it('uses CSS variables with fallbacks', () => {
94
+ const wrapper = mount(DatametriaToast, {
95
+ props: { message: 'Test', modelValue: true }
96
+ })
97
+ expect(wrapper.html()).toContain('--dm-')
98
+ })
99
+ })
@@ -1,95 +1,95 @@
1
- import { ref, computed, watch, onMounted } from 'vue'
2
-
3
- /**
4
- * Composable para controle de escalabilidade de acessibilidade
5
- * Implementa escala controlada entre 0.8x e 2.0x conforme WCAG 2.1
6
- */
7
- export function useAccessibilityScale() {
8
- const scale = ref(1.0)
9
- const STORAGE_KEY = 'datametria-accessibility-scale'
10
- const MIN_SCALE = 0.8
11
- const MAX_SCALE = 2.0
12
-
13
- // Computed para escala clampada
14
- const clampedScale = computed(() =>
15
- Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale.value))
16
- )
17
-
18
- // Computed para classe CSS
19
- const scaleClass = computed(() => {
20
- const s = clampedScale.value
21
- if (s <= 0.9) return 'scale-small'
22
- if (s >= 1.1) return 'scale-large'
23
- return 'scale-normal'
24
- })
25
-
26
- // Aplicar escala ao documento
27
- const applyScale = (newScale: number) => {
28
- const finalScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, newScale))
29
- document.documentElement.style.setProperty('--user-scale', finalScale.toString())
30
- scale.value = finalScale
31
- }
32
-
33
- // Salvar no localStorage
34
- const saveScale = (scaleValue: number) => {
35
- try {
36
- localStorage.setItem(STORAGE_KEY, scaleValue.toString())
37
- } catch (error) {
38
- console.warn('Failed to save accessibility scale:', error)
39
- }
40
- }
41
-
42
- // Carregar do localStorage
43
- const loadScale = (): number => {
44
- try {
45
- const saved = localStorage.getItem(STORAGE_KEY)
46
- return saved ? parseFloat(saved) : 1.0
47
- } catch (error) {
48
- console.warn('Failed to load accessibility scale:', error)
49
- return 1.0
50
- }
51
- }
52
-
53
- // Definir escala
54
- const setScale = (newScale: number) => {
55
- applyScale(newScale)
56
- saveScale(clampedScale.value)
57
- }
58
-
59
- // Incrementar escala
60
- const increaseScale = () => {
61
- setScale(scale.value + 0.1)
62
- }
63
-
64
- // Decrementar escala
65
- const decreaseScale = () => {
66
- setScale(scale.value - 0.1)
67
- }
68
-
69
- // Reset para padrão
70
- const resetScale = () => {
71
- setScale(1.0)
72
- }
73
-
74
- // Watch para mudanças na escala
75
- watch(scale, (newScale) => {
76
- applyScale(newScale)
77
- })
78
-
79
- // Inicializar na montagem
80
- onMounted(() => {
81
- const savedScale = loadScale()
82
- applyScale(savedScale)
83
- })
84
-
85
- return {
86
- scale: clampedScale,
87
- scaleClass,
88
- setScale,
89
- increaseScale,
90
- decreaseScale,
91
- resetScale,
92
- minScale: MIN_SCALE,
93
- maxScale: MAX_SCALE
94
- }
1
+ import { ref, computed, watch, onMounted } from 'vue'
2
+
3
+ /**
4
+ * Composable para controle de escalabilidade de acessibilidade
5
+ * Implementa escala controlada entre 0.8x e 2.0x conforme WCAG 2.1
6
+ */
7
+ export function useAccessibilityScale() {
8
+ const scale = ref(1.0)
9
+ const STORAGE_KEY = 'datametria-accessibility-scale'
10
+ const MIN_SCALE = 0.8
11
+ const MAX_SCALE = 2.0
12
+
13
+ // Computed para escala clampada
14
+ const clampedScale = computed(() =>
15
+ Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale.value))
16
+ )
17
+
18
+ // Computed para classe CSS
19
+ const scaleClass = computed(() => {
20
+ const s = clampedScale.value
21
+ if (s <= 0.9) return 'scale-small'
22
+ if (s >= 1.1) return 'scale-large'
23
+ return 'scale-normal'
24
+ })
25
+
26
+ // Aplicar escala ao documento
27
+ const applyScale = (newScale: number) => {
28
+ const finalScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, newScale))
29
+ document.documentElement.style.setProperty('--user-scale', finalScale.toString())
30
+ scale.value = finalScale
31
+ }
32
+
33
+ // Salvar no localStorage
34
+ const saveScale = (scaleValue: number) => {
35
+ try {
36
+ localStorage.setItem(STORAGE_KEY, scaleValue.toString())
37
+ } catch (error) {
38
+ console.warn('Failed to save accessibility scale:', error)
39
+ }
40
+ }
41
+
42
+ // Carregar do localStorage
43
+ const loadScale = (): number => {
44
+ try {
45
+ const saved = localStorage.getItem(STORAGE_KEY)
46
+ return saved ? parseFloat(saved) : 1.0
47
+ } catch (error) {
48
+ console.warn('Failed to load accessibility scale:', error)
49
+ return 1.0
50
+ }
51
+ }
52
+
53
+ // Definir escala
54
+ const setScale = (newScale: number) => {
55
+ applyScale(newScale)
56
+ saveScale(clampedScale.value)
57
+ }
58
+
59
+ // Incrementar escala
60
+ const increaseScale = () => {
61
+ setScale(scale.value + 0.1)
62
+ }
63
+
64
+ // Decrementar escala
65
+ const decreaseScale = () => {
66
+ setScale(scale.value - 0.1)
67
+ }
68
+
69
+ // Reset para padrão
70
+ const resetScale = () => {
71
+ setScale(1.0)
72
+ }
73
+
74
+ // Watch para mudanças na escala
75
+ watch(scale, (newScale) => {
76
+ applyScale(newScale)
77
+ })
78
+
79
+ // Inicializar na montagem
80
+ onMounted(() => {
81
+ const savedScale = loadScale()
82
+ applyScale(savedScale)
83
+ })
84
+
85
+ return {
86
+ scale: clampedScale,
87
+ scaleClass,
88
+ setScale,
89
+ increaseScale,
90
+ decreaseScale,
91
+ resetScale,
92
+ minScale: MIN_SCALE,
93
+ maxScale: MAX_SCALE
94
+ }
95
95
  }
@@ -1,83 +1,83 @@
1
- import { ref, onMounted, onUnmounted } from 'vue'
2
-
3
- export const breakpoints = {
4
- xs: 475,
5
- sm: 640,
6
- md: 768,
7
- lg: 1024,
8
- xl: 1280,
9
- '2xl': 1536
10
- } as const
11
-
12
- export type Breakpoint = keyof typeof breakpoints
13
-
14
- export function useBreakpoints() {
15
- const windowWidth = ref(0)
16
-
17
- const updateWidth = () => {
18
- windowWidth.value = window.innerWidth
19
- }
20
-
21
- const isGreaterOrEqual = (breakpoint: Breakpoint) => {
22
- return windowWidth.value >= breakpoints[breakpoint]
23
- }
24
-
25
- const isLessOrEqual = (breakpoint: Breakpoint) => {
26
- return windowWidth.value <= breakpoints[breakpoint]
27
- }
28
-
29
- const isBetween = (min: Breakpoint, max: Breakpoint) => {
30
- return windowWidth.value >= breakpoints[min] && windowWidth.value <= breakpoints[max]
31
- }
32
-
33
- const getCurrentBreakpoint = (): Breakpoint => {
34
- const width = windowWidth.value
35
-
36
- if (width >= breakpoints['2xl']) return '2xl'
37
- if (width >= breakpoints.xl) return 'xl'
38
- if (width >= breakpoints.lg) return 'lg'
39
- if (width >= breakpoints.md) return 'md'
40
- if (width >= breakpoints.sm) return 'sm'
41
- return 'xs'
42
- }
43
-
44
- // Reactive breakpoint checks
45
- const isXs = () => windowWidth.value < breakpoints.sm
46
- const isSm = () => isGreaterOrEqual('sm') && windowWidth.value < breakpoints.md
47
- const isMd = () => isGreaterOrEqual('md') && windowWidth.value < breakpoints.lg
48
- const isLg = () => isGreaterOrEqual('lg') && windowWidth.value < breakpoints.xl
49
- const isXl = () => isGreaterOrEqual('xl') && windowWidth.value < breakpoints['2xl']
50
- const is2xl = () => isGreaterOrEqual('2xl')
51
-
52
- // Mobile/tablet/desktop helpers
53
- const isMobile = () => windowWidth.value < breakpoints.md
54
- const isTablet = () => isBetween('md', 'lg')
55
- const isDesktop = () => isGreaterOrEqual('lg')
56
-
57
- onMounted(() => {
58
- updateWidth()
59
- window.addEventListener('resize', updateWidth)
60
- })
61
-
62
- onUnmounted(() => {
63
- window.removeEventListener('resize', updateWidth)
64
- })
65
-
66
- return {
67
- windowWidth,
68
- breakpoints,
69
- isGreaterOrEqual,
70
- isLessOrEqual,
71
- isBetween,
72
- getCurrentBreakpoint,
73
- isXs,
74
- isSm,
75
- isMd,
76
- isLg,
77
- isXl,
78
- is2xl,
79
- isMobile,
80
- isTablet,
81
- isDesktop
82
- }
1
+ import { ref, onMounted, onUnmounted } from 'vue'
2
+
3
+ export const breakpoints = {
4
+ xs: 475,
5
+ sm: 640,
6
+ md: 768,
7
+ lg: 1024,
8
+ xl: 1280,
9
+ '2xl': 1536
10
+ } as const
11
+
12
+ export type Breakpoint = keyof typeof breakpoints
13
+
14
+ export function useBreakpoints() {
15
+ const windowWidth = ref(0)
16
+
17
+ const updateWidth = () => {
18
+ windowWidth.value = window.innerWidth
19
+ }
20
+
21
+ const isGreaterOrEqual = (breakpoint: Breakpoint) => {
22
+ return windowWidth.value >= breakpoints[breakpoint]
23
+ }
24
+
25
+ const isLessOrEqual = (breakpoint: Breakpoint) => {
26
+ return windowWidth.value <= breakpoints[breakpoint]
27
+ }
28
+
29
+ const isBetween = (min: Breakpoint, max: Breakpoint) => {
30
+ return windowWidth.value >= breakpoints[min] && windowWidth.value <= breakpoints[max]
31
+ }
32
+
33
+ const getCurrentBreakpoint = (): Breakpoint => {
34
+ const width = windowWidth.value
35
+
36
+ if (width >= breakpoints['2xl']) return '2xl'
37
+ if (width >= breakpoints.xl) return 'xl'
38
+ if (width >= breakpoints.lg) return 'lg'
39
+ if (width >= breakpoints.md) return 'md'
40
+ if (width >= breakpoints.sm) return 'sm'
41
+ return 'xs'
42
+ }
43
+
44
+ // Reactive breakpoint checks
45
+ const isXs = () => windowWidth.value < breakpoints.sm
46
+ const isSm = () => isGreaterOrEqual('sm') && windowWidth.value < breakpoints.md
47
+ const isMd = () => isGreaterOrEqual('md') && windowWidth.value < breakpoints.lg
48
+ const isLg = () => isGreaterOrEqual('lg') && windowWidth.value < breakpoints.xl
49
+ const isXl = () => isGreaterOrEqual('xl') && windowWidth.value < breakpoints['2xl']
50
+ const is2xl = () => isGreaterOrEqual('2xl')
51
+
52
+ // Mobile/tablet/desktop helpers
53
+ const isMobile = () => windowWidth.value < breakpoints.md
54
+ const isTablet = () => isBetween('md', 'lg')
55
+ const isDesktop = () => isGreaterOrEqual('lg')
56
+
57
+ onMounted(() => {
58
+ updateWidth()
59
+ window.addEventListener('resize', updateWidth)
60
+ })
61
+
62
+ onUnmounted(() => {
63
+ window.removeEventListener('resize', updateWidth)
64
+ })
65
+
66
+ return {
67
+ windowWidth,
68
+ breakpoints,
69
+ isGreaterOrEqual,
70
+ isLessOrEqual,
71
+ isBetween,
72
+ getCurrentBreakpoint,
73
+ isXs,
74
+ isSm,
75
+ isMd,
76
+ isLg,
77
+ isXl,
78
+ is2xl,
79
+ isMobile,
80
+ isTablet,
81
+ isDesktop
82
+ }
83
83
  }