Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • josselin.bardet/ngx-dsfr-components
  • sebastien.lepage/ngx-dsfr-components
2 results
Show changes
Commits on Source (109)
Showing
with 648 additions and 197 deletions
image: node:18.18-alpine
image: node:22-alpine
variables:
NPM_GLOBAL_CACHE: '.cache/.npm'
PAGES_URL: '$CI_PAGES_URL/$CI_COMMIT_REF_SLUG'
NPM_GLOBAL_CACHE: ".cache/.npm"
PAGES_URL: "$CI_PAGES_URL/$CI_COMMIT_REF_SLUG"
FTP_TARGET: "$CI_COMMIT_REF_SLUG"
stages:
- debug
- install
- build
- deploy
- e2e
- verify
- pages
- publish
.e2e_rules: &e2e_rules
- if: $E2E != null
.get_mr_info: &get_mr_info
- |
# Récupérer les informations de la merge request
if [ -n "$CI_MERGE_REQUEST_IID" ]; then
echo "Variable MERGE_REQUEST_IID disponible. Récupération des informations depuis la merge request."
MR_INFO=$(curl --silent --header "PRIVATE-TOKEN: $PROJECT_ACCESS_TOKEN" "$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID")
else
echo "Variable MERGE_REQUEST_IID non disponible. Récupération des informations de la première MR associée à la branche."
MERGE_REQUESTS=$(curl --silent --header "PRIVATE-TOKEN: $PROJECT_ACCESS_TOKEN" --get --data-urlencode "source_branch=$CI_COMMIT_REF_NAME" "$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests")
if [ ! -z "$MERGE_REQUESTS" -a "$MERGE_REQUESTS" != " " -a "$MERGE_REQUESTS" != "[]" ]; then
MR_INFO=$(echo "$MERGE_REQUESTS" | jq -r .[0])
fi
fi
echo $MR_INFO
.fail_if_no_mr_info: &fail_if_no_mr
- |
if [ -z ${MR_INFO+x} ]; then
echo "Erreur > pas de merge request associée à la branche $CI_COMMIT_REF_NAME"
exit 1
fi
.exit_if_no_mr_info: &exit_if_no_mr
- |
if [ -z ${MR_INFO+x} ]; then
echo "Pas de merge request associée à la branche $CI_COMMIT_REF_NAME"
exit 0
fi
install:
stage: install
cache:
key: '$CI_COMMIT_REF_SLUG'
key: "$CI_COMMIT_REF_SLUG"
paths:
- node_modules/
policy: pull-push
before_script:
- npm config set cache $NPM_GLOBAL_CACHE
script:
- npm -v
- npm install
when: always
build_lib:
stage: build
cache:
key: '$CI_COMMIT_REF_SLUG'
key: "$CI_COMMIT_REF_SLUG"
paths:
- node_modules/
policy: pull
......@@ -46,7 +82,7 @@ build_lib:
lint:
stage: build
cache:
key: '$CI_COMMIT_REF_SLUG'
key: "$CI_COMMIT_REF_SLUG"
paths:
- node_modules/
policy: pull
......@@ -61,7 +97,7 @@ lint:
test:
stage: build
cache:
key: '$CI_COMMIT_REF_SLUG'
key: "$CI_COMMIT_REF_SLUG"
paths:
- node_modules/
policy: pull
......@@ -76,7 +112,7 @@ test:
compile_stories:
stage: build
cache:
key: '$CI_COMMIT_REF_SLUG'
key: "$CI_COMMIT_REF_SLUG"
paths:
- node_modules/
policy: pull
......@@ -91,7 +127,7 @@ compile_stories:
build_storybook:
stage: build
cache:
key: '$CI_COMMIT_REF_SLUG'
key: "$CI_COMMIT_REF_SLUG"
paths:
- node_modules/
policy: pull
......@@ -110,23 +146,24 @@ build_storybook:
- if: $DEPLOY != null
- if: $PUB != null
- if: $FTP != null
- *e2e_rules
deploy_storybook:
deploy_env:
stage: deploy
script:
- echo "This job configures an environment."
environment:
name: storybook/$CI_COMMIT_REF_SLUG
url: $PAGES_URL
on_stop: remove_storybook
on_stop: remove_env
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $DEPLOY != null
remove_storybook:
remove_env:
stage: deploy
cache:
key: 'sp-storybook'
key: "sp-storybook"
paths:
- public
script:
......@@ -144,7 +181,7 @@ remove_storybook:
pages:
stage: pages
cache:
key: 'sp-storybook'
key: "sp-storybook"
paths:
- public
script:
......@@ -158,6 +195,139 @@ pages:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $DEPLOY != null
e2e_deploy:
stage: e2e
before_script:
- apk add --no-cache openssh
- apk add --no-cache lftp
script:
- lftp -c "set sftp:auto-confirm yes; set ftp:ssl-allow true; set ssl:verify-certificate no; open sftp://$E2E_FTP_HOSTNAME; user $E2E_FTP_USERNAME $E2E_FTP_PASSWORD; mirror -Rev storybook-static/ /var/www/html/$FTP_TARGET; quit"
dependencies:
- build_storybook
rules:
- *e2e_rules
e2e_run:
stage: e2e
image: mcr.microsoft.com/playwright:v1.49.0-noble
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- node_modules/
policy: pull
script:
- npm config set cache $NPM_GLOBAL_CACHE
- export CI=true URL=http://$E2E_FTP_HOSTNAME/$FTP_TARGET
- npx playwright test
needs:
- job: e2e_deploy
artifacts: false
rules:
- *e2e_rules
e2e_run_failure:
stage: e2e
before_script:
- apk add --no-cache curl
- apk add --no-cache jq
script:
# Récupérer les métadonnées de la MR
- *get_mr_info
- *fail_if_no_mr
# Récupérer l'iid de la MR
- MR_IID=$(echo "$MR_INFO" | jq -r .iid)
# Récupérer le titre de la MR
- MR_TITLE=$(echo "$MR_INFO" | jq -r .title)
# Mettre à jour le statut de la MR
- |
# Vérifier si le titre contient déjà "Draft:"
if echo "$MR_TITLE" | grep -Eq '^(Draft:|WIP:)'; then
echo "La merge request $MR_IID est déjà en statut DRAFT. Aucune action nécessaire."
else
# Ajouter "Draft:" au début du titre
NEW_TITLE="Draft: $MR_TITLE"
# Mettre à jour la merge request avec le nouveau titre
curl --silent --request PUT \
--header "PRIVATE-TOKEN: $PROJECT_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data "{\"title\": \"$NEW_TITLE\"}" \
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$MR_IID"
echo "La merge request $MR_IID a été repassée en statut DRAFT."
fi
# Mettre à jour les labels
- >-
curl
--request PUT
--header "PRIVATE-TOKEN: $PROJECT_ACCESS_TOKEN"
--header "Content-Type: application/json"
--data '{"remove_labels": "e2e_passed", "add_labels": "e2e_failed"}'
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$MR_IID"
needs:
- job: e2e_run
artifacts: false
rules:
- *e2e_rules
when: on_failure
e2e_run_success:
stage: e2e
before_script:
- apk add --no-cache curl
- apk add --no-cache jq
script:
- *get_mr_info
- *fail_if_no_mr
- MR_IID=$(echo "$MR_INFO" | jq -r .iid)
- >-
curl
--request PUT
--header "PRIVATE-TOKEN: $PROJECT_ACCESS_TOKEN"
--header "Content-Type: application/json"
--data '{"remove_labels": "e2e_failed", "add_labels": "e2e_passed"}'
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$MR_IID"
needs:
- job: e2e_run
artifacts: false
rules:
- *e2e_rules
when: on_success
mr_status:
stage: verify
before_script:
- apk add --no-cache curl
- apk add --no-cache jq
script:
- *get_mr_info
- *exit_if_no_mr
- |
MR_IID=$(echo "$MR_INFO" | jq -r .iid)
echo "MR_IID= $MR_IID"
MR_LABELS=$(echo "$MR_INFO" | jq -r .labels)
echo "MR_LABELS= $MR_LABELS"
MR_DRAFT_STATUS=$(echo "$MR_INFO" | jq -r .draft)
echo "MR_DRAFT_STATUS= $MR_DRAFT_STATUS"
if [ "$MR_DRAFT_STATUS" = "false" ] && [ "$MR_LABELS" != "${MR_LABELS%"e2e_passed"*}" ]; then
MR_TITLE=$(echo "$MR_INFO" | jq -r .title)
echo "MR_TITLE= $MR_TITLE"
ESCAPED_TITLE=$(printf '%s' "$MR_TITLE" | sed 's/\"/\\\"/g')
echo "ESCAPED_TITLE= $ESCAPED_TITLE"
curl --fail-with-body --silent --show-error --request PUT \
--header "PRIVATE-TOKEN: $PROJECT_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data "{\"title\":\"Draft: $ESCAPED_TITLE\", \"remove_labels\":\"e2e_passed\"}" \
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$MR_IID"
echo "La merge request $MR_IID a été repassée en statut DRAFT suite au dernier push."
else
echo "La merge request $MR_IID ne nécessite pas un retour en draft."
fi
dependencies: []
rules:
- if: $CI_PIPELINE_SOURCE == "push"
publish_npm:
stage: publish
script:
......@@ -171,15 +341,12 @@ publish_npm:
publish_storybook:
stage: publish
script:
- apk add lftp
- lftp --version
- apk add --no-cache lftp
- lftp -c "open $FTP_HOSTNAME; user $FTP_USERNAME $FTP_PASSWORD; mirror -Rev storybook-static/ ngx-dsfr/$FTP_TARGET; quit"
dependencies:
- build_storybook
variables:
FTP_TARGET: '$CI_COMMIT_REF_SLUG'
rules:
- if: $PUB != null
- if: $FTP != null
variables:
FTP_TARGET: '$FTP'
FTP_TARGET: "$FTP"
(N'hésitez pas à supprimer les paragraphes dont vous n'avez pas besoin)
### Décrire le bug
Une description précise du bug avec captures d'écrans ou Stackblitz si possible.
### Comportement attendu
Une description claire et concise de ce qui devrait se produire.
### Configuration et système utilisé
- **Version du ngx-dsfr-components : **
- **Appareil (mobile, tablette, desktop) : **
- **Système d’exploitation : **
- **Navigateur (et version si possible) : **
### Informations complémentaires
Ajouter toute autre information concernant le problème.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M19.3034 5.33716C17.9344 4.71103 16.4805 4.2547 14.9629 4C14.7719 4.32899 14.5596 4.77471 14.411 5.12492C12.7969 4.89144 11.1944 4.89144 9.60255 5.12492C9.45397 4.77471 9.2311 4.32899 9.05068 4C7.52251 4.2547 6.06861 4.71103 4.70915 5.33716C1.96053 9.39111 1.21766 13.3495 1.5891 17.2549C3.41443 18.5815 5.17612 19.388 6.90701 19.9187C7.33151 19.3456 7.71356 18.73 8.04255 18.0827C7.41641 17.8492 6.82211 17.5627 6.24904 17.2231C6.39762 17.117 6.5462 17.0003 6.68416 16.8835C10.1438 18.4648 13.8911 18.4648 17.3082 16.8835C17.4568 17.0003 17.5948 17.117 17.7434 17.2231C17.1703 17.5627 16.576 17.8492 15.9499 18.0827C16.2789 18.73 16.6609 19.3456 17.0854 19.9187C18.8152 19.388 20.5875 18.5815 22.4033 17.2549C22.8596 12.7341 21.6806 8.80747 19.3034 5.33716ZM8.5201 14.8459C7.48007 14.8459 6.63107 13.9014 6.63107 12.7447C6.63107 11.5879 7.45884 10.6434 8.5201 10.6434C9.57071 10.6434 10.4303 11.5879 10.4091 12.7447C10.4091 13.9014 9.57071 14.8459 8.5201 14.8459ZM15.4936 14.8459C14.4535 14.8459 13.6034 13.9014 13.6034 12.7447C13.6034 11.5879 14.4323 10.6434 15.4936 10.6434C16.5442 10.6434 17.4038 11.5879 17.3825 12.7447C17.3825 13.9014 16.5548 14.8459 15.4936 14.8459Z"></path></svg>
......@@ -10,8 +10,8 @@ const config: StorybookConfig = {
'@storybook/addon-links',
'@storybook/addon-actions',
'@storybook/addon-a11y',
'@storybook/addon-themes',
//'storybook-addon-angular-router', not compatible with SB 8 for now
'@storybook/addon-themes',
],
babel: async (options: any) => ({
...options,
......
......@@ -8,6 +8,15 @@
<script src="dsfr.module.js"></script>
<style>
/** 1.12 Pour contrecarrer storybook 8 qui force la couleur des titres (composant notice) */
h2,
h3,
h4,
h5,
h6 {
color: inherit;
}
/* commons.stories.css */
.text-sm {
font-size: small;
......
......@@ -44,7 +44,7 @@ const preview = {
parameters: {
controls: {},
docs: {
//toc: true,
toc: { disable: false, headingSelector: 'h2, h3' },
story: {
inline: true,
},
......@@ -80,6 +80,8 @@ const preview = {
},
},
},
tags: ['autodocs'],
};
export default preview;
......
......@@ -4,12 +4,51 @@ import { version } from '../projects/ngx-dsfr-components/package.json';
export default create({
base: 'light',
brandTitle: `
<div>
<img src="img/marianne.svg" height="16px"/>
</div>
<div style="font-size:1.1rem">ngx-dsfr</div>
<div style="font-size:0.8rem;color:grey">${version}</div>
<style>
.link-brand {
height: 100%;
margin: -3px -4px;
padding: 2px 3px;
border: 1px solid transparent;
border-radius: 3px;
color: inherit;
text-decoration: none;
}
.link-discord {
display: inline-block;
//border: 1px solid #d6e0e7;
color: grey;
margin-top: 8px;
}
.link-discord:hover {
color: rgb(2, 156, 253);
background: rgba(2, 156, 253, 0.14);
}
.link-discord:focus {
box-shadow: rgb(2, 156, 253) 0px 0px 0px 1px inset;
outline: none;
}
.link-discord img {
padding-top: 3px;
vertical-align: sub;
}
.link-discord span {
margin-left: 4px;
margin-right: 8px;
}
</style>
<a href=".." target="_blank" class="link-brand css-mq3wcb">
<div>
<img src="img/marianne.svg" height="16px"/>
</div>
<div style="font-size:1.1rem">ngx-dsfr</div>
<div style="font-size:0.8rem;color:grey">${version}</div>
</a>
<a title="Ouvrir discord - nouvelle fenêtre" class="link-brand link-discord" href="https://discord.gg/8zxkX57aEf" target="_blank">
<img src="img/discord-fill.svg" height="18px"/>
<span> Discord </span>
</a>
`,
brandUrl: '/',
brandUrl: null,
// brandImage: "img/marianne.svg",
});
......@@ -12,9 +12,13 @@
"javascript.preferences.importModuleSpecifier": "relative",
"typescript.tsdk": "node_modules\\typescript\\lib",
"typescript.preferences.autoImportFileExcludePatterns": ["out-tsc"],
"npm.exclude": "**/dist/**",
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[yaml]": {
"editor.defaultFormatter": "redhat.vscode-yaml"
}
}
# @edugouvfr/ngx-dsfr - Changelog
## 1.12.2
## 1.13.3 (2025-03-06)
- fix(accordeon): `expandedChange` est parfois désynchronisé des événements `conceal` et `disclose` du script DSFR
- fix(input): suppression du pattern ajouté en cas de type `number`
- fix(notice): suppression de styles impactant les balises de titre
- fix(table): le nombre total d'éléments n'est pas recalculé dynamiquement en l'absence de `pagination` ou `serverSide`
## 1.13.2 (2025-02-13)
- fix(chore): suppression dépendance obsolète `uuidv4`
- fix(breadcrumb): espaces indésirables sur le dernier élément (page courante)
- fix(date): la propriété `disabled` n'est pas transmise au fieldset
- fix(header): application de `active` sur l'item parent de menu déroulant en l'absence de `routerLink`
## 1.13.1 (2025-01-31)
- fix(badge): mauvaise prise en compte de `size` et `noIcon` en l'absence de `severity`
- fix(checkbox): visuel incohérent dans le cas d'une checkbox `disabled` à l'état `indeterminate`
- fix(date): ajout d'une précision sur le champ concerné pour l'erreur de valeur non numérique
- fix(date): support de l'attribut natif `readonly`
- fix(sidemenu): régression le menu ne s'ouvre plus en mode mobile
## 1.13.0 (2025-01-22)
- break(chore): version minimale requise angular 17
- feat(chore): montée de version DSFR 1.13.0
- feat(chore/translate): ajout du pipe `DsfrI18n` pour améliorer la gestion de la traduction
- feat(button): les buttons sont du type `button` par défaut
- feat(buttons-group): ajout de la propriété `groupMarkup` permettant de remplacer le conteneur `<ul>` par une `<div>`
- feat(card/tile): possibilité de transformer le markup du lien de download en `button`
- feat(checkbox/toggle): ajout de la propriété `labelSrOnly`
- feat(header/table): possibilité de donner une valeur initiale à l'input de recherche
- feat(header/table): exposition du `placeholder` de l'input de recherche, et du `title` du bouton associé
- feat(input/select): ajout du support de la sévérité `warning` pour les messages liés aux inputs
- feat(modal): ajout de l'input `concealingBackdrop` permettant de désactiver la fermeture au click sur le fond
- feat(modal): ajout du slot `modalFooterTemplate` permettant de fournir les boutons d'action via un template
- feat(radio-rich): les pictogrammes sont optionnels
- feat(table): ajout de `formatFunction` dans la définition des colonnes
- fix(alert): suppression de la balise `<p>` résiduelle si le titre n'est pas présent en version `small`
- fix(button): correction de la redondance de la restitution du button avec une liseuse d'écran et de sa documentation
- fix(consent): exposition de l'identifiant de la modale `modalId`
- fix(consent-banner): correction du style du lien
- fix(consent-manager): mise à jour d'une finalité selon l'état de ses sous-finalités
- fix(footer): permettre d'utiliser `DsfrNavigation` pour le lien du logo
- fix(header): suppression de l'attribut `aria-current` sur le lien du logo avec `routerLink`
- fix(pagination): retire `<button>` sur les points de suspension
- fix(select): gestion dynamique de l'existence de groupes
- fix(table): correction de l'attribut `aria-sort` sur le tri des colonnes
## 1.12.9 (2025-01-03)
- fix(download-group): attribut `assessFile` manquant sur l'interface `DsfrDownload`
- fix(sidemenu): le titre de rubrique est placé dans une balise `<p>` à la place de `<div>`
- fix(sidemenu): ajout de `aria-labelledby` pour référencer le titre depuis le menu si il est renseigné (accessibilité)
- fix(table): possibilité d'aligner les boutons d'actions du header à droite
- fix(table): mauvaise gestion de l'affichage de la valeur 'N/A' dans la table
## 1.12.8 (2024-12-13)
- fix(date pattern): prise en compte des dates avec une timezone négative
- fix(header): régression 1.12.7 pas de fermeture du méga-menu au clic sur les liens
- fix(header): correction du markup des liens sur les menus déroulants
- fix(tile): ajout des breakpoints MD/LG au mode horizontal des tuiles
- fix(tile): pouvoir customiser le pictogramme en mode téléchargement
## 1.12.7 (2024-12-10)
- fix(header): régression 1.12.6, désactiver la navigation sur les entrées de menu en cas de `routerLink`
- fix(header): correction du titre en bleu
- fix(form-input): l'attribut natif `readonly` n'est pas répercuté depuis le composant `form-input`
- fix(card/tag/tile): correction sur l'utilisation du composant lien en interne (propriété `label` dynamique)
- fix(sidemenu): correction de `routerLink` pour les entrées de menu actives
- fix(table): en mode serveur, la page courante reste à 0 après plusieurs requêtes si l'une ne donne pas de résultats
## 1.12.6 (2024-11-22)
- fix(follow): correction de l'accessibilité des liens `follow-link`
- fix(header): correction de `routerLink` pour les entrées de menu actives
- fix(tag-groups/toggle-group/button-group): l'insertion des li n'est pas gérée dynamiquement
- fix(tile): en version SM, correction des pictogrammes qui étaient tronqués
- fix(tile): éléments `<p>` résiduels si les éléments optionnels ne sont pas renseignés
## 1.12.5 (2024-11-08)
- fix(tile): régression sur la zone cliquable
## 1.12.4 (2024-10-30)
- fix(card): la propriété `routePath` n'est pas prise en compte pour le lien
- fix(card): élément `<div>` vide résiduel si tableau de tags|badges non null mais vide
- fix(download): réhabilitation du composant `download` pour représenter le lien de téléchargement
- fix(fieldset): correction de l'`aria-labelledby`
- fix(input): ne pas activer l'attribut spellCheck par défaut
- fix(modal): événement `conceal` inattendu au chargement du composant (même fermé)
- fix(select/radio): correction accessibilité, l'attribut required ne prend pas de valeur
- fix(table): correction de l'application de la sélection au changement de page en mode serveur
- fix(table): ajout d'une méthode `setSelectedRows` pour permettre la sélection programmatique des lignes après initialisation
- fix(table): désactiver les éléments de pagination à l'affichage d'une table sans résultat
## 1.12.3 (2024-10-10)
- fix(form-tel): régression > la validation ne s'exécute plus si un pattern est défini
## 1.12.2 (2024-10-08)
- fix(card/tile): régression de la taille des titres cliquables
- fix(button): possibilité de définir l'attribut aria-pressed (accessibilité)
- fix(buttons-group): effet de bord de la surcharge du CSS pour la modal sur les dsfr-btns-group
- fix(form): affichage du conteneur de messages même vide et conditionnement de aria-describedby (accessibilité)
- fix(translate): correction documentation, le label de la langue ne doit pas contenir le code
- chore(i18n): permettre d'étendre les bundles d'internationalisation (notamment pour les extensions)
## 1.11.9 (2024-10-03)
......@@ -27,7 +131,7 @@
- fix(stepper): impossible de changer le niveau de titre
## 1.12 (2024-09-13)
## 1.12.0 (2024-09-13)
- feat: montée de version DSFR 1.12.1
- feat(alert): ajout du slot `message`
......@@ -45,7 +149,7 @@
- fix(backtotop): l'ancrage fonctionne correctement
- fix(card/tile): `linkTarget` n'est pas utilisé dans le téléchargement
- fix(content): structure html modifiée pour respecter le DSFR
- fix(footer): changement d'ordres des liens institutionnels & ajout de l'attribut `title`
- fix(footer): changement d'ordre des liens institutionnels et ajout de l'attribut `title`
- fix(form): bloc message affiché seulement si il y a un message de renseigné
- fix(range): correctif accessibilité, le labelled-by de l'input ne pointait vers aucun label
- fix(tag): correctif accessibilité, en mode cliquable, le `disabled` est correctement propagé aux enfants du tag
......@@ -55,8 +159,8 @@
- fix(consent-banner): le texte du lien de la bannière de consentement se met bien à jour au changement de langage
- fix(footer): ajout de traductions manquante
- fix(header): slot `headerTools` et `headerToolsMobile` documentation manquante et régression mode mobile
- fix(modal): en mode mobile, les boutons prennent toute la largeur
- fix(stepper): ajout de traductions manquante
- fix(modal): en mode mobile, les boutons d'actions ne prennent pas toute la largeur
- fix(stepper): ajout de traductions manquantes
## 1.11.6 (2024-08-21)
......@@ -556,10 +660,10 @@
- break: `tag` suppression input `noRedirect` (cf. guide de migration)
- break: `tile` suppression input `noRedirect` (cf. guide de migration)
- break: `tile` suppression input `href` au profit de `link`
- break: `toogle` l'input `defaultChecked` a été supprimé
- break: `toogle` l'input `showCheckedLabel` a été renommé `showCheckedHint`
- break: `toogle` l'input `dataLabelChecked` a été renommé `checkedHintLabel`
- break: `toogle` l'input `dataLabelUnchecked` a été renommé `uncheckedHintLabel`
- break: `toggle` l'input `defaultChecked` a été supprimé
- break: `toggle` l'input `showCheckedLabel` a été renommé `showCheckedHint`
- break: `toggle` l'input `dataLabelChecked` a été renommé `checkedHintLabel`
- break: `toggle` l'input `dataLabelUnchecked` a été renommé `uncheckedHintLabel`
- break: `toggles-group` la propriété `hideSeparators` est renommée `showSeparators` (et la fonction est inversée)
- feat: `accordion` un identifiant unique est généré pour l'index.
- feat: `buttons-group` ajout de la propriété `iconPosition`
......@@ -570,7 +674,7 @@
- feat: `link` ajout de l'input `routerLinkExtras` pour pouvoir paramétrer la route
- feat: `sideMenu` Les `controlId` de chaque nœud sont générés automatiquement par défaut
- feat: `tile` ajout propriété `route` permettant d'inhiber le href natif au profit d'une navigation programmatique
- feat: `toogle` le slot `label` est à présent le slot par défaut (plus besoin de sélecteur)
- feat: `toggle` le slot `label` est à présent le slot par défaut (plus besoin de sélecteur)
- feat: `toggle` par défaut, l'`id` est généré automatiquement
- fix: `button` suppression de la valeur par défaut sur l'input `iconPosition` (cf. guide de migration)
- fix: `card` detail displays `detailBottom` value instead of `detail` value
......
......@@ -32,7 +32,7 @@ Ce fichier présente les conventions de formalisme et les règles de conception
### Pré-requis
- node : >=18.16.1
- angular >= 16
- angular >= 17
### Démarrer
......
......@@ -9,15 +9,13 @@ de la Direction du Numérique pour l'Éducation (DNE).
## Dépendances
Notre package s'appuie actuellement sur la [version 1.12.1 du DSFR](https://github.com/GouvernementFR/dsfr/releases/tag/v1.12.1).
Notre package s'appuie actuellement sur la [version 1.13.0 du DSFR](https://github.com/GouvernementFR/dsfr/releases/tag/v1.13.0).
Nous développons avec Angular 17 mais notre bibliothèque est compatible Angular 17 à 19.
Nous développons avec Angular 16 mais notre bibliothèque est compatible Angular 17 et 18.
## Composants additionnels
## Licence et droit d'utilisation
Le contenu de ce projet est placé sous licence [EUPL 1.2](https://eupl.eu/1.2/fr/).
Pour rappel, l'usage du DSFR est réservé aux sites Internet de l'État, se référer aux cf. [CGU du DSFR](https://github.com/GouvernementFR/dsfr/blob/main/doc/legal/cgu.md).
Nous proposons des [composants d'extension](https://foad.phm.education.gouv.fr/edugouvfr/ngx-dsfr-ext/) aux éléments officiels du DSFR : multiselect, autocomplete, etc.
Ce package [ngx-dsfr-ext](https://foad.phm.education.gouv.fr/edugouvfr/ngx-dsfr-ext/) a pour but de couvrir les besoins fonctionnels qui ne sont pas encore couverts par le DSFR. Elle a été construite en cohérence avec les composants DSFR existants, dans le respect des fondamentaux techniques et des principes de design du DSFR.
## Installation
......@@ -34,15 +32,15 @@ Pour rappel, l'usage du DSFR est réservé aux sites Internet de l'État, se ré
```json
"styles": [
"projects/app-demo/src/styles.scss",
"./node_modules/@gouvfr/dsfr/dist/dsfr/dsfr.css",
"./node_modules/@gouvfr/dsfr/dist/utility/utility.css",
"./node_modules/@gouvfr/dsfr/dist/dsfr/dsfr.main.min.css",
"./node_modules/@gouvfr/dsfr/dist/utility/utility.main.min.css",
],
"scripts": [
"./node_modules/@gouvfr/dsfr/dist/dsfr/dsfr.module.js"
]
```
- Ajouter les meta tag du dsfr dans le fichier `index.html`.
- Ajouter les meta tags du dsfr dans le fichier `index.html`.
_Exemple: [Prise en main](https://www.systeme-de-design.gouv.fr/utilisation-et-organisation/developpeurs/prise-en-main)_
......@@ -69,11 +67,19 @@ _Exemple: [Prise en main](https://www.systeme-de-design.gouv.fr/utilisation-et-o
<dsfr-button>...</dsfr-button>
```
## Documentation
- Idem pour vos composants `standalone`. Les nouveaux composants créés dans la librairie sont également créés en standalone.
Se référer au Storybook de la version que vous utilisez :
```typescript
@Component({
selector: 'app-component',
imports: [DsfrModalModule, DsfrButtonModule],
})
export class MonComposant implements OnInit {}
```
https://foad.phm.education.gouv.fr/edugouvfr/ngx-dsfr/
## Documentation
Se référer au Storybook de la version que vous utilisez : https://foad.phm.education.gouv.fr/edugouvfr/ngx-dsfr/
Icônes de la documentation :
......@@ -90,49 +96,101 @@ https://gitlab.mim-libre.fr/men/transverse/dsmen/ngx-dsfr-components.git
## Fonctionnement
### Gestion des liens
### Configuration globale
Certains composants permettent de naviguer vers d'autres pages web ou d'autres parties de votre site/application web.
Il est possible de définir certains paramètres de configuration de manière globale en utilisant la méthode statique
`forRoot` du module `DsfrConfigModule`.
Pour obtenir un hyperlien "classique" avec une URL (absolue ou relative) utilisez la propriété `link` du composant.
Par exemple, pour indiquer de manière globale le chemin sur lequel est exposé le répertoire des pictogrammes
illustratifs DSFR, procédez comme suit :
Si vous souhaitez plutôt exécuter une navigation Angular (via le Router) voici comment procéder :
```typescript
@NgModule({
imports: [
DsfrConfigModule.forRoot({
artworkDirPath: 'custom/path/to/artwork',
}),
]
})
```
#### Navigation gérée programmatiquement via `router#navigate`
Et pour les exporter au niveau du build dans `angular.json` :
- Renseigner la propriété `route` en input du composant
- Mettez-vous à l'écoute de l'output `routeSelect`
- L'événement `(routeSelect)` est émis avec la valeur de `route` et l'événement natif n'est pas propagé
- Vous pouvez alors exécuter `router#navigate`
```json
"assets": [
{
"input": "node_modules/@gouvfr/dsfr/dist/artwork",
"glob": "**/*",
"output": "custom/path/to/artwork"
}
],
```
### Internationalisation
Les libellés des composants sont nativement disponibles en français (par défaut) et en anglais.
Le changement de langue des composants est géré à travers l'usage du composant `dsfr-translate` et le pipe `dsfrI18n`.
Il est possible d'étendre les traductions ou d'ajouter des nouvelles langues,
[Cf. documentation du composant Translate](/docs/components-translate--docs)
### Gestion des icônes
Une propriété `icon` est une classe du DSFR, exemple `fr-icon-home-4-fill`.
Les icônes du DSFR sont incluses dans le projet et donc disponibles automatiquement. La liste des icônes du DSFR est disponible sur leur documentation [DSFR Icônes](https://www.systeme-de-design.gouv.fr/fondamentaux/icone/)
En cas de nécessité d'ajouter d'autres icônes, il est conseillé de se limiter aux icônes de la librairie [Remix Icons](https://remixicon.com/)
#### Navigation gérée via une directive `routerLink` (si supporté par le composant)
Il n'existe pas pour l'instant de manière d'ajouter toute la librairie d'icônes en une fois, pour conserver le style `DSFR` appliqué aux icpones il est nécessaire de les ajouter une à une dans votre projet de la façon suivante :
- Renseigner la propriété `routePath`
- ajouter le fichier `.svg` correspondant à l'icône (fonction `Copy SVG` sur RemixIcon)
- création de la classe `CSS` correspondante, en commençant le nom de classe par `fr-icon-*`.
ex:
```CSS
.fr-icon-play::before,
.fr-icon-play::after {
-webkit-mask-image: url('../shared/icons/carousel/play.svg');
mask-image: url('../shared/icons/carousel/play.svg');
}
```
### Gestion des liens
Certains composants permettent de naviguer vers d'autres pages web ou d'autres parties de votre site/application web.
Pour obtenir un hyperlien "classique" (navigation externe) avec une URL (absolue ou relative) utilisez la propriété `link` du composant.
Si vous souhaitez plutôt exécuter une navigation Angular (via le Router) voici comment procéder :
#### Navigation gérée via la directive `routerLink` (si supporté par le composant)
- Renseigner la propriété `routePath` au niveau du composant (ou `routerLink` au niveau du modèle)
- Cela va intégrer une directive `routerLink` au niveau du lien de navigation interne au composant
- `routerLinkExtras` vous permet d'ajouter les paramètres optionnels tels les queryParams
- Ou de laisser la gestion de façon externe au composant en utilisant l'attribut `route`.
Dans ce cas, .
### Gestion des icônes
#### Navigation gérée programmatiquement via `router#navigate`
Une propriété `icon` est :
- Renseigner la propriété `route` en input du composant
- Mettez-vous à l'écoute de l'output `routeSelect`
- L'événement `(routeSelect)` est émis avec la valeur de `route` et l'événement natif n'est pas propagé
- Vous pouvez alors exécuter `router#navigate`
- soit une classe du DSFR, exemple `fr-icon-home-4-fill` ;
- soit un identifiant du référentiel d'icônes Remix, exemple : `ri-home-4-fill`.
[Cf. documentation du composant Lien](/docs/link--docs)
### Gestion des libellés
### Propriétés et slots
Dans de nombreux composants, il vous est possible de fournir certains contenus de deux façons :
- soit via un point d'injection (ng-content) prévu à cet effet :
- soit via une propriété (input) :
- Ex.: `<dsfr-link link="#bottom">Bas de page</dsfr-link>`
Ex.: `<dsfr-link link="#bottom" label="Bas de page"></dsfr-link>`
- soit via une propriété (input) :
- soit via un point d'injection (ng-content) prévu à cet effet :
- Ex.: `<dsfr-link link="#bottom" label="Bas de page"></dsfr-link>`
Ex.: `<dsfr-link link="#bottom">Bas de page</dsfr-link>`
🔥 Si les deux (slot+input) sont renseignés c'est l'input qui sera utilisé en priorité
🔥 Si les deux (slot + input) sont renseignés c'est l'input qui sera utilisé en priorité.
🔥 L'input ne supporte pas le format HTML, si vous avez besoin de formater votre contenu en utilisant des balises HTML
vous devez obligatoirement passer par le slot
......@@ -140,57 +198,39 @@ vous devez obligatoirement passer par le slot
Exemples de composants supportant cette possibilité :
- forms : checkbox, input, select, toggle, radio(legend)
- accordion(content, heading)
- table(caption)
- tile(description)
- accordion (content, heading)
- table (caption)
- tile (description)
### Internationalisation
## Licence et droit d'utilisation
Les libellés des composants sont nativement disponibles en français (par défaut) et en anglais.
Le changement de langue des composants est géré à travers l'usage du composant `dsfr-translate`.
Le contenu de ce projet est placé sous licence [EUPL 1.2](https://eupl.eu/1.2/fr/).
### Gestion de la configuration globale
Pour rappel, l'usage du DSFR est réservé aux sites Internet de l'État, se référer aux cf. [CGU du DSFR](https://github.com/GouvernementFR/dsfr/blob/main/doc/legal/cgu.md).
Il est possible de définir certains paramètres de configuration de manière globale en utilisant la méthode statique
`forRoot` du module `DsfrConfigModule`.
## Demander une évolution ou signaler une anomalie
Par exemple, pour indiquer de manière globale le chemin sur lequel est exposé le répertoire des pictogrammes
illustratifs DSFR, procédez comme suit :
#### Équipes internes Éduc. Nat.
```typescript
@NgModule({
imports: [
DsfrConfigModule.forRoot({
artworkDirPath: 'custom/path/to/artwork',
}),
]
})
```
- Passer par les [issues du Gitlab Forge](https://gitlab.forge.education.gouv.fr/dsmen/components/ngx-dsfr-components/-/issues)
#### Autres ministères ou délégations de service public
- Passer par les [issues Gitlab Mim-Libre](https://gitlab.mim-libre.fr/publication-codes-men/transverse/ngx-dsfr-components/-/issues)
## Contact
- Liste de diffusion de l'équipe de développement [dsmen-core@ldiff.forge.education.gouv.fr](mailto:dsmen-core@ldiff.forge.education.gouv.fr)
- [Canal Mattermost](https://mattermost.forge.education.gouv.fr/tyforge/channels/ngx-dsfr-components) (interne forge Éduc. Nat.)
- [Discord](https://discord.gg/8zxkX57aEf)
## Feuille de route
- T4 2024 : ajout de fonctionnalités sur les tableaux complexes
- T1 2025 : possibilité d'un menu déroulant dans le header (utilisateur connecté)
- 2025 : implémentation de la V2 du DSFR
## Demander une évolution ou signaler une anomalie
### Équipes internes Éduc. Nat.
- Passer par les [issues du Gitlab Forge](https://gitlab.forge.education.gouv.fr/dsmen/components/ngx-dsfr-components/-/issues)
### Autres ministères ou délégations de service public
- Passer par les [issues Gitlab Mim-Libre](https://gitlab.mim-libre.fr/publication-codes-men/transverse/ngx-dsfr-components/-/issues)
### Composants additionnels
Nous proposons une implémentation de composants au "style DSFR" (multiselect, autocomplete etc.) pour ceux qui ne sont pas encore proposées par la librairie du DSFR sur notre librairie d'extensions [ngx-dsfr-ext](https://foad.phm.education.gouv.fr/edugouvfr/ngx-dsfr-ext/)
- T2 2025 :
- ajout de fonctionnalités sur les tableaux complexes et le composant `consent`
- améliorations chore et migration angular 18
- ajout et améliorations des composants d'extension (`file-upload`, `datepicker`, `dropdown-menu` ...)
- fin 2025 : implémentation de la V2 du DSFR
## Contribuer
......
......@@ -31,8 +31,8 @@
"configDir": ".storybook",
"port": 6006,
"styles": [
"node_modules/@gouvfr/dsfr/dist/dsfr/dsfr.css",
"node_modules/@gouvfr/dsfr/dist/utility/utility.css"
"node_modules/@gouvfr/dsfr/dist/dsfr/dsfr.main.min.css",
"node_modules/@gouvfr/dsfr/dist/utility/utility.main.min.css"
],
"compodoc": true,
"compodocArgs": [
......@@ -43,6 +43,7 @@
"--silent",
"--minimal",
"--disablePrivate",
"--disableProtected",
"--disableInternal",
"--disableLifeCycleHooks"
]
......@@ -53,8 +54,8 @@
"options": {
"browserTarget": "ngx-dsfr-components:build",
"styles": [
"node_modules/@gouvfr/dsfr/dist/dsfr/dsfr.css",
"node_modules/@gouvfr/dsfr/dist/utility/utility.css"
"node_modules/@gouvfr/dsfr/dist/dsfr/dsfr.main.min.css",
"node_modules/@gouvfr/dsfr/dist/utility/utility.main.min.css"
],
"compodoc": true,
"compodocArgs": [
......@@ -65,6 +66,7 @@
"--silent",
"--minimal",
"--disablePrivate",
"--disableProtected",
"--disableInternal",
"--disableLifeCycleHooks"
]
......@@ -87,8 +89,8 @@
"options": {
"tsConfig": "tsconfig.json",
"styles": [
"node_modules/@gouvfr/dsfr/dist/dsfr/dsfr.css",
"node_modules/@gouvfr/dsfr/dist/utility/utility.css"
"node_modules/@gouvfr/dsfr/dist/dsfr/dsfr.main.min.css",
"node_modules/@gouvfr/dsfr/dist/utility/utility.main.min.css"
],
"scripts": ["node_modules/@gouvfr/dsfr/dist/dsfr/dsfr.module.min.js"]
}
......
......@@ -22,76 +22,78 @@
"test:jest": "cross-env LANG=fr-FR TZ=Europe/Paris jest --runInBand",
"test:storybook": "test-storybook",
"test:playwright": "npx playwright test",
"test:playwright:ui": "npx playwright test --ui",
"check:dsfr": "node --experimental-vm-modules index --analyze=dsfr --dir=projects/ngx-dsfr-components/src/lib",
"check:dsfr:verbose": "npm run check:dsfr -- --verbose",
"check:circular": "madge --circular --extensions ts projects/ngx-dsfr-components/src/lib"
},
"private": true,
"dependencies": {
"@angular/animations": "16.2.12",
"@angular/cli": "16.2.14",
"@angular/common": "16.2.12",
"@angular/compiler": "16.2.12",
"@angular/core": "16.2.12",
"@angular/forms": "16.2.12",
"@angular/platform-browser": "16.2.12",
"@angular/platform-browser-dynamic": "16.2.12",
"@angular/router": "16.2.12",
"@gouvfr/dsfr": "1.12.1",
"@angular/animations": "17.3.12",
"@angular/cli": "17.3.11",
"@angular/common": "17.3.12",
"@angular/compiler": "17.3.12",
"@angular/core": "17.3.12",
"@angular/forms": "17.3.12",
"@angular/platform-browser": "17.3.12",
"@angular/platform-browser-dynamic": "17.3.12",
"@angular/router": "17.3.12",
"@gouvfr/dsfr": "1.13.0",
"rxjs": "7.6.0",
"tslib": "2.4.0",
"uuidv4": "6.2.13",
"zone.js": "0.13.3"
"uuid": "11.0.5",
"zone.js": "0.14.10"
},
"devDependencies": {
"@angular-devkit/build-angular": "16.2.15",
"@angular-eslint/builder": "16.3.1",
"@angular-eslint/eslint-plugin": "16.3.1",
"@angular-eslint/eslint-plugin-template": "16.3.1",
"@angular-eslint/schematics": "16.3.1",
"@angular-eslint/template-parser": "16.3.1",
"@angular/compiler-cli": "16.2.12",
"@angular-devkit/build-angular": "17.3.11",
"@angular-eslint/builder": "17.5.3",
"@angular-eslint/eslint-plugin": "17.5.3",
"@angular-eslint/eslint-plugin-template": "17.5.3",
"@angular-eslint/schematics": "17.5.3",
"@angular-eslint/template-parser": "17.5.3",
"@angular/compiler-cli": "17.3.12",
"@babel/core": "7.24.4",
"@compodoc/compodoc": "1.1.25",
"@compodoc/compodoc": "1.1.26",
"@jest/globals": "^29.7.0",
"@playwright/test": "1.43.1",
"@storybook/addon-a11y": "8.2.9",
"@storybook/addon-essentials": "8.2.9",
"@storybook/addon-interactions": "8.2.9",
"@storybook/addon-links": "8.2.9",
"@storybook/addon-themes": "8.2.9",
"@storybook/angular": "8.2.9",
"@storybook/blocks": "8.2.9",
"@storybook/manager-api": "8.2.9",
"@storybook/test-runner": "^0.19.1",
"@storybook/theming": "8.2.9",
"@playwright/test": "1.49.0",
"@storybook/addon-a11y": "8.5.0",
"@storybook/addon-essentials": "8.5.0",
"@storybook/addon-interactions": "8.5.0",
"@storybook/addon-links": "8.5.0",
"@storybook/addon-themes": "8.5.0",
"@storybook/angular": "8.5.0",
"@storybook/blocks": "8.5.0",
"@storybook/manager-api": "8.5.0",
"@storybook/test-runner": "^0.21.0",
"@storybook/theming": "8.5.0",
"@types/jest": "^29.5.12",
"@types/node": "^20.11.20",
"@typescript-eslint/eslint-plugin": "5.62.0",
"@typescript-eslint/parser": "5.62.0",
"@typescript-eslint/eslint-plugin": "7.2.0",
"@typescript-eslint/parser": "7.2.0",
"args-parser": "1.3.0",
"babel-loader": "9.1.3",
"chromatic": "6.17.2",
"copyfiles": "2.4.1",
"cross-env": "7.0.3",
"eslint": "8.55.0",
"eslint": "8.57.0",
"eslint-config-prettier": "8.7.0",
"eslint-plugin-prettier": "5.1.3",
"eslint-plugin-storybook": "0.8.0",
"eslint-plugin-storybook": "0.11.2",
"http-server": "^14.1.1",
"jest": "29.7.0",
"jest-preset-angular": "14.2.2",
"jest-preset-angular": "14.4.2",
"madge": "7.0.0",
"ng-packagr": "16.2.3",
"ng-packagr": "17.3.0",
"prettier": "3.2.5",
"raw-loader": "4.0.2",
"readline": "1.3.0",
"rimraf": "3.0.2",
"storybook": "8.2.9",
"storybook": "8.5.0",
"strip-comments": "^2.0.1",
"ts-jest": "29.2.5",
"typescript": "5.1.6"
"typescript": "5.4.5"
},
"engines": {
"node": ">=18.16.1"
"node": ">=18.19.1"
}
}
......@@ -3,11 +3,14 @@ import { defineConfig } from '@playwright/test';
export default defineConfig({
// Run all tests in parallel.
fullyParallel: true,
fullyParallel: process.env.CI ? false : true,
// Glob patterns or regular expressions that match test files.
testMatch: '**/*.e2e.ts',
use: {
baseURL: 'http://localhost:6006/',
headless: true,
baseURL: process.env.URL || 'http://localhost:6006/',
locale: 'fr-FR',
timezoneId: 'Europe/Paris',
},
workers: process.env.CI ? 1 : undefined,
workers: process.env.CI ? 1 : '25%',
});
import { Meta, Markdown } from '@storybook/blocks';
import { Markdown, Meta } from '@storybook/blocks';
import changelog from '../../../CHANGELOG.md';
<Meta title="Introduction/Changelog" />
<Markdown>{changelog}</Markdown>
<style>
{`
.css-1wizkyk{
display:none;
`}
</style>
import { Meta, Markdown } from '@storybook/blocks';
import { Markdown, Meta } from '@storybook/blocks';
import migration from '../../../MIGRATION.md';
<Meta title="Introduction/Migration" />
<Markdown>{migration}</Markdown>
<style>
{`
.css-1wizkyk{
display:none;
`}
</style>
import { Markdown, Meta } from '@storybook/blocks';
import readme from '../../../README.md';
import { Meta, Markdown } from '@storybook/blocks';
<Meta title="Introduction/Readme" />
<Markdown>{readme}</Markdown>
<style>
{`
.css-q7khkg, .css-1wizkyk{
width: 13rem;
`}
{`
.css-qa4clq {
max-width: 1000px;
width: 75%;
`}
</style>
{
"name": "@edugouvfr/ngx-dsfr",
"version": "1.12.2",
"version": "1.13.3",
"bin": "./index.js",
"description": "NgxDsfr est un portage Angular des éléments d'interface du Système de Design de l'État Français (DSFR).",
"license": "EUPL-1.2",
......@@ -14,16 +14,16 @@
],
"homepage": "https://foad.phm.education.gouv.fr/edugouvfr/ngx-dsfr/",
"peerDependencies": {
"@angular/common": ">=16",
"@angular/core": ">=16",
"@angular/forms": ">=16",
"@angular/common": ">=17",
"@angular/core": ">=17",
"@angular/forms": ">=17",
"rxjs": "^6 || ^7",
"args-parser": ">=1.3.0"
"args-parser": ">=1.3.0",
"uuid": ">=8.3.0"
},
"dependencies": {
"@gouvfr/dsfr": "1.12.1",
"tslib": "^2.4.0",
"uuidv4": "^6.2.13"
"@gouvfr/dsfr": "1.13.0",
"tslib": "^2.4.0"
},
"engines": {
"node": ">=18.16.1"
......
<section class="fr-accordion">
<!-- Titre obligatoire -->
<edu-heading [level]="headingLevel" defaultLevel="H3" customClass="fr-accordion__title">
<ng-container *ngTemplateOutlet="accordionButton"></ng-container>
</edu-heading>
<div class="fr-collapse" [id]="accordionId">
<div #dsfrAccordion class="fr-collapse" [id]="accordionId">
<!-- La propriété content est prioritaire sur le slot -->
<ng-container *ngIf="content">{{ content }}</ng-container>
<ng-content *ngIf="!content"></ng-content>
<!-- TODO @deprecated à supprimer en v2 au profit du slot par défaut -->
<ng-content *ngIf="!content" select="[content]"></ng-content>
<!-- TODO @deprecated à supprimer en v2 au profit du slot par défaut -->
<ng-content *ngIf="!content" select="[body]"></ng-content>
@if (content) {
{{ content }}
} @else {
<ng-content></ng-content>
<!-- TODO @deprecated à supprimer en v2 au profit du slot par défaut -->
<ng-content select="[content]"></ng-content>
<!-- TODO @deprecated à supprimer en v2 au profit du slot par défaut -->
<ng-content select="[body]"></ng-content>
}
</div>
</section>
......@@ -20,12 +24,12 @@
class="fr-accordion__btn"
[attr.aria-expanded]="expanded"
[attr.aria-controls]="accordionId"
[attr.aria-label]="headingAriaLabel"
(watchAttr)="onExpandButtonAttrChange($event)"
watchAttrName="aria-expanded">
[attr.aria-label]="headingAriaLabel">
<!-- La propriété heading est prioritaire sur le slot de même nom -->
<ng-container *ngIf="heading">{{ heading }}</ng-container>
<!-- TODO à déprécier en v2 au profit d'un simple outerHTML car trop compliqué, il faut l'expliquer, pas intuitif -->
<ng-content *ngIf="!heading" select="[heading]"></ng-content>
@if (heading) {
{{ heading }}
} @else {
<ng-content select="[heading]"></ng-content>
}
</button>
</ng-template>
import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
import {
AfterViewInit,
Component,
ElementRef,
EventEmitter,
inject,
Input,
OnDestroy,
OnInit,
Output,
Renderer2,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import { DsfrHeadingLevel, DsfrHeadingLevelConst, newUniqueId } from '../../shared';
@Component({
......@@ -6,9 +19,9 @@ import { DsfrHeadingLevel, DsfrHeadingLevelConst, newUniqueId } from '../../shar
templateUrl: './accordion.component.html',
encapsulation: ViewEncapsulation.None,
})
export class DsfrAccordionComponent implements OnInit {
export class DsfrAccordionComponent implements OnInit, AfterViewInit, OnDestroy {
/**
* Contenu de l'accordéon, accepte du html, prioritaire sur le slot par défaut.
* Contenu de l'accordéon, texte simple. Prioritaire sur le slot par défaut.
*/
@Input() content: string;
......@@ -18,7 +31,7 @@ export class DsfrAccordionComponent implements OnInit {
@Input() expanded: boolean = false;
/**
* Le titre de l'accordéon est du texte simple.
* Le titre de l'accordéon est du texte simple. Utiliser le slot [heading] sinon.
*/
@Input() heading: string;
......@@ -44,23 +57,36 @@ export class DsfrAccordionComponent implements OnInit {
*/
@Output() expandedChange = new EventEmitter<boolean>();
/** @internal */
@ViewChild('dsfrAccordion') accordion: ElementRef;
/** @internal */
accordionId: string;
constructor() {}
private _unlisten: { (): void }[] = [];
private renderer2 = inject(Renderer2);
ngOnInit() {
this.accordionId = newUniqueId();
}
/** @internal */
onExpandButtonAttrChange(mutation: MutationRecord) {
if (mutation.attributeName === 'aria-expanded') {
const elem: HTMLElement = <HTMLElement>mutation.target;
if (String(this.expanded) !== elem.ariaExpanded) {
this.expanded = elem.ariaExpanded === 'true';
/**
* Ecouter les events scripts dsfr conceal/disclose sur l'accordéon
*/
ngAfterViewInit() {
this._unlisten.push(
this.renderer2.listen(this.accordion.nativeElement, 'dsfr.conceal', () => {
this.expanded = false;
this.expandedChange.emit(this.expanded);
}),
this.renderer2.listen(this.accordion.nativeElement, 'dsfr.disclose', () => {
this.expanded = true;
this.expandedChange.emit(this.expanded);
}
}
}),
);
}
ngOnDestroy() {
this._unlisten.forEach((unlistenFunc) => unlistenFunc());
}
}