@pyreweb/fabric 1.2.6

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 (210) hide show
  1. package/README.md +119 -0
  2. package/dist/fabric.cjs.js +18109 -0
  3. package/dist/fabric.css +2180 -0
  4. package/dist/fabric.esm.js +18062 -0
  5. package/dist/fabric.min.js +18112 -0
  6. package/dist/types/components/atoms/FAvatar/FAvatar.test.d.ts +1 -0
  7. package/dist/types/components/atoms/FBadge/FBadge.test.d.ts +1 -0
  8. package/dist/types/components/atoms/FButton/FButton.test.d.ts +1 -0
  9. package/dist/types/components/atoms/FCheckbox/FCheckbox.test.d.ts +1 -0
  10. package/dist/types/components/atoms/FDivider/FDivider.test.d.ts +1 -0
  11. package/dist/types/components/atoms/FIcon/FIcon.test.d.ts +1 -0
  12. package/dist/types/components/atoms/FInput/FInput.test.d.ts +1 -0
  13. package/dist/types/components/atoms/FLoader/FLoader.test.d.ts +1 -0
  14. package/dist/types/components/atoms/FRadio/FRadio.test.d.ts +1 -0
  15. package/dist/types/components/atoms/FTextarea/FTextarea.test.d.ts +1 -0
  16. package/dist/types/components/atoms/FToggle/FToggle.test.d.ts +1 -0
  17. package/dist/types/components/atoms/FTypography/FTypography.test.d.ts +1 -0
  18. package/dist/types/components/atoms/index.d.ts +13 -0
  19. package/dist/types/components/molecules/FAccordionItem/FAccordionItem.test.d.ts +1 -0
  20. package/dist/types/components/molecules/FAlert/FAlert.test.d.ts +1 -0
  21. package/dist/types/components/molecules/FBreadcrumb/FBreadcrumb.test.d.ts +1 -0
  22. package/dist/types/components/molecules/FButtonGroup/FButtonGroup.test.d.ts +1 -0
  23. package/dist/types/components/molecules/FCard/FCard.test.d.ts +1 -0
  24. package/dist/types/components/molecules/FDatePicker/FDatePicker.test.d.ts +1 -0
  25. package/dist/types/components/molecules/FEmptyState/FEmptyState.test.d.ts +1 -0
  26. package/dist/types/components/molecules/FFilePreview/FFilePreview.test.d.ts +1 -0
  27. package/dist/types/components/molecules/FFormField/FFormField.test.d.ts +1 -0
  28. package/dist/types/components/molecules/FListItem/FListItem.test.d.ts +1 -0
  29. package/dist/types/components/molecules/FPagination/FPagination.test.d.ts +1 -0
  30. package/dist/types/components/molecules/FSearchBar/FSearchBar.test.d.ts +1 -0
  31. package/dist/types/components/molecules/FSelect/FSelect.test.d.ts +1 -0
  32. package/dist/types/components/molecules/FStatCard/FStatCard.test.d.ts +1 -0
  33. package/dist/types/components/molecules/FTabs/FTabs.test.d.ts +1 -0
  34. package/dist/types/components/molecules/FToast/FToast.test.d.ts +1 -0
  35. package/dist/types/components/molecules/index.d.ts +18 -0
  36. package/dist/types/components/organisms/FActivityFeed/FActivityFeed.test.d.ts +1 -0
  37. package/dist/types/components/organisms/FDataTable/FDataTable.test.d.ts +1 -0
  38. package/dist/types/components/organisms/FDrawer/FDrawer.test.d.ts +1 -0
  39. package/dist/types/components/organisms/FFileUpload/FFileUpload.test.d.ts +1 -0
  40. package/dist/types/components/organisms/FFilterSidebar/FFilterSidebar.test.d.ts +1 -0
  41. package/dist/types/components/organisms/FForm/FForm.test.d.ts +1 -0
  42. package/dist/types/components/organisms/FModal/FModal.test.d.ts +1 -0
  43. package/dist/types/components/organisms/FNavigationSidebar/FNavigationSidebar.test.d.ts +1 -0
  44. package/dist/types/components/organisms/FOnboardingStepper/FOnboardingStepper.test.d.ts +1 -0
  45. package/dist/types/components/organisms/FOnboardingStepper/FStepperProgress.test.d.ts +1 -0
  46. package/dist/types/components/organisms/FPageHeader/FPageHeader.test.d.ts +1 -0
  47. package/dist/types/components/organisms/FProfileSection/FProfileSection.test.d.ts +1 -0
  48. package/dist/types/components/organisms/FToastProvider/FToastProvider.test.d.ts +1 -0
  49. package/dist/types/components/organisms/FUserMenu/FUserMenu.test.d.ts +1 -0
  50. package/dist/types/components/organisms/index.d.ts +14 -0
  51. package/dist/types/components/utils/FThemeProvider.test.d.ts +1 -0
  52. package/dist/types/components/utils/index.d.ts +2 -0
  53. package/dist/types/components.d.ts +602 -0
  54. package/dist/types/composables/index.d.ts +12 -0
  55. package/dist/types/composables/useDataTableState.d.ts +106 -0
  56. package/dist/types/composables/useDataTableState.test.d.ts +1 -0
  57. package/dist/types/composables/useFormValidation.d.ts +49 -0
  58. package/dist/types/composables/useFormValidation.test.d.ts +1 -0
  59. package/dist/types/composables/useSidebarState.d.ts +65 -0
  60. package/dist/types/composables/useSidebarState.test.d.ts +1 -0
  61. package/dist/types/index.d.ts +19 -0
  62. package/dist/types/types.d.ts +529 -0
  63. package/package.json +100 -0
  64. package/src/components/atoms/FAvatar/FAvatar.stories.js +100 -0
  65. package/src/components/atoms/FAvatar/FAvatar.test.ts +95 -0
  66. package/src/components/atoms/FAvatar/FAvatar.vue +190 -0
  67. package/src/components/atoms/FBadge/FBadge.stories.js +129 -0
  68. package/src/components/atoms/FBadge/FBadge.test.ts +93 -0
  69. package/src/components/atoms/FBadge/FBadge.vue +103 -0
  70. package/src/components/atoms/FButton/FButton.stories.js +122 -0
  71. package/src/components/atoms/FButton/FButton.test.ts +98 -0
  72. package/src/components/atoms/FButton/FButton.vue +147 -0
  73. package/src/components/atoms/FCheckbox/FCheckbox.stories.js +96 -0
  74. package/src/components/atoms/FCheckbox/FCheckbox.test.ts +64 -0
  75. package/src/components/atoms/FCheckbox/FCheckbox.vue +76 -0
  76. package/src/components/atoms/FDivider/FDivider.stories.js +104 -0
  77. package/src/components/atoms/FDivider/FDivider.test.ts +80 -0
  78. package/src/components/atoms/FDivider/FDivider.vue +117 -0
  79. package/src/components/atoms/FIcon/FIcon.stories.js +189 -0
  80. package/src/components/atoms/FIcon/FIcon.test.ts +99 -0
  81. package/src/components/atoms/FIcon/FIcon.vue +192 -0
  82. package/src/components/atoms/FInput/FInput.stories.js +119 -0
  83. package/src/components/atoms/FInput/FInput.test.ts +79 -0
  84. package/src/components/atoms/FInput/FInput.vue +88 -0
  85. package/src/components/atoms/FLoader/FLoader.stories.js +109 -0
  86. package/src/components/atoms/FLoader/FLoader.test.ts +66 -0
  87. package/src/components/atoms/FLoader/FLoader.vue +97 -0
  88. package/src/components/atoms/FRadio/FRadio.stories.js +105 -0
  89. package/src/components/atoms/FRadio/FRadio.test.ts +75 -0
  90. package/src/components/atoms/FRadio/FRadio.vue +119 -0
  91. package/src/components/atoms/FTextarea/FTextarea.stories.js +126 -0
  92. package/src/components/atoms/FTextarea/FTextarea.test.ts +94 -0
  93. package/src/components/atoms/FTextarea/FTextarea.vue +156 -0
  94. package/src/components/atoms/FToggle/FToggle.stories.js +108 -0
  95. package/src/components/atoms/FToggle/FToggle.test.ts +96 -0
  96. package/src/components/atoms/FToggle/FToggle.vue +123 -0
  97. package/src/components/atoms/FTypography/FTypography.stories.js +127 -0
  98. package/src/components/atoms/FTypography/FTypography.test.ts +93 -0
  99. package/src/components/atoms/FTypography/FTypography.vue +78 -0
  100. package/src/components/atoms/index.ts +27 -0
  101. package/src/components/molecules/FAccordionItem/FAccordionItem.stories.js +71 -0
  102. package/src/components/molecules/FAccordionItem/FAccordionItem.test.ts +61 -0
  103. package/src/components/molecules/FAccordionItem/FAccordionItem.vue +105 -0
  104. package/src/components/molecules/FAlert/FAlert.stories.js +87 -0
  105. package/src/components/molecules/FAlert/FAlert.test.ts +59 -0
  106. package/src/components/molecules/FAlert/FAlert.vue +108 -0
  107. package/src/components/molecules/FBreadcrumb/FBreadcrumb.stories.js +90 -0
  108. package/src/components/molecules/FBreadcrumb/FBreadcrumb.test.ts +76 -0
  109. package/src/components/molecules/FBreadcrumb/FBreadcrumb.vue +117 -0
  110. package/src/components/molecules/FButtonGroup/FButtonGroup.stories.js +82 -0
  111. package/src/components/molecules/FButtonGroup/FButtonGroup.test.ts +44 -0
  112. package/src/components/molecules/FButtonGroup/FButtonGroup.vue +31 -0
  113. package/src/components/molecules/FCard/FCard.stories.js +136 -0
  114. package/src/components/molecules/FCard/FCard.test.ts +87 -0
  115. package/src/components/molecules/FCard/FCard.vue +75 -0
  116. package/src/components/molecules/FDatePicker/FDatePicker.stories.js +305 -0
  117. package/src/components/molecules/FDatePicker/FDatePicker.test.ts +282 -0
  118. package/src/components/molecules/FDatePicker/FDatePicker.vue +750 -0
  119. package/src/components/molecules/FEmptyState/FEmptyState.stories.js +98 -0
  120. package/src/components/molecules/FEmptyState/FEmptyState.test.ts +82 -0
  121. package/src/components/molecules/FEmptyState/FEmptyState.vue +89 -0
  122. package/src/components/molecules/FFilePreview/FFilePreview.stories.js +130 -0
  123. package/src/components/molecules/FFilePreview/FFilePreview.test.ts +70 -0
  124. package/src/components/molecules/FFilePreview/FFilePreview.vue +125 -0
  125. package/src/components/molecules/FFormField/FFormField.stories.js +149 -0
  126. package/src/components/molecules/FFormField/FFormField.test.ts +85 -0
  127. package/src/components/molecules/FFormField/FFormField.vue +107 -0
  128. package/src/components/molecules/FListItem/FListItem.stories.js +158 -0
  129. package/src/components/molecules/FListItem/FListItem.test.ts +93 -0
  130. package/src/components/molecules/FListItem/FListItem.vue +113 -0
  131. package/src/components/molecules/FPagination/FPagination.stories.js +132 -0
  132. package/src/components/molecules/FPagination/FPagination.test.ts +79 -0
  133. package/src/components/molecules/FPagination/FPagination.vue +206 -0
  134. package/src/components/molecules/FSearchBar/FSearchBar.stories.js +129 -0
  135. package/src/components/molecules/FSearchBar/FSearchBar.test.ts +81 -0
  136. package/src/components/molecules/FSearchBar/FSearchBar.vue +180 -0
  137. package/src/components/molecules/FSelect/FSelect.stories.js +333 -0
  138. package/src/components/molecules/FSelect/FSelect.test.ts +478 -0
  139. package/src/components/molecules/FSelect/FSelect.vue +551 -0
  140. package/src/components/molecules/FStatCard/FStatCard.stories.js +144 -0
  141. package/src/components/molecules/FStatCard/FStatCard.test.ts +78 -0
  142. package/src/components/molecules/FStatCard/FStatCard.vue +106 -0
  143. package/src/components/molecules/FTabs/FTab.vue +63 -0
  144. package/src/components/molecules/FTabs/FTabs.stories.js +277 -0
  145. package/src/components/molecules/FTabs/FTabs.test.ts +264 -0
  146. package/src/components/molecules/FTabs/FTabs.vue +273 -0
  147. package/src/components/molecules/FToast/FToast.stories.js +150 -0
  148. package/src/components/molecules/FToast/FToast.test.ts +157 -0
  149. package/src/components/molecules/FToast/FToast.vue +283 -0
  150. package/src/components/molecules/index.ts +37 -0
  151. package/src/components/organisms/FActivityFeed/FActivityFeed.stories.js +217 -0
  152. package/src/components/organisms/FActivityFeed/FActivityFeed.test.ts +134 -0
  153. package/src/components/organisms/FActivityFeed/FActivityFeed.vue +589 -0
  154. package/src/components/organisms/FDataTable/FDataTable.stories.js +370 -0
  155. package/src/components/organisms/FDataTable/FDataTable.test.ts +248 -0
  156. package/src/components/organisms/FDataTable/FDataTable.vue +808 -0
  157. package/src/components/organisms/FDrawer/FDrawer.stories.js +296 -0
  158. package/src/components/organisms/FDrawer/FDrawer.test.ts +142 -0
  159. package/src/components/organisms/FDrawer/FDrawer.vue +303 -0
  160. package/src/components/organisms/FFileUpload/FFileUpload.stories.js +162 -0
  161. package/src/components/organisms/FFileUpload/FFileUpload.test.ts +103 -0
  162. package/src/components/organisms/FFileUpload/FFileUpload.vue +616 -0
  163. package/src/components/organisms/FFilterSidebar/FFilterSidebar.stories.js +161 -0
  164. package/src/components/organisms/FFilterSidebar/FFilterSidebar.test.ts +92 -0
  165. package/src/components/organisms/FFilterSidebar/FFilterSidebar.vue +458 -0
  166. package/src/components/organisms/FForm/FForm.stories.js +270 -0
  167. package/src/components/organisms/FForm/FForm.test.ts +63 -0
  168. package/src/components/organisms/FForm/FForm.vue +19 -0
  169. package/src/components/organisms/FModal/FModal.stories.js +227 -0
  170. package/src/components/organisms/FModal/FModal.test.ts +181 -0
  171. package/src/components/organisms/FModal/FModal.vue +319 -0
  172. package/src/components/organisms/FNavigationSidebar/FNavigationSidebar.stories.js +176 -0
  173. package/src/components/organisms/FNavigationSidebar/FNavigationSidebar.test.ts +95 -0
  174. package/src/components/organisms/FNavigationSidebar/FNavigationSidebar.vue +577 -0
  175. package/src/components/organisms/FOnboardingStepper/FOnboardingStepper.stories.js +197 -0
  176. package/src/components/organisms/FOnboardingStepper/FOnboardingStepper.test.ts +114 -0
  177. package/src/components/organisms/FOnboardingStepper/FOnboardingStepper.vue +212 -0
  178. package/src/components/organisms/FOnboardingStepper/FStepperProgress.stories.js +122 -0
  179. package/src/components/organisms/FOnboardingStepper/FStepperProgress.test.ts +130 -0
  180. package/src/components/organisms/FOnboardingStepper/FStepperProgress.vue +146 -0
  181. package/src/components/organisms/FPageHeader/FPageHeader.stories.js +142 -0
  182. package/src/components/organisms/FPageHeader/FPageHeader.test.ts +83 -0
  183. package/src/components/organisms/FPageHeader/FPageHeader.vue +241 -0
  184. package/src/components/organisms/FProfileSection/FProfileSection.stories.js +190 -0
  185. package/src/components/organisms/FProfileSection/FProfileSection.test.ts +85 -0
  186. package/src/components/organisms/FProfileSection/FProfileSection.vue +562 -0
  187. package/src/components/organisms/FToastProvider/FToastProvider.stories.js +290 -0
  188. package/src/components/organisms/FToastProvider/FToastProvider.test.ts +215 -0
  189. package/src/components/organisms/FToastProvider/FToastProvider.vue +214 -0
  190. package/src/components/organisms/FUserMenu/FUserMenu.stories.js +170 -0
  191. package/src/components/organisms/FUserMenu/FUserMenu.test.ts +102 -0
  192. package/src/components/organisms/FUserMenu/FUserMenu.vue +407 -0
  193. package/src/components/organisms/index.ts +29 -0
  194. package/src/components/utils/FThemeProvider.stories.js +236 -0
  195. package/src/components/utils/FThemeProvider.test.ts +244 -0
  196. package/src/components/utils/FThemeProvider.vue +191 -0
  197. package/src/components/utils/index.ts +3 -0
  198. package/src/components.d.ts +602 -0
  199. package/src/composables/README.md +233 -0
  200. package/src/composables/index.ts +25 -0
  201. package/src/composables/useDataTableState.test.ts +378 -0
  202. package/src/composables/useDataTableState.ts +361 -0
  203. package/src/composables/useFormValidation.test.ts +198 -0
  204. package/src/composables/useFormValidation.ts +178 -0
  205. package/src/composables/useSidebarState.test.ts +307 -0
  206. package/src/composables/useSidebarState.ts +201 -0
  207. package/src/env.d.ts +14 -0
  208. package/src/index.ts +167 -0
  209. package/src/styles/tailwind.css +173 -0
  210. package/src/types.ts +740 -0
