@cnamts/synapse 0.0.8-alpha → 0.0.10-alpha

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 (156) hide show
  1. package/dist/design-system-v3.d.ts +1152 -127
  2. package/dist/design-system-v3.js +4888 -2605
  3. package/dist/design-system-v3.umd.cjs +1 -1
  4. package/dist/style.css +1 -1
  5. package/package.json +1 -1
  6. package/src/assets/settings.scss +1 -1
  7. package/src/components/ContextualMenu/Accessibilite.mdx +14 -0
  8. package/src/components/ContextualMenu/Accessibilite.stories.ts +191 -0
  9. package/src/components/ContextualMenu/AccessibiliteItems.ts +89 -0
  10. package/src/components/ContextualMenu/constants/ExpertiseLevelEnum.ts +4 -0
  11. package/src/components/Customs/SySelect/SySelect.stories.ts +7 -7
  12. package/src/components/Customs/SySelect/SySelect.vue +9 -4
  13. package/src/components/Customs/SySelect/tests/SySelect.spec.ts +2 -2
  14. package/src/components/Customs/SyTextField/SyTextField.stories.ts +187 -2
  15. package/src/components/Customs/SyTextField/SyTextField.vue +185 -16
  16. package/src/components/Customs/SyTextField/tests/SyTextField.spec.ts +2 -4
  17. package/src/components/Customs/SyTextField/tests/__snapshots__/SyTextField.spec.ts.snap +18 -16
  18. package/src/components/Customs/SyTextField/types.d.ts +2 -2
  19. package/src/components/DatePicker/Accessibilite.mdx +14 -0
  20. package/src/components/DatePicker/Accessibilite.stories.ts +191 -0
  21. package/src/components/DatePicker/AccessibiliteItems.ts +233 -0
  22. package/src/components/DatePicker/DatePicker.mdx +186 -0
  23. package/src/components/DatePicker/DatePicker.stories.ts +787 -0
  24. package/src/components/DatePicker/DatePicker.vue +574 -0
  25. package/src/components/DatePicker/DateTextInput.vue +409 -0
  26. package/src/components/DatePicker/constants/ExpertiseLevelEnum.ts +4 -0
  27. package/src/components/DatePicker/tests/DatePicker.spec.ts +266 -0
  28. package/src/components/DialogBox/DialogBox.stories.ts +1 -1
  29. package/src/components/ExternalLinks/Accessibilite.mdx +14 -0
  30. package/src/components/ExternalLinks/Accessibilite.stories.ts +191 -0
  31. package/src/components/ExternalLinks/AccessibiliteItems.ts +197 -0
  32. package/src/components/ExternalLinks/constants/ExpertiseLevelEnum.ts +4 -0
  33. package/src/components/ExternalLinks/tests/__snapshots__/ExternalLinks.spec.ts.snap +9 -9
  34. package/src/components/FileList/FileList.mdx +103 -0
  35. package/src/components/FileList/FileList.stories.ts +562 -0
  36. package/src/components/FileList/FileList.vue +78 -0
  37. package/src/components/FileList/UploadItem/UploadItem.vue +270 -0
  38. package/src/components/FileList/UploadItem/locales.ts +9 -0
  39. package/src/components/FileList/tests/FileList.spec.ts +176 -0
  40. package/src/components/FilePreview/FilePreview.mdx +82 -0
  41. package/src/components/FilePreview/FilePreview.stories.ts +242 -0
  42. package/src/components/FilePreview/FilePreview.vue +68 -0
  43. package/src/components/FilePreview/config.ts +10 -0
  44. package/src/components/FilePreview/locales.ts +4 -0
  45. package/src/components/FilePreview/tests/FilePreview.spec.ts +124 -0
  46. package/src/components/FilePreview/tests/__snapshots__/FilePreview.spec.ts.snap +11 -0
  47. package/src/components/FileUpload/FileUpload.mdx +165 -0
  48. package/src/components/FileUpload/FileUpload.stories.ts +429 -0
  49. package/src/components/FileUpload/FileUpload.vue +195 -0
  50. package/src/components/FileUpload/FileUploadContent.vue +109 -0
  51. package/src/components/FileUpload/locales.ts +10 -0
  52. package/src/components/FileUpload/tests/FileUpload.spec.ts +332 -0
  53. package/src/components/FileUpload/tests/__snapshots__/FileUpload.spec.ts.snap +7 -0
  54. package/src/components/FileUpload/useFileDrop.ts +23 -0
  55. package/src/components/FileUpload/validateFiles.ts +39 -0
  56. package/src/components/NirField/NirField.stories.ts +1 -1
  57. package/src/components/NirField/NirField.vue +2 -1
  58. package/src/components/PasswordField/Accessibilite.mdx +14 -0
  59. package/src/components/PasswordField/Accessibilite.stories.ts +191 -0
  60. package/src/components/PasswordField/AccessibiliteItems.ts +184 -0
  61. package/src/components/PasswordField/PasswordField.vue +3 -3
  62. package/src/components/PasswordField/constants/ExpertiseLevelEnum.ts +4 -0
  63. package/src/components/PeriodField/PeriodField.mdx +32 -0
  64. package/src/components/PeriodField/PeriodField.stories.ts +807 -0
  65. package/src/components/PeriodField/PeriodField.vue +355 -0
  66. package/src/components/PeriodField/tests/PeriodField.spec.ts +348 -0
  67. package/src/components/PhoneField/PhoneField.vue +44 -60
  68. package/src/components/PhoneField/tests/PhoneField.spec.ts +0 -15
  69. package/src/components/RangeField/Accessibilite.mdx +14 -0
  70. package/src/components/RangeField/Accessibilite.stories.ts +191 -0
  71. package/src/components/RangeField/AccessibiliteItems.ts +179 -0
  72. package/src/components/RangeField/RangeField.mdx +54 -0
  73. package/src/components/RangeField/RangeField.stories.ts +189 -0
  74. package/src/components/RangeField/RangeField.vue +157 -0
  75. package/src/components/RangeField/RangeSlider/RangeSlider.vue +387 -0
  76. package/src/components/RangeField/RangeSlider/Tooltip/Tooltip.vue +64 -0
  77. package/src/components/RangeField/RangeSlider/tests/__snapshots__/rangeSlider.spec.ts.snap +27 -0
  78. package/src/components/RangeField/RangeSlider/tests/rangeSlider.spec.ts +100 -0
  79. package/src/components/RangeField/RangeSlider/tests/useDoubleSlider.spec.ts +246 -0
  80. package/src/components/RangeField/RangeSlider/tests/useMouseSlide.spec.ts +204 -0
  81. package/src/components/RangeField/RangeSlider/tests/useThumb.spec.ts +22 -0
  82. package/src/components/RangeField/RangeSlider/tests/useThumbKeyboard.spec.ts +233 -0
  83. package/src/components/RangeField/RangeSlider/tests/useTooltipsNudge.spec.ts +150 -0
  84. package/src/components/RangeField/RangeSlider/tests/useTrack.spec.ts +314 -0
  85. package/src/components/RangeField/RangeSlider/tests/vAnimateClick.spec.ts +32 -0
  86. package/src/components/RangeField/RangeSlider/types.ts +15 -0
  87. package/src/components/RangeField/RangeSlider/useMouseSlide.ts +109 -0
  88. package/src/components/RangeField/RangeSlider/useRangeSlider.ts +126 -0
  89. package/src/components/RangeField/RangeSlider/useThumb.ts +18 -0
  90. package/src/components/RangeField/RangeSlider/useThumbKeyboard.ts +84 -0
  91. package/src/components/RangeField/RangeSlider/useTooltipsNudge.ts +92 -0
  92. package/src/components/RangeField/RangeSlider/useTrack.ts +116 -0
  93. package/src/components/RangeField/RangeSlider/vAnimateClick.ts +19 -0
  94. package/src/components/RangeField/config.ts +7 -0
  95. package/src/components/RangeField/constants/ExpertiseLevelEnum.ts +4 -0
  96. package/src/components/RangeField/locales.ts +4 -0
  97. package/src/components/RangeField/tests/RangeField.spec.ts +224 -0
  98. package/src/components/RangeField/tests/__snapshots__/RangeField.spec.ts.snap +379 -0
  99. package/src/components/RatingPicker/Accessibilite.mdx +14 -0
  100. package/src/components/RatingPicker/Accessibilite.stories.ts +191 -0
  101. package/src/components/RatingPicker/AccessibiliteItems.ts +208 -0
  102. package/src/components/RatingPicker/EmotionPicker/EmotionPicker.vue +205 -0
  103. package/src/components/RatingPicker/EmotionPicker/locales.ts +3 -0
  104. package/src/components/RatingPicker/EmotionPicker/tests/EmotionPicker.spec.ts +104 -0
  105. package/src/components/RatingPicker/EmotionPicker/tests/__snapshots__/EmotionPicker.spec.ts.snap +66 -0
  106. package/src/components/RatingPicker/NumberPicker/NumberPicker.vue +159 -0
  107. package/src/components/RatingPicker/NumberPicker/locales.ts +4 -0
  108. package/src/components/RatingPicker/NumberPicker/tests/NumberPicker.spec.ts +73 -0
  109. package/src/components/RatingPicker/NumberPicker/tests/__snapshots__/NumberPicker.spec.ts.snap +105 -0
  110. package/src/components/RatingPicker/Rating.ts +45 -0
  111. package/src/components/RatingPicker/RatingPicker.mdx +56 -0
  112. package/src/components/RatingPicker/RatingPicker.stories.ts +515 -0
  113. package/src/components/RatingPicker/RatingPicker.vue +122 -0
  114. package/src/components/RatingPicker/StarsPicker/StarsPicker.vue +116 -0
  115. package/src/components/RatingPicker/StarsPicker/tests/StarsPicker.spec.ts +95 -0
  116. package/src/components/RatingPicker/StarsPicker/tests/__snapshots__/StarsPicker.spec.ts.snap +36 -0
  117. package/src/components/RatingPicker/constants/ExpertiseLevelEnum.ts +4 -0
  118. package/src/components/RatingPicker/locales.ts +3 -0
  119. package/src/components/RatingPicker/tests/Rating.spec.ts +104 -0
  120. package/src/components/RatingPicker/tests/RatingPicker.spec.ts +187 -0
  121. package/src/components/RatingPicker/tests/__snapshots__/RatingPicker.spec.ts.snap +108 -0
  122. package/src/components/SearchListField/Accessibilite.mdx +14 -0
  123. package/src/components/SearchListField/Accessibilite.stories.ts +191 -0
  124. package/src/components/SearchListField/AccessibiliteItems.ts +310 -0
  125. package/src/components/SearchListField/SearchListField.mdx +74 -0
  126. package/src/components/SearchListField/SearchListField.stories.ts +126 -0
  127. package/src/components/SearchListField/SearchListField.vue +194 -0
  128. package/src/components/SearchListField/constants/ExpertiseLevelEnum.ts +4 -0
  129. package/src/components/SearchListField/locales.ts +5 -0
  130. package/src/components/SearchListField/tests/SearchListField.spec.ts +323 -0
  131. package/src/components/SearchListField/types.d.ts +4 -0
  132. package/src/components/SelectBtnField/Accessibilite.mdx +14 -0
  133. package/src/components/SelectBtnField/Accessibilite.stories.ts +191 -0
  134. package/src/components/SelectBtnField/AccessibiliteItems.ts +191 -0
  135. package/src/components/SelectBtnField/SelectBtnField.mdx +50 -0
  136. package/src/components/SelectBtnField/SelectBtnField.stories.ts +763 -0
  137. package/src/components/SelectBtnField/SelectBtnField.vue +283 -0
  138. package/src/components/SelectBtnField/config.ts +11 -0
  139. package/src/components/SelectBtnField/constants/ExpertiseLevelEnum.ts +4 -0
  140. package/src/components/SelectBtnField/tests/SelectBtnField.spec.ts +327 -0
  141. package/src/components/SelectBtnField/tests/__snapshots__/SelectBtnField.spec.ts.snap +125 -0
  142. package/src/components/SelectBtnField/types.d.ts +11 -0
  143. package/src/components/SyAlert/SyAlert.vue +11 -9
  144. package/src/components/TableToolbar/TableToolbar.mdx +130 -0
  145. package/src/components/TableToolbar/TableToolbar.stories.ts +935 -0
  146. package/src/components/TableToolbar/TableToolbar.vue +168 -0
  147. package/src/components/TableToolbar/config.ts +24 -0
  148. package/src/components/TableToolbar/locales.ts +6 -0
  149. package/src/components/TableToolbar/tests/TableToolbar.spec.ts +166 -0
  150. package/src/components/TableToolbar/tests/__snapshots__/TableToolbar.spec.ts.snap +359 -0
  151. package/src/components/index.ts +11 -1
  152. package/src/composables/rules/useFieldValidation.ts +174 -44
  153. package/src/designTokens/index.ts +3 -3
  154. package/src/stories/Fondamentaux/CustomisationEtThemes.mdx +52 -2
  155. package/src/utils/calcHumanFileSize/index.ts +12 -0
  156. package/src/utils/calcHumanFileSize/tests/calcHumanFileSize.spec.ts +21 -0
