@cnamts/synapse 0.0.0-alpha.0
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.
- package/LICENSE +21 -0
- package/README.md +2 -0
- package/dist/design-system-v3.d.ts +246 -0
- package/dist/design-system-v3.js +5425 -0
- package/dist/design-system-v3.umd.cjs +2 -0
- package/dist/style.css +1 -0
- package/package.json +104 -0
- package/src/assets/tokens.scss +500 -0
- package/src/components/Alert/Alert.mdx +36 -0
- package/src/components/Alert/Alert.stories.ts +115 -0
- package/src/components/Alert/Alert.vue +248 -0
- package/src/components/Alert/locales.ts +3 -0
- package/src/components/Alert/tests/Alert.spec.ts +105 -0
- package/src/components/Alert/tests/__snapshots__/Alert.spec.ts.snap +15 -0
- package/src/components/BackBtn/BackBtn.mdx +26 -0
- package/src/components/BackBtn/BackBtn.stories.ts +138 -0
- package/src/components/BackBtn/BackBtn.vue +60 -0
- package/src/components/BackBtn/locales.ts +3 -0
- package/src/components/BackBtn/tests/BackBtn.spec.ts +103 -0
- package/src/components/BackBtn/tests/__snapshots__/BackBtn.spec.ts.snap +9 -0
- package/src/components/BackToTopBtn/BackToTopBtn.mdx +52 -0
- package/src/components/BackToTopBtn/BackToTopBtn.stories.ts +188 -0
- package/src/components/BackToTopBtn/BackToTopBtn.vue +137 -0
- package/src/components/BackToTopBtn/config.ts +12 -0
- package/src/components/BackToTopBtn/locales.ts +3 -0
- package/src/components/BackToTopBtn/tests/BackToTopBtn.spec.ts +173 -0
- package/src/components/BackToTopBtn/tests/__snapshots__/BackToTopBtn.spec.ts.snap +17 -0
- package/src/components/Beta/beta.mdx +5 -0
- package/src/components/CopyBtn/CopyBtn.mdx +38 -0
- package/src/components/CopyBtn/CopyBtn.stories.ts +209 -0
- package/src/components/CopyBtn/CopyBtn.vue +103 -0
- package/src/components/CopyBtn/config.ts +17 -0
- package/src/components/CopyBtn/locales.ts +3 -0
- package/src/components/CopyBtn/tests/CopyBtn.spec.ts +99 -0
- package/src/components/CopyBtn/tests/__snapshots__/CopyBtn.spec.ts.snap +7 -0
- package/src/components/Deprecated/deprecated.mdx +5 -0
- package/src/components/DownloadBtn/DownloadBtn.mdx +94 -0
- package/src/components/DownloadBtn/DownloadBtn.stories.ts +211 -0
- package/src/components/DownloadBtn/DownloadBtn.vue +113 -0
- package/src/components/DownloadBtn/config.ts +13 -0
- package/src/components/DownloadBtn/tests/DownloadBtn.spec.ts +82 -0
- package/src/components/DownloadBtn/tests/__snapshots__/DownloadBtn.spec.ts.snap +17 -0
- package/src/components/DownloadBtn/tests/data/filePromise.ts +53 -0
- package/src/components/DownloadBtn/tests/data/test.json +0 -0
- package/src/components/FranceConnectBtn/FranceConnectBtn.mdx +34 -0
- package/src/components/FranceConnectBtn/FranceConnectBtn.stories.ts +92 -0
- package/src/components/FranceConnectBtn/FranceConnectBtn.vue +154 -0
- package/src/components/FranceConnectBtn/locales.ts +6 -0
- package/src/components/FranceConnectBtn/tests/FranceConnectBtn.spec.ts +62 -0
- package/src/components/FranceConnectBtn/tests/__snapshots__/FranceConnectBtn.spec.ts.snap +36 -0
- package/src/components/LangBtn/LangBtn.mdx +37 -0
- package/src/components/LangBtn/LangBtn.stories.ts +147 -0
- package/src/components/LangBtn/LangBtn.vue +167 -0
- package/src/components/LangBtn/config.ts +17 -0
- package/src/components/LangBtn/locales.ts +3 -0
- package/src/components/LangBtn/tests/Config.spec.ts +24 -0
- package/src/components/LangBtn/tests/LangBtn.spec.ts +283 -0
- package/src/components/LangBtn/tests/__snapshots__/LangBtn.spec.ts.snap +11 -0
- package/src/components/LangBtn/types.d.ts +7 -0
- package/src/components/NotificationBar/NotificationBar.mdx +94 -0
- package/src/components/NotificationBar/NotificationBar.stories.ts +366 -0
- package/src/components/NotificationBar/NotificationBar.vue +296 -0
- package/src/components/NotificationBar/options.ts +15 -0
- package/src/components/NotificationBar/tests/NotificationBar.spec.ts +332 -0
- package/src/components/NotificationBar/tests/__snapshots__/NotificationBar.spec.ts.snap +7 -0
- package/src/components/NotificationBar/types.ts +7 -0
- package/src/components/PageContainer/PageContainer.mdx +29 -0
- package/src/components/PageContainer/PageContainer.stories.ts +115 -0
- package/src/components/PageContainer/PageContainer.vue +68 -0
- package/src/components/PageContainer/tests/PageContainer.spec.ts +56 -0
- package/src/components/PageContainer/tests/__snapshots__/PageContainer.spec.ts.snap +7 -0
- package/src/components/SkipLink/SkipLink.mdx +55 -0
- package/src/components/SkipLink/SkipLink.stories.ts +70 -0
- package/src/components/SkipLink/SkipLink.vue +79 -0
- package/src/components/SkipLink/locales.ts +3 -0
- package/src/components/SkipLink/tests/__snapshots__/skipLink.spec.ts.snap +3 -0
- package/src/components/SkipLink/tests/skipLink.spec.ts +46 -0
- package/src/components/index.ts +8 -0
- package/src/composables/useCustomizableOptions.ts +23 -0
- package/src/designTokens/bootstrapColors.md +66 -0
- package/src/designTokens/cnamColors.md +193 -0
- package/src/designTokens/index.ts +15 -0
- package/src/designTokens/tokens/bootstrap/bootstrapColors.ts +158 -0
- package/src/designTokens/tokens/bootstrap/bootstrapLightTheme.ts +22 -0
- package/src/designTokens/tokens/cnam/cnamColors.ts +171 -0
- package/src/designTokens/tokens/cnam/cnamContextual.ts +58 -0
- package/src/designTokens/tokens/cnam/cnamLightTheme.ts +90 -0
- package/src/designTokens/tokens/cnam/cnamSemantic.ts +87 -0
- package/src/designTokens/tokens/json/contextual-tokens.json +156 -0
- package/src/designTokens/tokens/json/primitives.json +209 -0
- package/src/designTokens/tokens/json/semantic.json +120 -0
- package/src/designTokens/utils/convertGaps.ts +11 -0
- package/src/designTokens/utils/convertSemanticsToken.ts +32 -0
- package/src/designTokens/utils/createFlattenTheme.ts +19 -0
- package/src/designTokens/utils/index.ts +4 -0
- package/src/main.ts +2 -0
- package/src/services/NotificationService.ts +27 -0
- package/src/stories/Fondamentaux/Accessibilite/Accessibilite.mdx +52 -0
- package/src/stories/Fondamentaux/Accessibilite/Accessibilite.stories.ts +36 -0
- package/src/stories/Fondamentaux/Accessibilite/AccessibiliteItems.ts +706 -0
- package/src/stories/Fondamentaux/Accessibilite/constants/ExpertiseLevelEnum.ts +5 -0
- package/src/stories/Fondamentaux/Accessibilite/constants/RGAALevelEnum.ts +4 -0
- package/src/stories/Fondamentaux/EcoConception/EcoConception.mdx +24 -0
- package/src/stories/Fondamentaux/EcoConception/Econception.stories.ts +30 -0
- package/src/stories/Fondamentaux/EcoConception/ecoDesignItems.ts +55 -0
- package/src/stories/GuideDuDev/CommentContribuer.mdx +22 -0
- package/src/stories/GuideDuDev/components.stories.ts +23 -0
- package/src/stories/GuideDuDev/moduleDeNotification.mdx +182 -0
- package/src/stories/GuideDuDev/vuetifyOptions.mdx +72 -0
- package/src/stories/Guidelines/Colors.mdx +220 -0
- package/src/stories/Guidelines/CustomisationEtThemes.mdx +3 -0
- package/src/stories/Guidelines/Introduction.mdx +35 -0
- package/src/stories/Guidelines/Typo.mdx +53 -0
- package/src/stories/Home/Accueil.mdx +7 -0
- package/src/stories/Home/PolitiqueDeConfidentialite.mdx +4 -0
- package/src/stories/Home/synapse.webp +0 -0
- package/src/temp/TestA11y.vue +14 -0
- package/src/temp/TestComponent.vue +37 -0
- package/src/temp/TestDTComponent.vue +93 -0
- package/src/temp/customizableOptions.vue +18 -0
- package/src/temp/gridsTests.vue +54 -0
- package/src/temp/options.json +5 -0
- package/src/types/vuetifyTypes.ts +3 -0
- package/src/utils/convertToUnit/index.ts +16 -0
- package/src/utils/convertToUnit/test/convertToUnit.spec.ts +32 -0
- package/src/utils/functions/copyToClipboard/index.ts +38 -0
- package/src/utils/functions/copyToClipboard/tests/copyToClipboard.spec.ts +104 -0
- package/src/utils/functions/downloadFile/index.ts +37 -0
- package/src/utils/functions/downloadFile/tests/downloadFile.spec.ts +69 -0
- package/src/utils/functions/downloadFile/types.ts +1 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Meta } from '@storybook/addon-docs';
|
|
2
|
+
|
|
3
|
+
<Meta title="Guidelines/Introduction" />
|
|
4
|
+
|
|
5
|
+
Principes de design
|
|
6
|
+
===================
|
|
7
|
+
|
|
8
|
+
Les principes éthiques de construction de notre Design System.
|
|
9
|
+
|
|
10
|
+
Être au service des usagers de la santé publique
|
|
11
|
+
------------------------------------------------
|
|
12
|
+
|
|
13
|
+
En tant que Design System pour l'Assurance Maladie, notre devoir est de **répondre avec efficacité et intelligence aux besoins** de nos différents profils d'utilisateur citoyen.
|
|
14
|
+
|
|
15
|
+
Nous recommandons pour chaque utilisation de notre Design System de **favoriser l'écoute et l'empathie auprès des utilisateurs finaux** en récoltant dès que le projet le permet un ou plusieurs retours d'expériences. En suivant ce principe, c'est depuis 2017 plus de 20 produits et services numériques qui ont permis d'affiner et de structurer les choix ergonomiques de notre Design System.
|
|
16
|
+
|
|
17
|
+
Afin d'accompagner les équipes de l'Assurance Maladie, **nous pouvons aider à la mise en place de tests utilisateurs, au passage et à l'analyse des résultats**. En cohérence avec les besoins ou les contraintes des projets, nous vous conseillerons sur la typologie de test à adopter afin de répondre avec performance à vos besoins.
|
|
18
|
+
|
|
19
|
+
Assurer l'accessibilité pour tous
|
|
20
|
+
---------------------------------
|
|
21
|
+
|
|
22
|
+
**L'accès aux produits numériques doit être permis à tous**. Pour cela, nous favorisons dans notre démarche de design le respect des [critères d'accessibilité](https://digital-design-system.netlify.app/fondamentaux/accessibilite) définis par le Référentiel Général d'Amélioration de l'Accessibilité.
|
|
23
|
+
|
|
24
|
+
Nous recommandons pour chaque utilisation de notre Design System de ne pas transformer les composants. La conception des composants vise à respecter quatre indices de performance. **Nos composants et leurs contenus informationnels sont perceptibles (1), manipulables (2), compréhensibles (3) et robustes (4) dans l'usage**. En complément, nous recommandons de proposer de la redondance informationnelle par un ou plusieurs moyens alternatifs lorsqu'une information est complexe à comprendre ou à atteindre sur l'interface.
|
|
25
|
+
|
|
26
|
+
Afin d'accompagner les équipes de l'Assurance Maladie, **nous pouvons aider à l'élaboration de vos interfaces pour qu'elles respectent les normes d'accessibilité**. Dans le cas où nos composants ne correspondent pas à vos exigences, nous pouvons collaborer avec vous à leurs adaptations.
|
|
27
|
+
|
|
28
|
+
Rendre évident ce qui est complexe
|
|
29
|
+
----------------------------------
|
|
30
|
+
|
|
31
|
+
Malgré la complexité des projets, notre Design System doit permettre **la construction d'interfaces fluides et simples d'utilisation** pour assurer aux utilisateurs finaux une bonne expérience numérique.
|
|
32
|
+
|
|
33
|
+
Nous recommandons pour chaque utilisation de notre Design System de favoriser des **compositions graphiques d'interfaces peu chargées avec des espaces vierges et une hiérarchisation des informations**. Afin de permettre le responsive design, les interfaces doivent être structurées en utilisant des grilles et permettre ainsi l'affichage multi-support. En complément, nous recommandons de favoriser des parcours utilisateurs courts et de les concevoir en respectant pour chaque étape [la théorie de l'action proposée par Don Norman](https://fr.wikipedia.org/wiki/Th%C3%A9orie_de_l%27action_(Norman)).
|
|
34
|
+
|
|
35
|
+
Afin d'accompagner les équipes de l'Assurance Maladie, nous pouvons aider à structurer les interfaces. En proposant par exemple des ateliers de cadrage participatifs dont l'objectif est de relever auprès de vos besoins et des besoins des utilisateurs finaux les informations pertinentes (insights) à afficher. C'est une étape importante pour l'organisation graphique des interfaces.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Meta, Typeset } from '@storybook/addon-docs';
|
|
2
|
+
|
|
3
|
+
<Meta title="Guidelines/Typographie" />
|
|
4
|
+
|
|
5
|
+
export const typography = {
|
|
6
|
+
type: {
|
|
7
|
+
primary: 'Arial',
|
|
8
|
+
},
|
|
9
|
+
weight: {
|
|
10
|
+
regular: '400',
|
|
11
|
+
bold: '700',
|
|
12
|
+
extrabold: '800',
|
|
13
|
+
black: '900',
|
|
14
|
+
},
|
|
15
|
+
size: {
|
|
16
|
+
s1: 12,
|
|
17
|
+
s2: 14,
|
|
18
|
+
s3: 16,
|
|
19
|
+
m1: 18,
|
|
20
|
+
m2: 20,
|
|
21
|
+
m3: 24,
|
|
22
|
+
l1: 28,
|
|
23
|
+
l2: 30,
|
|
24
|
+
l3: 32,
|
|
25
|
+
l4:40,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const SampleText = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
|
|
30
|
+
|
|
31
|
+
# Typography
|
|
32
|
+
|
|
33
|
+
**Font:** Arial
|
|
34
|
+
|
|
35
|
+
**Weights:** 400(regular), 700(bold), 800(extrabold), 900(black)
|
|
36
|
+
|
|
37
|
+
<Typeset
|
|
38
|
+
fontSizes={[
|
|
39
|
+
Number(typography.size.s1),
|
|
40
|
+
Number(typography.size.s2),
|
|
41
|
+
Number(typography.size.s3),
|
|
42
|
+
Number(typography.size.m1),
|
|
43
|
+
Number(typography.size.m2),
|
|
44
|
+
Number(typography.size.m3),
|
|
45
|
+
Number(typography.size.l1),
|
|
46
|
+
Number(typography.size.l2),
|
|
47
|
+
Number(typography.size.l3),
|
|
48
|
+
Number(typography.size.l4),
|
|
49
|
+
]}
|
|
50
|
+
fontWeight={typography.weight.black}
|
|
51
|
+
sampleText={SampleText}
|
|
52
|
+
fontFamily={typography.type.primary}
|
|
53
|
+
/>
|
|
Binary file
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { VBtn } from 'vuetify/components/VBtn'
|
|
4
|
+
|
|
5
|
+
const props = withDefaults(defineProps<{
|
|
6
|
+
prop1?: string
|
|
7
|
+
number1: number
|
|
8
|
+
number2: number
|
|
9
|
+
}>(), {
|
|
10
|
+
|
|
11
|
+
prop1: 'default value',
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
defineEmits([
|
|
15
|
+
'click',
|
|
16
|
+
])
|
|
17
|
+
|
|
18
|
+
const value = defineModel< string >()
|
|
19
|
+
const result = computed(() => {
|
|
20
|
+
return props.number1 + props.number2
|
|
21
|
+
})
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<div>
|
|
26
|
+
{{ prop1 }}
|
|
27
|
+
</div>
|
|
28
|
+
<v-btn @click="$emit('click', result)">
|
|
29
|
+
click me
|
|
30
|
+
</v-btn>
|
|
31
|
+
<div>
|
|
32
|
+
Result : {{ result }}
|
|
33
|
+
</div>
|
|
34
|
+
<div>{{ value }}</div>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<style lang="scss" scoped></style>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-col
|
|
3
|
+
cols="2"
|
|
4
|
+
class="d-flex flex-column ga-3 mx-auto"
|
|
5
|
+
>
|
|
6
|
+
<h1 class="text-center">
|
|
7
|
+
Test Heading
|
|
8
|
+
</h1>
|
|
9
|
+
<VBtn color="primary">
|
|
10
|
+
Primary Button
|
|
11
|
+
</VBtn>
|
|
12
|
+
<VBtn color="secondary">
|
|
13
|
+
Secondary Button
|
|
14
|
+
</VBtn>
|
|
15
|
+
<VBtn color="accent">
|
|
16
|
+
Accent Button
|
|
17
|
+
</VBtn>
|
|
18
|
+
<VBtn color="warning">
|
|
19
|
+
Warning Button
|
|
20
|
+
</VBtn>
|
|
21
|
+
<VBtn color="success">
|
|
22
|
+
Success Button
|
|
23
|
+
</VBtn>
|
|
24
|
+
<VBtn color="error">
|
|
25
|
+
Error Button
|
|
26
|
+
</VBtn>
|
|
27
|
+
<VBtn color="info">
|
|
28
|
+
Info Button
|
|
29
|
+
</VBtn>
|
|
30
|
+
<VBtn color="risquePro">
|
|
31
|
+
RisquePro Button
|
|
32
|
+
</VBtn>
|
|
33
|
+
<VBtn color="backgroundAssure">
|
|
34
|
+
Assure Button
|
|
35
|
+
</VBtn>
|
|
36
|
+
<VBtn color="transparentBlue18">
|
|
37
|
+
TransparentBlue18 Button
|
|
38
|
+
</VBtn>
|
|
39
|
+
<VBtn color="transparentBlue8">
|
|
40
|
+
TransparentBlue8 Button
|
|
41
|
+
</VBtn>
|
|
42
|
+
<VBtn color="transparentBlue0">
|
|
43
|
+
TransparentBlue0 Button
|
|
44
|
+
</VBtn>
|
|
45
|
+
<VBtn color="toto">
|
|
46
|
+
Toto Button
|
|
47
|
+
</VBtn>
|
|
48
|
+
<div>
|
|
49
|
+
<div
|
|
50
|
+
class="border-xl"
|
|
51
|
+
style="height: 64px; width: 64px;"
|
|
52
|
+
>
|
|
53
|
+
<div class="text-caption">
|
|
54
|
+
border-xl
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
<div>
|
|
59
|
+
<v-card-title class="title">
|
|
60
|
+
Titre
|
|
61
|
+
</v-card-title>
|
|
62
|
+
<v-card-title class="alt-title">
|
|
63
|
+
Titre Alternatif
|
|
64
|
+
</v-card-title>
|
|
65
|
+
<v-card-text class="body-text">
|
|
66
|
+
Ceci est un corps de texte.
|
|
67
|
+
</v-card-text>
|
|
68
|
+
<v-btn class="link-label">
|
|
69
|
+
Lien ou libellé
|
|
70
|
+
</v-btn>
|
|
71
|
+
</div>
|
|
72
|
+
</v-col>
|
|
73
|
+
<div />
|
|
74
|
+
</template>
|
|
75
|
+
|
|
76
|
+
<style lang="scss" scoped>
|
|
77
|
+
<<<<<<<< HEAD:src/temp/TestDTComponent.vue
|
|
78
|
+
@import '../assets/tokens';
|
|
79
|
+
========
|
|
80
|
+
@import '../../../assets/tokens';
|
|
81
|
+
>>>>>>>> 82f4bcd (chore(deps): update dependency vue to v3.5.2 (#66)):src/components/Temp/TestDesignTokensComponent/TestDTComponent.vue
|
|
82
|
+
|
|
83
|
+
h1 {
|
|
84
|
+
color: $orange-base;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
button {
|
|
88
|
+
background-color: $blue-base;
|
|
89
|
+
color: $white-base;
|
|
90
|
+
border-radius: $radius-rounded;
|
|
91
|
+
//padding: $padding-10 $padding-4;
|
|
92
|
+
}
|
|
93
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import useCustomizableOptions, { type CustomizableOptions } from '@/composables/useCustomizableOptions'
|
|
3
|
+
import defaultOptions from './options.json'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<CustomizableOptions & {
|
|
6
|
+
title: string
|
|
7
|
+
}>()
|
|
8
|
+
|
|
9
|
+
const options = useCustomizableOptions(defaultOptions, props)
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<div>
|
|
14
|
+
<VBtn v-bind="options.btn">
|
|
15
|
+
{{ title }}
|
|
16
|
+
</VBtn>
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { reactive, ref, onMounted } from 'vue'
|
|
3
|
+
const state = reactive({
|
|
4
|
+
screenWidth: 0,
|
|
5
|
+
gridColumns: 1,
|
|
6
|
+
})
|
|
7
|
+
const calculateColumns = (width: number) => {
|
|
8
|
+
state.screenWidth = width
|
|
9
|
+
if (width >= 1200) {
|
|
10
|
+
state.gridColumns = 12
|
|
11
|
+
}
|
|
12
|
+
else if (width >= 900) {
|
|
13
|
+
state.gridColumns = 3
|
|
14
|
+
}
|
|
15
|
+
else if (width >= 600) {
|
|
16
|
+
state.gridColumns = 2
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
state.gridColumns = 1
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const targetElement = ref<HTMLElement | null>(null)
|
|
23
|
+
|
|
24
|
+
onMounted(() => {
|
|
25
|
+
targetElement.value = document.body
|
|
26
|
+
calculateColumns(targetElement.value.clientWidth)
|
|
27
|
+
})
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<v-container>
|
|
32
|
+
<v-col>
|
|
33
|
+
<div class="bg-blue-accent-4 top-0 text-center">
|
|
34
|
+
Menu
|
|
35
|
+
</div>
|
|
36
|
+
<div class="page-container">
|
|
37
|
+
<v-col
|
|
38
|
+
v-for="n in 12"
|
|
39
|
+
:key="n"
|
|
40
|
+
class="bg-amber-darken-4"
|
|
41
|
+
>
|
|
42
|
+
{{ n }}
|
|
43
|
+
</v-col>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="bg-blue-accent-4 top-0 text-center">
|
|
46
|
+
Footer
|
|
47
|
+
</div>
|
|
48
|
+
</v-col>
|
|
49
|
+
</v-container>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<style lang="scss" scoped>
|
|
53
|
+
@import '@/assets/tokens';
|
|
54
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Convert a value to CSS unit */
|
|
2
|
+
export function convertToUnit(
|
|
3
|
+
str: string | number | null | undefined,
|
|
4
|
+
unit = 'px',
|
|
5
|
+
): string | undefined {
|
|
6
|
+
if (str === undefined || str === null || str === '') {
|
|
7
|
+
return undefined
|
|
8
|
+
}
|
|
9
|
+
else if (isNaN(+str)) {
|
|
10
|
+
// If NaN, it's a string with unit, use as is
|
|
11
|
+
return String(str)
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
return `${Number(str)}${unit}`
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { convertToUnit } from '../'
|
|
3
|
+
|
|
4
|
+
describe('convertToUnit', () => {
|
|
5
|
+
it('returns undefined when the value is null', () => {
|
|
6
|
+
expect(convertToUnit(null)).toBeUndefined()
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it('returns undefined when the value is undefined', () => {
|
|
10
|
+
expect(convertToUnit(undefined)).toBeUndefined()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('returns undefined when the value is an empty string', () => {
|
|
14
|
+
expect(convertToUnit('')).toBeUndefined()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('returns the value when it is a string with unit', () => {
|
|
18
|
+
expect(convertToUnit('16px')).toBe('16px')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('converts the value when it is a string without unit', () => {
|
|
22
|
+
expect(convertToUnit('16')).toBe('16px')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('converts the value to a string when it is a number', () => {
|
|
26
|
+
expect(convertToUnit(16)).toBe('16px')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('converts the value to a string with custom unit when it is a number', () => {
|
|
30
|
+
expect(convertToUnit(1, 'rem')).toBe('1rem')
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Copy text to the clipboard */
|
|
2
|
+
export function copyToClipboard(textToCopy: string): void {
|
|
3
|
+
// Use a textarea, so we can use execCommand
|
|
4
|
+
const el = document.createElement('textarea')
|
|
5
|
+
|
|
6
|
+
el.value = textToCopy
|
|
7
|
+
el.setAttribute('readonly', '')
|
|
8
|
+
el.style.position = 'absolute'
|
|
9
|
+
el.style.left = '-9999px'
|
|
10
|
+
|
|
11
|
+
document.body.appendChild(el)
|
|
12
|
+
|
|
13
|
+
const selection = document.getSelection()
|
|
14
|
+
|
|
15
|
+
let selected: Range | false = false
|
|
16
|
+
|
|
17
|
+
if (selection) {
|
|
18
|
+
// Store previous selection (or false) to restore it after
|
|
19
|
+
selected = selection.rangeCount > 0 ? selection.getRangeAt(0) : false
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
el.select()
|
|
23
|
+
|
|
24
|
+
if (navigator.clipboard) {
|
|
25
|
+
navigator.clipboard.writeText(textToCopy)
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
document.execCommand('copy')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
document.body.removeChild(el)
|
|
32
|
+
|
|
33
|
+
// If a selection existed before copying
|
|
34
|
+
if (selection && selected) {
|
|
35
|
+
selection.removeAllRanges() // Unselect everything on the HTML document
|
|
36
|
+
selection.addRange(selected) // Restore the original selection
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach, vi } from 'vitest'
|
|
2
|
+
import { copyToClipboard } from '../'
|
|
3
|
+
|
|
4
|
+
interface TSelection {
|
|
5
|
+
rangeCount?: number
|
|
6
|
+
getRangeAt?: (index: number) => string | null
|
|
7
|
+
removeAllRanges?: () => null
|
|
8
|
+
addRange?: () => null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Custom document type
|
|
12
|
+
interface TDocument {
|
|
13
|
+
getSelection: () => TSelection | null
|
|
14
|
+
execCommand: () => boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Override default type
|
|
18
|
+
declare let document: TDocument
|
|
19
|
+
|
|
20
|
+
/** Mock functions on document */
|
|
21
|
+
function mockDocument(options: TSelection) {
|
|
22
|
+
document.getSelection = () => {
|
|
23
|
+
return options
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
document.execCommand = vi.fn()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const txt = 'test'
|
|
30
|
+
|
|
31
|
+
describe('copyToClipboard', () => {
|
|
32
|
+
it('copies text to the clipboard', () => {
|
|
33
|
+
mockDocument({
|
|
34
|
+
rangeCount: 0,
|
|
35
|
+
getRangeAt: () => null,
|
|
36
|
+
removeAllRanges: () => null,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const writeTextMock = vi.fn()
|
|
40
|
+
|
|
41
|
+
Object.defineProperty(navigator, 'clipboard', {
|
|
42
|
+
value: {
|
|
43
|
+
writeText: writeTextMock,
|
|
44
|
+
},
|
|
45
|
+
writable: true,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
copyToClipboard(txt)
|
|
49
|
+
|
|
50
|
+
expect(writeTextMock).toHaveBeenCalledWith(txt)
|
|
51
|
+
expect(document.execCommand).not.toHaveBeenCalled()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('copies text to the clipboard when text is already selected', () => {
|
|
55
|
+
mockDocument({
|
|
56
|
+
rangeCount: 2,
|
|
57
|
+
getRangeAt: (index: number) => ['a', 'b'][index],
|
|
58
|
+
removeAllRanges: () => null,
|
|
59
|
+
addRange: () => null,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const writeTextMock = vi.fn()
|
|
63
|
+
|
|
64
|
+
Object.defineProperty(navigator, 'clipboard', {
|
|
65
|
+
value: {
|
|
66
|
+
writeText: writeTextMock,
|
|
67
|
+
},
|
|
68
|
+
writable: true,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
copyToClipboard(txt)
|
|
72
|
+
|
|
73
|
+
expect(writeTextMock).toHaveBeenCalledWith(txt)
|
|
74
|
+
expect(document.execCommand).not.toHaveBeenCalled()
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('copies text to the clipboard when text is already selected and navigator.clipboard is unavailable', () => {
|
|
78
|
+
mockDocument({
|
|
79
|
+
rangeCount: 2,
|
|
80
|
+
getRangeAt: (index: number) => ['a', 'b'][index],
|
|
81
|
+
removeAllRanges: () => null,
|
|
82
|
+
addRange: () => null,
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
Object.defineProperty(navigator, 'clipboard', {
|
|
86
|
+
value: null,
|
|
87
|
+
writable: true,
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
copyToClipboard(txt)
|
|
91
|
+
|
|
92
|
+
expect(document.execCommand).toHaveBeenCalledWith('copy')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('does not copies text when getSelection is unavailable', () => {
|
|
96
|
+
document.getSelection = vi.fn(() => null)
|
|
97
|
+
|
|
98
|
+
expect(copyToClipboard(txt)).toBeUndefined()
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
afterEach(() => {
|
|
102
|
+
vi.clearAllMocks()
|
|
103
|
+
})
|
|
104
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { BufferSource } from './types'
|
|
2
|
+
|
|
3
|
+
/** Download a file */
|
|
4
|
+
export function downloadFile(
|
|
5
|
+
content: BufferSource | Blob | string,
|
|
6
|
+
filename: string,
|
|
7
|
+
type: string,
|
|
8
|
+
utf8Bom = false, // UTF-8 header for Excel files
|
|
9
|
+
): void {
|
|
10
|
+
/**
|
|
11
|
+
* \ufeff = UTF-8 encoding
|
|
12
|
+
* @see https://stackoverflow.com/a/18925211
|
|
13
|
+
*/
|
|
14
|
+
const blobContent: BlobPart[] = utf8Bom ? ['\ufeff', content] : [content]
|
|
15
|
+
// https://stackoverflow.com/questions/69552023/after-update-typescript-3-7-2-to-latest-typescript-4-4-4-error-ts2339
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
|
+
const nav = window.navigator as any
|
|
18
|
+
|
|
19
|
+
const blob = new Blob(blobContent, { type })
|
|
20
|
+
|
|
21
|
+
if (nav.msSaveOrOpenBlob) {
|
|
22
|
+
nav.msSaveOrOpenBlob(blob, filename)
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
const link = document.createElement('a')
|
|
26
|
+
|
|
27
|
+
link.target = '_blank'
|
|
28
|
+
link.style.display = 'none'
|
|
29
|
+
link.rel = 'noopener noreferrer'
|
|
30
|
+
link.href = window.URL.createObjectURL(blob)
|
|
31
|
+
link.download = filename
|
|
32
|
+
document.body.appendChild(link)
|
|
33
|
+
link.click()
|
|
34
|
+
document.body.removeChild(link)
|
|
35
|
+
window.URL.revokeObjectURL(link.href)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { downloadFile } from '../'
|
|
4
|
+
|
|
5
|
+
describe('downloadFile', () => {
|
|
6
|
+
// https://stackoverflow.com/questions/69552023/after-update-typescript-3-7-2-to-latest-typescript-4-4-4-error-ts2339
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- mock IE navigator
|
|
8
|
+
const nav = window.navigator as any
|
|
9
|
+
const mockCreateObjectURL = vi.fn(() => 'mockObjectURL')
|
|
10
|
+
const mockRevokeObjectURL = vi.fn()
|
|
11
|
+
|
|
12
|
+
const file = new File(['test'], 'attestation.txt', {
|
|
13
|
+
type: 'text/plain',
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
global.URL.createObjectURL = mockCreateObjectURL
|
|
18
|
+
global.URL.revokeObjectURL = mockRevokeObjectURL
|
|
19
|
+
})
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
vi.resetAllMocks()
|
|
22
|
+
document.body.innerHTML = ''
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('download a file', () => {
|
|
26
|
+
mockCreateObjectURL.mockReturnValue('http://exemple.com/')
|
|
27
|
+
downloadFile(file, file.name, file.type)
|
|
28
|
+
expect(mockCreateObjectURL).toHaveBeenCalledWith(
|
|
29
|
+
new Blob([file], { type: file.type }),
|
|
30
|
+
)
|
|
31
|
+
expect(mockRevokeObjectURL).toHaveBeenCalledWith('http://exemple.com/')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('download a file with utf8Bom', () => {
|
|
35
|
+
mockCreateObjectURL.mockReturnValue('http://exemple.com/')
|
|
36
|
+
downloadFile(file, file.name, file.type, true)
|
|
37
|
+
expect(mockCreateObjectURL).toHaveBeenCalled()
|
|
38
|
+
expect(mockRevokeObjectURL).toHaveBeenCalledWith('http://exemple.com/')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('download a file with content type BufferSource', () => {
|
|
42
|
+
const buffer = new ArrayBuffer(8)
|
|
43
|
+
mockCreateObjectURL.mockReturnValue('http://exemple.com/')
|
|
44
|
+
downloadFile(buffer, file.name, file.type)
|
|
45
|
+
expect(mockCreateObjectURL).toHaveBeenCalledWith(
|
|
46
|
+
new Blob([buffer], { type: file.type }),
|
|
47
|
+
)
|
|
48
|
+
expect(mockRevokeObjectURL).toHaveBeenCalledWith('http://exemple.com/')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('download a file with content type String', () => {
|
|
52
|
+
mockCreateObjectURL.mockReturnValue('http://exemple.com/')
|
|
53
|
+
downloadFile('test', file.name, file.type)
|
|
54
|
+
expect(mockCreateObjectURL).toHaveBeenCalledWith(
|
|
55
|
+
new Blob(['test'], { type: file.type }),
|
|
56
|
+
)
|
|
57
|
+
expect(mockRevokeObjectURL).toHaveBeenCalledWith('http://exemple.com/')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('download a file on Internet Explorer', () => {
|
|
61
|
+
const mockSaveOrOpenBlob = vi.fn()
|
|
62
|
+
nav.msSaveOrOpenBlob = mockSaveOrOpenBlob
|
|
63
|
+
downloadFile(file, file.name, file.type)
|
|
64
|
+
expect(mockSaveOrOpenBlob).toHaveBeenCalledWith(
|
|
65
|
+
new Blob([file], { type: file.type }),
|
|
66
|
+
file.name,
|
|
67
|
+
)
|
|
68
|
+
})
|
|
69
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type BufferSource = ArrayBufferView | ArrayBuffer
|