@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,103 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+
4
+ interface Props {
5
+ content?: string | number;
6
+ variant?: 'primary' | 'success' | 'warning' | 'error' | 'neutral';
7
+ shape?: 'pill' | 'circle' | 'rounded';
8
+ size?: 'sm' | 'md' | 'lg';
9
+ dot?: boolean;
10
+ outlined?: boolean;
11
+ tag?: string;
12
+ }
13
+
14
+ const props = withDefaults(defineProps<Props>(), {
15
+ content: '',
16
+ variant: 'primary',
17
+ shape: 'pill',
18
+ size: 'md',
19
+ dot: false,
20
+ outlined: false,
21
+ tag: 'span'
22
+ });
23
+
24
+ const computedAriaLabel = computed(() => {
25
+ return props.dot ? `Status: ${props.variant}` : undefined;
26
+ });
27
+
28
+ const computedClasses = computed(() => {
29
+ const base =
30
+ 'inline-flex items-center justify-center font-sans font-medium transition-colors duration-200';
31
+
32
+ const variantStyles = {
33
+ primary: props.outlined
34
+ ? 'border border-primary-500 text-primary-500 bg-transparent'
35
+ : 'bg-primary-500 text-white',
36
+ success: props.outlined
37
+ ? 'border border-success-500 text-success-500 bg-transparent'
38
+ : 'bg-success-500 text-white',
39
+ warning: props.outlined
40
+ ? 'border border-warning-600 text-warning-600 bg-transparent'
41
+ : 'bg-warning-600 text-white',
42
+ error: props.outlined
43
+ ? 'border border-danger-500 text-danger-500 bg-transparent'
44
+ : 'bg-danger-500 text-white',
45
+ neutral: props.outlined
46
+ ? 'border border-neutral-500 text-neutral-500 bg-transparent'
47
+ : 'bg-neutral-500 text-white'
48
+ };
49
+
50
+ if (props.dot) {
51
+ const dotSizes = {
52
+ sm: 'w-1.5 h-1.5',
53
+ md: 'w-2 h-2',
54
+ lg: 'w-3 h-3'
55
+ };
56
+
57
+ const dotColors = {
58
+ primary: 'bg-primary-500',
59
+ success: 'bg-success-500',
60
+ warning: 'bg-warning-600',
61
+ error: 'bg-danger-500',
62
+ neutral: 'bg-neutral-500'
63
+ };
64
+
65
+ return [
66
+ base,
67
+ 'rounded-full',
68
+ dotSizes[props.size],
69
+ dotColors[props.variant]
70
+ ].join(' ');
71
+ }
72
+
73
+ const sizeStyles = {
74
+ sm:
75
+ props.shape === 'circle'
76
+ ? 'w-4 h-4 text-[10px]'
77
+ : 'px-1.5 py-0.5 text-[10px]',
78
+ md: props.shape === 'circle' ? 'w-6 h-6 text-xs' : 'px-2.5 py-0.5 text-xs',
79
+ lg: props.shape === 'circle' ? 'w-8 h-8 text-sm' : 'px-3 py-1 text-sm'
80
+ };
81
+
82
+ const shapeStyles = {
83
+ pill: 'rounded-full',
84
+ circle: 'rounded-full',
85
+ rounded: 'rounded-md'
86
+ };
87
+
88
+ return [
89
+ base,
90
+ variantStyles[props.variant],
91
+ sizeStyles[props.size],
92
+ shapeStyles[props.shape]
93
+ ].join(' ');
94
+ });
95
+ </script>
96
+
97
+ <template>
98
+ <component :is="tag" :class="computedClasses" :aria-label="computedAriaLabel">
99
+ <template v-if="!dot">
100
+ <slot>{{ content }}</slot>
101
+ </template>
102
+ </component>
103
+ </template>
@@ -0,0 +1,122 @@
1
+ import FButton from './FButton.vue';
2
+
3
+ export default {
4
+ title: 'Atoms/FButton',
5
+ component: FButton,
6
+ tags: ['autodocs'],
7
+ argTypes: {
8
+ variant: {
9
+ control: { type: 'select' },
10
+ options: ['primary', 'secondary', 'outline', 'ghost', 'danger', 'link'],
11
+ description: 'Variante visuelle du bouton'
12
+ },
13
+ size: {
14
+ control: { type: 'select' },
15
+ options: ['xs', 'sm', 'md', 'lg', 'xl'],
16
+ description: 'Taille du bouton'
17
+ },
18
+ type: {
19
+ control: { type: 'select' },
20
+ options: ['button', 'submit', 'reset'],
21
+ description: 'Type HTML du bouton'
22
+ },
23
+ disabled: {
24
+ control: 'boolean',
25
+ description: 'État désactivé du bouton'
26
+ },
27
+ loading: {
28
+ control: 'boolean',
29
+ description: 'Affiche un indicateur de chargement'
30
+ },
31
+ block: {
32
+ control: 'boolean',
33
+ description: 'Bouton en pleine largeur'
34
+ },
35
+ rounded: {
36
+ control: 'boolean',
37
+ description: 'Bouton arrondi (pill)'
38
+ }
39
+ }
40
+ };
41
+
42
+ const Template = (args, { argTypes }) => ({
43
+ components: { FButton },
44
+ props: Object.keys(argTypes),
45
+ template: '<FButton v-bind="$props">{{ label }}</FButton>',
46
+ data() {
47
+ return { label: args.label || 'Bouton' };
48
+ }
49
+ });
50
+
51
+ export const Primary = Template.bind({});
52
+ Primary.args = {
53
+ variant: 'primary',
54
+ label: 'Bouton primaire'
55
+ };
56
+
57
+ export const Secondary = Template.bind({});
58
+ Secondary.args = {
59
+ variant: 'secondary',
60
+ label: 'Bouton secondaire'
61
+ };
62
+
63
+ export const Outline = Template.bind({});
64
+ Outline.args = {
65
+ variant: 'outline',
66
+ label: 'Bouton outline'
67
+ };
68
+
69
+ export const Ghost = Template.bind({});
70
+ Ghost.args = {
71
+ variant: 'ghost',
72
+ label: 'Bouton ghost'
73
+ };
74
+
75
+ export const Danger = Template.bind({});
76
+ Danger.args = {
77
+ variant: 'danger',
78
+ label: 'Bouton danger'
79
+ };
80
+
81
+ export const Link = Template.bind({});
82
+ Link.args = {
83
+ variant: 'link',
84
+ label: 'Bouton lien'
85
+ };
86
+
87
+ export const Sizes = () => ({
88
+ components: { FButton },
89
+ template: `
90
+ <div class="flex items-center gap-2 flex-wrap">
91
+ <FButton size="xs">Extra petit</FButton>
92
+ <FButton size="sm">Petit</FButton>
93
+ <FButton size="md">Moyen</FButton>
94
+ <FButton size="lg">Grand</FButton>
95
+ <FButton size="xl">Extra grand</FButton>
96
+ </div>
97
+ `
98
+ });
99
+
100
+ export const Loading = Template.bind({});
101
+ Loading.args = {
102
+ loading: true,
103
+ label: 'Chargement...'
104
+ };
105
+
106
+ export const Disabled = Template.bind({});
107
+ Disabled.args = {
108
+ disabled: true,
109
+ label: 'Désactivé'
110
+ };
111
+
112
+ export const Block = Template.bind({});
113
+ Block.args = {
114
+ block: true,
115
+ label: 'Bouton pleine largeur'
116
+ };
117
+
118
+ export const Rounded = Template.bind({});
119
+ Rounded.args = {
120
+ rounded: true,
121
+ label: 'Bouton arrondi'
122
+ };
@@ -0,0 +1,98 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import FButton from './FButton.vue';
4
+
5
+ describe('FButton', () => {
6
+ it('renders correctly with default props', () => {
7
+ const wrapper = mount(FButton, {
8
+ slots: {
9
+ default: 'Click me'
10
+ }
11
+ });
12
+ expect(wrapper.text()).toContain('Click me');
13
+ expect(wrapper.find('button').exists()).toBe(true);
14
+ });
15
+
16
+ it('applies primary variant by default', () => {
17
+ const wrapper = mount(FButton);
18
+ const button = wrapper.find('button');
19
+ expect(button.classes().join(' ')).toContain('bg-primary-600');
20
+ });
21
+
22
+ it('applies correct variant classes', () => {
23
+ const variants = [
24
+ 'primary',
25
+ 'secondary',
26
+ 'outline',
27
+ 'ghost',
28
+ 'danger',
29
+ 'link'
30
+ ] as const;
31
+ variants.forEach((variant) => {
32
+ const wrapper = mount(FButton, {
33
+ propsData: { variant }
34
+ });
35
+ expect(wrapper.find('button').exists()).toBe(true);
36
+ });
37
+ });
38
+
39
+ it('applies correct size classes', () => {
40
+ const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const;
41
+ sizes.forEach((size) => {
42
+ const wrapper = mount(FButton, {
43
+ propsData: { size }
44
+ });
45
+ expect(wrapper.find('button').exists()).toBe(true);
46
+ });
47
+ });
48
+
49
+ it('emits click event when clicked', async () => {
50
+ const wrapper = mount(FButton);
51
+ await wrapper.find('button').trigger('click');
52
+ expect(wrapper.emitted('click')).toHaveLength(1);
53
+ });
54
+
55
+ it('does not emit click when disabled', async () => {
56
+ const wrapper = mount(FButton, {
57
+ propsData: { disabled: true }
58
+ });
59
+ await wrapper.find('button').trigger('click');
60
+ expect(wrapper.emitted('click')).toBeUndefined();
61
+ });
62
+
63
+ it('does not emit click when loading', async () => {
64
+ const wrapper = mount(FButton, {
65
+ propsData: { loading: true }
66
+ });
67
+ await wrapper.find('button').trigger('click');
68
+ expect(wrapper.emitted('click')).toBeUndefined();
69
+ });
70
+
71
+ it('shows loader when loading', () => {
72
+ const wrapper = mount(FButton, {
73
+ propsData: { loading: true }
74
+ });
75
+ expect(wrapper.findComponent({ name: 'FLoader' }).exists()).toBe(true);
76
+ });
77
+
78
+ it('applies block class when block prop is true', () => {
79
+ const wrapper = mount(FButton, {
80
+ propsData: { block: true }
81
+ });
82
+ expect(wrapper.find('button').classes()).toContain('w-full');
83
+ });
84
+
85
+ it('applies rounded-full when rounded prop is true', () => {
86
+ const wrapper = mount(FButton, {
87
+ propsData: { rounded: true }
88
+ });
89
+ expect(wrapper.find('button').classes()).toContain('rounded-full');
90
+ });
91
+
92
+ it('sets correct button type', () => {
93
+ const wrapper = mount(FButton, {
94
+ propsData: { type: 'submit' }
95
+ });
96
+ expect(wrapper.find('button').attributes('type')).toBe('submit');
97
+ });
98
+ });
@@ -0,0 +1,147 @@
1
+ <script>
2
+ import { computed } from 'vue';
3
+ import FLoader from '../FLoader/FLoader.vue';
4
+
5
+ export default {
6
+ name: 'FButton',
7
+ components: {
8
+ FLoader
9
+ },
10
+ props: {
11
+ variant: {
12
+ type: String,
13
+ default: 'primary',
14
+ validator: (value) =>
15
+ ['primary', 'secondary', 'outline', 'ghost', 'danger', 'link'].includes(
16
+ value
17
+ )
18
+ },
19
+ size: {
20
+ type: String,
21
+ default: 'md',
22
+ validator: (value) => ['xs', 'sm', 'md', 'lg', 'xl'].includes(value)
23
+ },
24
+ type: {
25
+ type: String,
26
+ default: 'button',
27
+ validator: (value) => ['button', 'submit', 'reset'].includes(value)
28
+ },
29
+ disabled: {
30
+ type: Boolean,
31
+ default: false
32
+ },
33
+ loading: {
34
+ type: Boolean,
35
+ default: false
36
+ },
37
+ block: {
38
+ type: Boolean,
39
+ default: false
40
+ },
41
+ rounded: {
42
+ type: Boolean,
43
+ default: false
44
+ },
45
+ iconLeft: {
46
+ type: String,
47
+ default: undefined
48
+ },
49
+ iconRight: {
50
+ type: String,
51
+ default: undefined
52
+ }
53
+ },
54
+ emits: ['click'],
55
+ setup(props, { emit }) {
56
+ const handleClick = (event) => {
57
+ if (props.disabled || props.loading) {
58
+ event.preventDefault();
59
+ return;
60
+ }
61
+ emit('click', event);
62
+ };
63
+
64
+ const baseClasses =
65
+ 'inline-flex items-center justify-center font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none';
66
+
67
+ const transitionClasses =
68
+ 'transition-colors duration-[var(--transition-duration-base)] ease-[var(--transition-easing-standard)]';
69
+
70
+ const variantClasses = computed(() => {
71
+ const variants = {
72
+ primary:
73
+ 'bg-primary-600 text-white hover:bg-primary-700 focus:ring-primary-500 border border-transparent',
74
+ secondary:
75
+ 'bg-white text-gray-700 border border-gray-300 hover:bg-gray-50 focus:ring-primary-500',
76
+ outline:
77
+ 'bg-transparent text-primary-600 border border-primary-600 hover:bg-primary-50 focus:ring-primary-500',
78
+ ghost:
79
+ 'bg-transparent text-gray-600 hover:bg-gray-100 hover:text-gray-900 focus:ring-gray-500',
80
+ danger:
81
+ 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500 border border-transparent',
82
+ link: 'text-primary-600 underline-offset-4 hover:underline p-0 h-auto focus:ring-0'
83
+ };
84
+ return variants[props.variant];
85
+ });
86
+
87
+ const sizeClasses = computed(() => {
88
+ if (props.variant === 'link') return '';
89
+
90
+ const sizes = {
91
+ xs: 'text-xs px-2.5 py-1.5',
92
+ sm: 'text-sm px-3 py-2',
93
+ md: 'text-sm px-4 py-2',
94
+ lg: 'text-base px-4 py-2',
95
+ xl: 'text-base px-6 py-3'
96
+ };
97
+ return sizes[props.size];
98
+ });
99
+
100
+ const classes = computed(() => [
101
+ baseClasses,
102
+ transitionClasses,
103
+ variantClasses.value,
104
+ sizeClasses.value,
105
+ props.block ? 'w-full flex' : '',
106
+ props.rounded ? 'rounded-full' : 'rounded-md',
107
+ props.loading
108
+ ? 'cursor-wait relative text-transparent hover:text-transparent !transition-none'
109
+ : ''
110
+ ]);
111
+
112
+ return {
113
+ handleClick,
114
+ classes
115
+ };
116
+ }
117
+ };
118
+ </script>
119
+
120
+ <template>
121
+ <button
122
+ :type="type"
123
+ :class="classes"
124
+ :disabled="disabled || loading"
125
+ :aria-disabled="disabled || loading"
126
+ @click="handleClick"
127
+ >
128
+ <div
129
+ v-if="loading"
130
+ class="absolute inset-0 flex items-center justify-center text-current"
131
+ >
132
+ <FLoader size="sm" class="text-current opacity-100" />
133
+ </div>
134
+
135
+ <span :class="{ 'opacity-0': loading }" class="flex items-center gap-2">
136
+ <slot name="prefix">
137
+ <span v-if="iconLeft" :class="iconLeft" aria-hidden="true" />
138
+ </slot>
139
+
140
+ <slot />
141
+
142
+ <slot name="suffix">
143
+ <span v-if="iconRight" :class="iconRight" aria-hidden="true" />
144
+ </slot>
145
+ </span>
146
+ </button>
147
+ </template>
@@ -0,0 +1,96 @@
1
+ import FCheckbox from './FCheckbox.vue';
2
+
3
+ export default {
4
+ title: 'Atoms/FCheckbox',
5
+ component: FCheckbox,
6
+ tags: ['autodocs'],
7
+ argTypes: {
8
+ checked: {
9
+ control: 'boolean',
10
+ description: 'État coché de la checkbox'
11
+ },
12
+ label: {
13
+ control: 'text',
14
+ description: 'Libellé de la checkbox'
15
+ },
16
+ disabled: {
17
+ control: 'boolean',
18
+ description: 'État désactivé'
19
+ },
20
+ error: {
21
+ control: 'boolean',
22
+ description: "État d'erreur"
23
+ }
24
+ }
25
+ };
26
+
27
+ const Template = (args, { argTypes }) => ({
28
+ components: { FCheckbox },
29
+ props: Object.keys(argTypes),
30
+ data() {
31
+ return { isChecked: args.checked || false };
32
+ },
33
+ template: '<FCheckbox v-bind="$props" v-model="isChecked" />'
34
+ });
35
+
36
+ export const Default = Template.bind({});
37
+ Default.args = {
38
+ label: "J'accepte les conditions"
39
+ };
40
+
41
+ export const Checked = Template.bind({});
42
+ Checked.args = {
43
+ label: 'Option sélectionnée',
44
+ checked: true
45
+ };
46
+
47
+ export const Disabled = Template.bind({});
48
+ Disabled.args = {
49
+ label: 'Option désactivée',
50
+ disabled: true
51
+ };
52
+
53
+ export const DisabledChecked = Template.bind({});
54
+ DisabledChecked.args = {
55
+ label: 'Option désactivée et cochée',
56
+ disabled: true,
57
+ checked: true
58
+ };
59
+
60
+ export const WithError = Template.bind({});
61
+ WithError.args = {
62
+ label: 'Champ obligatoire',
63
+ error: true
64
+ };
65
+
66
+ export const States = () => ({
67
+ components: { FCheckbox },
68
+ data() {
69
+ return {
70
+ checked1: false,
71
+ checked2: true,
72
+ checked3: false,
73
+ checked4: false
74
+ };
75
+ },
76
+ template: `
77
+ <div class="flex flex-col gap-3">
78
+ <FCheckbox v-model="checked1" label="Normal" />
79
+ <FCheckbox v-model="checked2" label="Coché" />
80
+ <FCheckbox v-model="checked3" label="Désactivé" disabled />
81
+ <FCheckbox v-model="checked4" label="Erreur" error />
82
+ </div>
83
+ `
84
+ });
85
+
86
+ export const WithSlot = () => ({
87
+ components: { FCheckbox },
88
+ data() {
89
+ return { checked: false };
90
+ },
91
+ template: `
92
+ <FCheckbox v-model="checked">
93
+ J'accepte les <a href="#" class="text-primary-600 underline">conditions d'utilisation</a>
94
+ </FCheckbox>
95
+ `
96
+ });
@@ -0,0 +1,64 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import FCheckbox from './FCheckbox.vue';
4
+
5
+ describe('FCheckbox', () => {
6
+ it('renders correctly with default props', () => {
7
+ const wrapper = mount(FCheckbox);
8
+ expect(wrapper.find('input[type="checkbox"]').exists()).toBe(true);
9
+ });
10
+
11
+ it('displays label when provided', () => {
12
+ const wrapper = mount(FCheckbox, {
13
+ propsData: { label: 'Accept terms' }
14
+ });
15
+ expect(wrapper.text()).toContain('Accept terms');
16
+ });
17
+
18
+ it('displays slot content as label', () => {
19
+ const wrapper = mount(FCheckbox, {
20
+ slots: {
21
+ default: 'Custom label'
22
+ }
23
+ });
24
+ expect(wrapper.text()).toContain('Custom label');
25
+ });
26
+
27
+ it('is unchecked by default', () => {
28
+ const wrapper = mount(FCheckbox);
29
+ expect((wrapper.find('input').element as HTMLInputElement).checked).toBe(
30
+ false
31
+ );
32
+ });
33
+
34
+ it('respects checked prop', () => {
35
+ const wrapper = mount(FCheckbox, {
36
+ propsData: { checked: true }
37
+ });
38
+ expect((wrapper.find('input').element as HTMLInputElement).checked).toBe(
39
+ true
40
+ );
41
+ });
42
+
43
+ it('emits change event when toggled', async () => {
44
+ const wrapper = mount(FCheckbox);
45
+ const input = wrapper.find('input');
46
+ await input.setChecked(true);
47
+ expect(wrapper.emitted('change')).toBeTruthy();
48
+ });
49
+
50
+ it('applies disabled styles', () => {
51
+ const wrapper = mount(FCheckbox, {
52
+ propsData: { disabled: true }
53
+ });
54
+ expect(wrapper.find('label').classes()).toContain('cursor-not-allowed');
55
+ });
56
+
57
+ it('applies error styles', () => {
58
+ const wrapper = mount(FCheckbox, {
59
+ propsData: { error: true }
60
+ });
61
+ const input = wrapper.find('input');
62
+ expect(input.classes().join(' ')).toContain('border-danger');
63
+ });
64
+ });
@@ -0,0 +1,76 @@
1
+ <template>
2
+ <label :class="wrapperClasses">
3
+ <input
4
+ type="checkbox"
5
+ :class="inputClasses"
6
+ :checked="checked"
7
+ :disabled="disabled"
8
+ :aria-invalid="error"
9
+ @change="updateInput"
10
+ @focus="$emit('focus', $event)"
11
+ @blur="$emit('blur', $event)"
12
+ />
13
+ <span v-if="label || $slots.default" :class="labelClasses">
14
+ <slot>{{ label }}</slot>
15
+ </span>
16
+ </label>
17
+ </template>
18
+
19
+ <script>
20
+ export default {
21
+ name: 'FCheckbox',
22
+ model: {
23
+ prop: 'checked',
24
+ event: 'change'
25
+ },
26
+ props: {
27
+ checked: {
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
+ error: {
40
+ type: Boolean,
41
+ default: false
42
+ }
43
+ },
44
+ computed: {
45
+ wrapperClasses() {
46
+ return [
47
+ 'inline-flex items-center select-none',
48
+ this.disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'
49
+ ];
50
+ },
51
+ inputClasses() {
52
+ return [
53
+ 'shrink-0 w-4 h-4 rounded border focus:outline-none focus:ring-2 focus:ring-offset-1',
54
+ 'transition-colors duration-[var(--transition-duration-base)] ease-[var(--transition-easing-standard)]',
55
+ this.error
56
+ ? 'border-danger-500 text-danger-500 focus:ring-danger-500/20'
57
+ : 'border-neutral-300 text-primary-600 focus:border-primary-600 focus:ring-primary-600/20',
58
+ this.disabled ? 'cursor-not-allowed' : 'cursor-pointer'
59
+ ];
60
+ },
61
+ labelClasses() {
62
+ return [
63
+ 'ml-2 font-sans text-sm',
64
+ this.error ? 'text-danger-500' : 'text-neutral-700'
65
+ ];
66
+ }
67
+ },
68
+ methods: {
69
+ updateInput(event) {
70
+ if (!this.disabled) {
71
+ this.$emit('change', event.target.checked);
72
+ }
73
+ }
74
+ }
75
+ };
76
+ </script>