@@ -0,0 +1,104 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import { vuetify } from '@tests/unit/setup'
4
+
5
+ import EmotionPicker from '../EmotionPicker.vue'
6
+
7
+ describe('EmotionPicker', () => {
8
+ it('renders correctly', () => {
9
+ const wrapper = mount(EmotionPicker, {
10
+ global: {
11
+ plugins: [vuetify],
12
+ },
13
+ propsData: {
14
+ label: 'Pourriez-vous donner une note ?',
15
+ itemLabels: ['Pas du tout', 'Peut-être', 'Oui super'],
16
+ },
17
+ })
18
+
19
+ expect(wrapper.html()).toContain('Pourriez-vous donner une note ?')
20
+ expect(wrapper.html()).toContain('Pas du tout')
21
+ expect(wrapper.html()).toContain('Peut-être')
22
+ expect(wrapper.html()).toContain('Oui super')
23
+ expect(wrapper.html()).toMatchSnapshot()
24
+ })
25
+
26
+ it('renders correctly with only 2 items', () => {
27
+ const wrapper = mount(EmotionPicker, {
28
+ global: {
29
+ plugins: [vuetify],
30
+ },
31
+ propsData: {
32
+ label: 'Pourriez-vous donner une note ?',
33
+ itemLabels: ['Pas du tout', 'Not used', 'Oui super'],
34
+ length: 2,
35
+ },
36
+ })
37
+
38
+ expect(wrapper.html()).toContain('Pourriez-vous donner une note ?')
39
+ expect(wrapper.html()).toContain('Pas du tout')
40
+ expect(wrapper.html()).toContain('Oui super')
41
+ expect(wrapper.html()).not.toContain('Not used')
42
+ expect(wrapper.html()).toMatchSnapshot()
43
+ })
44
+
45
+ it('renders in mobile mode', async () => {
46
+ // @ts-expect-error - Property 'happyDOM' does not exist on type 'Window & typeof globalThis'.
47
+ window.happyDOM.setInnerWidth(600)
48
+
49
+ const wrapper = mount(EmotionPicker, {
50
+ global: {
51
+ plugins: [vuetify],
52
+ },
53
+ propsData: {
54
+ label: 'Pourriez-vous donner une note ?',
55
+ itemLabels: ['Pas du tout', 'Peut-être', 'Oui super'],
56
+ },
57
+ })
58
+
59
+ await wrapper.vm.$nextTick()
60
+
61
+ expect(wrapper.html()).toContain('70px')
62
+ })
63
+
64
+ it('emit the right value when an item is clicked', async () => {
65
+ const wrapper = mount(EmotionPicker, {
66
+ global: {
67
+ plugins: [vuetify],
68
+ },
69
+ })
70
+
71
+ const items = wrapper.findAll('button')
72
+ await items[0].trigger('click')
73
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy()
74
+ expect(wrapper.emitted('update:modelValue')?.[0]?.[0]).toBe(1)
75
+ })
76
+
77
+ it('have no labels when no labels are provided', () => {
78
+ const wrapper = mount(EmotionPicker, {
79
+ global: {
80
+ plugins: [vuetify],
81
+ },
82
+ propsData: {
83
+ itemLabels: [],
84
+ },
85
+ })
86
+
87
+ wrapper.findAll('button').forEach((button) => {
88
+ expect(button.text()).toBe('')
89
+ })
90
+ })
91
+
92
+ it('change the style of the buttons when an item is clicked', async () => {
93
+ const wrapper = mount(EmotionPicker, {
94
+ global: {
95
+ plugins: [vuetify],
96
+ },
97
+ })
98
+
99
+ const items = wrapper.findAll('button')
100
+
101
+ await wrapper.setProps({ modelValue: 1 })
102
+ expect(items[0].classes()).toContain('v-btn--active')
103
+ })
104
+ })
@@ -0,0 +1,66 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`EmotionPicker > renders correctly 1`] = `
4
+ "<fieldset data-v-9f5befe4="" class="vd-emotion-picker">
5
+ <legend data-v-9f5befe4="" class="text-h6 mb-6">Pourriez-vous donner une note ?</legend>
6
+ <div data-v-9f5befe4="" class="v-rating v-theme--light max-width-none mx-n1 mx-sm-n2"><label for="v-rating-0-0" class=""><span class="v-rating__hidden">Rating 0 of 3</span>
7
+ <!---->
8
+ </label><input class="v-rating__hidden" name="v-rating-0" id="v-rating-0-0" type="radio" checked="" tabindex="-1" value="0">
9
+ <div class="v-rating__wrapper">
10
+ <!---->
11
+ <div class="v-rating__item"><label for="v-rating-0-1" class=""><span class="v-rating__hidden">Rating 1 of 3</span><button data-v-9f5befe4="" type="button" class="v-btn v-theme--light v-btn--density-default v-btn--size-default v-btn--variant-text sad rounded-lg px-1 px-sm-4 mx-1 mx-sm-2" style="min-height: 88px; min-width: 88px;" title="Pas du tout" aria-pressed="false"><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
12
+ <!----><span class="v-btn__content" data-no-activator=""><i data-v-9f5befe4="" class="M20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12M15.5,8C16.3,8 17,8.7 17,9.5C17,10.3 16.3,11 15.5,11C14.7,11 14,10.3 14,9.5C14,8.7 14.7,8 15.5,8M10,9.5C10,10.3 9.3,11 8.5,11C7.7,11 7,10.3 7,9.5C7,8.7 7.7,8 8.5,8C9.3,8 10,8.7 10,9.5M12,14C13.75,14 15.29,14.72 16.19,15.81L14.77,17.23C14.32,16.5 13.25,16 12,16C10.75,16 9.68,16.5 9.23,17.23L7.81,15.81C8.71,14.72 10.25,14 12,14Z mdi v-icon notranslate v-theme--light text-currentColor pa-0" style="font-size: 40px; height: 40px; width: 40px;" aria-hidden="true"></i><span data-v-9f5befe4="" class="text-secondary text-subtitle-2 mt-1">Pas du tout</span></span>
13
+ <!---->
14
+ <!---->
15
+ </button></label><input class="v-rating__hidden" name="v-rating-0" id="v-rating-0-1" type="radio" tabindex="-1" value="1"></div>
16
+ <!---->
17
+ </div>
18
+ <div class="v-rating__wrapper">
19
+ <!---->
20
+ <div class="v-rating__item"><label for="v-rating-0-2" class=""><span class="v-rating__hidden">Rating 2 of 3</span><button data-v-9f5befe4="" type="button" class="v-btn v-theme--light v-btn--density-default v-btn--size-default v-btn--variant-text neutral rounded-lg px-1 px-sm-4 mx-1 mx-sm-2" style="min-height: 88px; min-width: 88px;" title="Peut-être" aria-pressed="false"><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
21
+ <!----><span class="v-btn__content" data-no-activator=""><i data-v-9f5befe4="" class="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M8.5,11A1.5,1.5 0 0,1 7,9.5A1.5,1.5 0 0,1 8.5,8A1.5,1.5 0 0,1 10,9.5A1.5,1.5 0 0,1 8.5,11M17,9.5A1.5,1.5 0 0,1 15.5,11A1.5,1.5 0 0,1 14,9.5A1.5,1.5 0 0,1 15.5,8A1.5,1.5 0 0,1 17,9.5M16,14V16H8V14H16Z mdi v-icon notranslate v-theme--light text-currentColor pa-0" style="font-size: 40px; height: 40px; width: 40px;" aria-hidden="true"></i><span data-v-9f5befe4="" class="text-secondary text-subtitle-2 mt-1">Peut-être</span></span>
22
+ <!---->
23
+ <!---->
24
+ </button></label><input class="v-rating__hidden" name="v-rating-0" id="v-rating-0-2" type="radio" tabindex="-1" value="2"></div>
25
+ <!---->
26
+ </div>
27
+ <div class="v-rating__wrapper">
28
+ <!---->
29
+ <div class="v-rating__item"><label for="v-rating-0-3" class=""><span class="v-rating__hidden">Rating 3 of 3</span><button data-v-9f5befe4="" type="button" class="v-btn v-theme--light v-btn--density-default v-btn--size-default v-btn--variant-text happy rounded-lg px-1 px-sm-4 mx-1 mx-sm-2" style="min-height: 88px; min-width: 88px;" title="Oui super" aria-pressed="false"><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
30
+ <!----><span class="v-btn__content" data-no-activator=""><i data-v-9f5befe4="" class="M20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12M10,9.5C10,10.3 9.3,11 8.5,11C7.7,11 7,10.3 7,9.5C7,8.7 7.7,8 8.5,8C9.3,8 10,8.7 10,9.5M17,9.5C17,10.3 16.3,11 15.5,11C14.7,11 14,10.3 14,9.5C14,8.7 14.7,8 15.5,8C16.3,8 17,8.7 17,9.5M12,17.23C10.25,17.23 8.71,16.5 7.81,15.42L9.23,14C9.68,14.72 10.75,15.23 12,15.23C13.25,15.23 14.32,14.72 14.77,14L16.19,15.42C15.29,16.5 13.75,17.23 12,17.23Z mdi v-icon notranslate v-theme--light text-currentColor pa-0" style="font-size: 40px; height: 40px; width: 40px;" aria-hidden="true"></i><span data-v-9f5befe4="" class="text-secondary text-subtitle-2 mt-1">Oui super</span></span>
31
+ <!---->
32
+ <!---->
33
+ </button></label><input class="v-rating__hidden" name="v-rating-0" id="v-rating-0-3" type="radio" tabindex="-1" value="3"></div>
34
+ <!---->
35
+ </div>
36
+ </div>
37
+ </fieldset>"
38
+ `;
39
+
40
+ exports[`EmotionPicker > renders correctly with only 2 items 1`] = `
41
+ "<fieldset data-v-9f5befe4="" class="vd-emotion-picker">
42
+ <legend data-v-9f5befe4="" class="text-h6 mb-6">Pourriez-vous donner une note ?</legend>
43
+ <div data-v-9f5befe4="" class="v-rating v-theme--light max-width-none mx-n1 mx-sm-n2"><label for="v-rating-0-0" class=""><span class="v-rating__hidden">Rating 0 of 2</span>
44
+ <!---->
45
+ </label><input class="v-rating__hidden" name="v-rating-0" id="v-rating-0-0" type="radio" checked="" tabindex="-1" value="0">
46
+ <div class="v-rating__wrapper">
47
+ <!---->
48
+ <div class="v-rating__item"><label for="v-rating-0-1" class=""><span class="v-rating__hidden">Rating 1 of 2</span><button data-v-9f5befe4="" type="button" class="v-btn v-theme--light v-btn--density-default v-btn--size-default v-btn--variant-text sad rounded-lg px-1 px-sm-4 mx-1 mx-sm-2" style="min-height: 88px; min-width: 88px;" title="Pas du tout" aria-pressed="false"><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
49
+ <!----><span class="v-btn__content" data-no-activator=""><i data-v-9f5befe4="" class="M20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12M15.5,8C16.3,8 17,8.7 17,9.5C17,10.3 16.3,11 15.5,11C14.7,11 14,10.3 14,9.5C14,8.7 14.7,8 15.5,8M10,9.5C10,10.3 9.3,11 8.5,11C7.7,11 7,10.3 7,9.5C7,8.7 7.7,8 8.5,8C9.3,8 10,8.7 10,9.5M12,14C13.75,14 15.29,14.72 16.19,15.81L14.77,17.23C14.32,16.5 13.25,16 12,16C10.75,16 9.68,16.5 9.23,17.23L7.81,15.81C8.71,14.72 10.25,14 12,14Z mdi v-icon notranslate v-theme--light text-currentColor pa-0" style="font-size: 40px; height: 40px; width: 40px;" aria-hidden="true"></i><span data-v-9f5befe4="" class="text-secondary text-subtitle-2 mt-1">Pas du tout</span></span>
50
+ <!---->
51
+ <!---->
52
+ </button></label><input class="v-rating__hidden" name="v-rating-0" id="v-rating-0-1" type="radio" tabindex="-1" value="1"></div>
53
+ <!---->
54
+ </div>
55
+ <div class="v-rating__wrapper">
56
+ <!---->
57
+ <div class="v-rating__item"><label for="v-rating-0-2" class=""><span class="v-rating__hidden">Rating 2 of 2</span><button data-v-9f5befe4="" type="button" class="v-btn v-theme--light v-btn--density-default v-btn--size-default v-btn--variant-text happy rounded-lg px-1 px-sm-4 mx-1 mx-sm-2" style="min-height: 88px; min-width: 88px;" title="Oui super" aria-pressed="false"><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
58
+ <!----><span class="v-btn__content" data-no-activator=""><i data-v-9f5befe4="" class="M20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12M10,9.5C10,10.3 9.3,11 8.5,11C7.7,11 7,10.3 7,9.5C7,8.7 7.7,8 8.5,8C9.3,8 10,8.7 10,9.5M17,9.5C17,10.3 16.3,11 15.5,11C14.7,11 14,10.3 14,9.5C14,8.7 14.7,8 15.5,8C16.3,8 17,8.7 17,9.5M12,17.23C10.25,17.23 8.71,16.5 7.81,15.42L9.23,14C9.68,14.72 10.75,15.23 12,15.23C13.25,15.23 14.32,14.72 14.77,14L16.19,15.42C15.29,16.5 13.75,17.23 12,17.23Z mdi v-icon notranslate v-theme--light text-currentColor pa-0" style="font-size: 40px; height: 40px; width: 40px;" aria-hidden="true"></i><span data-v-9f5befe4="" class="text-secondary text-subtitle-2 mt-1">Oui super</span></span>
59
+ <!---->
60
+ <!---->
61
+ </button></label><input class="v-rating__hidden" name="v-rating-0" id="v-rating-0-2" type="radio" tabindex="-1" value="2"></div>
62
+ <!---->
63
+ </div>
64
+ </div>
65
+ </fieldset>"
66
+ `;
@@ -0,0 +1,159 @@
1
+ <script lang="ts" setup>
2
+ import { computed, onMounted } from 'vue'
3
+ import { RatingEnum, useRating } from '../Rating'
4
+ import { locales } from './locales'
5
+ import type { PropType } from 'vue'
6
+
7
+ import SySelect from '@/components/Customs/SySelect/SySelect.vue'
8
+
9
+ import { useDisplay } from 'vuetify'
10
+
11
+ interface SelectItem {
12
+ text: string
13
+ value: number
14
+ }
15
+
16
+ const props = defineProps({
17
+ label: {
18
+ type: String as PropType<string | undefined>,
19
+ default: RatingEnum.NUMBER,
20
+ },
21
+ length: {
22
+ type: Number,
23
+ default: 10,
24
+ },
25
+ itemLabels: {
26
+ type: Array as PropType<string[]>,
27
+ default: () => [],
28
+ },
29
+ readonly: {
30
+ type: Boolean,
31
+ default: false,
32
+ },
33
+ modelValue: {
34
+ type: Number,
35
+ default: -1,
36
+ },
37
+ })
38
+
39
+ const { smAndDown } = useDisplay()
40
+ const isMobile = computed(() => smAndDown.value)
41
+
42
+ const emit = defineEmits(['update:modelValue'])
43
+ const { hasAnswered, emitInputEvent } = useRating(props, emit)
44
+
45
+ const selectItems = computed<SelectItem[]>(() => {
46
+ return [...Array(props.length)].map((_, index) => ({
47
+ text: `${index + 1}`,
48
+ value: index + 1,
49
+ }))
50
+ })
51
+
52
+ const shouldDisplayLabels = computed(() => props.itemLabels.length === 2)
53
+
54
+ onMounted(() => {
55
+ const hiddenInputs = document.querySelectorAll('.v-rating__hidden')
56
+ hiddenInputs.forEach((input) => {
57
+ (input as HTMLElement).setAttribute('aria-hidden', 'true')
58
+ })
59
+ })
60
+ </script>
61
+
62
+ <template>
63
+ <fieldset class="vd-number-picker">
64
+ <legend class="d-sr-only">
65
+ <slot name="label">
66
+ {{ props.label }}
67
+ </slot>
68
+ </legend>
69
+ <SySelect
70
+ v-if="isMobile"
71
+ :model-value="props.modelValue === -1 ? undefined : props.modelValue"
72
+ :label="props.label"
73
+ :disabled="props.readonly || hasAnswered"
74
+ :items="selectItems"
75
+ @update:model-value="(value) => emit('update:modelValue', value)"
76
+ />
77
+ <template v-else>
78
+ <legend class="text-h6 mb-6">
79
+ <slot name="label">
80
+ {{ props.label }}
81
+ </slot>
82
+ </legend>
83
+ <div
84
+ v-if="!hasAnswered"
85
+ class="d-inline-block"
86
+ >
87
+ <VRating
88
+ :model-value="props.modelValue"
89
+ :length="props.length"
90
+ :readonly="props.readonly || hasAnswered"
91
+ class="d-flex flex-wrap max-width-none"
92
+ @update:model-value="(value) => emit('update:modelValue', value)"
93
+ >
94
+ <template #item="{ index }">
95
+ <VBtn
96
+ :aria-label="locales.ariaLabel(index + 1, props.length)"
97
+ :disabled="props.readonly"
98
+ size="x-small"
99
+ variant="outlined"
100
+ color="primary"
101
+ height="36px"
102
+ class="text-body-2 pa-0 mr-2"
103
+ @click="emitInputEvent(index + 1)"
104
+ >
105
+ {{ index + 1 }}
106
+ </VBtn>
107
+ </template>
108
+ </VRating>
109
+ <div
110
+ v-if="shouldDisplayLabels"
111
+ class="d-flex justify-space-between mt-1"
112
+ >
113
+ <span
114
+ :aria-label="`${locales.ariaLabel(1, props.length)} ${
115
+ props.itemLabels[0]
116
+ }.`"
117
+ class="text-caption"
118
+ v-text="props.itemLabels[0]"
119
+ />
120
+ <span
121
+ :aria-label="`${locales.ariaLabel(props.length, props.length)} ${
122
+ props.itemLabels[1]
123
+ }.`"
124
+ class="text-caption mr-2"
125
+ v-text="props.itemLabels[1]"
126
+ />
127
+ </div>
128
+ </div>
129
+ <p
130
+ v-else
131
+ :aria-label="locales.ariaLabel(props.modelValue, props.length)"
132
+ class="mb-0 d-flex align-center"
133
+ >
134
+ <VBtn
135
+ aria-hidden="true"
136
+ :disabled="true"
137
+ size="x-small"
138
+ variant="outlined"
139
+ color="primary"
140
+ height="36px"
141
+ class="vd-btn-answer text-body-2 mr-1 pa-0"
142
+ >
143
+ {{ props.modelValue }}
144
+ </VBtn>
145
+ / {{ props.length }}
146
+ </p>
147
+ </template>
148
+ </fieldset>
149
+ </template>
150
+
151
+ <style lang="scss" scoped>
152
+ .vd-number-picker {
153
+ border: 0;
154
+ }
155
+
156
+ .vd-btn-answer.v-btn.v-btn--disabled {
157
+ opacity: 1;
158
+ }
159
+ </style>
@@ -0,0 +1,4 @@
1
+ export const locales = {
2
+ ariaLabel: (index: number, length: number): string =>
3
+ `Note de ${index} sur ${length}.`,
4
+ }
@@ -0,0 +1,73 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import { vuetify } from '@tests/unit/setup'
4
+
5
+ import NumberPicker from '../NumberPicker.vue'
6
+
7
+ describe('NumberPicker', () => {
8
+ it('renders correctly', () => {
9
+ const wrapper = mount(NumberPicker, {
10
+ global: {
11
+ plugins: [vuetify],
12
+ },
13
+ propsData: {
14
+ label: 'Pourriez-vous donner une note ?',
15
+ },
16
+ })
17
+
18
+ const btns = wrapper.findAll('button')
19
+
20
+ expect(btns).toHaveLength(10)
21
+ btns.forEach((btn, i) => {
22
+ expect(btn.text()).toBe((i + 1).toString())
23
+ })
24
+ expect(wrapper.html()).toMatchSnapshot()
25
+ })
26
+
27
+ it('emits an event when a number is selected', async () => {
28
+ const wrapper = mount(NumberPicker, {
29
+ global: {
30
+ plugins: [vuetify],
31
+ },
32
+ })
33
+
34
+ await wrapper.findAll('button')!.at(3)!.trigger('click')
35
+
36
+ expect(wrapper.emitted('update:modelValue')![0]).toEqual([4])
37
+ })
38
+
39
+ it('change the displayed value when the modelValue is updated', async () => {
40
+ const wrapper = mount(NumberPicker, {
41
+ global: {
42
+ plugins: [vuetify],
43
+ },
44
+ props: {
45
+ modelValue: 3,
46
+ },
47
+ })
48
+
49
+ const btn = wrapper.findAll('button')[0]
50
+
51
+ await wrapper.setProps({ modelValue: 4 })
52
+ expect(btn.text()).toBe('4')
53
+
54
+ await wrapper.setProps({ modelValue: 5 })
55
+ expect(btn.text()).toBe('5')
56
+ })
57
+
58
+ it('renders correctly in xs window', async () => {
59
+ // @ts-expect-error - Property 'happyDOM' does not exist on type 'Window & typeof globalThis'.
60
+ window.happyDOM.setInnerWidth(600)
61
+
62
+ const wrapper = mount(NumberPicker, {
63
+ global: {
64
+ plugins: [vuetify],
65
+ },
66
+ })
67
+ await wrapper.vm.$nextTick()
68
+
69
+ const select = wrapper.find('.sy-select')
70
+
71
+ expect(select.exists()).toBe(true)
72
+ })
73
+ })
@@ -0,0 +1,105 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`NumberPicker > renders correctly 1`] = `
4
+ "<fieldset data-v-30bbac4d="" class="vd-number-picker">
5
+ <legend data-v-30bbac4d="" class="d-sr-only">Pourriez-vous donner une note ?</legend>
6
+ <legend data-v-30bbac4d="" class="text-h6 mb-6">Pourriez-vous donner une note ?</legend>
7
+ <div data-v-30bbac4d="" class="d-inline-block">
8
+ <div data-v-30bbac4d="" class="v-rating v-theme--light d-flex flex-wrap max-width-none"><label for="v-rating-0-0" class=""><span class="v-rating__hidden">Rating 0 of 10</span>
9
+ <!---->
10
+ </label><input class="v-rating__hidden" name="v-rating-0" id="v-rating-0-0" type="radio" checked="" tabindex="-1" value="0">
11
+ <div class="v-rating__wrapper">
12
+ <!---->
13
+ <div class="v-rating__item"><label for="v-rating-0-1" class=""><span class="v-rating__hidden">Rating 1 of 10</span><button data-v-30bbac4d="" type="button" class="v-btn v-theme--light text-primary v-btn--density-default v-btn--size-x-small v-btn--variant-outlined text-body-2 pa-0 mr-2" style="height: 36px;" aria-label="Note de 1 sur 10."><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
14
+ <!----><span class="v-btn__content" data-no-activator="">1</span>
15
+ <!---->
16
+ <!---->
17
+ </button></label><input class="v-rating__hidden" name="v-rating-0" id="v-rating-0-1" type="radio" tabindex="-1" value="1"></div>
18
+ <!---->
19
+ </div>
20
+ <div class="v-rating__wrapper">
21
+ <!---->
22
+ <div class="v-rating__item"><label for="v-rating-0-2" class=""><span class="v-rating__hidden">Rating 2 of 10</span><button data-v-30bbac4d="" type="button" class="v-btn v-theme--light text-primary v-btn--density-default v-btn--size-x-small v-btn--variant-outlined text-body-2 pa-0 mr-2" style="height: 36px;" aria-label="Note de 2 sur 10."><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
23
+ <!----><span class="v-btn__content" data-no-activator="">2</span>
24
+ <!---->
25
+ <!---->
26
+ </button></label><input class="v-rating__hidden" name="v-rating-0" id="v-rating-0-2" type="radio" tabindex="-1" value="2"></div>
27
+ <!---->
28
+ </div>
29
+ <div class="v-rating__wrapper">
30
+ <!---->
31
+ <div class="v-rating__item"><label for="v-rating-0-3" class=""><span class="v-rating__hidden">Rating 3 of 10</span><button data-v-30bbac4d="" type="button" class="v-btn v-theme--light text-primary v-btn--density-default v-btn--size-x-small v-btn--variant-outlined text-body-2 pa-0 mr-2" style="height: 36px;" aria-label="Note de 3 sur 10."><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
32
+ <!----><span class="v-btn__content" data-no-activator="">3</span>
33
+ <!---->
34
+ <!---->
35
+ </button></label><input class="v-rating__hidden" name="v-rating-0" id="v-rating-0-3" type="radio" tabindex="-1" value="3"></div>
36
+ <!---->
37
+ </div>
38
+ <div class="v-rating__wrapper">
39
+ <!---->
40
+ <div class="v-rating__item"><label for="v-rating-0-4" class=""><span class="v-rating__hidden">Rating 4 of 10</span><button data-v-30bbac4d="" type="button" class="v-btn v-theme--light text-primary v-btn--density-default v-btn--size-x-small v-btn--variant-outlined text-body-2 pa-0 mr-2" style="height: 36px;" aria-label="Note de 4 sur 10."><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
41
+ <!----><span class="v-btn__content" data-no-activator="">4</span>
42
+ <!---->
43
+ <!---->
44
+ </button></label><input class="v-rating__hidden" name="v-rating-0" id="v-rating-0-4" type="radio" tabindex="-1" value="4"></div>
45
+ <!---->
46
+ </div>
47
+ <div class="v-rating__wrapper">
48
+ <!---->
49
+ <div class="v-rating__item"><label for="v-rating-0-5" class=""><span class="v-rating__hidden">Rating 5 of 10</span><button data-v-30bbac4d="" type="button" class="v-btn v-theme--light text-primary v-btn--density-default v-btn--size-x-small v-btn--variant-outlined text-body-2 pa-0 mr-2" style="height: 36px;" aria-label="Note de 5 sur 10."><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
50
+ <!----><span class="v-btn__content" data-no-activator="">5</span>
51
+ <!---->
52
+ <!---->
53
+ </button></label><input class="v-rating__hidden" name="v-rating-0" id="v-rating-0-5" type="radio" tabindex="-1" value="5"></div>
54
+ <!---->
55
+ </div>
56
+ <div class="v-rating__wrapper">
57
+ <!---->
58
+ <div class="v-rating__item"><label for="v-rating-0-6" class=""><span class="v-rating__hidden">Rating 6 of 10</span><button data-v-30bbac4d="" type="button" class="v-btn v-theme--light text-primary v-btn--density-default v-btn--size-x-small v-btn--variant-outlined text-body-2 pa-0 mr-2" style="height: 36px;" aria-label="Note de 6 sur 10."><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
59
+ <!----><span class="v-btn__content" data-no-activator="">6</span>
60
+ <!---->
61
+ <!---->
62
+ </button></label><input class="v-rating__hidden" name="v-rating-0" id="v-rating-0-6" type="radio" tabindex="-1" value="6"></div>
63
+ <!---->
64
+ </div>
65
+ <div class="v-rating__wrapper">
66
+ <!---->
67
+ <div class="v-rating__item"><label for="v-rating-0-7" class=""><span class="v-rating__hidden">Rating 7 of 10</span><button data-v-30bbac4d="" type="button" class="v-btn v-theme--light text-primary v-btn--density-default v-btn--size-x-small v-btn--variant-outlined text-body-2 pa-0 mr-2" style="height: 36px;" aria-label="Note de 7 sur 10."><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
68
+ <!----><span class="v-btn__content" data-no-activator="">7</span>
69
+ <!---->
70
+ <!---->
71
+ </button></label><input class="v-rating__hidden" name="v-rating-0" id="v-rating-0-7" type="radio" tabindex="-1" value="7"></div>
72
+ <!---->
73
+ </div>
74
+ <div class="v-rating__wrapper">
75
+ <!---->
76
+ <div class="v-rating__item"><label for="v-rating-0-8" class=""><span class="v-rating__hidden">Rating 8 of 10</span><button data-v-30bbac4d="" type="button" class="v-btn v-theme--light text-primary v-btn--density-default v-btn--size-x-small v-btn--variant-outlined text-body-2 pa-0 mr-2" style="height: 36px;" aria-label="Note de 8 sur 10."><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
77
+ <!----><span class="v-btn__content" data-no-activator="">8</span>
78
+ <!---->
79
+ <!---->
80
+ </button></label><input class="v-rating__hidden" name="v-rating-0" id="v-rating-0-8" type="radio" tabindex="-1" value="8"></div>
81
+ <!---->
82
+ </div>
83
+ <div class="v-rating__wrapper">
84
+ <!---->
85
+ <div class="v-rating__item"><label for="v-rating-0-9" class=""><span class="v-rating__hidden">Rating 9 of 10</span><button data-v-30bbac4d="" type="button" class="v-btn v-theme--light text-primary v-btn--density-default v-btn--size-x-small v-btn--variant-outlined text-body-2 pa-0 mr-2" style="height: 36px;" aria-label="Note de 9 sur 10."><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
86
+ <!----><span class="v-btn__content" data-no-activator="">9</span>
87
+ <!---->
88
+ <!---->
89
+ </button></label><input class="v-rating__hidden" name="v-rating-0" id="v-rating-0-9" type="radio" tabindex="-1" value="9"></div>
90
+ <!---->
91
+ </div>
92
+ <div class="v-rating__wrapper">
93
+ <!---->
94
+ <div class="v-rating__item"><label for="v-rating-0-10" class=""><span class="v-rating__hidden">Rating 10 of 10</span><button data-v-30bbac4d="" type="button" class="v-btn v-theme--light text-primary v-btn--density-default v-btn--size-x-small v-btn--variant-outlined text-body-2 pa-0 mr-2" style="height: 36px;" aria-label="Note de 10 sur 10."><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
95
+ <!----><span class="v-btn__content" data-no-activator="">10</span>
96
+ <!---->
97
+ <!---->
98
+ </button></label><input class="v-rating__hidden" name="v-rating-0" id="v-rating-0-10" type="radio" tabindex="-1" value="10"></div>
99
+ <!---->
100
+ </div>
101
+ </div>
102
+ <!--v-if-->
103
+ </div>
104
+ </fieldset>"
105
+ `;
@@ -0,0 +1,45 @@
1
+ import { ref, computed } from 'vue'
2
+
3
+ export interface RatingInterface {
4
+ emitInputEvent(event: string | number): void
5
+ }
6
+
7
+ export enum RatingEnum {
8
+ EMOTION = 'emotion',
9
+ NUMBER = 'number',
10
+ STARS = 'stars',
11
+ }
12
+
13
+ export const RATING_ENUM_VALUES = Object.values(RatingEnum)
14
+
15
+ export enum AlertTypeEnum {
16
+ INFO = 'info',
17
+ SUCCESS = 'success',
18
+ WARNING = 'warning',
19
+ ERROR = 'error',
20
+ }
21
+
22
+ export const ALERT_TYPE_ENUM_VALUES = Object.values(AlertTypeEnum)
23
+
24
+ export function useRating(props: {
25
+ label: string | null | undefined
26
+ readonly: boolean
27
+ modelValue: number
28
+ }, emit: (event: 'update:modelValue', value: number) => void) {
29
+ const internalValue = ref<number>(props.modelValue)
30
+
31
+ const hasAnswered = computed(() => internalValue.value !== -1)
32
+
33
+ function emitInputEvent(value: string | number): void {
34
+ if (!props.readonly) {
35
+ internalValue.value = typeof value === 'number' ? value : parseInt(value, 10)
36
+ emit('update:modelValue', internalValue.value) // Emit the updated value
37
+ }
38
+ }
39
+
40
+ return {
41
+ internalValue,
42
+ hasAnswered,
43
+ emitInputEvent,
44
+ }
45
+ }
@@ -0,0 +1,56 @@
1
+ import {Canvas, Meta, Controls, Source} from '@storybook/blocks';
2
+ import * as RatingPickerStories from './RatingPicker.stories';
3
+ import RatingPicker from './RatingPicker.vue';
4
+
5
+ <Meta title="Composants/Feedback/RatingPicker" component={RatingPicker}/>
6
+
7
+ # RatingPicker
8
+
9
+ L’élément `RatingPicker` permet de recueillir l’avis d’un utilisateur.
10
+
11
+ <Canvas story={{height: '150px'}} of={RatingPickerStories.Default}/>
12
+
13
+ # API
14
+
15
+ <Controls of={RatingPickerStories.Default}/>
16
+
17
+ ## Utilisation de base
18
+
19
+ <Source
20
+ dark
21
+ code={`
22
+ <script setup lang="ts">
23
+ import { ref } from 'vue'
24
+ import { RatingPicker } from '@cnamts/synapse'
25
+
26
+ const ratingEmotion = ref(-1)
27
+ const ratingNumber = ref(-1)
28
+ const ratingStars = ref(-1)
29
+ </script>
30
+
31
+ <template>
32
+ {{ ratingEmotion }}
33
+ <RatingPicker
34
+ v-model="ratingEmotion"
35
+ label="Êtes-vous satisfait de ce service ?"
36
+ type="emotion"
37
+ />
38
+ <br>
39
+ {{ ratingNumber }}
40
+ <RatingPicker
41
+ v-model="ratingNumber"
42
+ label="Êtes-vous satisfait de ce service ?"
43
+ type="number"
44
+ />
45
+ <br>
46
+ {{ ratingStars }}
47
+ <RatingPicker
48
+ v-model="ratingStars"
49
+ label="Êtes-vous satisfait de ce service ?"
50
+ type="stars"
51
+ />
52
+ </template>
53
+ `}
54
+ />
55
+
56
+