@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,156 @@
1
+ <template>
2
+ <div :class="wrapperClasses">
3
+ <label v-if="label" :for="inputId" :class="labelClasses">
4
+ {{ label }}
5
+ </label>
6
+ <textarea
7
+ :id="inputId"
8
+ :class="textareaClasses"
9
+ :value="value"
10
+ :placeholder="placeholder"
11
+ :disabled="disabled"
12
+ :readonly="readonly"
13
+ :rows="rows"
14
+ :maxlength="maxlength"
15
+ :aria-invalid="error"
16
+ :aria-describedby="errorMessage ? errorId : undefined"
17
+ @input="handleInput"
18
+ @focus="$emit('focus', $event)"
19
+ @blur="$emit('blur', $event)"
20
+ ></textarea>
21
+ <div v-if="hasCounter || errorMessage" :class="footerClasses">
22
+ <span v-if="errorMessage" :id="errorId" :class="errorMessageClasses">
23
+ {{ errorMessage }}
24
+ </span>
25
+ <span
26
+ v-if="hasCounter"
27
+ :class="[counterClasses, { 'ml-auto': !errorMessage }]"
28
+ >
29
+ {{ characterCount }}/{{ maxlength }}
30
+ </span>
31
+ </div>
32
+ </div>
33
+ </template>
34
+
35
+ <script>
36
+ let idCounter = 0;
37
+
38
+ export default {
39
+ name: 'FTextarea',
40
+ props: {
41
+ value: {
42
+ type: String,
43
+ default: ''
44
+ },
45
+ label: {
46
+ type: String,
47
+ default: ''
48
+ },
49
+ placeholder: {
50
+ type: String,
51
+ default: ''
52
+ },
53
+ rows: {
54
+ type: [Number, String],
55
+ default: 3
56
+ },
57
+ disabled: {
58
+ type: Boolean,
59
+ default: false
60
+ },
61
+ readonly: {
62
+ type: Boolean,
63
+ default: false
64
+ },
65
+ error: {
66
+ type: Boolean,
67
+ default: false
68
+ },
69
+ errorMessage: {
70
+ type: String,
71
+ default: ''
72
+ },
73
+ maxlength: {
74
+ type: [Number, String],
75
+ default: null
76
+ },
77
+ showCounter: {
78
+ type: Boolean,
79
+ default: false
80
+ }
81
+ },
82
+ data() {
83
+ const id = ++idCounter;
84
+ return {
85
+ inputId: `ftextarea-${id}`,
86
+ errorId: `ftextarea-error-${id}`
87
+ };
88
+ },
89
+ computed: {
90
+ wrapperClasses() {
91
+ return 'flex flex-col';
92
+ },
93
+ labelClasses() {
94
+ const baseClasses = 'font-sans text-sm text-neutral-700 mb-1';
95
+ const errorClasses = this.error ? 'text-danger-500' : '';
96
+
97
+ return [baseClasses, errorClasses].filter(Boolean).join(' ');
98
+ },
99
+ textareaClasses() {
100
+ const baseClasses =
101
+ 'block w-full font-sans border rounded box-border focus:outline-none focus:ring-2 resize-y';
102
+
103
+ const transitionClasses =
104
+ 'transition-all duration-[var(--transition-duration-base)] ease-[var(--transition-easing-standard)]';
105
+
106
+ const stateClasses = this.error
107
+ ? 'border-danger-500 focus:border-danger-500 focus:ring-danger-500/20'
108
+ : 'border-neutral-300 focus:border-primary-500 focus:ring-primary-500/20';
109
+
110
+ const disabledClasses = this.disabled
111
+ ? 'bg-neutral-100 cursor-not-allowed opacity-70'
112
+ : '';
113
+
114
+ const paddingClasses = 'py-2.5 px-3.5 text-sm';
115
+
116
+ return [
117
+ baseClasses,
118
+ transitionClasses,
119
+ paddingClasses,
120
+ stateClasses,
121
+ disabledClasses
122
+ ]
123
+ .filter(Boolean)
124
+ .join(' ');
125
+ },
126
+ footerClasses() {
127
+ return 'flex justify-between items-center mt-1';
128
+ },
129
+ errorMessageClasses() {
130
+ return 'font-sans text-xs text-danger-500';
131
+ },
132
+ counterClasses() {
133
+ const baseClasses = 'font-sans text-xs';
134
+ const stateClasses = this.isOverLimit
135
+ ? 'text-danger-500'
136
+ : 'text-neutral-500';
137
+
138
+ return [baseClasses, stateClasses].filter(Boolean).join(' ');
139
+ },
140
+ characterCount() {
141
+ return this.value ? this.value.length : 0;
142
+ },
143
+ isOverLimit() {
144
+ return this.maxlength && this.characterCount > Number(this.maxlength);
145
+ },
146
+ hasCounter() {
147
+ return this.showCounter && this.maxlength;
148
+ }
149
+ },
150
+ methods: {
151
+ handleInput(event) {
152
+ this.$emit('input', event.target.value);
153
+ }
154
+ }
155
+ };
156
+ </script>
@@ -0,0 +1,108 @@
1
+ import FToggle from './FToggle.vue';
2
+
3
+ export default {
4
+ title: 'Atoms/FToggle',
5
+ component: FToggle,
6
+ tags: ['autodocs'],
7
+ argTypes: {
8
+ value: {
9
+ control: 'boolean',
10
+ description: 'État du toggle (activé/désactivé)'
11
+ },
12
+ label: {
13
+ control: 'text',
14
+ description: 'Libellé du toggle'
15
+ },
16
+ disabled: {
17
+ control: 'boolean',
18
+ description: 'État désactivé'
19
+ },
20
+ color: {
21
+ control: { type: 'select' },
22
+ options: ['blue', 'green', 'red', 'orange', 'purple'],
23
+ description: 'Couleur du toggle'
24
+ }
25
+ }
26
+ };
27
+
28
+ const Template = (args, { argTypes }) => ({
29
+ components: { FToggle },
30
+ props: Object.keys(argTypes),
31
+ data() {
32
+ return { isEnabled: args.value || false };
33
+ },
34
+ template: '<FToggle v-bind="$props" v-model="isEnabled" />'
35
+ });
36
+
37
+ export const Default = Template.bind({});
38
+ Default.args = {
39
+ label: 'Activer les notifications'
40
+ };
41
+
42
+ export const Enabled = Template.bind({});
43
+ Enabled.args = {
44
+ label: 'Notifications activées',
45
+ value: true
46
+ };
47
+
48
+ export const Disabled = Template.bind({});
49
+ Disabled.args = {
50
+ label: 'Option désactivée',
51
+ disabled: true
52
+ };
53
+
54
+ export const DisabledOn = Template.bind({});
55
+ DisabledOn.args = {
56
+ label: 'Option activée (désactivée)',
57
+ value: true,
58
+ disabled: true
59
+ };
60
+
61
+ export const Colors = () => ({
62
+ components: { FToggle },
63
+ data() {
64
+ return {
65
+ blue: true,
66
+ green: true,
67
+ red: true,
68
+ orange: true,
69
+ purple: true
70
+ };
71
+ },
72
+ template: `
73
+ <div class="flex flex-col gap-4">
74
+ <FToggle v-model="blue" color="blue" label="Bleu (défaut)" />
75
+ <FToggle v-model="green" color="green" label="Vert" />
76
+ <FToggle v-model="red" color="red" label="Rouge" />
77
+ <FToggle v-model="orange" color="orange" label="Orange" />
78
+ <FToggle v-model="purple" color="purple" label="Violet" />
79
+ </div>
80
+ `
81
+ });
82
+
83
+ export const WithoutLabel = Template.bind({});
84
+ WithoutLabel.args = {};
85
+
86
+ export const Interactive = () => ({
87
+ components: { FToggle },
88
+ data() {
89
+ return {
90
+ darkMode: false,
91
+ notifications: true,
92
+ autoSave: true
93
+ };
94
+ },
95
+ template: `
96
+ <div class="flex flex-col gap-4 p-4 border rounded-lg">
97
+ <h3 class="font-semibold text-neutral-800">Paramètres</h3>
98
+ <FToggle v-model="darkMode" label="Mode sombre" />
99
+ <FToggle v-model="notifications" label="Notifications push" />
100
+ <FToggle v-model="autoSave" label="Sauvegarde automatique" />
101
+ <div class="mt-4 text-sm text-neutral-500">
102
+ <p>Mode sombre: {{ darkMode ? 'Activé' : 'Désactivé' }}</p>
103
+ <p>Notifications: {{ notifications ? 'Activées' : 'Désactivées' }}</p>
104
+ <p>Auto-save: {{ autoSave ? 'Activé' : 'Désactivé' }}</p>
105
+ </div>
106
+ </div>
107
+ `
108
+ });
@@ -0,0 +1,96 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import FToggle from './FToggle.vue';
4
+
5
+ describe('FToggle', () => {
6
+ it('renders correctly with default props', () => {
7
+ const wrapper = mount(FToggle);
8
+ expect(wrapper.find('button[role="switch"]').exists()).toBe(true);
9
+ });
10
+
11
+ it('displays label when provided', () => {
12
+ const wrapper = mount(FToggle, {
13
+ propsData: { label: 'Enable notifications' }
14
+ });
15
+ expect(wrapper.text()).toContain('Enable notifications');
16
+ });
17
+
18
+ it('is off by default', () => {
19
+ const wrapper = mount(FToggle);
20
+ expect(wrapper.find('button').attributes('aria-checked')).toBe('false');
21
+ });
22
+
23
+ it('respects value prop', () => {
24
+ const wrapper = mount(FToggle, {
25
+ propsData: { value: true }
26
+ });
27
+ expect(wrapper.find('button').attributes('aria-checked')).toBe('true');
28
+ });
29
+
30
+ it('emits input event when toggled', async () => {
31
+ const wrapper = mount(FToggle, {
32
+ propsData: { value: false }
33
+ });
34
+ await wrapper.find('button').trigger('click');
35
+ expect(wrapper.emitted('input')).toBeTruthy();
36
+ });
37
+
38
+ it('emits change event when toggled', async () => {
39
+ const wrapper = mount(FToggle, {
40
+ propsData: { value: false }
41
+ });
42
+ await wrapper.find('button').trigger('click');
43
+ expect(wrapper.emitted('change')).toBeTruthy();
44
+ });
45
+
46
+ it('has disabled attribute on button when disabled', () => {
47
+ const wrapper = mount(FToggle, {
48
+ propsData: { disabled: true }
49
+ });
50
+ expect(wrapper.find('button').attributes('disabled')).toBeDefined();
51
+ });
52
+
53
+ it('applies correct color classes', () => {
54
+ const colors = ['blue', 'green', 'red', 'orange', 'purple'] as const;
55
+ colors.forEach((color) => {
56
+ const wrapper = mount(FToggle, {
57
+ propsData: { color, value: true }
58
+ });
59
+ expect(wrapper.find('button').exists()).toBe(true);
60
+ });
61
+ });
62
+
63
+ it('toggles when Space key is pressed', async () => {
64
+ const wrapper = mount(FToggle, {
65
+ propsData: { value: false }
66
+ });
67
+ await wrapper.find('button').trigger('keydown.space');
68
+ expect(wrapper.emitted('input')).toBeTruthy();
69
+ expect(wrapper.emitted('input')?.[0]).toEqual([true]);
70
+ });
71
+
72
+ it('toggles when Enter key is pressed', async () => {
73
+ const wrapper = mount(FToggle, {
74
+ propsData: { value: false }
75
+ });
76
+ await wrapper.find('button').trigger('keydown.enter');
77
+ expect(wrapper.emitted('input')).toBeTruthy();
78
+ expect(wrapper.emitted('input')?.[0]).toEqual([true]);
79
+ });
80
+
81
+ it('does not toggle when disabled and Space is pressed', async () => {
82
+ const wrapper = mount(FToggle, {
83
+ propsData: { value: false, disabled: true }
84
+ });
85
+ await wrapper.find('button').trigger('keydown.space');
86
+ expect(wrapper.emitted('input')).toBeFalsy();
87
+ });
88
+
89
+ it('does not toggle when disabled and Enter is pressed', async () => {
90
+ const wrapper = mount(FToggle, {
91
+ propsData: { value: false, disabled: true }
92
+ });
93
+ await wrapper.find('button').trigger('keydown.enter');
94
+ expect(wrapper.emitted('input')).toBeFalsy();
95
+ });
96
+ });
@@ -0,0 +1,123 @@
1
+ <template>
2
+ <label :class="containerClasses">
3
+ <button
4
+ type="button"
5
+ role="switch"
6
+ :class="switchClasses"
7
+ :aria-checked="String(value)"
8
+ :disabled="disabled"
9
+ @click="handleToggle"
10
+ @keydown.space.prevent="handleToggle"
11
+ @keydown.enter.prevent="handleToggle"
12
+ @focus="$emit('focus', $event)"
13
+ @blur="$emit('blur', $event)"
14
+ >
15
+ <span :class="thumbClasses" aria-hidden="true" />
16
+ </button>
17
+ <span v-if="label" :class="labelClasses">
18
+ {{ label }}
19
+ </span>
20
+ </label>
21
+ </template>
22
+
23
+ <script>
24
+ export default {
25
+ name: 'FToggle',
26
+ props: {
27
+ value: {
28
+ type: Boolean,
29
+ default: false
30
+ },
31
+ label: {
32
+ type: String,
33
+ default: ''
34
+ },
35
+ disabled: {
36
+ type: Boolean,
37
+ default: false
38
+ },
39
+ color: {
40
+ type: String,
41
+ default: 'blue',
42
+ validator: (value) =>
43
+ ['blue', 'green', 'red', 'orange', 'purple'].includes(value)
44
+ }
45
+ },
46
+ computed: {
47
+ containerClasses() {
48
+ const baseClasses = 'inline-flex items-center cursor-pointer font-sans';
49
+ const disabledClasses = this.disabled
50
+ ? 'cursor-not-allowed opacity-50'
51
+ : '';
52
+
53
+ return [baseClasses, disabledClasses].filter(Boolean).join(' ');
54
+ },
55
+ switchClasses() {
56
+ const baseClasses =
57
+ 'relative inline-flex items-center h-6 w-11 rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 flex-shrink-0';
58
+
59
+ const transitionClasses =
60
+ 'transition-colors duration-[var(--transition-duration-base)] ease-[var(--transition-easing-standard)]';
61
+
62
+ const colorClasses = {
63
+ blue: this.value
64
+ ? 'bg-primary-500 focus:ring-primary-500/20'
65
+ : 'bg-neutral-300 focus:ring-primary-500/20',
66
+ green: this.value
67
+ ? 'bg-success-500 focus:ring-success-500/20'
68
+ : 'bg-neutral-300 focus:ring-success-500/20',
69
+ red: this.value
70
+ ? 'bg-danger-500 focus:ring-danger-500/20'
71
+ : 'bg-neutral-300 focus:ring-danger-500/20',
72
+ orange: this.value
73
+ ? 'bg-warning-500 focus:ring-warning-500/20'
74
+ : 'bg-neutral-300 focus:ring-warning-500/20',
75
+ purple: this.value
76
+ ? 'bg-primary-500 focus:ring-primary-500/20'
77
+ : 'bg-neutral-300 focus:ring-primary-500/20'
78
+ };
79
+
80
+ const disabledClasses = this.disabled
81
+ ? 'cursor-not-allowed'
82
+ : 'cursor-pointer';
83
+
84
+ return [
85
+ baseClasses,
86
+ transitionClasses,
87
+ colorClasses[this.color],
88
+ disabledClasses
89
+ ]
90
+ .filter(Boolean)
91
+ .join(' ');
92
+ },
93
+ thumbClasses() {
94
+ const baseClasses =
95
+ 'inline-block w-4 h-4 rounded-full bg-white shadow transform';
96
+
97
+ const transitionClasses =
98
+ 'transition-transform duration-[var(--transition-duration-base)] ease-[var(--transition-easing-standard)]';
99
+
100
+ const positionClasses = this.value ? 'translate-x-6' : 'translate-x-1';
101
+
102
+ return [baseClasses, transitionClasses, positionClasses]
103
+ .filter(Boolean)
104
+ .join(' ');
105
+ },
106
+ labelClasses() {
107
+ const baseClasses = 'ml-2 text-sm text-neutral-800 select-none';
108
+
109
+ const disabledClasses = this.disabled ? 'text-neutral-400' : '';
110
+
111
+ return [baseClasses, disabledClasses].filter(Boolean).join(' ');
112
+ }
113
+ },
114
+ methods: {
115
+ handleToggle() {
116
+ if (!this.disabled) {
117
+ this.$emit('input', !this.value);
118
+ this.$emit('change', !this.value);
119
+ }
120
+ }
121
+ }
122
+ };
123
+ </script>
@@ -0,0 +1,127 @@
1
+ import FTypography from './FTypography.vue';
2
+
3
+ export default {
4
+ title: 'Atoms/FTypography',
5
+ component: FTypography,
6
+ tags: ['autodocs'],
7
+ argTypes: {
8
+ variant: {
9
+ control: { type: 'select' },
10
+ options: [
11
+ 'h1',
12
+ 'h2',
13
+ 'h3',
14
+ 'h4',
15
+ 'h5',
16
+ 'h6',
17
+ 'body',
18
+ 'caption',
19
+ 'overline'
20
+ ],
21
+ description: 'Variante typographique'
22
+ },
23
+ tag: {
24
+ control: 'text',
25
+ description: 'Balise HTML personnalisée'
26
+ },
27
+ truncate: {
28
+ control: 'boolean',
29
+ description: 'Tronquer le texte avec ellipse'
30
+ }
31
+ }
32
+ };
33
+
34
+ const Template = (args, { argTypes }) => ({
35
+ components: { FTypography },
36
+ props: Object.keys(argTypes),
37
+ data() {
38
+ return { content: args.content || 'Texte exemple' };
39
+ },
40
+ template: '<FTypography v-bind="$props">{{ content }}</FTypography>'
41
+ });
42
+
43
+ export const Default = Template.bind({});
44
+ Default.args = {
45
+ content: 'Texte par défaut (body)'
46
+ };
47
+
48
+ export const Headings = () => ({
49
+ components: { FTypography },
50
+ template: `
51
+ <div class="flex flex-col gap-4">
52
+ <FTypography variant="h1">Titre H1</FTypography>
53
+ <FTypography variant="h2">Titre H2</FTypography>
54
+ <FTypography variant="h3">Titre H3</FTypography>
55
+ <FTypography variant="h4">Titre H4</FTypography>
56
+ <FTypography variant="h5">Titre H5</FTypography>
57
+ <FTypography variant="h6">Titre H6</FTypography>
58
+ </div>
59
+ `
60
+ });
61
+
62
+ export const Body = Template.bind({});
63
+ Body.args = {
64
+ variant: 'body',
65
+ content:
66
+ 'Ceci est un paragraphe de texte standard. Il utilise la variante body qui est adaptée au contenu principal.'
67
+ };
68
+
69
+ export const Caption = Template.bind({});
70
+ Caption.args = {
71
+ variant: 'caption',
72
+ content: 'Texte de légende ou note secondaire'
73
+ };
74
+
75
+ export const Overline = Template.bind({});
76
+ Overline.args = {
77
+ variant: 'overline',
78
+ content: 'TEXTE EN SURTITRE'
79
+ };
80
+
81
+ export const Truncate = () => ({
82
+ components: { FTypography },
83
+ template: `
84
+ <div class="w-64 border border-neutral-200 rounded p-4">
85
+ <FTypography variant="body" truncate>
86
+ Ce texte est très long et sera tronqué avec une ellipse car il dépasse la largeur du conteneur.
87
+ </FTypography>
88
+ </div>
89
+ `
90
+ });
91
+
92
+ export const CustomTag = () => ({
93
+ components: { FTypography },
94
+ template: `
95
+ <div class="flex flex-col gap-4">
96
+ <FTypography variant="h1" tag="div">H1 rendu comme div</FTypography>
97
+ <FTypography variant="body" tag="span">Body rendu comme span</FTypography>
98
+ </div>
99
+ `
100
+ });
101
+
102
+ export const AllVariants = () => ({
103
+ components: { FTypography },
104
+ template: `
105
+ <div class="flex flex-col gap-6">
106
+ <div>
107
+ <FTypography variant="overline">SECTION</FTypography>
108
+ <FTypography variant="h1">Titre Principal H1</FTypography>
109
+ </div>
110
+ <div>
111
+ <FTypography variant="h2">Sous-titre H2</FTypography>
112
+ <FTypography variant="body">
113
+ Paragraphe de texte standard avec la variante body. Ce style est utilisé pour le contenu principal de la page.
114
+ </FTypography>
115
+ </div>
116
+ <div>
117
+ <FTypography variant="h3">Section H3</FTypography>
118
+ <FTypography variant="body">
119
+ Un autre paragraphe de texte qui explique quelque chose d'important.
120
+ </FTypography>
121
+ <FTypography variant="caption">
122
+ Note: Ceci est un texte de légende ou une note secondaire.
123
+ </FTypography>
124
+ </div>
125
+ </div>
126
+ `
127
+ });
@@ -0,0 +1,93 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import FTypography from './FTypography.vue';
4
+
5
+ describe('FTypography', () => {
6
+ it('renders correctly with default props', () => {
7
+ const wrapper = mount(FTypography, {
8
+ slots: {
9
+ default: 'Text content'
10
+ }
11
+ });
12
+ expect(wrapper.text()).toContain('Text content');
13
+ });
14
+
15
+ it('renders as p tag by default (body variant)', () => {
16
+ const wrapper = mount(FTypography, {
17
+ slots: { default: 'Paragraph' }
18
+ });
19
+ expect(wrapper.find('p').exists()).toBe(true);
20
+ });
21
+
22
+ it('renders correct heading tags for h variants', () => {
23
+ const headings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const;
24
+ headings.forEach((variant) => {
25
+ const wrapper = mount(FTypography, {
26
+ propsData: { variant },
27
+ slots: { default: 'Heading' }
28
+ });
29
+ expect(wrapper.find(variant).exists()).toBe(true);
30
+ });
31
+ });
32
+
33
+ it('renders span for caption variant', () => {
34
+ const wrapper = mount(FTypography, {
35
+ propsData: { variant: 'caption' },
36
+ slots: { default: 'Caption text' }
37
+ });
38
+ expect(wrapper.find('span').exists()).toBe(true);
39
+ });
40
+
41
+ it('renders span for overline variant', () => {
42
+ const wrapper = mount(FTypography, {
43
+ propsData: { variant: 'overline' },
44
+ slots: { default: 'Overline text' }
45
+ });
46
+ expect(wrapper.find('span').exists()).toBe(true);
47
+ });
48
+
49
+ it('uses custom tag when specified', () => {
50
+ const wrapper = mount(FTypography, {
51
+ propsData: { tag: 'div' },
52
+ slots: { default: 'Content' }
53
+ });
54
+ expect(wrapper.find('div').exists()).toBe(true);
55
+ });
56
+
57
+ it('applies truncate classes when truncate prop is true', () => {
58
+ const wrapper = mount(FTypography, {
59
+ propsData: { truncate: true },
60
+ slots: { default: 'Long text that might be truncated' }
61
+ });
62
+ const classString = wrapper.classes().join(' ');
63
+ expect(classString).toContain('overflow-hidden');
64
+ });
65
+
66
+ it('applies correct variant classes', () => {
67
+ const variants = [
68
+ 'h1',
69
+ 'h2',
70
+ 'h3',
71
+ 'h4',
72
+ 'h5',
73
+ 'h6',
74
+ 'body',
75
+ 'caption',
76
+ 'overline'
77
+ ] as const;
78
+ variants.forEach((variant) => {
79
+ const wrapper = mount(FTypography, {
80
+ propsData: { variant },
81
+ slots: { default: 'Text' }
82
+ });
83
+ expect(wrapper.exists()).toBe(true);
84
+ });
85
+ });
86
+
87
+ it('applies base font classes', () => {
88
+ const wrapper = mount(FTypography, {
89
+ slots: { default: 'Text' }
90
+ });
91
+ expect(wrapper.classes()).toContain('font-sans');
92
+ });
93
+ });