@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
package/package.json ADDED
@@ -0,0 +1,100 @@
1
+ {
2
+ "name": "@pyreweb/fabric",
3
+ "version": "1.2.6",
4
+ "description": "Système de design et bibliothèque de composants VueJS de la société Pyréweb",
5
+ "type": "module",
6
+ "overrides": {
7
+ "json5": "^2.2.3",
8
+ "loader-utils": "^3.2.1",
9
+ "postcss": "^8.4.31"
10
+ },
11
+ "main": "dist/fabric.cjs.js",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/pyreweb/fabric.git"
15
+ },
16
+ "bugs": {
17
+ "url": "https://github.com/pyreweb/fabric/issues"
18
+ },
19
+ "homepage": "https://github.com/pyreweb/fabric#readme",
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "module": "dist/fabric.esm.js",
24
+ "types": "dist/types/index.d.ts",
25
+ "unpkg": "dist/fabric.min.js",
26
+ "files": [
27
+ "dist",
28
+ "src"
29
+ ],
30
+ "scripts": {
31
+ "build": "rollup -c",
32
+ "dev": "rollup -c -w",
33
+ "test": "vitest run",
34
+ "typecheck": "vue-tsc --noEmit",
35
+ "lint": "eslint --ext .js,.ts,.vue src .storybook",
36
+ "lint:fix": "eslint --ext .js,.ts,.vue src .storybook --fix",
37
+ "format": "prettier --write \"src/**/*.{js,ts,vue}\" \".storybook/**/*.{js,ts}\" \"*.{js,cjs,ts}\"",
38
+ "format:check": "prettier --check \"src/**/*.{js,ts,vue}\" \".storybook/**/*.{js,ts}\" \"*.{js,cjs,ts}\"",
39
+ "storybook": "storybook dev -p 6006",
40
+ "build-storybook": "storybook build",
41
+ "preview-storybook": "npx serve storybook-static -l 6006"
42
+ },
43
+ "keywords": [
44
+ "javascript",
45
+ "components",
46
+ "design",
47
+ "vuejs",
48
+ "library",
49
+ "component",
50
+ "system",
51
+ "vue",
52
+ "js",
53
+ "design-system",
54
+ "system-design",
55
+ "components-library"
56
+ ],
57
+ "author": "Pyréweb",
58
+ "license": "MIT",
59
+ "peerDependencies": {
60
+ "vue": "^2.6.0 || ^2.7.0"
61
+ },
62
+ "devDependencies": {
63
+ "@rollup/plugin-commonjs": "^28.0.2",
64
+ "@rollup/plugin-node-resolve": "^15.3.1",
65
+ "@rollup/plugin-terser": "^0.4.4",
66
+ "@rollup/plugin-typescript": "^12.3.0",
67
+ "@storybook/addon-essentials": "^7.6.20",
68
+ "@storybook/addon-links": "^7.6.20",
69
+ "@storybook/builder-vite": "^7.6.20",
70
+ "@storybook/vue": "^7.6.20",
71
+ "@storybook/vue-vite": "^7.6.20",
72
+ "@tailwindcss/postcss": "^4.1.17",
73
+ "@typescript-eslint/eslint-plugin": "^8.48.1",
74
+ "@typescript-eslint/parser": "^8.48.1",
75
+ "@vitejs/plugin-vue2": "^2.3.4",
76
+ "@vue/test-utils": "^1.3.6",
77
+ "autoprefixer": "^10.4.22",
78
+ "eslint": "^8.57.1",
79
+ "eslint-config-prettier": "^9.1.2",
80
+ "eslint-plugin-prettier": "^4.2.5",
81
+ "eslint-plugin-vue": "^9.33.0",
82
+ "jsdom": "^26.1.0",
83
+ "prettier": "^2.8.8",
84
+ "react": "^18.3.1",
85
+ "react-dom": "^18.3.1",
86
+ "rollup": "^3.29.5",
87
+ "rollup-plugin-postcss": "^4.0.2",
88
+ "rollup-plugin-vue": "^5.1.9",
89
+ "storybook": "^7.6.20",
90
+ "tailwindcss": "^4.1.17",
91
+ "typescript": "^5.9.3",
92
+ "vitest": "^3.2.4",
93
+ "vue": "^2.7.16",
94
+ "vue-template-compiler": "^2.7.16",
95
+ "vue-tsc": "^3.1.5"
96
+ },
97
+ "dependencies": {
98
+ "vue-virtual-scroller": "^1.1.2"
99
+ }
100
+ }
@@ -0,0 +1,100 @@
1
+ import FAvatar from './FAvatar.vue';
2
+
3
+ export default {
4
+ title: 'Atoms/FAvatar',
5
+ component: FAvatar,
6
+ tags: ['autodocs'],
7
+ argTypes: {
8
+ src: {
9
+ control: 'text',
10
+ description: "URL de l'image de l'avatar"
11
+ },
12
+ alt: {
13
+ control: 'text',
14
+ description: "Texte alternatif de l'image"
15
+ },
16
+ initials: {
17
+ control: 'text',
18
+ description: 'Initiales à afficher'
19
+ },
20
+ name: {
21
+ control: 'text',
22
+ description: 'Nom complet pour générer les initiales automatiquement'
23
+ },
24
+ size: {
25
+ control: { type: 'select' },
26
+ options: ['xs', 'sm', 'md', 'lg', 'xl'],
27
+ description: "Taille de l'avatar"
28
+ },
29
+ shape: {
30
+ control: { type: 'select' },
31
+ options: ['circle', 'square'],
32
+ description: "Forme de l'avatar"
33
+ },
34
+ status: {
35
+ control: { type: 'select' },
36
+ options: [null, 'online', 'busy', 'away', 'offline'],
37
+ description: 'Indicateur de statut'
38
+ }
39
+ }
40
+ };
41
+
42
+ const Template = (args, { argTypes }) => ({
43
+ components: { FAvatar },
44
+ props: Object.keys(argTypes),
45
+ template: '<FAvatar v-bind="$props" />'
46
+ });
47
+
48
+ export const WithImage = Template.bind({});
49
+ WithImage.args = {
50
+ src: 'https://i.pravatar.cc/150?img=1',
51
+ alt: 'Avatar utilisateur'
52
+ };
53
+
54
+ export const WithInitials = Template.bind({});
55
+ WithInitials.args = {
56
+ initials: 'JD'
57
+ };
58
+
59
+ export const WithName = Template.bind({});
60
+ WithName.args = {
61
+ name: 'Jean Dupont'
62
+ };
63
+
64
+ export const Sizes = () => ({
65
+ components: { FAvatar },
66
+ template: `
67
+ <div class="flex items-center gap-4">
68
+ <FAvatar size="xs" initials="XS" />
69
+ <FAvatar size="sm" initials="SM" />
70
+ <FAvatar size="md" initials="MD" />
71
+ <FAvatar size="lg" initials="LG" />
72
+ <FAvatar size="xl" initials="XL" />
73
+ </div>
74
+ `
75
+ });
76
+
77
+ export const Shapes = () => ({
78
+ components: { FAvatar },
79
+ template: `
80
+ <div class="flex items-center gap-4">
81
+ <FAvatar shape="circle" initials="JD" />
82
+ <FAvatar shape="square" initials="JD" />
83
+ </div>
84
+ `
85
+ });
86
+
87
+ export const WithStatus = () => ({
88
+ components: { FAvatar },
89
+ template: `
90
+ <div class="flex items-center gap-4">
91
+ <FAvatar status="online" initials="ON" />
92
+ <FAvatar status="busy" initials="BU" />
93
+ <FAvatar status="away" initials="AW" />
94
+ <FAvatar status="offline" initials="OF" />
95
+ </div>
96
+ `
97
+ });
98
+
99
+ export const Placeholder = Template.bind({});
100
+ Placeholder.args = {};
@@ -0,0 +1,95 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import FAvatar from './FAvatar.vue';
4
+
5
+ describe('FAvatar', () => {
6
+ it('renders correctly with default props', () => {
7
+ const wrapper = mount(FAvatar);
8
+ expect(wrapper.find('div').exists()).toBe(true);
9
+ });
10
+
11
+ it('displays image when src is provided', () => {
12
+ const wrapper = mount(FAvatar, {
13
+ propsData: { src: 'https://example.com/avatar.jpg', alt: 'Test Avatar' }
14
+ });
15
+ const img = wrapper.find('img');
16
+ expect(img.exists()).toBe(true);
17
+ expect(img.attributes('src')).toBe('https://example.com/avatar.jpg');
18
+ expect(img.attributes('alt')).toBe('Test Avatar');
19
+ });
20
+
21
+ it('displays initials when provided', () => {
22
+ const wrapper = mount(FAvatar, {
23
+ propsData: { initials: 'JD' }
24
+ });
25
+ expect(wrapper.text()).toContain('JD');
26
+ });
27
+
28
+ it('computes initials from name', () => {
29
+ const wrapper = mount(FAvatar, {
30
+ propsData: { name: 'John Doe' }
31
+ });
32
+ expect(wrapper.text()).toContain('JD');
33
+ });
34
+
35
+ it('computes initials from single name', () => {
36
+ const wrapper = mount(FAvatar, {
37
+ propsData: { name: 'John' }
38
+ });
39
+ expect(wrapper.text()).toContain('JO');
40
+ });
41
+
42
+ it('applies correct size classes', () => {
43
+ const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const;
44
+ const sizeClasses = {
45
+ xs: 'w-6',
46
+ sm: 'w-8',
47
+ md: 'w-10',
48
+ lg: 'w-12',
49
+ xl: 'w-16'
50
+ };
51
+
52
+ sizes.forEach((size) => {
53
+ const wrapper = mount(FAvatar, {
54
+ propsData: { size }
55
+ });
56
+ expect(wrapper.classes().join(' ')).toContain(sizeClasses[size]);
57
+ });
58
+ });
59
+
60
+ it('applies circle shape by default', () => {
61
+ const wrapper = mount(FAvatar);
62
+ const container = wrapper.find('[class*="rounded"]');
63
+ expect(container.classes()).toContain('rounded-full');
64
+ });
65
+
66
+ it('applies square shape when specified', () => {
67
+ const wrapper = mount(FAvatar, {
68
+ propsData: { shape: 'square' }
69
+ });
70
+ const container = wrapper.find('[class*="rounded"]');
71
+ expect(container.classes()).toContain('rounded-lg');
72
+ });
73
+
74
+ it('displays status indicator when status is provided', () => {
75
+ const wrapper = mount(FAvatar, {
76
+ propsData: { status: 'online' }
77
+ });
78
+ expect(wrapper.find('[class*="bg-success"]').exists()).toBe(true);
79
+ });
80
+
81
+ it('emits click event when clicked', async () => {
82
+ const wrapper = mount(FAvatar);
83
+ await wrapper.trigger('click');
84
+ expect(wrapper.emitted('click')).toHaveLength(1);
85
+ });
86
+
87
+ it('falls back to placeholder when image fails to load', async () => {
88
+ const wrapper = mount(FAvatar, {
89
+ propsData: { src: 'invalid-url.jpg' }
90
+ });
91
+ const img = wrapper.find('img');
92
+ await img.trigger('error');
93
+ expect(wrapper.find('img').exists()).toBe(false);
94
+ });
95
+ });
@@ -0,0 +1,190 @@
1
+ <template>
2
+ <div
3
+ class="relative inline-flex flex-shrink-0"
4
+ :class="[sizeClasses.wrapper]"
5
+ @click="handleClick"
6
+ >
7
+ <div
8
+ :class="containerClasses"
9
+ :role="showImage ? undefined : 'img'"
10
+ :aria-label="computedAriaLabel"
11
+ >
12
+ <img
13
+ v-if="showImage"
14
+ :src="src"
15
+ :alt="alt"
16
+ class="w-full h-full object-cover"
17
+ loading="lazy"
18
+ @error="handleImageError"
19
+ />
20
+
21
+ <span
22
+ v-else
23
+ :class="[
24
+ 'flex items-center justify-center w-full h-full',
25
+ fontSizeClasses
26
+ ]"
27
+ >
28
+ <template v-if="displayInitials">
29
+ {{ displayInitials }}
30
+ </template>
31
+ <svg
32
+ v-else
33
+ class="w-3/5 h-3/5 text-white opacity-90"
34
+ fill="currentColor"
35
+ viewBox="0 0 20 20"
36
+ xmlns="http://www.w3.org/2000/svg"
37
+ >
38
+ <path
39
+ fill-rule="evenodd"
40
+ d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
41
+ clip-rule="evenodd"
42
+ />
43
+ </svg>
44
+ </span>
45
+ </div>
46
+
47
+ <span v-if="status" :class="statusClasses" />
48
+ </div>
49
+ </template>
50
+
51
+ <script setup lang="ts">
52
+ import { computed, ref, watch } from 'vue';
53
+
54
+ const props = withDefaults(
55
+ defineProps<{
56
+ src?: string;
57
+ alt?: string;
58
+ initials?: string;
59
+ name?: string;
60
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
61
+ shape?: 'circle' | 'square';
62
+ status?: 'online' | 'busy' | 'away' | 'offline' | null;
63
+ placeholderClass?: string;
64
+ }>(),
65
+ {
66
+ src: '',
67
+ alt: '',
68
+ initials: '',
69
+ name: '',
70
+ size: 'md',
71
+ shape: 'circle',
72
+ status: null,
73
+ placeholderClass: 'bg-neutral-400 text-white'
74
+ }
75
+ );
76
+
77
+ const emit = defineEmits<{
78
+ (e: 'click', event: MouseEvent): void;
79
+ }>();
80
+
81
+ const imageError = ref(false);
82
+
83
+ watch(
84
+ () => props.src,
85
+ () => {
86
+ imageError.value = false;
87
+ }
88
+ );
89
+
90
+ const showImage = computed(() => {
91
+ return props.src && !imageError.value;
92
+ });
93
+
94
+ const computeInitialsFromName = (name: string): string => {
95
+ if (!name) return '';
96
+ const parts = name.trim().split(/\s+/).filter(Boolean);
97
+ if (parts.length === 0) return '';
98
+ if (parts.length >= 2) {
99
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
100
+ }
101
+ return parts[0].substring(0, 2).toUpperCase();
102
+ };
103
+
104
+ const displayInitials = computed(() => {
105
+ if (props.initials) {
106
+ return props.initials.substring(0, 2).toUpperCase();
107
+ }
108
+ if (props.name) {
109
+ return computeInitialsFromName(props.name);
110
+ }
111
+ return '';
112
+ });
113
+
114
+ const computedAriaLabel = computed(() => {
115
+ if (props.alt) return props.alt;
116
+ if (props.name) return props.name;
117
+ if (props.initials) return `Avatar ${props.initials}`;
118
+ return 'Avatar';
119
+ });
120
+
121
+ const sizeClasses = computed(() => {
122
+ const sizes = {
123
+ xs: { wrapper: 'w-6 h-6', status: 'w-1.5 h-1.5' },
124
+ sm: { wrapper: 'w-8 h-8', status: 'w-2 h-2' },
125
+ md: { wrapper: 'w-10 h-10', status: 'w-2.5 h-2.5' },
126
+ lg: { wrapper: 'w-12 h-12', status: 'w-3 h-3' },
127
+ xl: { wrapper: 'w-16 h-16', status: 'w-4 h-4' }
128
+ };
129
+ return sizes[props.size];
130
+ });
131
+
132
+ const fontSizeClasses = computed(() => {
133
+ const fonts = {
134
+ xs: 'text-xs',
135
+ sm: 'text-xs',
136
+ md: 'text-sm',
137
+ lg: 'text-base',
138
+ xl: 'text-lg'
139
+ };
140
+ return [fonts[props.size], 'font-sans font-medium select-none'].join(' ');
141
+ });
142
+
143
+ const containerClasses = computed(() => {
144
+ const shapes = {
145
+ circle: 'rounded-full',
146
+ square: 'rounded-lg'
147
+ };
148
+
149
+ const background = !showImage.value ? props.placeholderClass : '';
150
+
151
+ return [
152
+ 'w-full h-full overflow-hidden flex items-center justify-center',
153
+ shapes[props.shape],
154
+ background
155
+ ]
156
+ .filter(Boolean)
157
+ .join(' ');
158
+ });
159
+
160
+ const statusClasses = computed(() => {
161
+ if (!props.status) return '';
162
+
163
+ const colors = {
164
+ online: 'bg-success-500',
165
+ busy: 'bg-danger-500',
166
+ away: 'bg-warning-500',
167
+ offline: 'bg-neutral-500'
168
+ };
169
+
170
+ const position =
171
+ props.shape === 'circle' ? '-bottom-0.5 -right-0.5' : '-bottom-1 -right-1';
172
+ const border = 'border-2 border-white';
173
+
174
+ return [
175
+ 'absolute rounded-full box-content',
176
+ position,
177
+ border,
178
+ sizeClasses.value.status,
179
+ colors[props.status]
180
+ ].join(' ');
181
+ });
182
+
183
+ const handleImageError = () => {
184
+ imageError.value = true;
185
+ };
186
+
187
+ const handleClick = (event: MouseEvent) => {
188
+ emit('click', event);
189
+ };
190
+ </script>
@@ -0,0 +1,129 @@
1
+ import FBadge from './FBadge.vue';
2
+
3
+ export default {
4
+ title: 'Atoms/FBadge',
5
+ component: FBadge,
6
+ tags: ['autodocs'],
7
+ argTypes: {
8
+ content: {
9
+ control: 'text',
10
+ description: 'Contenu du badge'
11
+ },
12
+ variant: {
13
+ control: { type: 'select' },
14
+ options: ['primary', 'success', 'warning', 'error', 'neutral'],
15
+ description: 'Variante de couleur'
16
+ },
17
+ shape: {
18
+ control: { type: 'select' },
19
+ options: ['pill', 'circle', 'rounded'],
20
+ description: 'Forme du badge'
21
+ },
22
+ size: {
23
+ control: { type: 'select' },
24
+ options: ['sm', 'md', 'lg'],
25
+ description: 'Taille du badge'
26
+ },
27
+ dot: {
28
+ control: 'boolean',
29
+ description: 'Afficher comme point'
30
+ },
31
+ outlined: {
32
+ control: 'boolean',
33
+ description: 'Style avec bordure'
34
+ }
35
+ }
36
+ };
37
+
38
+ const Template = (args, { argTypes }) => ({
39
+ components: { FBadge },
40
+ props: Object.keys(argTypes),
41
+ template: '<FBadge v-bind="$props" />'
42
+ });
43
+
44
+ export const Primary = Template.bind({});
45
+ Primary.args = {
46
+ content: 'Badge',
47
+ variant: 'primary'
48
+ };
49
+
50
+ export const Success = Template.bind({});
51
+ Success.args = {
52
+ content: 'Succès',
53
+ variant: 'success'
54
+ };
55
+
56
+ export const Warning = Template.bind({});
57
+ Warning.args = {
58
+ content: 'Attention',
59
+ variant: 'warning'
60
+ };
61
+
62
+ export const Error = Template.bind({});
63
+ Error.args = {
64
+ content: 'Erreur',
65
+ variant: 'error'
66
+ };
67
+
68
+ export const Neutral = Template.bind({});
69
+ Neutral.args = {
70
+ content: 'Neutre',
71
+ variant: 'neutral'
72
+ };
73
+
74
+ export const Sizes = () => ({
75
+ components: { FBadge },
76
+ template: `
77
+ <div class="flex items-center gap-2">
78
+ <FBadge content="Small" size="sm" />
79
+ <FBadge content="Medium" size="md" />
80
+ <FBadge content="Large" size="lg" />
81
+ </div>
82
+ `
83
+ });
84
+
85
+ export const Shapes = () => ({
86
+ components: { FBadge },
87
+ template: `
88
+ <div class="flex items-center gap-2">
89
+ <FBadge content="Pill" shape="pill" />
90
+ <FBadge content="8" shape="circle" />
91
+ <FBadge content="Rounded" shape="rounded" />
92
+ </div>
93
+ `
94
+ });
95
+
96
+ export const Outlined = () => ({
97
+ components: { FBadge },
98
+ template: `
99
+ <div class="flex items-center gap-2">
100
+ <FBadge content="Primary" variant="primary" outlined />
101
+ <FBadge content="Success" variant="success" outlined />
102
+ <FBadge content="Warning" variant="warning" outlined />
103
+ <FBadge content="Error" variant="error" outlined />
104
+ </div>
105
+ `
106
+ });
107
+
108
+ export const Dots = () => ({
109
+ components: { FBadge },
110
+ template: `
111
+ <div class="flex items-center gap-2">
112
+ <FBadge dot variant="primary" />
113
+ <FBadge dot variant="success" />
114
+ <FBadge dot variant="warning" />
115
+ <FBadge dot variant="error" />
116
+ </div>
117
+ `
118
+ });
119
+
120
+ export const Numbers = () => ({
121
+ components: { FBadge },
122
+ template: `
123
+ <div class="flex items-center gap-2">
124
+ <FBadge :content="1" shape="circle" />
125
+ <FBadge :content="42" shape="circle" />
126
+ <FBadge :content="99" variant="error" shape="circle" />
127
+ </div>
128
+ `
129
+ });
@@ -0,0 +1,93 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import FBadge from './FBadge.vue';
4
+
5
+ describe('FBadge', () => {
6
+ it('renders correctly with default props', () => {
7
+ const wrapper = mount(FBadge, {
8
+ slots: {
9
+ default: 'Badge'
10
+ }
11
+ });
12
+ expect(wrapper.text()).toContain('Badge');
13
+ });
14
+
15
+ it('displays content from prop', () => {
16
+ const wrapper = mount(FBadge, {
17
+ propsData: { content: 'Test' }
18
+ });
19
+ expect(wrapper.text()).toContain('Test');
20
+ });
21
+
22
+ it('displays numeric content', () => {
23
+ const wrapper = mount(FBadge, {
24
+ propsData: { content: 42 }
25
+ });
26
+ expect(wrapper.text()).toContain('42');
27
+ });
28
+
29
+ it('applies correct variant classes', () => {
30
+ const variants = [
31
+ 'primary',
32
+ 'success',
33
+ 'warning',
34
+ 'error',
35
+ 'neutral'
36
+ ] as const;
37
+ variants.forEach((variant) => {
38
+ const wrapper = mount(FBadge, {
39
+ propsData: { variant, content: 'Test' }
40
+ });
41
+ expect(wrapper.find('span').exists()).toBe(true);
42
+ });
43
+ });
44
+
45
+ it('applies correct size classes', () => {
46
+ const sizes = ['sm', 'md', 'lg'] as const;
47
+ sizes.forEach((size) => {
48
+ const wrapper = mount(FBadge, {
49
+ propsData: { size, content: 'Test' }
50
+ });
51
+ expect(wrapper.find('span').exists()).toBe(true);
52
+ });
53
+ });
54
+
55
+ it('renders as dot when dot prop is true', () => {
56
+ const wrapper = mount(FBadge, {
57
+ propsData: { dot: true }
58
+ });
59
+ expect(wrapper.text()).toBe('');
60
+ expect(wrapper.classes().join(' ')).toContain('rounded-full');
61
+ });
62
+
63
+ it('applies outlined style', () => {
64
+ const wrapper = mount(FBadge, {
65
+ propsData: { outlined: true, content: 'Test' }
66
+ });
67
+ expect(wrapper.classes().join(' ')).toContain('border');
68
+ });
69
+
70
+ it('applies correct shape classes', () => {
71
+ const shapes = ['pill', 'circle', 'rounded'] as const;
72
+ shapes.forEach((shape) => {
73
+ const wrapper = mount(FBadge, {
74
+ propsData: { shape, content: 'X' }
75
+ });
76
+ expect(wrapper.find('span').exists()).toBe(true);
77
+ });
78
+ });
79
+
80
+ it('uses custom tag when specified', () => {
81
+ const wrapper = mount(FBadge, {
82
+ propsData: { tag: 'div', content: 'Test' }
83
+ });
84
+ expect(wrapper.find('div').exists()).toBe(true);
85
+ });
86
+
87
+ it('sets aria-label for dot variant', () => {
88
+ const wrapper = mount(FBadge, {
89
+ propsData: { dot: true, variant: 'success' }
90
+ });
91
+ expect(wrapper.attributes('aria-label')).toBe('Status: success');
92
+ });
93
+ });