@@ -0,0 +1,236 @@
1
+ import FThemeProvider from './FThemeProvider.vue';
2
+
3
+ export default {
4
+ title: 'Utils/FThemeProvider',
5
+ component: FThemeProvider,
6
+ argTypes: {
7
+ defaultTheme: {
8
+ control: { type: 'select' },
9
+ options: ['light', 'dark', 'auto'],
10
+ description: 'Default theme to use when no preference is stored'
11
+ },
12
+ storageKey: {
13
+ control: { type: 'text' },
14
+ description: 'Key used for localStorage persistence'
15
+ },
16
+ enablePersistence: {
17
+ control: { type: 'boolean' },
18
+ description: 'Enable or disable localStorage persistence'
19
+ }
20
+ }
21
+ };
22
+
23
+ const Template = (args, { argTypes }) => ({
24
+ components: { FThemeProvider },
25
+ props: Object.keys(argTypes),
26
+ template: `
27
+ <f-theme-provider v-bind="$props" v-slot="{ theme, toggleTheme, setTheme }">
28
+ <div class="min-h-screen p-8 transition-colors duration-300"
29
+ :style="{
30
+ backgroundColor: 'var(--theme-background)',
31
+ color: 'var(--theme-foreground)'
32
+ }">
33
+ <div class="max-w-4xl mx-auto space-y-6">
34
+ <!-- Theme Controls -->
35
+ <div class="flex gap-4 mb-8">
36
+ <button
37
+ @click="toggleTheme"
38
+ class="px-4 py-2 rounded-lg font-medium transition-colors"
39
+ :style="{
40
+ backgroundColor: 'var(--theme-primary)',
41
+ color: 'var(--theme-primary-foreground)'
42
+ }">
43
+ Basculer le Thème
44
+ </button>
45
+ <button
46
+ @click="setTheme('light')"
47
+ class="px-4 py-2 rounded-lg border transition-colors"
48
+ :style="{
49
+ borderColor: 'var(--theme-border)',
50
+ backgroundColor: theme === 'light' ? 'var(--theme-primary)' : 'var(--theme-card)',
51
+ color: theme === 'light' ? 'var(--theme-primary-foreground)' : 'var(--theme-foreground)'
52
+ }">
53
+ Mode Clair
54
+ </button>
55
+ <button
56
+ @click="setTheme('dark')"
57
+ class="px-4 py-2 rounded-lg border transition-colors"
58
+ :style="{
59
+ borderColor: 'var(--theme-border)',
60
+ backgroundColor: theme === 'dark' ? 'var(--theme-primary)' : 'var(--theme-card)',
61
+ color: theme === 'dark' ? 'var(--theme-primary-foreground)' : 'var(--theme-foreground)'
62
+ }">
63
+ Mode Sombre
64
+ </button>
65
+ </div>
66
+
67
+ <!-- Current Theme Display -->
68
+ <div class="p-4 rounded-lg border"
69
+ :style="{
70
+ backgroundColor: 'var(--theme-card)',
71
+ borderColor: 'var(--theme-border)'
72
+ }">
73
+ <h3 class="text-lg font-semibold mb-2">
74
+ Thème actuel : <strong>{{ theme }}</strong>
75
+ </h3>
76
+ <p :style="{ color: 'var(--theme-muted-foreground)' }">
77
+ Le thème est automatiquement persisté dans localStorage et appliqué à tous les composants.
78
+ </p>
79
+ </div>
80
+
81
+ <!-- Color Palette Demo -->
82
+ <div class="space-y-4">
83
+ <h2 class="text-2xl font-bold">Palette de Couleurs</h2>
84
+
85
+ <!-- Primary Colors -->
86
+ <div>
87
+ <h3 class="text-lg font-semibold mb-2">Primaire</h3>
88
+ <div class="flex gap-2 flex-wrap">
89
+ <div v-for="shade in [50, 100, 200, 300, 400, 500, 600, 700, 800, 900]"
90
+ :key="shade"
91
+ class="w-16 h-16 rounded flex items-center justify-center text-xs font-bold"
92
+ :style="{
93
+ backgroundColor: 'var(--color-primary-' + shade + ')',
94
+ color: shade >= 500 ? 'white' : 'black'
95
+ }">
96
+ {{ shade }}
97
+ </div>
98
+ </div>
99
+ </div>
100
+
101
+ <!-- Success Colors -->
102
+ <div>
103
+ <h3 class="text-lg font-semibold mb-2">Succès</h3>
104
+ <div class="flex gap-2 flex-wrap">
105
+ <div v-for="shade in [50, 100, 200, 300, 400, 500, 600, 700, 800, 900]"
106
+ :key="shade"
107
+ class="w-16 h-16 rounded flex items-center justify-center text-xs font-bold"
108
+ :style="{
109
+ backgroundColor: 'var(--color-success-' + shade + ')',
110
+ color: shade >= 500 ? 'white' : 'black'
111
+ }">
112
+ {{ shade }}
113
+ </div>
114
+ </div>
115
+ </div>
116
+
117
+ <!-- Danger Colors -->
118
+ <div>
119
+ <h3 class="text-lg font-semibold mb-2">Danger</h3>
120
+ <div class="flex gap-2 flex-wrap">
121
+ <div v-for="shade in [50, 100, 200, 300, 400, 500, 600, 700, 800, 900]"
122
+ :key="shade"
123
+ class="w-16 h-16 rounded flex items-center justify-center text-xs font-bold"
124
+ :style="{
125
+ backgroundColor: 'var(--color-danger-' + shade + ')',
126
+ color: shade >= 500 ? 'white' : 'black'
127
+ }">
128
+ {{ shade }}
129
+ </div>
130
+ </div>
131
+ </div>
132
+
133
+ <!-- Warning Colors -->
134
+ <div>
135
+ <h3 class="text-lg font-semibold mb-2">Avertissement</h3>
136
+ <div class="flex gap-2 flex-wrap">
137
+ <div v-for="shade in [50, 100, 200, 300, 400, 500, 600, 700, 800, 900]"
138
+ :key="shade"
139
+ class="w-16 h-16 rounded flex items-center justify-center text-xs font-bold"
140
+ :style="{
141
+ backgroundColor: 'var(--color-warning-' + shade + ')',
142
+ color: shade >= 500 ? 'white' : 'black'
143
+ }">
144
+ {{ shade }}
145
+ </div>
146
+ </div>
147
+ </div>
148
+ </div>
149
+
150
+ <!-- Semantic Theme Variables -->
151
+ <div class="space-y-4 mt-8">
152
+ <h2 class="text-2xl font-bold">Variables Thématiques Sémantiques</h2>
153
+ <div class="grid grid-cols-2 gap-4">
154
+ <div class="p-4 rounded-lg" :style="{ backgroundColor: 'var(--theme-card)', border: '1px solid var(--theme-border)' }">
155
+ <p class="font-mono text-sm mb-2">--theme-background</p>
156
+ <div class="w-full h-12 rounded" :style="{ backgroundColor: 'var(--theme-background)', border: '1px solid var(--theme-border)' }"></div>
157
+ </div>
158
+ <div class="p-4 rounded-lg" :style="{ backgroundColor: 'var(--theme-card)', border: '1px solid var(--theme-border)' }">
159
+ <p class="font-mono text-sm mb-2">--theme-foreground</p>
160
+ <div class="w-full h-12 rounded" :style="{ backgroundColor: 'var(--theme-foreground)' }"></div>
161
+ </div>
162
+ <div class="p-4 rounded-lg" :style="{ backgroundColor: 'var(--theme-card)', border: '1px solid var(--theme-border)' }">
163
+ <p class="font-mono text-sm mb-2">--theme-card</p>
164
+ <div class="w-full h-12 rounded" :style="{ backgroundColor: 'var(--theme-card)', border: '1px solid var(--theme-border)' }"></div>
165
+ </div>
166
+ <div class="p-4 rounded-lg" :style="{ backgroundColor: 'var(--theme-card)', border: '1px solid var(--theme-border)' }">
167
+ <p class="font-mono text-sm mb-2">--theme-muted</p>
168
+ <div class="w-full h-12 rounded" :style="{ backgroundColor: 'var(--theme-muted)' }"></div>
169
+ </div>
170
+ <div class="p-4 rounded-lg" :style="{ backgroundColor: 'var(--theme-card)', border: '1px solid var(--theme-border)' }">
171
+ <p class="font-mono text-sm mb-2">--theme-primary</p>
172
+ <div class="w-full h-12 rounded" :style="{ backgroundColor: 'var(--theme-primary)' }"></div>
173
+ </div>
174
+ <div class="p-4 rounded-lg" :style="{ backgroundColor: 'var(--theme-card)', border: '1px solid var(--theme-border)' }">
175
+ <p class="font-mono text-sm mb-2">--theme-border</p>
176
+ <div class="w-full h-12 rounded border-4" :style="{ borderColor: 'var(--theme-border)' }"></div>
177
+ </div>
178
+ </div>
179
+ </div>
180
+
181
+ <!-- Example Cards -->
182
+ <div class="space-y-4 mt-8">
183
+ <h2 class="text-2xl font-bold">Exemples de Cartes</h2>
184
+ <div class="grid grid-cols-3 gap-4">
185
+ <div class="p-6 rounded-lg" :style="{ backgroundColor: 'var(--theme-card)', border: '1px solid var(--theme-border)' }">
186
+ <h3 class="text-lg font-semibold mb-2">Carte 1</h3>
187
+ <p :style="{ color: 'var(--theme-muted-foreground)' }">
188
+ Contenu de la première carte avec un style thématique.
189
+ </p>
190
+ </div>
191
+ <div class="p-6 rounded-lg" :style="{ backgroundColor: 'var(--theme-card)', border: '1px solid var(--theme-border)' }">
192
+ <h3 class="text-lg font-semibold mb-2">Carte 2</h3>
193
+ <p :style="{ color: 'var(--theme-muted-foreground)' }">
194
+ Contenu de la deuxième carte.
195
+ </p>
196
+ </div>
197
+ <div class="p-6 rounded-lg" :style="{ backgroundColor: 'var(--theme-card)', border: '1px solid var(--theme-border)' }">
198
+ <h3 class="text-lg font-semibold mb-2">Carte 3</h3>
199
+ <p :style="{ color: 'var(--theme-muted-foreground)' }">
200
+ Contenu de la troisième carte.
201
+ </p>
202
+ </div>
203
+ </div>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ </f-theme-provider>
208
+ `
209
+ });
210
+
211
+ export const Default = Template.bind({});
212
+ Default.args = {
213
+ defaultTheme: 'light',
214
+ storageKey: 'fabric-theme',
215
+ enablePersistence: true
216
+ };
217
+
218
+ export const DarkMode = Template.bind({});
219
+ DarkMode.args = {
220
+ defaultTheme: 'dark',
221
+ storageKey: 'fabric-theme',
222
+ enablePersistence: true
223
+ };
224
+
225
+ export const AutoMode = Template.bind({});
226
+ AutoMode.args = {
227
+ defaultTheme: 'auto',
228
+ storageKey: 'fabric-theme',
229
+ enablePersistence: true
230
+ };
231
+
232
+ export const NoPersistence = Template.bind({});
233
+ NoPersistence.args = {
234
+ defaultTheme: 'light',
235
+ enablePersistence: false
236
+ };
@@ -0,0 +1,244 @@
1
+ import { mount, createLocalVue } from '@vue/test-utils';
2
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
3
+ import FThemeProvider from './FThemeProvider.vue';
4
+
5
+ const localVue = createLocalVue();
6
+
7
+ describe('FThemeProvider', () => {
8
+ let wrapper;
9
+
10
+ beforeEach(() => {
11
+ // Clear localStorage before each test
12
+ localStorage.clear();
13
+ });
14
+
15
+ afterEach(() => {
16
+ if (wrapper) {
17
+ wrapper.destroy();
18
+ }
19
+ });
20
+
21
+ it('renders with default light theme', () => {
22
+ wrapper = mount(FThemeProvider, {
23
+ localVue,
24
+ slots: {
25
+ default: '<div>Content</div>'
26
+ }
27
+ });
28
+
29
+ expect(wrapper.attributes('data-theme')).toBe('light');
30
+ });
31
+
32
+ it('renders with dark theme when specified', () => {
33
+ wrapper = mount(FThemeProvider, {
34
+ localVue,
35
+ propsData: {
36
+ defaultTheme: 'dark'
37
+ },
38
+ slots: {
39
+ default: '<div>Content</div>'
40
+ }
41
+ });
42
+
43
+ expect(wrapper.attributes('data-theme')).toBe('dark');
44
+ });
45
+
46
+ it('exposes theme, toggleTheme, and setTheme via scoped slot', () => {
47
+ wrapper = mount(FThemeProvider, {
48
+ localVue,
49
+ scopedSlots: {
50
+ default: function (props) {
51
+ return this.$createElement('div', [
52
+ this.$createElement(
53
+ 'span',
54
+ { attrs: { id: 'theme' } },
55
+ props.theme
56
+ ),
57
+ this.$createElement('button', {
58
+ attrs: { id: 'toggle' },
59
+ on: { click: props.toggleTheme }
60
+ }),
61
+ this.$createElement('button', {
62
+ attrs: { id: 'set-dark' },
63
+ on: {
64
+ click: () => {
65
+ props.setTheme('dark');
66
+ }
67
+ }
68
+ })
69
+ ]);
70
+ }
71
+ }
72
+ });
73
+
74
+ expect(wrapper.find('#theme').text()).toBe('light');
75
+ });
76
+
77
+ it('toggles theme when toggleTheme is called', async () => {
78
+ wrapper = mount(FThemeProvider, {
79
+ localVue,
80
+ scopedSlots: {
81
+ default: function (props) {
82
+ return this.$createElement('div', [
83
+ this.$createElement('button', {
84
+ attrs: { id: 'toggle' },
85
+ on: { click: props.toggleTheme }
86
+ })
87
+ ]);
88
+ }
89
+ }
90
+ });
91
+
92
+ expect(wrapper.attributes('data-theme')).toBe('light');
93
+
94
+ await wrapper.find('#toggle').trigger('click');
95
+ await wrapper.vm.$nextTick();
96
+
97
+ expect(wrapper.attributes('data-theme')).toBe('dark');
98
+
99
+ await wrapper.find('#toggle').trigger('click');
100
+ await wrapper.vm.$nextTick();
101
+
102
+ expect(wrapper.attributes('data-theme')).toBe('light');
103
+ });
104
+
105
+ it('sets specific theme when setTheme is called', async () => {
106
+ wrapper = mount(FThemeProvider, {
107
+ localVue,
108
+ scopedSlots: {
109
+ default: function (props) {
110
+ return this.$createElement('div', [
111
+ this.$createElement('button', {
112
+ attrs: { id: 'set-dark' },
113
+ on: {
114
+ click: () => {
115
+ props.setTheme('dark');
116
+ }
117
+ }
118
+ })
119
+ ]);
120
+ }
121
+ }
122
+ });
123
+
124
+ expect(wrapper.attributes('data-theme')).toBe('light');
125
+
126
+ await wrapper.find('#set-dark').trigger('click');
127
+ await wrapper.vm.$nextTick();
128
+
129
+ expect(wrapper.attributes('data-theme')).toBe('dark');
130
+ });
131
+
132
+ it('persists theme to localStorage when enablePersistence is true', async () => {
133
+ wrapper = mount(FThemeProvider, {
134
+ localVue,
135
+ propsData: {
136
+ enablePersistence: true,
137
+ storageKey: 'test-theme'
138
+ },
139
+ scopedSlots: {
140
+ default: function (props) {
141
+ return this.$createElement('button', {
142
+ attrs: { id: 'toggle' },
143
+ on: { click: props.toggleTheme }
144
+ });
145
+ }
146
+ }
147
+ });
148
+
149
+ await wrapper.find('#toggle').trigger('click');
150
+ await wrapper.vm.$nextTick();
151
+
152
+ expect(localStorage.getItem('test-theme')).toBe('dark');
153
+ });
154
+
155
+ it('does not persist theme when enablePersistence is false', async () => {
156
+ wrapper = mount(FThemeProvider, {
157
+ localVue,
158
+ propsData: {
159
+ enablePersistence: false,
160
+ storageKey: 'test-theme'
161
+ },
162
+ scopedSlots: {
163
+ default: function (props) {
164
+ return this.$createElement('button', {
165
+ attrs: { id: 'toggle' },
166
+ on: { click: props.toggleTheme }
167
+ });
168
+ }
169
+ }
170
+ });
171
+
172
+ await wrapper.find('#toggle').trigger('click');
173
+ await wrapper.vm.$nextTick();
174
+
175
+ expect(localStorage.getItem('test-theme')).toBeNull();
176
+ });
177
+
178
+ it('loads theme from localStorage on mount', () => {
179
+ localStorage.setItem('fabric-theme', 'dark');
180
+
181
+ wrapper = mount(FThemeProvider, {
182
+ localVue,
183
+ propsData: {
184
+ enablePersistence: true
185
+ },
186
+ slots: {
187
+ default: '<div>Content</div>'
188
+ }
189
+ });
190
+
191
+ expect(wrapper.attributes('data-theme')).toBe('dark');
192
+ });
193
+
194
+ it('emits theme-change event when theme changes', async () => {
195
+ wrapper = mount(FThemeProvider, {
196
+ localVue,
197
+ scopedSlots: {
198
+ default: function (props) {
199
+ return this.$createElement('button', {
200
+ attrs: { id: 'toggle' },
201
+ on: { click: props.toggleTheme }
202
+ });
203
+ }
204
+ }
205
+ });
206
+
207
+ await wrapper.find('#toggle').trigger('click');
208
+ await wrapper.vm.$nextTick();
209
+
210
+ expect(wrapper.emitted('theme-change')).toBeTruthy();
211
+ const emitted = wrapper.emitted('theme-change');
212
+ if (emitted) {
213
+ // First emission is from initialization (light), second is from toggle (dark)
214
+ expect(emitted.length).toBeGreaterThanOrEqual(2);
215
+ expect(emitted[1][0]).toBe('dark');
216
+ }
217
+ });
218
+
219
+ it('handles auto theme based on system preference', () => {
220
+ // Mock matchMedia to return dark preference
221
+ window.matchMedia = (query: string) => ({
222
+ matches: query === '(prefers-color-scheme: dark)',
223
+ media: query,
224
+ onchange: null,
225
+ addListener: () => {},
226
+ removeListener: () => {},
227
+ addEventListener: () => {},
228
+ removeEventListener: () => {},
229
+ dispatchEvent: () => true
230
+ });
231
+
232
+ wrapper = mount(FThemeProvider, {
233
+ localVue,
234
+ propsData: {
235
+ defaultTheme: 'auto'
236
+ },
237
+ slots: {
238
+ default: '<div>Content</div>'
239
+ }
240
+ });
241
+
242
+ expect(wrapper.attributes('data-theme')).toBe('dark');
243
+ });
244
+ });
@@ -0,0 +1,191 @@
1
+ <template>
2
+ <div :data-theme="currentTheme">
3
+ <slot
4
+ :theme="currentTheme"
5
+ :toggle-theme="toggleTheme"
6
+ :set-theme="setTheme"
7
+ />
8
+ </div>
9
+ </template>
10
+
11
+ <script>
12
+ /**
13
+ * FThemeProvider - Theme management component for Fabric Design System
14
+ *
15
+ * This component provides theme switching functionality (light/dark mode)
16
+ * and manages theme persistence using localStorage.
17
+ *
18
+ * See the Storybook stories for usage examples.
19
+ */
20
+ export default {
21
+ name: 'FThemeProvider',
22
+ provide() {
23
+ return {
24
+ theme: () => this.currentTheme,
25
+ toggleTheme: this.toggleTheme,
26
+ setTheme: this.setTheme
27
+ };
28
+ },
29
+ props: {
30
+ /**
31
+ * Default theme to use when no preference is stored
32
+ * @type {'light' | 'dark' | 'auto'}
33
+ * @default 'light'
34
+ */
35
+ defaultTheme: {
36
+ type: String,
37
+ default: 'light',
38
+ validator: (value) => ['light', 'dark', 'auto'].includes(value)
39
+ },
40
+ /**
41
+ * Key used for localStorage persistence
42
+ * @type {string}
43
+ * @default 'fabric-theme'
44
+ */
45
+ storageKey: {
46
+ type: String,
47
+ default: 'fabric-theme'
48
+ },
49
+ /**
50
+ * Enable or disable localStorage persistence
51
+ * @type {boolean}
52
+ * @default true
53
+ */
54
+ enablePersistence: {
55
+ type: Boolean,
56
+ default: true
57
+ }
58
+ },
59
+ data() {
60
+ return {
61
+ currentTheme: this.defaultTheme === 'light' ? 'light' : 'dark',
62
+ storedTheme: null,
63
+ mediaQuery: null
64
+ };
65
+ },
66
+ created() {
67
+ this.initializeTheme();
68
+ },
69
+ mounted() {
70
+ this.setupMediaQuery();
71
+ },
72
+ beforeDestroy() {
73
+ if (this.mediaQuery) {
74
+ this.mediaQuery.removeEventListener(
75
+ 'change',
76
+ this.handleMediaQueryChange
77
+ );
78
+ }
79
+ },
80
+ methods: {
81
+ /**
82
+ * Initialize theme from localStorage or system preference
83
+ */
84
+ initializeTheme() {
85
+ let theme = this.defaultTheme;
86
+
87
+ // Try to get theme from localStorage if persistence is enabled
88
+ if (this.enablePersistence && typeof window !== 'undefined') {
89
+ try {
90
+ const stored = localStorage.getItem(this.storageKey);
91
+ if (stored && ['light', 'dark', 'auto'].includes(stored)) {
92
+ theme = stored;
93
+ this.storedTheme = stored;
94
+ }
95
+ } catch (error) {
96
+ console.warn('Failed to read theme from localStorage:', error);
97
+ }
98
+ }
99
+
100
+ this.applyTheme(theme);
101
+ },
102
+
103
+ /**
104
+ * Setup media query listener for auto theme
105
+ */
106
+ setupMediaQuery() {
107
+ if (typeof window === 'undefined') return;
108
+
109
+ try {
110
+ this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
111
+ this.mediaQuery.addEventListener('change', this.handleMediaQueryChange);
112
+ } catch (error) {
113
+ console.warn('Failed to setup media query listener:', error);
114
+ }
115
+ },
116
+
117
+ /**
118
+ * Handle media query changes (for auto theme)
119
+ */
120
+ handleMediaQueryChange() {
121
+ // Only re-apply if stored theme is 'auto'
122
+ if (this.storedTheme === 'auto') {
123
+ this.applyTheme('auto');
124
+ }
125
+ },
126
+
127
+ /**
128
+ * Resolve 'auto' theme to actual theme based on system preference
129
+ */
130
+ resolveTheme(theme) {
131
+ if (theme === 'auto') {
132
+ if (
133
+ typeof window !== 'undefined' &&
134
+ window.matchMedia &&
135
+ window.matchMedia('(prefers-color-scheme: dark)').matches
136
+ ) {
137
+ return 'dark';
138
+ }
139
+ return 'light';
140
+ }
141
+ return theme;
142
+ },
143
+
144
+ /**
145
+ * Apply the given theme
146
+ * @param {'light' | 'dark' | 'auto'} theme
147
+ */
148
+ applyTheme(theme) {
149
+ const resolvedTheme = this.resolveTheme(theme);
150
+ this.currentTheme = resolvedTheme;
151
+ this.storedTheme = theme; // Remember the original preference
152
+
153
+ // Emit theme change event
154
+ this.$emit('theme-change', resolvedTheme);
155
+ },
156
+
157
+ /**
158
+ * Toggle between light and dark themes
159
+ */
160
+ toggleTheme() {
161
+ const newTheme = this.currentTheme === 'light' ? 'dark' : 'light';
162
+ this.setTheme(newTheme);
163
+ },
164
+
165
+ /**
166
+ * Set a specific theme
167
+ * @param {'light' | 'dark' | 'auto'} theme
168
+ */
169
+ setTheme(theme) {
170
+ if (!['light', 'dark', 'auto'].includes(theme)) {
171
+ console.warn(
172
+ `Invalid theme: ${theme}. Must be 'light', 'dark', or 'auto'.`
173
+ );
174
+ return;
175
+ }
176
+
177
+ this.storedTheme = theme;
178
+ this.applyTheme(theme);
179
+
180
+ // Persist to localStorage if enabled
181
+ if (this.enablePersistence && typeof window !== 'undefined') {
182
+ try {
183
+ localStorage.setItem(this.storageKey, theme);
184
+ } catch (error) {
185
+ console.warn('Failed to save theme to localStorage:', error);
186
+ }
187
+ }
188
+ }
189
+ }
190
+ };
191
+ </script>
@@ -0,0 +1,3 @@
1
+ import FThemeProvider from './FThemeProvider.vue';
2
+
3
+ export { FThemeProvider };