├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .github
└── workflows
│ └── ci.yaml
├── .gitignore
├── .prettierignore
├── .prettierrc.json
├── .storybook
├── DocsContainer.js
├── customTheme.js
├── main.js
├── manager-head.html
├── preview.js
└── static
│ ├── CNAME
│ ├── logo.png
│ └── repo-card.png
├── CONTRIBUTING.md
├── LICENSE
├── README.fr.md
├── README.md
├── package.json
├── patches
└── @gouvfr+dsfr+1.13.2.patch
├── publiccode.yml
├── scripts
├── README.md
├── build
│ ├── build.ts
│ ├── cssToTs
│ │ ├── breakpoints.ts
│ │ ├── classNames.ts
│ │ ├── colorDecisionAndCorrespondingOptions.ts
│ │ ├── colorDecisions.ts
│ │ ├── colorOptions.ts
│ │ ├── cssToTs.ts
│ │ ├── cssVariable.ts
│ │ ├── index.ts
│ │ ├── spacing.ts
│ │ └── typography.ts
│ ├── icons.ts
│ ├── index.ts
│ ├── main.css
│ ├── marianne-index.css
│ ├── parseCss.ts
│ └── patchCssForMui.ts
├── link-in-external-project.ts
├── link-in-integration-apps.ts
├── picto-builder.ts
├── tools
│ ├── cssColorRegexp.ts
│ ├── multiReplace.ts
│ ├── threeDigitColorHexToSixDigitsColorHex.ts
│ └── tsc.ts
└── tsconfig.json
├── src
├── Accordion.tsx
├── AgentConnectButton.tsx
├── Alert.tsx
├── Badge.tsx
├── Breadcrumb.tsx
├── Button.tsx
├── ButtonsGroup.tsx
├── CallOut.tsx
├── Card.tsx
├── Chart
│ ├── BarChart.tsx
│ ├── BarLineChart.tsx
│ ├── GaugeChart.tsx
│ ├── LineChart.tsx
│ ├── MultiLineChart.tsx
│ ├── PieChart.tsx
│ ├── RadarChart.tsx
│ ├── ScatterChart.tsx
│ └── chartWrapper.tsx
├── Checkbox.tsx
├── Display
│ ├── Artwork
│ │ ├── Artwork.tsx
│ │ ├── ArtworkGov.tsx
│ │ ├── ArtworkWhiteLabel
│ │ │ ├── ArtworkWhiteLabel.tsx
│ │ │ ├── DisplayArtworkWhiteLabelProvider.tsx
│ │ │ ├── context.ts
│ │ │ └── useArtworkWhiteLabel.ts
│ │ └── index.ts
│ ├── Display.tsx
│ └── index.ts
├── Download.tsx
├── Follow.tsx
├── Footer.tsx
├── FranceConnectButton.tsx
├── Header
│ ├── Header.tsx
│ ├── index.ts
│ └── useIsHeaderMenuModalOpen.ts
├── Highlight.tsx
├── Input.tsx
├── LanguageSelect.tsx
├── MainNavigation
│ ├── MainNavigation.tsx
│ ├── MegaMenu.tsx
│ ├── Menu.tsx
│ └── index.ts
├── Modal
│ ├── Modal.tsx
│ ├── index.ts
│ └── useIsModalOpen.ts
├── MonCompteProButton.tsx
├── Notice.tsx
├── Pagination.tsx
├── ProConnectButton.tsx
├── Quote.tsx
├── RadioButtons.tsx
├── Range.tsx
├── SearchBar
│ ├── SearchBar.tsx
│ ├── SearchButton.tsx
│ └── index.tsx
├── SegmentedControl.tsx
├── Select.tsx
├── SelectNext.tsx
├── SideMenu.tsx
├── SkipLinks.tsx
├── Stepper.tsx
├── Summary.tsx
├── Table.tsx
├── Tabs.tsx
├── Tag.tsx
├── TagsGroup.tsx
├── Tile.tsx
├── ToggleSwitch.tsx
├── ToggleSwitchGroup.tsx
├── Tooltip.tsx
├── Upload.tsx
├── assets
│ ├── agentconnect-btn-alternatif-hover.svg
│ ├── agentconnect-btn-alternatif.svg
│ ├── agentconnect-btn-principal-hover.svg
│ ├── agentconnect-btn-principal.svg
│ ├── agentconnect.css
│ ├── blank-favicon.svg
│ ├── dsfr_plus_icons.css
│ ├── dsfr_plus_icons.scss
│ ├── language-select.css
│ ├── moncomptepro.css
│ ├── proconnect-btn.css
│ └── search-bar.css
├── bin
│ ├── README.md
│ ├── copy-dsfr-to-public.ts
│ ├── only-include-used-icons.ts
│ ├── react-dsfr.ts
│ ├── readPublicDirPath.ts
│ ├── tools
│ │ ├── crawl.ts
│ │ ├── crawlSync.ts
│ │ ├── fnv1aHashToHex.ts
│ │ ├── fs.existsAsync.ts
│ │ ├── getAbsoluteAndInOsFormatPath.ts
│ │ ├── getProjectRoot.ts
│ │ ├── modifyHtmlHrefs.ts
│ │ └── transformCodebase.ts
│ └── tsconfig.json
├── blocks
│ └── PasswordInput.tsx
├── consentManagement
│ ├── ConsentBannerAndConsentManagement
│ │ ├── ConsentBanner.tsx
│ │ ├── ConsentBannerAndConsentManagement.tsx
│ │ ├── ConsentManagement.tsx
│ │ ├── index.ts
│ │ └── translation.tsx
│ ├── Placeholder.tsx
│ ├── createConsentManagement.ts
│ ├── index.ts
│ ├── processConsentChanges.ts
│ ├── types.ts
│ └── useConsent.ts
├── eulerianAnalytics.ts
├── fr
│ ├── breakpoints.ts
│ ├── colors.ts
│ ├── cx.ts
│ ├── index.ts
│ ├── shadows.ts
│ └── spacing.ts
├── global.d.ts
├── i18n.ts
├── link.tsx
├── mui
│ ├── index.ts
│ ├── mui.tsx
│ └── useIsGov.tsx
├── next-app-router
│ ├── DsfrHead.tsx
│ ├── DsfrProvider.tsx
│ ├── getHtmlAttributes.tsx
│ ├── index.ts
│ ├── server-only-index.ts
│ └── zz_internal
│ │ ├── defaultColorScheme.ts
│ │ └── fontUrlByFileBasename.ts
├── next-pagesdir.tsx
├── picto
│ ├── Accessibility.tsx
│ ├── Airport.tsx
│ ├── Application.tsx
│ ├── Archive.tsx
│ ├── ArmyTank.tsx
│ ├── Art.tsx
│ ├── Astronaut.tsx
│ ├── Audio.tsx
│ ├── Avatar.tsx
│ ├── Backpack.tsx
│ ├── Base.tsx
│ ├── Binders.tsx
│ ├── Book.tsx
│ ├── Calendar.tsx
│ ├── Catalog.tsx
│ ├── CityHall.tsx
│ ├── Coding.tsx
│ ├── Community.tsx
│ ├── Companie.tsx
│ ├── Compass.tsx
│ ├── Conclusion.tsx
│ ├── ConnectionLost.tsx
│ ├── Contract.tsx
│ ├── Culture.tsx
│ ├── DataVisualization.tsx
│ ├── Datalma.tsx
│ ├── DigitalArt.tsx
│ ├── Doctor.tsx
│ ├── Document.tsx
│ ├── DocumentAdd.tsx
│ ├── DocumentDownload.tsx
│ ├── DocumentSearch.tsx
│ ├── DrivingLicense.tsx
│ ├── DrivingLicenseNew.tsx
│ ├── EarOff.tsx
│ ├── Ecosystem.tsx
│ ├── Environment.tsx
│ ├── Error.tsx
│ ├── EyeOff.tsx
│ ├── Factory.tsx
│ ├── Firefighter.tsx
│ ├── FlowList.tsx
│ ├── FlowSettings.tsx
│ ├── Food.tsx
│ ├── Gendarmerie.tsx
│ ├── Grocery.tsx
│ ├── Health.tsx
│ ├── Hospital.tsx
│ ├── House.tsx
│ ├── HumanCooperation.tsx
│ ├── InProgress.tsx
│ ├── Information.tsx
│ ├── Innovation.tsx
│ ├── InternationalDrivingLicense.tsx
│ ├── InternationalDrivingLicenseNew.tsx
│ ├── Internet.tsx
│ ├── JusticeScales.tsx
│ ├── Language.tsx
│ ├── Leaf.tsx
│ ├── LocationFrance.tsx
│ ├── LocationOverseasFrance.tsx
│ ├── Luggage.tsx
│ ├── MainSend.tsx
│ ├── Map.tsx
│ ├── MapPin.tsx
│ ├── MedicalResearch.tsx
│ ├── MentalDisabilities.tsx
│ ├── Money.tsx
│ ├── Moon.tsx
│ ├── Mountain.tsx
│ ├── NationalIdentityCard.tsx
│ ├── NationalIdentityCardPassport.tsx
│ ├── NavyAnchor.tsx
│ ├── NavyBachi.tsx
│ ├── Notification.tsx
│ ├── NuclearPlant.tsx
│ ├── Paint.tsx
│ ├── Passport.tsx
│ ├── Pictures.tsx
│ ├── Podcast.tsx
│ ├── Police.tsx
│ ├── PressCard.tsx
│ ├── School.tsx
│ ├── Search.tsx
│ ├── SelfTraining.tsx
│ ├── SignDocument.tsx
│ ├── Smartphone.tsx
│ ├── Success.tsx
│ ├── Sun.tsx
│ ├── System.tsx
│ ├── TaxStamp.tsx
│ ├── TechnicalError.tsx
│ ├── TravelBack.tsx
│ ├── Tree.tsx
│ ├── Vaccine.tsx
│ ├── VehicleRegistrationDocument.tsx
│ ├── Video.tsx
│ ├── VideoGames.tsx
│ ├── Virus.tsx
│ ├── Warning.tsx
│ ├── Wheelchair.tsx
│ ├── index.d.ts
│ ├── index.ts
│ ├── pictogrammes-svg
│ │ ├── Accessibility.svg
│ │ ├── Airport.svg
│ │ ├── Application.svg
│ │ ├── Archive.svg
│ │ ├── ArmyTank.svg
│ │ ├── Art.svg
│ │ ├── Astronaut.svg
│ │ ├── Audio.svg
│ │ ├── Avatar.svg
│ │ ├── Backpack.svg
│ │ ├── Base.svg
│ │ ├── Binders.svg
│ │ ├── Book.svg
│ │ ├── Calendar.svg
│ │ ├── Catalog.svg
│ │ ├── CityHall.svg
│ │ ├── Coding.svg
│ │ ├── Community.svg
│ │ ├── Companie.svg
│ │ ├── Compass.svg
│ │ ├── Conclusion.svg
│ │ ├── ConnectionLost.svg
│ │ ├── Contract.svg
│ │ ├── Culture.svg
│ │ ├── DataVisualization.svg
│ │ ├── Datalma.svg
│ │ ├── DigitalArt.svg
│ │ ├── Doctor.svg
│ │ ├── Document.svg
│ │ ├── DocumentAdd.svg
│ │ ├── DocumentDownload.svg
│ │ ├── DocumentSearch.svg
│ │ ├── DrivingLicense.svg
│ │ ├── DrivingLicenseNew.svg
│ │ ├── EarOff.svg
│ │ ├── Ecosystem.svg
│ │ ├── Environment.svg
│ │ ├── Error.svg
│ │ ├── EyeOff.svg
│ │ ├── Factory.svg
│ │ ├── Firefighter.svg
│ │ ├── FlowList.svg
│ │ ├── FlowSettings.svg
│ │ ├── Food.svg
│ │ ├── Gendarmerie.svg
│ │ ├── Grocery.svg
│ │ ├── Health.svg
│ │ ├── Hospital.svg
│ │ ├── House.svg
│ │ ├── HumanCooperation.svg
│ │ ├── InProgress.svg
│ │ ├── Information.svg
│ │ ├── Innovation.svg
│ │ ├── InternationalDrivingLicense.svg
│ │ ├── InternationalDrivingLicenseNew.svg
│ │ ├── Internet.svg
│ │ ├── JusticeScales.svg
│ │ ├── Language.svg
│ │ ├── Leaf.svg
│ │ ├── LocationFrance.svg
│ │ ├── LocationOverseasFrance.svg
│ │ ├── Luggage.svg
│ │ ├── MainSend.svg
│ │ ├── Map.svg
│ │ ├── MapPin.svg
│ │ ├── MedicalResearch.svg
│ │ ├── MentalDisabilities.svg
│ │ ├── Money.svg
│ │ ├── Moon.svg
│ │ ├── Mountain.svg
│ │ ├── NationalIdentityCard.svg
│ │ ├── NationalIdentityCardPassport.svg
│ │ ├── NavyAnchor.svg
│ │ ├── NavyBachi.svg
│ │ ├── Notification.svg
│ │ ├── NuclearPlant.svg
│ │ ├── Paint.svg
│ │ ├── Passport.svg
│ │ ├── Pictures.svg
│ │ ├── Podcast.svg
│ │ ├── Police.svg
│ │ ├── PressCard.svg
│ │ ├── School.svg
│ │ ├── Search.svg
│ │ ├── SelfTraining.svg
│ │ ├── SignDocument.svg
│ │ ├── Smartphone.svg
│ │ ├── Success.svg
│ │ ├── Sun.svg
│ │ ├── System.svg
│ │ ├── TaxStamp.svg
│ │ ├── TechnicalError.svg
│ │ ├── TravelBack.svg
│ │ ├── Tree.svg
│ │ ├── Vaccine.svg
│ │ ├── VehicleRegistrationDocument.svg
│ │ ├── Video.svg
│ │ ├── VideoGames.svg
│ │ ├── Virus.svg
│ │ ├── Warning.svg
│ │ └── Wheelchair.svg
│ └── utils
│ │ └── IconWrapper.tsx
├── shared
│ └── Fieldset.tsx
├── spa.ts
├── start.ts
├── tools
│ ├── Deferred.ts
│ ├── JSX.ts
│ ├── StatefulObservable
│ │ ├── README.md
│ │ ├── StatefulObservable.ts
│ │ ├── hooks
│ │ │ ├── index.ts
│ │ │ ├── useObservable.ts
│ │ │ └── useRerenderOnChange.ts
│ │ └── index.ts
│ ├── UnpackProps.ts
│ ├── cx.ts
│ ├── deepAssign.ts
│ ├── deepCopy.ts
│ ├── generateValidHtmlId.ts
│ ├── getAssetUrl.ts
│ ├── getBaseFontSizePx.ts
│ ├── isBrowser.ts
│ ├── memoize.ts
│ ├── observeInputValue.ts
│ ├── partition.ts
│ ├── powerhooks
│ │ ├── useCallbackFactory.ts
│ │ ├── useConst.ts
│ │ ├── useConstCallback.ts
│ │ └── useNamedState.tsx
│ ├── signal.ts
│ ├── structuredCloneButFunctions.ts
│ ├── useAnalyticsId.ts
│ └── useWindowInnerSize.ts
├── tsconfig.json
├── tss.ts
├── useBreakpointsValues.ts
├── useBreakpointsValuesPx.ts
├── useColors.ts
├── useIsDark
│ ├── client.ts
│ ├── constants.ts
│ ├── index.ts
│ ├── scriptToRunAsap.ts
│ └── server.ts
└── zz_internal
│ └── brandTopAndHomeLinkProps.ts
├── stories
├── Accordion.stories.tsx
├── AgentConnectButton.stories.tsx
├── Alert.stories.tsx
├── Badge.stories.tsx
├── Breadcrumb.stories.tsx
├── Button.stories.tsx
├── ButtonsGroup.stories.tsx
├── CallOut.stories.tsx
├── Card.stories.tsx
├── Checkbox.stories.tsx
├── ColorHelper
│ ├── ColorDecisionCard.tsx
│ ├── ColorHelper.tsx
│ ├── CopyToClipboardButton.tsx
│ ├── Search.tsx
│ ├── index.ts
│ └── tools
│ │ └── useQueryParams.ts
├── ConsentManagement.stories.tsx
├── Display.stories.tsx
├── Download.stories.tsx
├── Follow.stories.tsx
├── Footer.stories.tsx
├── FranceConnectButton.stories.tsx
├── Header.stories.tsx
├── Highlight.stories.tsx
├── Input.stories.tsx
├── LanguageSelect.stories.tsx
├── MainNavigation.stories.tsx
├── Modal.stories.tsx
├── MonComptePro.stories.tsx
├── Notice.stories.tsx
├── Pagination.stories.tsx
├── Pictograms.stories.mdx
├── ProConnectButton.stories.tsx
├── Quote.stories.tsx
├── RadioButtons.stories.tsx
├── Range.stories.tsx
├── RotatingLogo.tsx
├── SearchBar.stories.tsx
├── SegmentedControl.stories.tsx
├── Select.stories.tsx
├── SelectNext.stories.tsx
├── SideMenu.stories.tsx
├── SkipLinks.stories.tsx
├── Stepper.stories.tsx
├── Summary.stories.tsx
├── Table.stories.tsx
├── Tabs.stories.tsx
├── Tag.stories.tsx
├── TagsGroup.stories.tsx
├── Tile.stories.tsx
├── ToggleSwitch.stories.tsx
├── ToggleSwitchGroup.stories.tsx
├── Tooltip.stories.tsx
├── Upload.stories.tsx
├── assets
│ ├── city-hall.svg
│ ├── logo-in.png
│ ├── logo-out.png
│ ├── placeholder.16x9.png
│ └── placeholder.9x16.png
├── blocks
│ ├── PasswordInput.stories.tsx
│ └── sectionName.ts
├── charts
│ ├── BarChart.stories.tsx
│ ├── BarLineChart.stories.tsx
│ ├── GaugeChart.stories.tsx
│ ├── LineChart.stories.tsx
│ ├── MultiLineChart.stories.tsx
│ ├── PieChart.stories.tsx
│ ├── RadarChart.stories.tsx
│ ├── ScatterChart.stories.tsx
│ └── sectionName.ts
├── colorHelper.stories.mdx
├── getStory.tsx
├── global.d.ts
├── intro.stories.mdx
├── picto
│ ├── Pictograms.tsx
│ └── Search.tsx
├── sectionName.ts
└── utils.css
├── test
├── integration
│ ├── README.md
│ ├── cra
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── public
│ │ │ ├── index.html
│ │ │ └── robots.txt
│ │ ├── src
│ │ │ ├── Home.tsx
│ │ │ ├── Mui.tsx
│ │ │ ├── MyDialog.tsx
│ │ │ ├── Picto.tsx
│ │ │ ├── index.tsx
│ │ │ ├── react-app-env.d.ts
│ │ │ └── router.ts
│ │ ├── tsconfig.json
│ │ └── yarn.lock
│ ├── next-appdir
│ │ ├── .eslintrc
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── app
│ │ │ ├── DefaultTrustedPolicy.tsx
│ │ │ ├── Follow.tsx
│ │ │ ├── Navigation.tsx
│ │ │ ├── StartDsfr.tsx
│ │ │ ├── consentManagement.tsx
│ │ │ ├── defaultColorScheme.ts
│ │ │ ├── dsfr-chart
│ │ │ │ └── page.tsx
│ │ │ ├── layout.tsx
│ │ │ ├── main.module.css
│ │ │ ├── mui
│ │ │ │ └── page.tsx
│ │ │ └── page.tsx
│ │ ├── middleware.ts
│ │ ├── next-env.d.ts
│ │ ├── next.config.js
│ │ ├── package.json
│ │ ├── public
│ │ │ ├── favicon.ico
│ │ │ └── vercel.svg
│ │ ├── tsconfig.json
│ │ ├── ui
│ │ │ ├── ClientComponent.tsx
│ │ │ ├── ClientFooterItem.tsx
│ │ │ └── ClientHeaderQuickAccessItem.tsx
│ │ └── yarn.lock
│ ├── next-pagesdir
│ │ ├── .eslintrc
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── next-env.d.ts
│ │ ├── next.config.js
│ │ ├── package.json
│ │ ├── pages
│ │ │ ├── _app.tsx
│ │ │ ├── _document.tsx
│ │ │ ├── index.tsx
│ │ │ └── mui.tsx
│ │ ├── tsconfig.json
│ │ └── yarn.lock
│ └── vite
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── public
│ │ └── .keep
│ │ ├── src
│ │ ├── Home.tsx
│ │ ├── Mui.tsx
│ │ ├── Picto.tsx
│ │ ├── consentManagement.tsx
│ │ ├── main.tsx
│ │ └── vite-env.d.ts
│ │ ├── tsconfig.json
│ │ ├── tsconfig.node.json
│ │ ├── vite.config.ts
│ │ └── yarn.lock
├── runtime
│ ├── README.md
│ ├── lib
│ │ ├── gdpr.test.ts
│ │ └── spacing.test.ts
│ └── scripts
│ │ ├── breakpoints
│ │ ├── generateBreakpointsTsCode.test.ts
│ │ └── parseBreakpointsValues.test.ts
│ │ ├── classNames
│ │ ├── generateClassNamesTsCode.test.ts
│ │ └── parseClassNames.test.ts
│ │ ├── colorDecisions
│ │ ├── generateGetColorDecisionsTsCode.test.ts
│ │ ├── parseColorDecisionName.test.ts
│ │ └── parseColorDecisions.test.ts
│ │ ├── colorOptions
│ │ ├── generateGetColorOptionsTsCode.test.ts
│ │ ├── getThemePath.test.ts
│ │ ├── parseColorOptionName.test.ts
│ │ └── parseColorOptions.test.ts
│ │ ├── cssVariable.test.ts
│ │ ├── icons
│ │ ├── generateIconsRawCssCode.test.ts
│ │ └── getPatchedRawCssCodeForCompatWithRemixIcon.test.ts
│ │ ├── spacing
│ │ ├── generateSpacingTsCode.test.ts
│ │ └── parseSpacing.test.ts
│ │ └── typography
│ │ ├── generateTypographyTsCode.test.ts
│ │ └── parseTypographyVariants.test.ts
└── types
│ ├── Checkbox.tsx
│ ├── README.md
│ ├── RadioButtons.tsx
│ ├── Select.tsx
│ ├── Tag.tsx
│ ├── Tooltip.tsx
│ ├── consentManagement.tsx
│ ├── i18n.ts
│ ├── libBinSync.ts
│ └── spacing.ts
├── tsproject.json
├── vitest.config.js
└── yarn.lock
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 | CHANGELOG.md
4 | .yarn_home/
5 | /test/integration/
6 | /storybook-static/
7 |
8 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "plugins": ["@typescript-eslint"],
5 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier", "plugin:storybook/recommended"],
6 | "rules": {
7 | "no-extra-boolean-cast": "off",
8 | "@typescript-eslint/explicit-module-boundary-types": "off",
9 | "@typescript-eslint/no-explicit-any": "off",
10 | "@typescript-eslint/no-namespace": "off",
11 | "@typescript-eslint/ban-types": "off",
12 | "@typescript-eslint/ban-ts-comment": "off",
13 | },
14 |
15 | };
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | test/integration/**/* linguist-documentation
2 | .eslintrc.js linguist-documentation
3 | src/bin/copy-dsfr-to-public.ts -linguist-detectable
4 | src/bin/only-include-used-icons.ts -linguist-detectable
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
39 | .vscode
40 | .idea
41 |
42 | .DS_Store
43 |
44 | /dist
45 | /.yarn_home
46 | /dsfr
47 | /src/fr/generatedFromCss
48 | /storybook-static
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | /dist/
3 | /CHANGELOG.md
4 | /.yarn_home/
5 | /test/integration/
6 | /dsfr/
7 | /src/fr/generatedFromCss/
8 | /storybook-static/
9 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "tabWidth": 4,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": false,
7 | "quoteProps": "preserve",
8 | "trailingComma": "none",
9 | "bracketSpacing": true,
10 | "arrowParens": "avoid"
11 | }
12 |
--------------------------------------------------------------------------------
/.storybook/customTheme.js:
--------------------------------------------------------------------------------
1 | import { create } from "@storybook/theming";
2 |
3 | const brandImage= "logo.png";
4 | const brandTitle= "@codegouvfr/react-dsfr";
5 | const brandUrl= "https://github.com/codegouvfr/react-dsfr";
6 | const fontBase= '"Marianne", arial, sans-serif';
7 | const fontCode= "monospace";
8 |
9 | export const darkTheme = create({
10 | "base": "dark",
11 | "appBg": "#1E1E1E",
12 | "appContentBg": "#161616",
13 | "barBg": "#161616",
14 | "colorSecondary": "#8585F6",
15 | "textColor": "#FFFFFF",
16 | brandImage,
17 | brandTitle,
18 | brandUrl,
19 | fontBase,
20 | fontCode
21 | });
22 |
23 | export const lightTheme = create({
24 | "base": "light",
25 | "appBg": "#F6F6F6",
26 | "appContentBg": "#FFFFFF",
27 | "barBg": "#FFFFFF",
28 | "colorSecondary": "#000091",
29 | "textColor": "#212121",
30 | brandImage,
31 | brandTitle,
32 | brandUrl,
33 | fontBase,
34 | fontCode
35 | });
36 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "stories": [
3 | "../stories/*.stories.mdx",
4 | "../stories/*.stories.@(ts|tsx)",
5 | "../stories/blocks/*.stories.@(ts|tsx)",
6 | "../stories/charts/*.stories.@(ts|tsx)",
7 | "../stories/picto/*.stories.@(ts|tsx)",
8 | ],
9 | "addons": [
10 | "@storybook/addon-links",
11 | "@storybook/addon-essentials",
12 | "storybook-dark-mode",
13 | "@storybook/addon-a11y"
14 | ],
15 | "core": {
16 | "builder": "webpack5"
17 | },
18 | "staticDirs": ["../dist", "./static"]
19 | };
20 |
--------------------------------------------------------------------------------
/.storybook/manager-head.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
react-dsfr components
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/.storybook/static/CNAME:
--------------------------------------------------------------------------------
1 | components.react-dsfr.codegouv.studio
--------------------------------------------------------------------------------
/.storybook/static/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codegouvfr/react-dsfr/320fd6280a0ffd343c28e051e1a1a1ec3db9af6f/.storybook/static/logo.png
--------------------------------------------------------------------------------
/.storybook/static/repo-card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codegouvfr/react-dsfr/320fd6280a0ffd343c28e051e1a1a1ec3db9af6f/.storybook/static/repo-card.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 GitHub user u/garronej
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/patches/@gouvfr+dsfr+1.13.2.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/@gouvfr/dsfr/dist/artwork/dark.svg b/node_modules/@gouvfr/dsfr/dist/artwork/dark.svg
2 | index e4d335c..6c53dce 100644
3 | --- a/node_modules/@gouvfr/dsfr/dist/artwork/dark.svg
4 | +++ b/node_modules/@gouvfr/dsfr/dist/artwork/dark.svg
5 | @@ -12,3 +12,4 @@
6 |
7 |
8 |
9 | +
10 | diff --git a/node_modules/@gouvfr/dsfr/dist/dsfr.module.js b/node_modules/@gouvfr/dsfr/dist/dsfr.module.js
11 | index 3566bc7..e58a87f 100644
12 | --- a/node_modules/@gouvfr/dsfr/dist/dsfr.module.js
13 | +++ b/node_modules/@gouvfr/dsfr/dist/dsfr.module.js
14 | @@ -6453,9 +6453,11 @@ class HeaderLinks extends api.core.Instance {
15 | case api.Modes.ANGULAR:
16 | case api.Modes.REACT:
17 | case api.Modes.VUE:
18 | + /*
19 | this.warn(`header__tools-links content is different from header__menu-links content.
20 | As you're using a dynamic framework, you should handle duplication of this content yourself, please refer to documentation:
21 | ${api.header.doc}`);
22 | + */
23 | break;
24 |
25 | default:
26 |
--------------------------------------------------------------------------------
/scripts/README.md:
--------------------------------------------------------------------------------
1 | In this directory are the scripts that are run at build time.
2 | We run them directly using `ts-node`.
3 |
--------------------------------------------------------------------------------
/scripts/build/cssToTs/colorDecisionAndCorrespondingOptions.ts:
--------------------------------------------------------------------------------
1 | import { parseColorOptions } from "./colorOptions";
2 | import { parseColorDecisions } from "./colorDecisions";
3 | import { exclude } from "tsafe/exclude";
4 | import type { ColorDecision } from "./colorDecisions";
5 | import type { ColorOption } from "./colorOptions";
6 | import { symToStr } from "tsafe/symToStr";
7 |
8 | export type ColorDecisionAndCorrespondingOption = Omit & {
9 | colorOption: ColorOption;
10 | };
11 |
12 | export function generateColorDecisionAndCorrespondingOptionsTsCode(rawCssCode: string): string {
13 | const colorOptions = parseColorOptions(rawCssCode);
14 |
15 | const colorDecisionAndCorrespondingOptions = parseColorDecisions(rawCssCode)
16 | .map(colorDecision => {
17 | const colorOption = colorOptions.find(
18 | colorOption =>
19 | colorOption.themePath.join(".") === colorDecision.optionThemePath.join(".")
20 | );
21 | return colorOption === undefined ? undefined : ([colorDecision, colorOption] as const);
22 | })
23 | .filter(exclude(undefined))
24 | .map(
25 | ([
26 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
27 | { optionThemePath, ...colorDecision },
28 | colorOption
29 | ]): ColorDecisionAndCorrespondingOption => ({
30 | ...colorDecision,
31 | colorOption
32 | })
33 | );
34 |
35 | return [
36 | ``,
37 | `export const ${symToStr({ colorDecisionAndCorrespondingOptions })}= ${JSON.stringify(
38 | colorDecisionAndCorrespondingOptions,
39 | null,
40 | 4
41 | )} as const;`,
42 | ``
43 | ].join("\n");
44 | }
45 |
--------------------------------------------------------------------------------
/scripts/build/cssToTs/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./cssToTs";
2 |
--------------------------------------------------------------------------------
/scripts/build/index.ts:
--------------------------------------------------------------------------------
1 | import "./build";
2 |
--------------------------------------------------------------------------------
/scripts/build/main.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 | @import url(./dsfr/utility/icons/icons.min.css);
3 | @import url(./dsfr/dsfr.css);
--------------------------------------------------------------------------------
/scripts/build/parseCss.ts:
--------------------------------------------------------------------------------
1 | import css from "css";
2 | import memoize from "memoizee";
3 |
4 | export type ParsedCss = css.Stylesheet;
5 |
6 | export const parseCss = memoize((rawCssCode: string): ParsedCss => css.parse(rawCssCode));
7 |
--------------------------------------------------------------------------------
/scripts/build/patchCssForMui.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */
2 | import * as css from "css";
3 |
4 | export function patchCssForMui(params: { rawDsfrCssCode: string }) {
5 | const { rawDsfrCssCode } = params;
6 |
7 | const parsedCss = css.parse(rawDsfrCssCode);
8 |
9 | (function callee(rules: any[], media: string): any[] {
10 | return [
11 | ...rules.filter(({ type }) => type === "rule").map(rule => [rule, media]),
12 | ...rules
13 | .filter(({ type }) => type === "media")
14 | .map(({ rules, media }) => callee(rules, media))
15 | .flat()
16 | ];
17 | })(parsedCss.stylesheet!.rules as any[], "root").forEach(
18 | ([rule, media]) =>
19 | (rule.selectors = rule.selectors.map((selector: string) => {
20 | const selectorNotMui = `${selector}:not([class^="Mui"])`;
21 |
22 | if (media === "(hover: hover) and (pointer: fine)") {
23 | if (
24 | selector === "button:not(:disabled):hover" ||
25 | selector === "button:not(:disabled):active"
26 | ) {
27 | return selectorNotMui;
28 | }
29 | }
30 |
31 | return selector;
32 | }))
33 | );
34 |
35 | return {
36 | "rawDsfrCssCodePatchedForMui": css.stringify(parsedCss, { "compress": false }),
37 | "rawDsfrCssCodePatchedForMuiMinified": css.stringify(parsedCss, { "compress": true })
38 | };
39 | }
40 |
--------------------------------------------------------------------------------
/scripts/tools/cssColorRegexp.ts:
--------------------------------------------------------------------------------
1 | export const cssColorRegexp =
2 | /(#(?:[0-9a-fA-F]{2}){2,4}$|(#[0-9a-fA-F]{3}$)|(rgb|hsl)a?\((-?\d+%?[,\s]+){2,3}\s*[\d.]+%?\)$|black$|silver$|gray$|whitesmoke$|maroon$|red$|purple$|fuchsia$|green$|lime$|olivedrab$|yellow$|navy$|blue$|teal$|aquamarine$|orange$|aliceblue$|antiquewhite$|aqua$|azure$|beige$|bisque$|blanchedalmond$|blueviolet$|brown$|burlywood$|cadetblue$|chartreuse$|chocolate$|coral$|cornflowerblue$|cornsilk$|crimson$|darkblue$|darkcyan$|darkgoldenrod$|darkgray$|darkgreen$|darkgrey$|darkkhaki$|darkmagenta$|darkolivegreen$|darkorange$|darkorchid$|darkred$|darksalmon$|darkseagreen$|darkslateblue$|darkslategray$|darkslategrey$|darkturquoise$|darkviolet$|deeppink$|deepskyblue$|dimgray$|dimgrey$|dodgerblue$|firebrick$|floralwhite$|forestgreen$|gainsboro$|ghostwhite$|goldenrod$|gold$|greenyellow$|grey$|honeydew$|hotpink$|indianred$|indigo$|ivory$|khaki$|lavenderblush$|lavender$|lawngreen$|lemonchiffon$|lightblue$|lightcoral$|lightcyan$|lightgoldenrodyellow$|lightgray$|lightgreen$|lightgrey$|lightpink$|lightsalmon$|lightseagreen$|lightskyblue$|lightslategray$|lightslategrey$|lightsteelblue$|lightyellow$|limegreen$|linen$|mediumaquamarine$|mediumblue$|mediumorchid$|mediumpurple$|mediumseagreen$|mediumslateblue$|mediumspringgreen$|mediumturquoise$|mediumvioletred$|midnightblue$|mintcream$|mistyrose$|moccasin$|navajowhite$|oldlace$|olive$|orangered$|orchid$|palegoldenrod$|palegreen$|paleturquoise$|palevioletred$|papayawhip$|peachpuff$|peru$|pink$|plum$|powderblue$|rosybrown$|royalblue$|saddlebrown$|salmon$|sandybrown$|seagreen$|seashell$|sienna$|skyblue$|slateblue$|slategray$|slategrey$|snow$|springgreen$|steelblue$|tan$|thistle$|tomato$|transparent$|turquoise$|violet$|wheat$|white$|yellowgreen$|rebeccapurple$)/i;
3 |
--------------------------------------------------------------------------------
/scripts/tools/multiReplace.ts:
--------------------------------------------------------------------------------
1 | import * as crypto from "crypto";
2 |
3 | export function multiReplace(params: { input: string; keyValues: Record }) {
4 | const { input, keyValues } = params;
5 |
6 | let output = input;
7 |
8 | const keyByHash = new Map();
9 |
10 | Object.keys(keyValues).forEach(key => {
11 | const hash = crypto.createHash("sha256").update(key).digest("hex");
12 |
13 | const newOutput = output.replace(new RegExp(key, "g"), hash);
14 |
15 | if (newOutput === output) {
16 | return;
17 | }
18 |
19 | keyByHash.set(hash, key);
20 |
21 | output = newOutput;
22 | });
23 |
24 | Array.from(keyByHash.entries()).forEach(
25 | ([hash, key]) => (output = output.replace(new RegExp(hash, "g"), keyValues[key]))
26 | );
27 |
28 | return output;
29 | }
30 |
--------------------------------------------------------------------------------
/scripts/tools/threeDigitColorHexToSixDigitsColorHex.ts:
--------------------------------------------------------------------------------
1 | export function threeDigitColorHexToSixDigitsColorHex(threeDigitColorHex: string) {
2 | let v = parseInt(threeDigitColorHex.substring(1), 16); // in rrggbb
3 | // nybble colors - fix to hex colors
4 | // 0x00000rgb -> 0x000r0g0b
5 | // 0x000r0g0b | 0x00r0g0b0 -> 0x00rrggbb
6 | const w = ((v & 0xf00) << 8) | ((v & 0x0f0) << 4) | (v & 0x00f);
7 | v = w | (w << 4);
8 | return `#${v.toString(16)}`;
9 | }
10 |
--------------------------------------------------------------------------------
/scripts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsproject.json",
3 | "compilerOptions": {
4 | "module": "CommonJS",
5 | "target": "ES2020",
6 | "esModuleInterop": true,
7 | "lib": ["DOM", "ES2021"],
8 | "rootDir": "."
9 | },
10 | "references": [{ "path": "../src/bin" }]
11 | }
12 |
--------------------------------------------------------------------------------
/src/Badge.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo, forwardRef, type ReactNode, type CSSProperties } from "react";
2 | import { symToStr } from "tsafe/symToStr";
3 | import { assert } from "tsafe/assert";
4 | import type { Equals } from "tsafe";
5 | import { fr } from "./fr";
6 | import { cx } from "./tools/cx";
7 | import type { AlertProps } from "./Alert";
8 | import { useAnalyticsId } from "./tools/useAnalyticsId";
9 |
10 | export type BadgeProps = {
11 | id?: string;
12 | className?: string;
13 | style?: CSSProperties;
14 | severity?: AlertProps.Severity | "new";
15 | small?: boolean;
16 | noIcon?: boolean;
17 | /** Default: "p" */
18 | as?: "p" | "span";
19 | children: NonNullable;
20 | };
21 |
22 | /** @see */
23 | export const Badge = memo(
24 | forwardRef((props, ref) => {
25 | const {
26 | id: props_id,
27 | className,
28 | as = "p",
29 | style,
30 | severity,
31 | small: isSmall = false,
32 | noIcon = false,
33 | children,
34 | ...rest
35 | } = props;
36 |
37 | assert>();
38 |
39 | const id = useAnalyticsId({
40 | "defaultIdPrefix": "fr-badge",
41 | "explicitlyProvidedId": props_id
42 | });
43 |
44 | return React.createElement(
45 | as,
46 | {
47 | "className": cx(
48 | fr.cx(
49 | "fr-badge",
50 | severity !== undefined && `fr-badge--${severity}`,
51 | { "fr-badge--sm": isSmall },
52 | { "fr-badge--no-icon": noIcon || severity === undefined }
53 | ),
54 | className
55 | ),
56 | id,
57 | style,
58 | ref,
59 | ...rest
60 | },
61 | <>{children}>
62 | );
63 | })
64 | );
65 |
66 | Badge.displayName = symToStr({ Badge });
67 |
68 | export default Badge;
69 |
--------------------------------------------------------------------------------
/src/Chart/BarChart.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import { symToStr } from "tsafe/symToStr";
4 | import "@gouvfr/dsfr-chart/BarChart/bar-chart.common";
5 | import "@gouvfr/dsfr-chart/BarChart/bar-chart.css";
6 | import {
7 | chartWrapper,
8 | IntrinsicGraphType,
9 | BaseChartProps,
10 | stringifyObjectValue,
11 | MultiChartProps,
12 | ChartLineProps,
13 | IntrinsicGraphLineType
14 | } from "./chartWrapper";
15 |
16 | declare global {
17 | namespace JSX {
18 | interface IntrinsicElements {
19 | // https://github.com/GouvernementFR/dsfr-chart/blob/v1.0.0/src/components/BarChart.vue#L79
20 | "bar-chart": {
21 | horizontal?: string;
22 | stacked?: string;
23 | } & IntrinsicGraphType &
24 | IntrinsicGraphLineType;
25 | }
26 | }
27 | }
28 |
29 | export type BarChartBaseProps = {
30 | horizontal?: boolean;
31 | stacked?: boolean;
32 | } & MultiChartProps &
33 | ChartLineProps;
34 |
35 | export type BarChartProps = BarChartBaseProps & BaseChartProps;
36 |
37 | /** @see */
38 | export const BarChart = chartWrapper((props: BarChartBaseProps) => {
39 | return ;
40 | }, "bar-chart");
41 | BarChart.displayName = symToStr({ BarChart });
42 |
43 | export default BarChart;
44 |
45 | // Just so that the icon is included: "fr-icon-arrow-go-back-fill"
46 |
--------------------------------------------------------------------------------
/src/Chart/BarLineChart.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import { symToStr } from "tsafe/symToStr";
4 | import "@gouvfr/dsfr-chart/BarLineChart/barline-chart.common";
5 | import "@gouvfr/dsfr-chart/BarLineChart/barline-chart.css";
6 | import {
7 | chartWrapper,
8 | IntrinsicGraphType,
9 | BaseChartProps,
10 | stringifyObjectValue,
11 | ChartProps,
12 | ChartLineProps,
13 | IntrinsicGraphLineType
14 | } from "./chartWrapper";
15 |
16 | declare global {
17 | namespace JSX {
18 | interface IntrinsicElements {
19 | // https://github.com/GouvernementFR/dsfr-chart/blob/v1.0.0/src/components/BarLineChart.vue#L75
20 | "bar-line-chart": {
21 | ybar: string;
22 | } & IntrinsicGraphType &
23 | IntrinsicGraphLineType;
24 | }
25 | }
26 | }
27 |
28 | export type BarLineChartBaseProps = {
29 | ybar: number[];
30 | name?: string;
31 | nameBar?: string;
32 | horizontal?: boolean;
33 | stacked?: boolean;
34 | } & Omit &
35 | ChartLineProps;
36 |
37 | export type BarLineChartProps = BarLineChartBaseProps & BaseChartProps;
38 |
39 | /** @see */
40 | export const BarLineChart = chartWrapper((props: BarLineChartBaseProps) => {
41 | return ;
42 | }, "bar-line-chart");
43 | BarLineChart.displayName = symToStr({ BarLineChart });
44 |
45 | export default BarLineChart;
46 |
--------------------------------------------------------------------------------
/src/Chart/GaugeChart.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import { symToStr } from "tsafe/symToStr";
4 | import "@gouvfr/dsfr-chart/GaugeChart/gauge-chart.common";
5 | import "@gouvfr/dsfr-chart/GaugeChart/gauge-chart.css";
6 | import { chartWrapper, BaseChartProps, stringifyObjectValue, ChartColor } from "./chartWrapper";
7 |
8 | declare global {
9 | namespace JSX {
10 | interface IntrinsicElements {
11 | // https://github.com/GouvernementFR/dsfr-chart/blob/v1.0.0/src/components/GaugeChart.vue#L55
12 | "gauge-chart": {
13 | value: string;
14 | init: string;
15 | target: string;
16 | color: string;
17 | };
18 | }
19 | }
20 | }
21 |
22 | export type GaugeChartBaseProps = {
23 | value: number;
24 | init: number;
25 | target: number;
26 | color?: ChartColor;
27 | };
28 |
29 | export type GaugeChartProps = GaugeChartBaseProps & BaseChartProps;
30 |
31 | /** @see */
32 | export const GaugeChart = chartWrapper(
33 | (props: GaugeChartBaseProps) => ,
34 | "gauge-chart"
35 | );
36 | GaugeChart.displayName = symToStr({ GaugeChart });
37 |
38 | export default GaugeChart;
39 |
--------------------------------------------------------------------------------
/src/Chart/LineChart.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import { symToStr } from "tsafe/symToStr";
4 | import "@gouvfr/dsfr-chart/LineChart/line-chart.common";
5 | import "@gouvfr/dsfr-chart/LineChart/line-chart.css";
6 | import {
7 | chartWrapper,
8 | ChartProps,
9 | IntrinsicGraphType,
10 | BaseChartProps,
11 | stringifyObjectValue,
12 | ChartLineProps,
13 | IntrinsicGraphLineType
14 | } from "./chartWrapper";
15 |
16 | declare global {
17 | namespace JSX {
18 | interface IntrinsicElements {
19 | // https://github.com/GouvernementFR/dsfr-chart/blob/v1.0.0/src/components/LineChart.vue#L70
20 | "line-chart": IntrinsicGraphType & IntrinsicGraphLineType;
21 | }
22 | }
23 | }
24 |
25 | export type LineChartBaseProps = ChartProps & ChartLineProps;
26 |
27 | export type LineChartProps = LineChartBaseProps & BaseChartProps;
28 |
29 | /** @see */
30 | export const LineChart = chartWrapper(
31 | (props: LineChartBaseProps) => ,
32 | "line-chart"
33 | );
34 | LineChart.displayName = symToStr({ LineChart });
35 |
36 | export default LineChart;
37 |
--------------------------------------------------------------------------------
/src/Chart/MultiLineChart.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import { symToStr } from "tsafe/symToStr";
4 | import "@gouvfr/dsfr-chart/MultiLineChart/multiline-chart.common";
5 | import "@gouvfr/dsfr-chart/MultiLineChart/multiline-chart.css";
6 | import {
7 | chartWrapper,
8 | IntrinsicGraphType,
9 | BaseChartProps,
10 | stringifyObjectValue,
11 | MultiChartProps,
12 | ChartLineProps,
13 | IntrinsicGraphLineType
14 | } from "./chartWrapper";
15 |
16 | declare global {
17 | namespace JSX {
18 | interface IntrinsicElements {
19 | // https://github.com/GouvernementFR/dsfr-chart/blob/v1.0.0/src/components/MultiLineChart.vue#L74
20 | "multiline-chart": IntrinsicGraphType & IntrinsicGraphLineType;
21 | }
22 | }
23 | }
24 |
25 | export type MultiLineChartBaseProps = MultiChartProps & ChartLineProps;
26 |
27 | export type MultiLineChartProps = MultiLineChartBaseProps & BaseChartProps;
28 |
29 | /** @see */
30 | export const MultiLineChart = chartWrapper(
31 | (props: MultiLineChartBaseProps) => ,
32 | "multiline-chart"
33 | );
34 | MultiLineChart.displayName = symToStr({ MultiLineChart });
35 |
36 | export default MultiLineChart;
37 |
--------------------------------------------------------------------------------
/src/Chart/PieChart.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import { symToStr } from "tsafe/symToStr";
4 | import "@gouvfr/dsfr-chart/PieChart/pie-chart.common";
5 | import "@gouvfr/dsfr-chart/PieChart/pie-chart.css";
6 | import {
7 | chartWrapper,
8 | type ChartProps,
9 | type IntrinsicGraphType,
10 | type BaseChartProps,
11 | stringifyObjectValue,
12 | type ChartColor
13 | } from "./chartWrapper";
14 |
15 | declare global {
16 | namespace JSX {
17 | interface IntrinsicElements {
18 | // https://github.com/GouvernementFR/dsfr-chart/blob/v1.0.0/src/components/PieChart.vue#L59
19 | "pie-chart": {
20 | fill?: string;
21 | } & IntrinsicGraphType;
22 | }
23 | }
24 | }
25 |
26 | export type PieChartBaseProps = {
27 | fill?: boolean;
28 | name?: string[];
29 | color?: ChartColor[];
30 | } & Omit;
31 |
32 | export type PieChartProps = PieChartBaseProps & BaseChartProps;
33 |
34 | /** @see */
35 | export const PieChart = chartWrapper(
36 | (props: PieChartBaseProps) => ,
37 | "pie-chart"
38 | );
39 | PieChart.displayName = symToStr({ PieChart });
40 |
41 | export default PieChart;
42 |
--------------------------------------------------------------------------------
/src/Chart/RadarChart.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import { symToStr } from "tsafe/symToStr";
4 | import "@gouvfr/dsfr-chart/RadarChart/radar-chart.common";
5 | import "@gouvfr/dsfr-chart/RadarChart/radar-chart.css";
6 | import {
7 | chartWrapper,
8 | IntrinsicGraphType,
9 | BaseChartProps,
10 | stringifyObjectValue,
11 | MultiChartProps
12 | } from "./chartWrapper";
13 |
14 | declare global {
15 | namespace JSX {
16 | interface IntrinsicElements {
17 | // https://github.com/GouvernementFR/dsfr-chart/blob/v1.0.0/src/components/RadarChart.vue#L57
18 | "radar-chart": IntrinsicGraphType;
19 | }
20 | }
21 | }
22 |
23 | export type RadarChartBaseProps = MultiChartProps;
24 |
25 | export type RadarChartProps = RadarChartBaseProps & BaseChartProps;
26 |
27 | /** @see */
28 | export const RadarChart = chartWrapper(
29 | (props: RadarChartBaseProps) => ,
30 | "radar-chart"
31 | );
32 | RadarChart.displayName = symToStr({ RadarChart });
33 |
34 | export default RadarChart;
35 |
--------------------------------------------------------------------------------
/src/Chart/ScatterChart.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import { symToStr } from "tsafe/symToStr";
4 | import "@gouvfr/dsfr-chart/ScatterChart/scatter-chart.common";
5 | import "@gouvfr/dsfr-chart/ScatterChart/scatter-chart.css";
6 | import {
7 | chartWrapper,
8 | BaseChartProps,
9 | MultiChartProps,
10 | IntrinsicGraphType,
11 | stringifyObjectValue,
12 | ChartLineProps,
13 | IntrinsicGraphLineType
14 | } from "./chartWrapper";
15 |
16 | declare global {
17 | namespace JSX {
18 | interface IntrinsicElements {
19 | // https://github.com/GouvernementFR/dsfr-chart/blob/v1.0.0/src/components/ScatterChart.vue#L75
20 | "scatter-chart": {
21 | showLine?: string;
22 | } & IntrinsicGraphType &
23 | IntrinsicGraphLineType;
24 | }
25 | }
26 | }
27 |
28 | type ScatterChartBaseProps = {
29 | x: number[][];
30 | showLine?: boolean;
31 | } & Omit &
32 | ChartLineProps;
33 |
34 | export type ScatterChartProps = ScatterChartBaseProps & BaseChartProps;
35 |
36 | /** @see */
37 | export const ScatterChart = chartWrapper(
38 | (props: ScatterChartBaseProps) => ,
39 | "scatter-chart"
40 | );
41 | ScatterChart.displayName = symToStr({ ScatterChart });
42 |
43 | export default ScatterChart;
44 |
--------------------------------------------------------------------------------
/src/Checkbox.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo, forwardRef } from "react";
2 | import { symToStr } from "tsafe/symToStr";
3 | import { Fieldset, type FieldsetProps } from "./shared/Fieldset";
4 |
5 | export type CheckboxProps = Omit;
6 |
7 | /** @see */
8 | export const Checkbox = memo(
9 | forwardRef((props, ref) => (
10 |
11 | ))
12 | );
13 |
14 | Checkbox.displayName = symToStr({ Checkbox });
15 |
16 | export default Checkbox;
17 |
--------------------------------------------------------------------------------
/src/Display/Artwork/Artwork.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React from "react";
4 | import { ArtworkGov } from "./ArtworkGov";
5 | import { useIsGov } from "../../mui/useIsGov";
6 | import { useArtworkWhiteLabel } from "./ArtworkWhiteLabel/useArtworkWhiteLabel";
7 | import { assert } from "tsafe/assert";
8 |
9 | export function Artwork(props: { theme: "light" | "dark" | "system"; className?: string }) {
10 | const { theme, className } = props;
11 |
12 | const { isGov } = useIsGov();
13 | const { ArtworkWhiteLabel } = useArtworkWhiteLabel();
14 |
15 | if (!isGov) {
16 | assert(ArtworkWhiteLabel !== undefined);
17 |
18 | return ;
19 | }
20 |
21 | return ;
22 | }
23 |
--------------------------------------------------------------------------------
/src/Display/Artwork/ArtworkGov.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { fr } from "../../fr";
3 | import ArtworkLightSvg from "../../dsfr/artwork/light.svg";
4 | import ArtworkDarkSvg from "../../dsfr/artwork/dark.svg";
5 | import ArtworkSystemSvg from "../../dsfr/artwork/system.svg";
6 | import { getAssetUrl } from "../../tools/getAssetUrl";
7 |
8 | export function ArtworkGov(props: { theme: "light" | "dark" | "system"; className?: string }) {
9 | const { theme, className } = props;
10 |
11 | return (
12 |
21 | {(["artwork-decorative", "artwork-minor", "artwork-major"] as const).map(label => (
22 | {
27 | switch (theme) {
28 | case "dark":
29 | return ArtworkDarkSvg;
30 | case "light":
31 | return ArtworkLightSvg;
32 | case "system":
33 | return ArtworkSystemSvg;
34 | }
35 | })()
36 | )}#${label}`}
37 | />
38 | ))}
39 |
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/src/Display/Artwork/ArtworkWhiteLabel/DisplayArtworkWhiteLabelProvider.tsx:
--------------------------------------------------------------------------------
1 | import React, { type ReactNode } from "react";
2 | import { context } from "./context";
3 | import { ArtworkWhiteLabel } from "./ArtworkWhiteLabel";
4 |
5 | export function DisplayArtworkWhiteLabelProvider(props: { children: ReactNode }) {
6 | const { children } = props;
7 |
8 | return {children} ;
9 | }
10 |
--------------------------------------------------------------------------------
/src/Display/Artwork/ArtworkWhiteLabel/context.ts:
--------------------------------------------------------------------------------
1 | import { JSX } from "../../../tools/JSX";
2 | import { createContext } from "react";
3 |
4 | export const context = createContext<
5 | ((props: { theme: "light" | "dark" | "system"; sizePx: number }) => JSX.Element) | undefined
6 | >(undefined);
7 |
--------------------------------------------------------------------------------
/src/Display/Artwork/ArtworkWhiteLabel/useArtworkWhiteLabel.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from "react";
2 | import { context } from "./context";
3 |
4 | export function useArtworkWhiteLabel() {
5 | const ArtworkWhiteLabel = useContext(context);
6 |
7 | return { ArtworkWhiteLabel };
8 | }
9 |
--------------------------------------------------------------------------------
/src/Display/Artwork/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./Artwork";
2 |
--------------------------------------------------------------------------------
/src/Display/index.ts:
--------------------------------------------------------------------------------
1 | export { headerFooterDisplayItem, addDisplayTranslations } from "./Display";
2 |
3 | /**
4 | * @deprecated: It's no longer needed to manually mount this component.
5 | * @see: https://components.react-dsfr.codegouv.studio/?path=/docs/components-display
6 | */
7 | export function Display() {
8 | return null;
9 | }
10 |
--------------------------------------------------------------------------------
/src/Download.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo, forwardRef, type CSSProperties, type ReactNode } from "react";
2 | import { symToStr } from "tsafe/symToStr";
3 | import { assert } from "tsafe/assert";
4 | import type { Equals } from "tsafe";
5 | import { fr } from "./fr";
6 | import { cx } from "./tools/cx";
7 | import { getLink, type RegisteredLinkProps } from "./link";
8 | import { useAnalyticsId } from "./tools/useAnalyticsId";
9 |
10 | export type DownloadProps = {
11 | id?: string;
12 | className?: string;
13 | style?: CSSProperties;
14 | details: ReactNode;
15 | label: ReactNode;
16 | linkProps: RegisteredLinkProps;
17 | classes?: Partial>;
18 | };
19 |
20 | /** @see */
21 | export const Download = memo(
22 | forwardRef((props, ref) => {
23 | const {
24 | className,
25 | style,
26 | details,
27 | label,
28 | linkProps,
29 | classes = {},
30 | id: props_id,
31 | ...rest
32 | } = props;
33 |
34 | assert>();
35 |
36 | const id = useAnalyticsId({
37 | "defaultIdPrefix": "fr-download",
38 | "explicitlyProvidedId": props_id
39 | });
40 |
41 | const { Link } = getLink();
42 |
43 | return (
44 |
50 |
51 |
56 | {label}
57 | {details}
58 |
59 |
60 |
61 | );
62 | })
63 | );
64 |
65 | Download.displayName = symToStr({ Download });
66 |
67 | export default Download;
68 |
--------------------------------------------------------------------------------
/src/Header/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./Header";
2 | export {
3 | Header,
4 | type HeaderProps,
5 | HeaderQuickAccessItem,
6 | type HeaderQuickAccessItemProps,
7 | addHeaderTranslations,
8 | useTranslation
9 | } from "./Header";
10 |
--------------------------------------------------------------------------------
/src/Header/useIsHeaderMenuModalOpen.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { headerMenuModalIdPrefix } from "./Header";
3 | import { useIsModalOpen } from "../Modal/useIsModalOpen";
4 | import { symToStr } from "tsafe/symToStr";
5 |
6 | export function useIsHeaderMenuModalOpen() {
7 | const [headerMenuModalId, setHeaderMenuModalId] = useState("");
8 |
9 | useEffect(() => {
10 | const matchingElements = document.querySelectorAll(`[id^='${headerMenuModalIdPrefix}']`);
11 |
12 | if (matchingElements.length > 1) {
13 | throw new Error(
14 | `There is more than one Header mounted on the page, you can't use ${symToStr({
15 | useIsHeaderMenuModalOpen
16 | })}`
17 | );
18 | }
19 |
20 | if (matchingElements.length === 0) {
21 | throw new Error(
22 | `The header is not mounted on the page, you can't use ${symToStr({
23 | useIsHeaderMenuModalOpen
24 | })}`
25 | );
26 | }
27 |
28 | setHeaderMenuModalId(matchingElements[0].id);
29 | }, []);
30 |
31 | return useIsModalOpen({
32 | "id": headerMenuModalId,
33 | "isOpenedByDefault": false
34 | });
35 | }
36 |
--------------------------------------------------------------------------------
/src/Highlight.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo, forwardRef, type ReactNode, type CSSProperties } from "react";
2 | import { symToStr } from "tsafe/symToStr";
3 | import { assert } from "tsafe/assert";
4 | import type { Equals } from "tsafe";
5 | import { fr } from "./fr";
6 | import { cx } from "./tools/cx";
7 | import { useAnalyticsId } from "./tools/useAnalyticsId";
8 |
9 | export type HighlightProps = {
10 | id?: string;
11 | className?: string;
12 | classes?: Partial>;
13 | size?: HighlightProps.Size;
14 | style?: CSSProperties;
15 | children: NonNullable;
16 | };
17 |
18 | export namespace HighlightProps {
19 | export type Size = "sm" | "lg";
20 | }
21 |
22 | /** @see */
23 | export const Highlight = memo(
24 | forwardRef((props, ref) => {
25 | const { className, classes = {}, style, children, size, id: id_props, ...rest } = props;
26 |
27 | assert>();
28 |
29 | const id = useAnalyticsId({
30 | "defaultIdPrefix": "fr-highlight",
31 | "explicitlyProvidedId": id_props
32 | });
33 |
34 | return (
35 |
42 |
43 | {children}
44 |
45 |
46 | );
47 | })
48 | );
49 |
50 | Highlight.displayName = symToStr({ Highlight });
51 |
52 | export default Highlight;
53 |
--------------------------------------------------------------------------------
/src/MainNavigation/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./MainNavigation";
2 | export { addMegaMenuTranslations } from "./MegaMenu";
3 |
4 | import { MainNavigation } from "./MainNavigation";
5 |
6 | export default MainNavigation;
7 |
--------------------------------------------------------------------------------
/src/Modal/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./Modal";
2 |
--------------------------------------------------------------------------------
/src/RadioButtons.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo, forwardRef } from "react";
2 | import { symToStr } from "tsafe/symToStr";
3 | import { Fieldset, type FieldsetProps } from "./shared/Fieldset";
4 |
5 | export type RadioButtonsProps = Omit;
6 |
7 | /** @see */
8 | export const RadioButtons = memo(
9 | forwardRef((props, ref) => (
10 |
11 | ))
12 | );
13 |
14 | RadioButtons.displayName = symToStr({ RadioButtons });
15 |
16 | export default RadioButtons;
17 |
--------------------------------------------------------------------------------
/src/SearchBar/index.tsx:
--------------------------------------------------------------------------------
1 | export { default } from "./SearchBar";
2 | export { SearchBar, type SearchBarProps, addSearchBarTranslations } from "./SearchBar";
3 |
--------------------------------------------------------------------------------
/src/TagsGroup.tsx:
--------------------------------------------------------------------------------
1 | import React, { CSSProperties, forwardRef, memo } from "react";
2 | import { assert, Equals } from "tsafe";
3 | import { symToStr } from "tsafe/symToStr";
4 | import Tag, { TagProps } from "./Tag";
5 | import { useAnalyticsId } from "./tools/useAnalyticsId";
6 | import { cx } from "./tools/cx";
7 | import { fr } from "./fr";
8 |
9 | export type TagsGroupProps = TagsGroupProps.Common;
10 |
11 | //https://main--ds-gouv.netlify.app/example/component/tag/#:~:text=Groupe%20de%20tags
12 | export namespace TagsGroupProps {
13 | export type Common = {
14 | id?: string;
15 | className?: string;
16 | style?: CSSProperties;
17 | /** @default false */
18 | smallTags?: TagProps["small"];
19 | /** 6 tags should be the maximum. */
20 | tags: [TagProps, ...TagProps[]];
21 | };
22 | }
23 |
24 | export const TagsGroup = memo(
25 | forwardRef((props, ref) => {
26 | const { id: props_id, className, tags, smallTags = false, style, ...rest } = props;
27 |
28 | assert>();
29 |
30 | const id = useAnalyticsId({
31 | "defaultIdPrefix": "fr-tags-group",
32 | "explicitlyProvidedId": props_id
33 | });
34 |
35 | const tagsGroupClassName = cx(
36 | fr.cx("fr-tags-group", smallTags && "fr-tags-group--sm"),
37 | className
38 | );
39 |
40 | return (
41 |
42 | {tags.map((tagProps, i) => (
43 |
44 |
45 |
46 | ))}
47 |
48 | );
49 | })
50 | );
51 |
52 | TagsGroup.displayName = symToStr({ TagsGroup });
53 |
54 | export default TagsGroup;
55 |
--------------------------------------------------------------------------------
/src/assets/agentconnect.css:
--------------------------------------------------------------------------------
1 |
2 | .agentconnect-button__link.agentconnect-button__link {
3 | width: 206px;
4 | height: 60px;
5 | display: inline-block;
6 | background-image: url("./agentconnect-btn-principal.svg");
7 | --active-tint: unset;
8 | background-color: transparent;
9 | background-position: 0 0;
10 | background-repeat: no-repeat;
11 | background-size: contain;
12 | }
13 |
14 | .agentconnect-button__preload-hover {
15 | visibility: collapse;
16 | }
17 | .agentconnect-button__link:hover, .agentconnect-button__preload-hover {
18 | background-image: url("./agentconnect-btn-principal-hover.svg");
19 | }
20 |
21 | :root[data-fr-theme=dark] .agentconnect-button__link {
22 | background-image: url("./agentconnect-btn-alternatif.svg");
23 | }
24 |
25 | :root[data-fr-theme=dark] .agentconnect-button__link:hover,
26 | :root[data-fr-theme=dark] .agentconnect-button__preload-hover {
27 | background-image: url("./agentconnect-btn-alternatif-hover.svg");
28 | }
29 |
30 | .agentconnect-button__hint {
31 | color: var(--text-action-high-blue-france);
32 | }
--------------------------------------------------------------------------------
/src/assets/blank-favicon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/dsfr_plus_icons.css:
--------------------------------------------------------------------------------
1 |
2 | @import url('../dsfr/utility/icons/icons.min.css');
3 | @import url('../dsfr/dsfr.min.css');
--------------------------------------------------------------------------------
/src/assets/dsfr_plus_icons.scss:
--------------------------------------------------------------------------------
1 | @use '../dsfr/utility/icons/icons.min.css';
2 | @use '../dsfr/dsfr.min.css';
--------------------------------------------------------------------------------
/src/assets/language-select.css:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | .language-select .short-label {
5 | text-transform: uppercase
6 | }
7 | .language-select .fr-menu {
8 | right: 0;
9 | }
10 |
11 | .language-select .fr-translate {
12 | display: inline-flex;;
13 | }
--------------------------------------------------------------------------------
/src/assets/search-bar.css:
--------------------------------------------------------------------------------
1 |
2 | .fr-search-bar {
3 | position: relative;
4 | }
5 |
6 | .fr-search-bar--lg input {
7 | height: 3rem;
8 | }
--------------------------------------------------------------------------------
/src/bin/README.md:
--------------------------------------------------------------------------------
1 | Here are the scripts exposed as utility to the user of `react-dsfr`
2 |
--------------------------------------------------------------------------------
/src/bin/react-dsfr.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const [, , commandName, ...args] = process.argv;
4 |
5 | (async () => {
6 | switch (commandName) {
7 | case "update-icons":
8 | {
9 | const { main } = await import("./only-include-used-icons");
10 |
11 | await main(args);
12 | }
13 | break;
14 | case "copy-static-assets":
15 | {
16 | const { main } = await import("./copy-dsfr-to-public");
17 |
18 | await main(args);
19 | }
20 | break;
21 | default:
22 | console.error(`Unknown command ${commandName}`);
23 | process.exit(-1);
24 | }
25 | })();
26 |
--------------------------------------------------------------------------------
/src/bin/tools/crawlSync.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "fs";
2 | import { join as pathJoin, relative as pathRelative } from "path";
3 |
4 | const crawlSyncRec = (dirPath: string, filePaths: string[]) => {
5 | for (const basename of fs.readdirSync(dirPath)) {
6 | const fileOrDirPath = pathJoin(dirPath, basename);
7 |
8 | if (fs.lstatSync(fileOrDirPath).isDirectory()) {
9 | crawlSyncRec(fileOrDirPath, filePaths);
10 |
11 | continue;
12 | }
13 |
14 | filePaths.push(fileOrDirPath);
15 | }
16 | };
17 |
18 | /** List all files in a given directory return paths relative to the dir_path */
19 | export function crawlSync(params: {
20 | dirPath: string;
21 | returnedPathsType: "absolute" | "relative to dirPath";
22 | }): string[] {
23 | const { dirPath, returnedPathsType } = params;
24 |
25 | const filePaths: string[] = [];
26 |
27 | crawlSyncRec(dirPath, filePaths);
28 |
29 | switch (returnedPathsType) {
30 | case "absolute":
31 | return filePaths;
32 | case "relative to dirPath":
33 | return filePaths.map(filePath => pathRelative(dirPath, filePath));
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/bin/tools/fnv1aHashToHex.ts:
--------------------------------------------------------------------------------
1 | export function fnv1aHashToHex(str: string) {
2 | let hash = 2166136261;
3 | for (let i = 0; i < str.length; i++) {
4 | hash ^= str.charCodeAt(i);
5 | hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
6 | }
7 | return (hash >>> 0).toString(16); // Convert to unsigned 32-bit integer and then to hexadecimal
8 | }
9 |
--------------------------------------------------------------------------------
/src/bin/tools/fs.existsAsync.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "fs/promises";
2 |
3 | export async function existsAsync(path: string) {
4 | try {
5 | await fs.stat(path);
6 | return true;
7 | } catch (error) {
8 | if ((error as Error & { code: string }).code === "ENOENT") return false;
9 | throw error;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/bin/tools/getAbsoluteAndInOsFormatPath.ts:
--------------------------------------------------------------------------------
1 | import { isAbsolute as pathIsAbsolute, sep as pathSep, join as pathJoin } from "path";
2 |
3 | export function getAbsoluteAndInOsFormatPath(params: { pathIsh: string; cwd: string }): string {
4 | const { pathIsh, cwd } = params;
5 |
6 | let pathOut = pathIsh;
7 |
8 | pathOut = pathOut.replace(/\//g, pathSep);
9 |
10 | pathOut = pathOut.endsWith(pathSep) ? pathOut.slice(0, -1) : pathOut;
11 |
12 | if (!pathIsAbsolute(pathOut)) {
13 | pathOut = pathJoin(cwd, pathOut);
14 | }
15 |
16 | return pathOut;
17 | }
18 |
--------------------------------------------------------------------------------
/src/bin/tools/getProjectRoot.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "fs";
2 | import * as path from "path";
3 |
4 | function getProjectRootRec(dirPath: string): string {
5 | if (fs.existsSync(path.join(dirPath, "package.json"))) {
6 | return dirPath;
7 | }
8 | return getProjectRootRec(path.join(dirPath, ".."));
9 | }
10 |
11 | let result: string | undefined = undefined;
12 |
13 | export function getProjectRoot(): string {
14 | if (result !== undefined) {
15 | return result;
16 | }
17 |
18 | return (result = getProjectRootRec(__dirname));
19 | }
20 |
--------------------------------------------------------------------------------
/src/bin/tools/modifyHtmlHrefs.ts:
--------------------------------------------------------------------------------
1 | export function modifyHtmlHrefs(params: {
2 | html: string;
3 | getModifiedHref: (href: string) => string;
4 | }): { modifiedHtml: string } {
5 | const { html, getModifiedHref } = params;
6 |
7 | let modifiedHtml = html;
8 |
9 | (
10 | [
11 | [/href="([^"]+)"/g, '"'],
12 | [/href='([^']+)'/g, "'"]
13 | ] as const
14 | ).forEach(
15 | ([regex, quoteSymbol]) =>
16 | (modifiedHtml = modifiedHtml.replace(
17 | regex,
18 | (...[, href]) => `href=${quoteSymbol}${getModifiedHref(href)}${quoteSymbol}`
19 | ))
20 | );
21 |
22 | return { modifiedHtml };
23 | }
24 |
--------------------------------------------------------------------------------
/src/bin/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsproject.json",
3 | "compilerOptions": {
4 | "module": "CommonJS",
5 | "target": "ES5",
6 | "esModuleInterop": true,
7 | "lib": ["DOM", "ES2021"],
8 | "outDir": "../../dist/bin",
9 | "rootDir": "."
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/consentManagement/ConsentBannerAndConsentManagement/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./ConsentBannerAndConsentManagement";
2 | export { addConsentManagementTranslations } from "./translation";
3 |
--------------------------------------------------------------------------------
/src/consentManagement/index.ts:
--------------------------------------------------------------------------------
1 | export { createConsentManagement } from "./createConsentManagement";
2 | export { addConsentManagementTranslations } from "./ConsentBannerAndConsentManagement";
3 |
--------------------------------------------------------------------------------
/src/consentManagement/types.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-unused-vars */
2 | import type { ReactNode } from "react";
3 |
4 | //See: test/types/consentManagement.ts to understand theses types
5 |
6 | export type FinalityConsent = {
7 | readonly [K in Finality as K extends `${infer _P}.${infer _C}` ? never : K]: boolean;
8 | } & {
9 | readonly [K in Finality as K extends `${infer P}.${infer _C}` ? P : never]: Readonly<
10 | Record
11 | > & { readonly isFullConsent: boolean };
12 | } & { readonly isFullConsent: boolean };
13 |
14 | export type ExtractFinalityFromFinalityDescription<
15 | FinalityDescription extends Record<
16 | string,
17 | { title: ReactNode; subFinalities?: Record }
18 | >
19 | > = {
20 | [K in keyof FinalityDescription]: K extends string
21 | ? FinalityDescription[K] extends { subFinalities: Record }
22 | ? `${K}.${ExtractFinalityFromFinalityDescription.SubFinalities}`
23 | : K
24 | : never;
25 | }[keyof FinalityDescription];
26 |
27 | export namespace ExtractFinalityFromFinalityDescription {
28 | export type SubFinalities = T extends { subFinalities: infer U }
29 | ? U extends Record
30 | ? keyof U
31 | : never
32 | : never;
33 | }
34 |
--------------------------------------------------------------------------------
/src/consentManagement/useConsent.ts:
--------------------------------------------------------------------------------
1 | import { isBrowser } from "../tools/isBrowser";
2 | import { useConstCallback } from "../tools/powerhooks/useConstCallback";
3 | import type { FinalityConsent } from "./types";
4 | import type { ConsentCallback, ProcessConsentChanges } from "./processConsentChanges";
5 |
6 | export type UseConsent = (params?: {
7 | consentCallback: ConsentCallback;
8 | }) => {
9 | finalityConsent: FinalityConsent | undefined;
10 | assumeConsent: (finality: Finality) => void;
11 | };
12 |
13 | export function createUseConsent(params: {
14 | useFinalityConsent: () => FinalityConsent | undefined;
15 | processConsentChanges: ProcessConsentChanges;
16 | useConsentCallback: (params: {
17 | consentCallback: ConsentCallback | undefined;
18 | }) => void;
19 | }): { useConsent: UseConsent } {
20 | const { useFinalityConsent, processConsentChanges, useConsentCallback } = params;
21 |
22 | const useConsentManagementClientSide: UseConsent = params => {
23 | const { consentCallback } = params ?? {};
24 |
25 | useConsentCallback({ consentCallback });
26 |
27 | const finalityConsent = useFinalityConsent();
28 |
29 | const assumeConsent = useConstCallback((finality: Finality) =>
30 | processConsentChanges({
31 | "type": "atomic change",
32 | finality,
33 | "isConsentGiven": true
34 | })
35 | );
36 |
37 | return {
38 | assumeConsent,
39 | finalityConsent
40 | };
41 | };
42 |
43 | const useConsentManagementServerSide: UseConsent = () => {
44 | return {
45 | "finalityConsent": undefined,
46 | "assumeConsent": () => {
47 | throw new Error("Cannot assume consent on the server side");
48 | }
49 | };
50 | };
51 |
52 | const useConsent = isBrowser ? useConsentManagementClientSide : useConsentManagementServerSide;
53 |
54 | return { useConsent };
55 | }
56 |
--------------------------------------------------------------------------------
/src/fr/colors.ts:
--------------------------------------------------------------------------------
1 | import { colorOptions, type ColorOptions } from "./generatedFromCss/colorOptions";
2 | import { getColorOptionsHex } from "./generatedFromCss/getColorOptionsHex";
3 | import { colorDecisions, type ColorDecisions } from "./generatedFromCss/colorDecisions";
4 | import { getColorDecisionsHex } from "./generatedFromCss/getColorDecisionsHex";
5 |
6 | export type Colors = {
7 | options: ColorOptions<"css var">;
8 | decisions: ColorDecisions<"css var">;
9 | getHex: (params: { isDark: boolean }) => {
10 | options: ColorOptions<"hex">;
11 | decisions: ColorDecisions<"hex">;
12 | };
13 | };
14 |
15 | export const colors: Colors = {
16 | "options": colorOptions,
17 | "decisions": colorDecisions,
18 | "getHex": (() => {
19 | const getHex: Colors["getHex"] = ({ isDark }) => {
20 | const options = getColorOptionsHex({ isDark });
21 |
22 | const decisions = getColorDecisionsHex({ "colorOptions": options });
23 |
24 | return {
25 | options,
26 | decisions
27 | };
28 | };
29 |
30 | const cache: Record<"light" | "dark", ReturnType | undefined> = {
31 | "light": undefined,
32 | "dark": undefined
33 | };
34 |
35 | return ({ isDark }) => (cache[isDark ? "dark" : "light"] ??= getHex({ isDark }));
36 | })()
37 | };
38 |
--------------------------------------------------------------------------------
/src/fr/cx.ts:
--------------------------------------------------------------------------------
1 | import type { FrClassName } from "./generatedFromCss/classNames";
2 | import { cx as genericCx } from "../tools/cx";
3 |
4 | export type FrCxArg =
5 | | undefined
6 | | null
7 | | FrClassName
8 | | boolean
9 | | Partial>
10 | | readonly FrCxArg[];
11 |
12 | /** Copy pasted from
13 | * https://github.com/emotion-js/emotion/blob/23f43ab9f24d44219b0b007a00f4ac681fe8712e/packages/react/src/class-names.js#L17-L63
14 | **/
15 | export const cx: (...args: FrCxArg[]) => string = genericCx;
16 |
--------------------------------------------------------------------------------
/src/fr/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./colors";
2 | export type { BreakpointKeys } from "./breakpoints";
3 | import { breakpoints } from "./breakpoints";
4 | import { spacing } from "./spacing";
5 | export type { SpacingToken } from "./spacing";
6 | import { cx } from "./cx";
7 | export type { FrCxArg } from "./cx";
8 | export type { Colors } from "./colors";
9 | export type { ColorOptions } from "./generatedFromCss/colorOptions";
10 | export type { ColorDecisions } from "./generatedFromCss/colorDecisions";
11 | import { colors } from "./colors";
12 | export type { FrClassName, FrIconClassName, RiIconClassName } from "./generatedFromCss/classNames";
13 | import { typography } from "./generatedFromCss/typography";
14 |
15 | export const fr = {
16 | breakpoints,
17 | spacing,
18 | cx,
19 | colors,
20 | typography
21 | };
22 |
--------------------------------------------------------------------------------
/src/fr/shadows.ts:
--------------------------------------------------------------------------------
1 | export const shadowsOption = [
2 | "none",
3 | /** Light / SM */
4 | "0px 2px 6px 0px rgba(0,0,18,0.16)",
5 | /** Light / MD */
6 | "0px 4px 12px 0px rgba(0,0,18,0.16)",
7 | /** Light / LG */
8 | "0px 6px 18px 0px rgba(0,0,18,0.16)",
9 | /** Dark / SM */
10 | "0px 2px 6px 0px rgba(0,0,18,0.32)",
11 | /** Dark / MD */
12 | "0px 4px 12px 0px rgba(0,0,18,0.32)",
13 | /** Dark / LG */
14 | "0px 4px 18px 0px rgba(0,0,18,0.32)"
15 | ] as const;
16 |
17 | // Shadows decisions (shadow applied to an usecase or a context)
18 |
19 | /** Raised */
20 | // Use Light or Dark / SM
21 |
22 | /** Overlap */
23 | // Use Light or Dark / MD
24 |
25 | /** Lifted */
26 | // Use Light or Dark / LG
27 |
--------------------------------------------------------------------------------
/src/global.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.svg" {
2 | const _default: string | { src: string };
3 | export default _default;
4 | }
5 |
6 | declare module "*.png" {
7 | const _default: string | { src: string };
8 | export default _default;
9 | }
10 |
11 | declare module "*.ico" {
12 | const _default: string | { src: string };
13 | export default _default;
14 | }
15 |
16 | declare module "*.webmanifest" {
17 | const _default: string;
18 | export default _default;
19 | }
20 |
21 | declare module "*.woff2" {
22 | const _default: string;
23 | export default _default;
24 | }
25 |
--------------------------------------------------------------------------------
/src/mui/index.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | export * from "./mui";
4 | import { MuiDsfrThemeProvider } from "./mui";
5 | export { useIsGov } from "./useIsGov";
6 | export default MuiDsfrThemeProvider;
7 |
--------------------------------------------------------------------------------
/src/mui/useIsGov.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React, { createContext, useContext, type ReactNode } from "react";
4 |
5 | const react = createContext(true);
6 |
7 | export function useIsGov() {
8 | const isGov = useContext(react);
9 |
10 | return { isGov };
11 | }
12 |
13 | export function IsGovProvider(props: { children: ReactNode; isGov: boolean }) {
14 | const { children, isGov } = props;
15 |
16 | return {children} ;
17 | }
18 |
--------------------------------------------------------------------------------
/src/next-app-router/getHtmlAttributes.tsx:
--------------------------------------------------------------------------------
1 | import { data_fr_scheme, data_fr_theme } from "../useIsDark/constants";
2 | import type { ColorScheme } from "../useIsDark";
3 | import {
4 | type DefaultColorScheme,
5 | setDefaultColorSchemeServerSide
6 | } from "./zz_internal/defaultColorScheme";
7 | import { setUseLang } from "../i18n";
8 |
9 | const suppressHydrationWarning = true;
10 |
11 | export function createGetHtmlAttributes(params: { defaultColorScheme: DefaultColorScheme }) {
12 | const { defaultColorScheme } = params;
13 |
14 | function getHtmlAttributes(params: {
15 | lang: string | undefined;
16 | }): { suppressHydrationWarning: true; lang?: string } & (
17 | | Record
18 | | {}
19 | ) {
20 | const { lang } = params;
21 |
22 | setDefaultColorSchemeServerSide({ defaultColorScheme });
23 |
24 | if (lang !== undefined) {
25 | setUseLang({ useLang: () => lang });
26 | }
27 |
28 | if (defaultColorScheme === "system") {
29 | return {
30 | lang,
31 | suppressHydrationWarning
32 | };
33 | }
34 |
35 | return {
36 | lang,
37 | suppressHydrationWarning,
38 | [data_fr_scheme]: defaultColorScheme,
39 | [data_fr_theme]: defaultColorScheme
40 | };
41 | }
42 | return { getHtmlAttributes };
43 | }
44 |
--------------------------------------------------------------------------------
/src/next-app-router/index.ts:
--------------------------------------------------------------------------------
1 | export type { RegisterLink } from "../link";
2 | export type { DefaultColorScheme } from "./zz_internal/defaultColorScheme";
3 | export { DsfrProviderBase, type DsfrProviderProps, StartDsfrOnHydration } from "./DsfrProvider";
4 |
--------------------------------------------------------------------------------
/src/next-app-router/server-only-index.ts:
--------------------------------------------------------------------------------
1 | export { createGetHtmlAttributes } from "./getHtmlAttributes";
2 | export { DsfrHeadBase, type DsfrHeadProps } from "./DsfrHead";
3 |
--------------------------------------------------------------------------------
/src/next-app-router/zz_internal/defaultColorScheme.ts:
--------------------------------------------------------------------------------
1 | import { assert } from "tsafe/assert";
2 | import type { ColorScheme } from "../../useIsDark";
3 |
4 | export type DefaultColorScheme = ColorScheme | "system";
5 |
6 | let defaultColorSchemeServerSide: DefaultColorScheme | undefined = undefined;
7 |
8 | export function getDefaultColorSchemeServerSide(): DefaultColorScheme {
9 | assert(defaultColorSchemeServerSide !== undefined);
10 | return defaultColorSchemeServerSide;
11 | }
12 |
13 | export function setDefaultColorSchemeServerSide(params: {
14 | defaultColorScheme: DefaultColorScheme;
15 | }): void {
16 | const { defaultColorScheme } = params;
17 |
18 | defaultColorSchemeServerSide = defaultColorScheme;
19 | }
20 |
--------------------------------------------------------------------------------
/src/next-app-router/zz_internal/fontUrlByFileBasename.ts:
--------------------------------------------------------------------------------
1 | import marianneLightWoff2Url from "@codegouvfr/react-dsfr/dsfr/fonts/Marianne-Light.woff2";
2 | import marianneItalicWoff2Url from "@codegouvfr/react-dsfr/dsfr/fonts/Marianne-Light_Italic.woff2";
3 | import marianneRegularWoff2Url from "@codegouvfr/react-dsfr/dsfr/fonts/Marianne-Regular.woff2";
4 | import marianneRegularItalicWoff2Url from "@codegouvfr/react-dsfr/dsfr/fonts/Marianne-Regular_Italic.woff2";
5 | import marianneMediumWoff2Url from "@codegouvfr/react-dsfr/dsfr/fonts/Marianne-Medium.woff2";
6 | import marianneMediumItalicWoff2Url from "@codegouvfr/react-dsfr/dsfr/fonts/Marianne-Medium_Italic.woff2";
7 | import marianneBoldWoff2Url from "@codegouvfr/react-dsfr/dsfr/fonts/Marianne-Bold.woff2";
8 | import marianneBoldItalicWoff2Url from "@codegouvfr/react-dsfr/dsfr/fonts/Marianne-Bold_Italic.woff2";
9 | import spectralRegularWoff2Url from "@codegouvfr/react-dsfr/dsfr/fonts/Spectral-Regular.woff2";
10 | import spectralExtraBoldWoff2Url from "@codegouvfr/react-dsfr/dsfr/fonts/Spectral-ExtraBold.woff2";
11 |
12 | export const fontUrlByFileBasename = {
13 | "Marianne-Light": marianneLightWoff2Url,
14 | "Marianne-Light_Italic": marianneItalicWoff2Url,
15 | "Marianne-Regular": marianneRegularWoff2Url,
16 | "Marianne-Regular_Italic": marianneRegularItalicWoff2Url,
17 | "Marianne-Medium": marianneMediumWoff2Url,
18 | "Marianne-Medium_Italic": marianneMediumItalicWoff2Url,
19 | "Marianne-Bold": marianneBoldWoff2Url,
20 | "Marianne-Bold_Italic": marianneBoldItalicWoff2Url,
21 | "Spectral-Regular": spectralRegularWoff2Url,
22 | "Spectral-ExtraBold": spectralExtraBoldWoff2Url
23 | } as const;
24 |
--------------------------------------------------------------------------------
/src/picto/Error.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { createIcon } from "./utils/IconWrapper";
3 |
4 | export default createIcon(
5 | <>
6 |
10 |
14 |
18 |
22 | >,
23 | "Error"
24 | );
25 |
--------------------------------------------------------------------------------
/src/picto/Success.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { createIcon } from "./utils/IconWrapper";
3 |
4 | export default createIcon(
5 | <>
6 |
12 |
16 |
20 |
24 | >,
25 | "Success"
26 | );
27 |
--------------------------------------------------------------------------------
/src/picto/Warning.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { createIcon } from "./utils/IconWrapper";
3 |
4 | export default createIcon(
5 | <>
6 |
10 |
14 |
18 |
22 |
26 | >,
27 | "Warning"
28 | );
29 |
--------------------------------------------------------------------------------
/src/picto/pictogrammes-svg/Datalma.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/picto/pictogrammes-svg/Document.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/picto/pictogrammes-svg/Error.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/picto/pictogrammes-svg/Moon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/picto/pictogrammes-svg/Success.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/picto/pictogrammes-svg/Warning.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/picto/utils/IconWrapper.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from "react";
2 |
3 | type IconSize = "small" | "medium" | "large" | "inherit" | (string & {});
4 | export type IconProps = {
5 | fontSize?: IconSize;
6 | } & Omit, "fontSize">;
7 |
8 | const getSize = (size: IconSize) => {
9 | switch (size) {
10 | case "small":
11 | return "1.25em";
12 | case "medium":
13 | return "1.5em";
14 | case "large":
15 | return "2.5em";
16 | case "inherit":
17 | return "inherit";
18 | default:
19 | return size;
20 | }
21 | };
22 |
23 | export const IconWrapper: React.FC = memo(
24 | ({ children, fontSize = "medium", ...props }) => (
25 |
36 | {children}
37 |
38 | )
39 | );
40 |
41 | export function createIcon(SvgPath: JSX.Element, displayName: string) {
42 | const IconComponent: React.FC = props => (
43 | {SvgPath}
44 | );
45 |
46 | IconComponent.displayName = displayName;
47 | return IconComponent;
48 | }
49 |
--------------------------------------------------------------------------------
/src/start.ts:
--------------------------------------------------------------------------------
1 | import { isBrowser } from "./tools/isBrowser";
2 | import { assert } from "tsafe/assert";
3 | import type { ColorScheme } from "./useIsDark";
4 | import { startClientSideIsDarkLogic } from "./useIsDark/client";
5 | import { Deferred } from "./tools/Deferred";
6 |
7 | type Params = {
8 | defaultColorScheme: ColorScheme | "system";
9 | verbose: boolean;
10 | nextParams:
11 | | {
12 | doPersistDarkModePreferenceWithCookie: boolean;
13 | registerEffectAction: (effect: () => void) => void;
14 | }
15 | | undefined;
16 | doCheckNonce: boolean;
17 | trustedTypesPolicyName: string;
18 | };
19 |
20 | let isStarted = false;
21 |
22 | export async function start(params: Params) {
23 | const { defaultColorScheme, verbose, nextParams, doCheckNonce, trustedTypesPolicyName } =
24 | params;
25 |
26 | assert(isBrowser);
27 |
28 | if (isStarted) {
29 | return;
30 | }
31 |
32 | isStarted = true;
33 |
34 | const registerEffectAction: (action: () => void) => void =
35 | nextParams === undefined ? action => action() : nextParams.registerEffectAction;
36 |
37 | startClientSideIsDarkLogic({
38 | "colorSchemeExplicitlyProvidedAsParameter": defaultColorScheme,
39 | "doPersistDarkModePreferenceWithCookie":
40 | nextParams === undefined ? false : nextParams.doPersistDarkModePreferenceWithCookie,
41 | registerEffectAction,
42 | doCheckNonce,
43 | trustedTypesPolicyName
44 | });
45 |
46 | // @ts-expect-error
47 | window.dsfr = {
48 | verbose,
49 | "mode": "react"
50 | };
51 |
52 | // @ts-expect-error
53 | await import("./dsfr/dsfr.module");
54 |
55 | dDsfrLoaded.resolve();
56 |
57 | registerEffectAction(() => {
58 | // @ts-expect-error
59 | window.dsfr.start();
60 | });
61 | }
62 |
63 | const dDsfrLoaded = new Deferred();
64 |
65 | export const prDsfrLoaded = dDsfrLoaded.pr;
66 |
--------------------------------------------------------------------------------
/src/tools/Deferred.ts:
--------------------------------------------------------------------------------
1 | import { overwriteReadonlyProp } from "tsafe/lab/overwriteReadonlyProp";
2 |
3 | export class Deferred {
4 | public readonly pr: Promise;
5 |
6 | /** NOTE: Does not need to be called bound to instance*/
7 | public readonly resolve: (value: T) => void;
8 | public readonly reject: (error: any) => void;
9 |
10 | constructor() {
11 | let resolve!: (value: T) => void;
12 | let reject!: (error: any) => void;
13 |
14 | this.pr = new Promise((resolve_, reject_) => {
15 | resolve = value => {
16 | overwriteReadonlyProp(this, "isPending", false);
17 | resolve_(value);
18 | };
19 |
20 | reject = error => {
21 | overwriteReadonlyProp(this, "isPending", false);
22 | reject_(error);
23 | };
24 | });
25 |
26 | this.resolve = resolve;
27 | this.reject = reject;
28 | }
29 |
30 | public readonly isPending: boolean = true;
31 | }
32 |
33 | export namespace Deferred {
34 | export type Unpack> = T extends Deferred ? U : never;
35 | }
36 |
37 | export class VoidDeferred extends Deferred {
38 | public declare readonly resolve: () => void;
39 | }
40 |
--------------------------------------------------------------------------------
/src/tools/JSX.ts:
--------------------------------------------------------------------------------
1 | import type { ReactElement } from "react";
2 |
3 | export namespace JSX {
4 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
5 | export interface Element extends ReactElement {}
6 | }
7 |
--------------------------------------------------------------------------------
/src/tools/StatefulObservable/README.md:
--------------------------------------------------------------------------------
1 | `StatefulObservable` is a construct that allow to avoid having to depend on [EVT](https://evt.land).
2 |
3 | A `StatefulObservable` can be used to implement a signal: Wire a non react-land function to a react component.
4 | Example:
5 |
6 | ```tsx
7 | import { createStatefulObservable, useRerenderOnChange } from "tools/StatefulObservable";
8 |
9 | const $counter = createStatefulObservable(() => 0);
10 |
11 | export function incrementCounter() {
12 | $counter.current++;
13 | }
14 |
15 | export function Counter() {
16 | useRerenderOnChange($counter);
17 |
18 | const counter = $counter.current;
19 |
20 | return Counter: {counter} ;
21 | }
22 | ```
23 |
24 | WARNING: Unlike `StatefulEvt`, `StatefulObservable` do not post when we first attach.
25 | If the current value was not yet evaluated `next()` is called on the initial value returned by the function that
26 | returns it.
27 |
--------------------------------------------------------------------------------
/src/tools/StatefulObservable/StatefulObservable.ts:
--------------------------------------------------------------------------------
1 | import { assert, is } from "tsafe/assert";
2 |
3 | export type StatefulObservable = {
4 | current: T;
5 | subscribe: (next: (data: T) => void) => Subscription;
6 | };
7 |
8 | export type Subscription = {
9 | unsubscribe(): void;
10 | };
11 |
12 | export function createStatefulObservable(getInitialValue: () => T): StatefulObservable {
13 | const nextFunctions: ((data: T) => void)[] = [];
14 |
15 | const { get, set } = (() => {
16 | let wrappedState: [T] | undefined = undefined;
17 |
18 | function set(data: T) {
19 | wrappedState = [data];
20 |
21 | nextFunctions.forEach(next => next(data));
22 | }
23 |
24 | return {
25 | "get": () => {
26 | if (wrappedState === undefined) {
27 | set(getInitialValue());
28 | assert(!is(wrappedState));
29 | }
30 | return wrappedState[0];
31 | },
32 | set
33 | };
34 | })();
35 |
36 | return Object.defineProperty(
37 | {
38 | "current": null as any as T,
39 | "subscribe": (next: (data: T) => void) => {
40 | nextFunctions.push(next);
41 |
42 | return {
43 | "unsubscribe": () => nextFunctions.splice(nextFunctions.indexOf(next), 1)
44 | };
45 | }
46 | },
47 | "current",
48 | {
49 | "enumerable": true,
50 | get,
51 | set
52 | }
53 | );
54 | }
55 |
--------------------------------------------------------------------------------
/src/tools/StatefulObservable/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./useObservable";
2 | export * from "./useRerenderOnChange";
3 |
--------------------------------------------------------------------------------
/src/tools/StatefulObservable/hooks/useObservable.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import type { Subscription } from "../StatefulObservable";
3 |
4 | /**
5 | * Equivalent of https://docs.evt.land/api/react-hooks
6 | */
7 | export function useObservable(
8 | effect: (params: { registerSubscription: (subscription: Subscription) => void }) => void,
9 | deps: React.DependencyList
10 | ): void {
11 | useEffect(() => {
12 | const subscriptions: Subscription[] = [];
13 |
14 | effect({
15 | "registerSubscription": subscription => subscriptions.push(subscription)
16 | });
17 |
18 | return () => {
19 | subscriptions.forEach(subscription => subscription.unsubscribe());
20 | subscriptions.length = 0;
21 | };
22 | }, deps);
23 | }
24 |
--------------------------------------------------------------------------------
/src/tools/StatefulObservable/hooks/useRerenderOnChange.ts:
--------------------------------------------------------------------------------
1 | import { useObservable } from "./useObservable";
2 | import { useState } from "react";
3 | import type { StatefulObservable } from "../StatefulObservable";
4 |
5 | /**
6 | * Equivalent of https://docs.evt.land/api/react-hooks
7 | * */
8 | export function useRerenderOnChange($: StatefulObservable): void {
9 | //NOTE: We use function in case the state is a function
10 | const [, setCurrent] = useState(() => $.current);
11 |
12 | useObservable(
13 | ({ registerSubscription }) => {
14 | const subscription = $.subscribe(current => setCurrent(() => current));
15 | registerSubscription(subscription);
16 | },
17 | [$]
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/src/tools/StatefulObservable/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./StatefulObservable";
2 | export * from "./hooks";
3 |
--------------------------------------------------------------------------------
/src/tools/UnpackProps.ts:
--------------------------------------------------------------------------------
1 | export type UnpackProps = T extends React.ComponentType ? P : never;
2 |
--------------------------------------------------------------------------------
/src/tools/cx.ts:
--------------------------------------------------------------------------------
1 | import { assert } from "tsafe/assert";
2 | import { typeGuard } from "tsafe/typeGuard";
3 |
4 | export type CxArg =
5 | | undefined
6 | | null
7 | | string
8 | | boolean
9 | | Partial>
10 | | readonly CxArg[];
11 |
12 | export const cx = (...args: CxArg[]): string => {
13 | const len = args.length;
14 | let i = 0;
15 | let cls = "";
16 | for (; i < len; i++) {
17 | const arg = args[i];
18 | if (arg == null) continue;
19 |
20 | let toAdd;
21 | switch (typeof arg) {
22 | case "boolean":
23 | break;
24 | case "object": {
25 | if (Array.isArray(arg)) {
26 | toAdd = cx(...arg);
27 | } else {
28 | assert(!typeGuard<{ length: number }>(arg, false));
29 |
30 | toAdd = "";
31 | for (const k in arg) {
32 | if (arg[k as string] && k) {
33 | toAdd && (toAdd += " ");
34 | toAdd += k;
35 | }
36 | }
37 | }
38 | break;
39 | }
40 | default: {
41 | toAdd = arg;
42 | }
43 | }
44 | if (toAdd) {
45 | cls && (cls += " ");
46 | cls += toAdd;
47 | }
48 | }
49 | return cls;
50 | };
51 |
--------------------------------------------------------------------------------
/src/tools/deepAssign.ts:
--------------------------------------------------------------------------------
1 | import { assert, is } from "tsafe/assert";
2 | import { structuredCloneButFunctions } from "./structuredCloneButFunctions";
3 |
4 | /** NOTE: Array a copied over, not merged. */
5 | export function deepAssign(params: {
6 | target: Record;
7 | source: Record;
8 | }): void {
9 | const { target, source } = params;
10 |
11 | Object.keys(source).forEach(key => {
12 | const dereferencedSource = source[key];
13 |
14 | if (dereferencedSource === undefined) {
15 | delete target[key];
16 | return;
17 | }
18 |
19 | if (dereferencedSource instanceof Date) {
20 | assign({
21 | target,
22 | key,
23 | value: new Date(dereferencedSource.getTime())
24 | });
25 |
26 | return;
27 | }
28 |
29 | if (dereferencedSource instanceof Array) {
30 | assign({
31 | target,
32 | key,
33 | value: structuredCloneButFunctions(dereferencedSource)
34 | });
35 |
36 | return;
37 | }
38 |
39 | if (dereferencedSource instanceof Function || !(dereferencedSource instanceof Object)) {
40 | assign({
41 | target,
42 | key,
43 | value: dereferencedSource
44 | });
45 |
46 | return;
47 | }
48 |
49 | if (!(target[key] instanceof Object)) {
50 | target[key] = {};
51 | }
52 |
53 | const dereferencedTarget = target[key];
54 |
55 | assert(is>(dereferencedTarget));
56 | assert(is>(dereferencedSource));
57 |
58 | deepAssign({
59 | target: dereferencedTarget,
60 | source: dereferencedSource
61 | });
62 | });
63 | }
64 |
65 | function assign(params: { target: Record; key: string; value: unknown }): void {
66 | const { target, key, value } = params;
67 |
68 | Object.defineProperty(target, key, {
69 | enumerable: true,
70 | writable: true,
71 | configurable: true,
72 | value
73 | });
74 | }
75 |
--------------------------------------------------------------------------------
/src/tools/deepCopy.ts:
--------------------------------------------------------------------------------
1 | /** Assert obj is serializable */
2 | export function deepCopy(obj: T): T {
3 | return JSON.parse(JSON.stringify(obj));
4 | }
5 |
--------------------------------------------------------------------------------
/src/tools/generateValidHtmlId.ts:
--------------------------------------------------------------------------------
1 | import type { ReactNode } from "react";
2 |
3 | export function generateValidHtmlId(params: {
4 | text: ReactNode;
5 | fallback?: string | number;
6 | }): string {
7 | const { text, fallback } = params;
8 |
9 | if (typeof text !== "string" || text === "") {
10 | return fallback !== undefined ? `-${fallback}` : "";
11 | }
12 |
13 | // Remove any space characters
14 | let result = text.replace(/\s+/g, "");
15 |
16 | // Replace any non-alphanumeric characters with underscores
17 | result = result.replace(/[^a-zA-Z0-9]/g, "_");
18 |
19 | return `-${result}`;
20 | }
21 |
--------------------------------------------------------------------------------
/src/tools/getAssetUrl.ts:
--------------------------------------------------------------------------------
1 | export function getAssetUrl(componentOrUrl: { src: string } | string): string {
2 | return typeof componentOrUrl === "string" ? componentOrUrl : componentOrUrl.src;
3 | }
4 |
--------------------------------------------------------------------------------
/src/tools/getBaseFontSizePx.ts:
--------------------------------------------------------------------------------
1 | import { assert } from "tsafe/assert";
2 | import { isBrowser } from "./isBrowser";
3 |
4 | export function getBaseFontSizePx(): number {
5 | if (!isBrowser) {
6 | return 16;
7 | }
8 |
9 | const htmlElement = document.querySelector("html");
10 |
11 | assert(htmlElement !== null);
12 |
13 | const computedStyle = window.getComputedStyle(htmlElement);
14 | const fontSize = computedStyle.getPropertyValue("font-size");
15 |
16 | const fontSizeInPixels = parseFloat(fontSize);
17 |
18 | return fontSizeInPixels;
19 | }
20 |
--------------------------------------------------------------------------------
/src/tools/isBrowser.ts:
--------------------------------------------------------------------------------
1 | export const isBrowser = typeof window === "object" && typeof document === "object";
2 |
--------------------------------------------------------------------------------
/src/tools/memoize.ts:
--------------------------------------------------------------------------------
1 | type SimpleType = number | string | boolean | null | undefined;
2 | type FuncWithSimpleParams = (...args: T) => R;
3 |
4 | export function memoize(
5 | fn: FuncWithSimpleParams,
6 | options?: {
7 | argsLength?: number;
8 | max?: number;
9 | }
10 | ): FuncWithSimpleParams {
11 | const cache = new Map>>();
12 |
13 | const { argsLength = fn.length, max = Infinity } = options ?? {};
14 |
15 | return ((...args: Parameters>) => {
16 | const key = JSON.stringify(
17 | args
18 | .slice(0, argsLength)
19 | .map(v => {
20 | if (v === null) {
21 | return "null";
22 | }
23 | if (v === undefined) {
24 | return "undefined";
25 | }
26 | switch (typeof v) {
27 | case "number":
28 | return `number-${v}`;
29 | case "string":
30 | return `string-${v}`;
31 | case "boolean":
32 | return `boolean-${v ? "true" : "false"}`;
33 | }
34 | })
35 | .join("-sIs9sAslOdeWlEdIos3-")
36 | );
37 |
38 | if (cache.has(key)) {
39 | return cache.get(key);
40 | }
41 |
42 | if (max === cache.size) {
43 | for (const key of cache.keys()) {
44 | cache.delete(key);
45 | break;
46 | }
47 | }
48 |
49 | const value = fn(...args);
50 |
51 | cache.set(key, value);
52 |
53 | return value;
54 | }) as any;
55 | }
56 |
--------------------------------------------------------------------------------
/src/tools/partition.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Creates an array of elements split into two groups, the first of which contains elements
3 | * `predicate` returns truthy for, the second of which contains elements `predicate` returns
4 | * falsy for. The predicate is invoked with one argument: (value).
5 | */
6 | export function partition(arr: T[], predicate: (value: T) => boolean): [T[], T[]] {
7 | return [arr.filter(value => predicate(value)), arr.filter(item => !predicate(item))];
8 | }
9 |
--------------------------------------------------------------------------------
/src/tools/powerhooks/useCallbackFactory.ts:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from "react";
2 | import { id } from "tsafe/id";
3 | import { memoize } from "../memoize";
4 |
5 | export type CallbackFactory = (
6 | ...factoryArgs: FactoryArgs
7 | ) => (...args: Args) => R;
8 |
9 | /**
10 | * https://docs.powerhooks.dev/api-reference/usecallbackfactory
11 | *
12 | * const callbackFactory= useCallbackFactory(
13 | * ([key]: [string], [params]: [{ foo: number; }]) => {
14 | * ...
15 | * },
16 | * []
17 | * );
18 | *
19 | * WARNING: Factory args should not be of variable length.
20 | *
21 | */
22 | export function useCallbackFactory<
23 | FactoryArgs extends (string | number | boolean)[],
24 | Args extends unknown[],
25 | R = void
26 | >(callback: (...callbackArgs: [FactoryArgs, Args]) => R): CallbackFactory {
27 | type Out = CallbackFactory;
28 |
29 | const callbackRef = useRef(callback);
30 |
31 | callbackRef.current = callback;
32 |
33 | const memoizedRef = useRef(undefined);
34 |
35 | return useState(() =>
36 | id((...factoryArgs) => {
37 | if (memoizedRef.current === undefined) {
38 | memoizedRef.current = memoize(
39 | (...factoryArgs: FactoryArgs) =>
40 | (...args: Args) =>
41 | callbackRef.current(factoryArgs, args),
42 | { "argsLength": factoryArgs.length }
43 | );
44 | }
45 |
46 | return memoizedRef.current(...factoryArgs);
47 | })
48 | )[0];
49 | }
50 |
--------------------------------------------------------------------------------
/src/tools/powerhooks/useConst.ts:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | /**
4 | * Compute a value on first render and never again,
5 | * Equivalent of const [x] = useState(()=> ...)
6 | */
7 | export function useConst(getValue: () => T): T {
8 | const [value] = useState(getValue);
9 | return value;
10 | }
11 |
--------------------------------------------------------------------------------
/src/tools/powerhooks/useConstCallback.ts:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from "react";
2 | import { Parameters } from "tsafe/Parameters";
3 |
4 | /** https://stackoverflow.com/questions/65890278/why-cant-usecallback-always-return-the-same-ref */
5 | export function useConstCallback unknown) | undefined | null>(
6 | callback: NonNullable
7 | ): T {
8 | const callbackRef = useRef(null as any);
9 |
10 | callbackRef.current = callback;
11 |
12 | return useState(
13 | () =>
14 | (...args: Parameters) =>
15 | callbackRef.current(...args)
16 | )[0] as T;
17 | }
18 |
--------------------------------------------------------------------------------
/src/tools/powerhooks/useNamedState.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { capitalize } from "tsafe/capitalize";
3 | import type { Dispatch, SetStateAction } from "react";
4 |
5 | export type UseNamedStateReturnType = Record &
6 | Record<`set${Capitalize}`, Dispatch>>;
7 |
8 | export function useNamedState(
9 | name: Name,
10 | initialState: T | (() => T)
11 | ): UseNamedStateReturnType {
12 | const [state, setState] = useState(initialState);
13 |
14 | return {
15 | [name]: state,
16 | [`set${capitalize(name)}`]: setState
17 | } as any;
18 | }
19 |
--------------------------------------------------------------------------------
/src/tools/signal.ts:
--------------------------------------------------------------------------------
1 | import { useConstCallback } from "./powerhooks/useConstCallback";
2 | import { overwriteReadonlyProp } from "tsafe/lab/overwriteReadonlyProp";
3 | import type { UseNamedStateReturnType } from "./powerhooks/useNamedState";
4 | import { typeGuard } from "tsafe/typeGuard";
5 | import { capitalize } from "tsafe/capitalize";
6 | import {
7 | createStatefulObservable,
8 | useRerenderOnChange,
9 | type StatefulObservable
10 | } from "./StatefulObservable";
11 |
12 | export function createSignal(params: {
13 | name: Name;
14 | initialValue: T;
15 | }): Record<`use${Capitalize}`, () => UseNamedStateReturnType> &
16 | Record<`$${Name}`, StatefulObservable> {
17 | const { name, initialValue } = params;
18 |
19 | const $xyz = createStatefulObservable(() => initialValue);
20 |
21 | function useXyz() {
22 | useRerenderOnChange($xyz);
23 |
24 | return {
25 | [name]: $xyz.current,
26 | [`set${capitalize(name)}`]: useConstCallback(
27 | (setStateAction: T | ((prevState: T) => T)) =>
28 | ($xyz.current = typeGuard<(prevState: T) => T>(
29 | setStateAction,
30 | typeof setStateAction === "function"
31 | )
32 | ? setStateAction($xyz.current)
33 | : setStateAction)
34 | )
35 | } as any;
36 | }
37 |
38 | overwriteReadonlyProp(useXyz as any, "name", `use${capitalize(name)}`);
39 |
40 | return {
41 | [useXyz.name]: useXyz,
42 | [`$${name}`]: $xyz
43 | } as any;
44 | }
45 |
--------------------------------------------------------------------------------
/src/tools/structuredCloneButFunctions.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Functionally equivalent to structuredClone but
3 | * functions are not cloned but kept as is.
4 | * (as opposed to structuredClone that chokes if it encounters a function)
5 | */
6 | export function structuredCloneButFunctions(
7 | o: T,
8 | replacer?: (params: { key: string; value: unknown }) => unknown
9 | ): T {
10 | if (!(o instanceof Object)) {
11 | return o;
12 | }
13 |
14 | if (typeof o === "function") {
15 | return o;
16 | }
17 |
18 | if (o instanceof Array) {
19 | return o.map(o => structuredCloneButFunctions(o, replacer)) as any;
20 | }
21 |
22 | return Object.fromEntries(
23 | Object.entries(o).map(([key, value]) => [
24 | key,
25 | structuredCloneButFunctions(
26 | replacer === undefined ? value : replacer({ key, value }),
27 | replacer
28 | )
29 | ])
30 | ) as any;
31 | }
32 |
--------------------------------------------------------------------------------
/src/tools/useAnalyticsId.ts:
--------------------------------------------------------------------------------
1 | import { useId } from "react";
2 |
3 | /**
4 | * Eulerian analytics requires every element to have a unique ID.
5 | * This hook help generate such an ID in the case they are not explicitly provided.
6 | */
7 | export function useAnalyticsId(params: { explicitlyProvidedId?: string; defaultIdPrefix: string }) {
8 | const { explicitlyProvidedId, defaultIdPrefix } = params;
9 |
10 | const id = useId();
11 |
12 | return explicitlyProvidedId ?? `${defaultIdPrefix}-${id}`;
13 | }
14 |
--------------------------------------------------------------------------------
/src/tools/useWindowInnerSize.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useReducer } from "react";
2 |
3 | export function useWindowInnerSize() {
4 | const [, triggerRerender] = useReducer(() => ({}), {});
5 |
6 | useEffect(() => {
7 | const handleResize = () => triggerRerender();
8 |
9 | window.addEventListener("resize", handleResize);
10 |
11 | return () => window.removeEventListener("resize", handleResize);
12 | }, []);
13 |
14 | return {
15 | "windowInnerWidth": window.innerWidth,
16 | "windowInnerHeight": window.innerHeight
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/src/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsproject.json",
3 | "compilerOptions": {
4 | "outDir": "../dist/",
5 | "rootDir": ".",
6 | "module": "ES2020",
7 | "target": "ES2017",
8 | "lib": ["es2015", "DOM", "ES2019.Object"],
9 | "moduleResolution": "node",
10 | "jsx": "react",
11 | "allowSyntheticDefaultImports": true
12 | },
13 | "exclude": ["./bin"]
14 | }
15 |
--------------------------------------------------------------------------------
/src/tss.ts:
--------------------------------------------------------------------------------
1 | import { useColors } from "./useColors";
2 | import { createMakeAndWithStyles } from "tss-react";
3 |
4 | /** @deprecated: Please use import { makeStyles } from "tss-react/dsfr"; instead. */
5 | export const { makeStyles, withStyles, useStyles } = createMakeAndWithStyles({
6 | "useTheme": useColors
7 | });
8 |
--------------------------------------------------------------------------------
/src/useBreakpointsValues.ts:
--------------------------------------------------------------------------------
1 | import { type BreakpointKeys, type BreakpointsValues } from "./fr/breakpoints";
2 | import { useBreakpointsValuesPx } from "./useBreakpointsValuesPx";
3 |
4 | export type { BreakpointKeys, BreakpointsValues };
5 |
6 | /** @deprecated Use import { useBreakpointsValuesPx } from "@codegouvfr/react-dsfr/useBreakpointsValuesPx"; instead */
7 | export const useBreakpointsValues = useBreakpointsValuesPx;
8 |
--------------------------------------------------------------------------------
/src/useBreakpointsValuesPx.ts:
--------------------------------------------------------------------------------
1 | import { useReducer, useEffect, useMemo } from "react";
2 | import { assert } from "tsafe/assert";
3 | import { breakpoints, type BreakpointKeys, type BreakpointsValues } from "./fr/breakpoints";
4 |
5 | export type { BreakpointKeys, BreakpointsValues };
6 |
7 | /** Return the breakpoint values in px, the values ger refreshed
8 | * when the base font size change. */
9 | export function useBreakpointsValuesPx() {
10 | const [breakpointsValuesDependency, triggerRefresh] = useReducer(() => ({}), {});
11 |
12 | useEffect(() => {
13 | const htmlElement = document.querySelector("html");
14 |
15 | assert(htmlElement !== null);
16 |
17 | // Create a new MutationObserver to detect changes in the base font size
18 | const observer = new MutationObserver(mutationsList => {
19 | if (
20 | mutationsList.find(
21 | mutation =>
22 | mutation.target === htmlElement &&
23 | mutation.attributeName === "style" &&
24 | (mutation.oldValue ?? "").indexOf("font-size") !== -1
25 | ) !== undefined
26 | ) {
27 | triggerRefresh();
28 | }
29 | });
30 |
31 | // Observe changes to the style attribute of the html element
32 | observer.observe(htmlElement, {
33 | "attributes": true,
34 | "attributeOldValue": true,
35 | "attributeFilter": ["style"]
36 | });
37 |
38 | return () => {
39 | observer.disconnect();
40 | };
41 | }, []);
42 |
43 | const breakpointsValues = useMemo(
44 | () => breakpoints.getPxValues(),
45 | [breakpointsValuesDependency]
46 | );
47 |
48 | return { breakpointsValues };
49 | }
50 |
--------------------------------------------------------------------------------
/src/useColors.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useIsDark } from "./useIsDark";
4 | import { fr, type ColorOptions, type ColorDecisions } from "./fr";
5 |
6 | export type ColorTheme = {
7 | isDark: boolean;
8 | options: ColorOptions<"css var">;
9 | decisions: ColorDecisions<"css var">;
10 | };
11 |
12 | /**
13 | * A hook is no longer required to get the colors, this will soon be deprecated
14 | * when the documentation is updated to reflect the new way of getting the colors.
15 | *
16 | * Before you would do:
17 | * ```ts
18 | * import { useColors } from "@codegouvfr/react-dsfr/useColors";
19 | * // ...
20 | * const theme = useColors();
21 | * // ...
22 | * theme.decisions.background.default.grey.default
23 | * ```
24 | * Now you should do:
25 | * ```ts
26 | * import { fr } from "@codegouvfr/react-dsfr";
27 | * // ...
28 | * fr.colors.decisions.background.default.grey.default
29 | * ```
30 | * We don't need a hook anymore as the the colors are expressed as CSS variables and thus don't need to be
31 | * switched at runtime when the user changes the dark mode.
32 | *
33 | * If however you need the colors in the HEX format you can do:
34 | *
35 | * ```ts
36 | * import { fr } from "@codegouvfr/react-dsfr";
37 | * import { useIsDark } from "@codegouvfr/react-dsfr/useIsDark";
38 | * // ...
39 | * const { isDark } = useIsDark();
40 | * const theme = fr.colors.getHex({ isDark });
41 | * // ...
42 | * theme.decisions.background.default.grey.default
43 | * ```
44 | **/
45 | export function useColors(): ColorTheme {
46 | const { isDark } = useIsDark();
47 |
48 | return {
49 | isDark,
50 | "options": fr.colors.options,
51 | "decisions": fr.colors.decisions
52 | };
53 | }
54 |
--------------------------------------------------------------------------------
/src/useIsDark/constants.ts:
--------------------------------------------------------------------------------
1 | export const data_fr_theme = "data-fr-theme";
2 | export const data_fr_scheme = "data-fr-scheme";
3 |
4 | export const rootColorSchemeStyleTagId = "dsfr-root-color-scheme";
5 |
--------------------------------------------------------------------------------
/src/useIsDark/index.ts:
--------------------------------------------------------------------------------
1 | import { assert } from "tsafe/assert";
2 | import { isBrowser } from "../tools/isBrowser";
3 | import { useIsDarkServerSide } from "./server";
4 | import { useIsDarkClientSide, getIsDarkClientSide } from "./client";
5 | export type { ColorScheme } from "./client";
6 |
7 | export const useIsDark = isBrowser ? useIsDarkClientSide : useIsDarkServerSide;
8 |
9 | export const getIsDark = () => {
10 | assert(isBrowser, "getIsDark can only be used on the client side");
11 |
12 | return getIsDarkClientSide();
13 | };
14 |
--------------------------------------------------------------------------------
/src/useIsDark/server.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { createContext, useContext } from "react";
4 | import type { UseIsDark } from "./client";
5 | import { useConstCallback } from "../tools/powerhooks/useConstCallback";
6 | import { assert } from "tsafe/assert";
7 |
8 | const ssrIsDarkContext = createContext(undefined);
9 |
10 | export const { Provider: SsrIsDarkProvider } = ssrIsDarkContext;
11 |
12 | export const useIsDarkServerSide: UseIsDark = () => {
13 | const setIsDark = useConstCallback(() => {
14 | /* nothing */
15 | });
16 |
17 | const isDark = useContext(ssrIsDarkContext);
18 |
19 | assert(isDark !== undefined, "Not within provider");
20 |
21 | return {
22 | isDark,
23 | setIsDark
24 | };
25 | };
26 |
--------------------------------------------------------------------------------
/src/zz_internal/brandTopAndHomeLinkProps.ts:
--------------------------------------------------------------------------------
1 | import type { ReactNode } from "react";
2 | import type { RegisteredLinkProps } from "../link";
3 |
4 | let wrap:
5 | | {
6 | brandTop: ReactNode;
7 | homeLinkProps: RegisteredLinkProps & { title: string };
8 | }
9 | | undefined = undefined;
10 |
11 | export function setBrandTopAndHomeLinkProps(params: {
12 | brandTop: ReactNode;
13 | homeLinkProps: RegisteredLinkProps & { title: string };
14 | }) {
15 | wrap = params;
16 | }
17 |
18 | export function getBrandTopAndHomeLinkProps() {
19 | return wrap;
20 | }
21 |
--------------------------------------------------------------------------------
/stories/AgentConnectButton.stories.tsx:
--------------------------------------------------------------------------------
1 | import { AgentConnectButton } from "../dist/AgentConnectButton";
2 | import { sectionName } from "./sectionName";
3 | import { getStoryFactory, logCallbacks } from "./getStory";
4 |
5 | const { meta, getStory } = getStoryFactory({
6 | sectionName,
7 | "wrappedComponent": { AgentConnectButton },
8 | "description": `
9 | - [See AgentConnect documentation](https://github.com/france-connect/Documentation-AgentConnect/blob/main/doc_fs/implementation_fca/bouton_fca.md)
10 | - [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/AgentConnectButton.tsx)`
11 | });
12 |
13 | export default meta;
14 |
15 | export const Default = getStory({
16 | "url": "https://example.com"
17 | });
18 |
19 | export const Centered = getStory({
20 | "style": {
21 | "textAlign": "center"
22 | },
23 | "url": "https://example.com"
24 | });
25 |
26 | export const WithOnClick = getStory({
27 | ...logCallbacks(["onClick"])
28 | });
29 |
--------------------------------------------------------------------------------
/stories/Breadcrumb.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Breadcrumb } from "../dist/Breadcrumb";
2 | import { sectionName } from "./sectionName";
3 | import { getStoryFactory } from "./getStory";
4 |
5 | const { meta, getStory } = getStoryFactory({
6 | sectionName,
7 | "wrappedComponent": { Breadcrumb },
8 | "description": `
9 | - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/fil-d-ariane)
10 | - [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/Breadcrumb.tsx)`,
11 | "disabledProps": ["lang"]
12 | });
13 |
14 | export default meta;
15 |
16 | export const Default = getStory({
17 | "homeLinkProps": { "href": "/" },
18 | "segments": [
19 | {
20 | "label": "Segment 1",
21 | "linkProps": {
22 | "href": "/segment-1"
23 | }
24 | },
25 | {
26 | "label": "Segment 2",
27 | "linkProps": {
28 | "href": "/segment-1/segment-2"
29 | }
30 | },
31 | {
32 | "label": "Segment 3",
33 | "linkProps": {
34 | "href": "/segment-1/segment-2/segment-3"
35 | }
36 | },
37 | {
38 | "label": "Segment 4",
39 | "linkProps": {
40 | "href": "/segment-1/segment-2/segment-3/segment-4"
41 | }
42 | }
43 | ],
44 | "currentPageLabel": "Page Actuelle"
45 | });
46 |
--------------------------------------------------------------------------------
/stories/ColorHelper/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./ColorHelper";
2 |
--------------------------------------------------------------------------------
/stories/ColorHelper/tools/useQueryParams.ts:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | const getQuery = () => {
4 | if (typeof window !== "undefined") {
5 | return new URLSearchParams(window.location.search);
6 | }
7 | return new URLSearchParams();
8 | };
9 |
10 | const getQueryStringVal = (key: string): string | null => {
11 | return getQuery().get(key);
12 | };
13 |
14 | export const useQueryParam = (key: string, defaultVal: string): [string, (val: string) => void] => {
15 | const [query, setQuery] = useState(getQueryStringVal(key) || defaultVal);
16 |
17 | const updateUrl = (newVal: string) => {
18 | setQuery(newVal);
19 |
20 | const query = getQuery();
21 |
22 | if (newVal.trim() !== "") {
23 | query.set(key, newVal);
24 | } else {
25 | query.delete(key);
26 | }
27 |
28 | // This check is necessary if using the hook with Gatsby
29 | if (typeof window !== "undefined") {
30 | const { protocol, pathname, host } = window.location;
31 | const newUrl = `${protocol}//${host}${pathname}?${query.toString()}`;
32 | window.history.pushState({}, "", newUrl);
33 | }
34 | };
35 |
36 | return [query, updateUrl];
37 | };
38 |
39 | export default useQueryParam;
40 |
--------------------------------------------------------------------------------
/stories/Download.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Download } from "../dist/Download";
2 | import { sectionName } from "./sectionName";
3 | import { getStoryFactory } from "./getStory";
4 |
5 | const { meta, getStory } = getStoryFactory({
6 | sectionName,
7 | "wrappedComponent": { Download },
8 | description: `
9 | - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/telechargement-de-fichier)
10 | - [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/Download.tsx)`,
11 | "argTypes": {
12 | "label": {
13 | "description": `Required - the label of the anchor element. In case the file to download is in a different language than the current document one, it should contains the mention of the language.`
14 | },
15 | "details": {
16 | "description": `Required - informations about the file to download (size, format, etc.). `
17 | }
18 | }
19 | });
20 |
21 | export default meta;
22 |
23 | export const Default = getStory({
24 | "label": "Télécharger le document lorem ipsum sit dolores amet",
25 | "details": "JPG – 61,88 ko",
26 | linkProps: {
27 | href: "[À MODIFIER]"
28 | }
29 | });
30 |
--------------------------------------------------------------------------------
/stories/FranceConnectButton.stories.tsx:
--------------------------------------------------------------------------------
1 | import { FranceConnectButton } from "../dist/FranceConnectButton";
2 | import { sectionName } from "./sectionName";
3 | import { getStoryFactory, logCallbacks } from "./getStory";
4 |
5 | const { meta, getStory } = getStoryFactory({
6 | sectionName,
7 | "wrappedComponent": { FranceConnectButton },
8 | "description": `
9 | - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/composants-et-modeles/composants/bouton-franceconnect/)
10 | - [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/FranceConnectButton.tsx)`
11 | });
12 |
13 | export default meta;
14 |
15 | export const Default = getStory({
16 | "url": "https://example.com"
17 | });
18 |
19 | export const Plus = getStory({
20 | "url": "https://example.com",
21 | "plus": true
22 | });
23 |
24 | export const Centered = getStory({
25 | "style": {
26 | "textAlign": "center"
27 | },
28 | "url": "https://example.com"
29 | });
30 |
31 | export const WithOnClick = getStory({
32 | ...logCallbacks(["onClick"])
33 | });
34 |
--------------------------------------------------------------------------------
/stories/MonComptePro.stories.tsx:
--------------------------------------------------------------------------------
1 | import { MonCompteProButton } from "../dist/MonCompteProButton";
2 | import { sectionName } from "./sectionName";
3 | import { getStoryFactory, logCallbacks } from "./getStory";
4 |
5 | const { meta, getStory } = getStoryFactory({
6 | sectionName,
7 | "wrappedComponent": { MonCompteProButton },
8 | "description": `
9 | - [See MonComptePro documentation](https://github.com/betagouv/moncomptepro#sp%C3%A9cifications-visuelles)
10 | - [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/MonCompteProButton.tsx)`
11 | });
12 |
13 | export default meta;
14 |
15 | export const Default = getStory({
16 | "url": "https://example.com"
17 | });
18 |
19 | export const Centered = getStory({
20 | "style": {
21 | "textAlign": "center"
22 | },
23 | "url": "https://example.com"
24 | });
25 |
26 | export const WithOnClick = getStory({
27 | ...logCallbacks(["onClick"])
28 | });
29 |
--------------------------------------------------------------------------------
/stories/Pagination.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Pagination } from "../dist/Pagination";
2 | import { sectionName } from "./sectionName";
3 | import { getStoryFactory } from "./getStory";
4 |
5 | const { meta, getStory } = getStoryFactory({
6 | sectionName,
7 | "wrappedComponent": { Pagination },
8 | "description": `
9 | - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/pagination)
10 | - [See DSFR demos](https://main--ds-gouv.netlify.app/example/component/pagination/)
11 | - [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/Pagination.tsx)`,
12 | "disabledProps": ["lang"]
13 | });
14 |
15 | export default meta;
16 |
17 | export const Default = getStory({
18 | count: 100,
19 | defaultPage: 2,
20 | showFirstLast: true,
21 | getPageLinkProps: pageNumber => ({ href: `/page/${pageNumber}` })
22 | });
23 |
24 | export const SummaryWithNoPage = getStory({
25 | count: 0,
26 | getPageLinkProps: pageNumber => ({ href: `/page/${pageNumber}` })
27 | });
28 |
29 | export const SummaryWithSinglePage = getStory({
30 | count: 1,
31 | getPageLinkProps: pageNumber => ({ href: `/page/${pageNumber}` })
32 | });
33 |
34 | export const SummaryWith132Pages = getStory({
35 | count: 132,
36 | defaultPage: 42,
37 | getPageLinkProps: pageNumber => ({ href: `/page/${pageNumber}` })
38 | });
39 |
40 | export const SummaryWithoutShowFirstLast = getStory({
41 | count: 45,
42 | defaultPage: 42,
43 | showFirstLast: false,
44 | getPageLinkProps: pageNumber => ({ href: `/page/${pageNumber}` })
45 | });
46 |
47 | export const SummaryWithLastPage = getStory({
48 | count: 24,
49 | defaultPage: 24,
50 | getPageLinkProps: pageNumber => ({ href: `/page/${pageNumber}` })
51 | });
52 |
--------------------------------------------------------------------------------
/stories/Pictograms.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta } from "@storybook/addon-docs";
2 | import { Pictograms } from "./picto/Pictograms";
3 |
4 |
16 |
17 |
20 |
--------------------------------------------------------------------------------
/stories/ProConnectButton.stories.tsx:
--------------------------------------------------------------------------------
1 | import { ProConnectButton } from "../dist/ProConnectButton";
2 | import { sectionName } from "./sectionName";
3 | import { getStoryFactory, logCallbacks } from "./getStory";
4 |
5 | const { meta, getStory } = getStoryFactory({
6 | sectionName,
7 | "wrappedComponent": { ProConnectButton },
8 | "description": `
9 | - [See DSFR documentation](https://github.com/numerique-gouv/agentconnect-documentation/blob/main/doc_fs/bouton_proconnect.md)
10 | - [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/ProConnectButton.tsx)`
11 | });
12 |
13 | export default meta;
14 |
15 | export const Default = getStory({
16 | "url": "https://example.com"
17 | });
18 |
19 | export const WithOnClick = getStory({
20 | ...logCallbacks(["onClick"])
21 | });
22 |
--------------------------------------------------------------------------------
/stories/RotatingLogo.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { memo, useState } from "react";
3 | import { useConstCallback } from "powerhooks";
4 | import { keyframes, createMakeAndWithStyles } from "tss-react";
5 | import keycloakifyLogoHeroMovingPngUrl from "./assets/logo-in.png";
6 | import keycloakifyLogoHeroStillPngUrl from "./assets/logo-out.png";
7 |
8 | const { makeStyles } = createMakeAndWithStyles({
9 | "useTheme": () => ({})
10 | });
11 |
12 | export type Props = {
13 | style?: React.CSSProperties;
14 | id?: string;
15 | onLoad?: () => void;
16 | };
17 |
18 | export const RotatingLogo = memo((props: Props) => {
19 | const { id, style, onLoad: onLoadProp } = props;
20 |
21 | const [isImageLoaded, setIsImageLoaded] = useState(false);
22 |
23 | const onLoad = useConstCallback(() => {
24 | setIsImageLoaded(true);
25 | onLoadProp?.();
26 | });
27 |
28 | const { classes } = useStyles({
29 | isImageLoaded
30 | });
31 | return (
32 |
33 |
38 |
44 |
45 | );
46 | });
47 |
48 | const useStyles = makeStyles<{ isImageLoaded: boolean }>({
49 | "name": { RotatingLogo }
50 | })((_theme, { isImageLoaded }) => ({
51 | "root": {
52 | "position": "relative"
53 | },
54 | "rotatingImg": {
55 | "animation": `${keyframes({
56 | "from": {
57 | "transform": "rotate(0deg)"
58 | },
59 | "to": {
60 | "transform": "rotate(360deg)"
61 | }
62 | })} infinite 20s linear`,
63 | "width": isImageLoaded ? "100%" : undefined,
64 | "height": isImageLoaded ? "auto" : undefined
65 | },
66 | "stillImg": {
67 | "position": "absolute",
68 | "top": "0",
69 | "left": "0",
70 | "width": isImageLoaded ? "100%" : undefined,
71 | "height": isImageLoaded ? "auto" : undefined
72 | }
73 | }));
74 |
--------------------------------------------------------------------------------
/stories/SkipLinks.stories.tsx:
--------------------------------------------------------------------------------
1 | import { SkipLinks, type SkipLinksProps } from "../dist/SkipLinks";
2 | import { sectionName } from "./sectionName";
3 | import { getStoryFactory } from "./getStory";
4 |
5 | const { meta, getStory } = getStoryFactory({
6 | sectionName,
7 | wrappedComponent: { SkipLinks },
8 | description: `
9 | - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/lien-d-evitement)
10 | - [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/SkipLinks.tsx)`
11 | });
12 |
13 | export default meta;
14 |
15 | export const Default = getStory({
16 | links: [
17 | {
18 | label: "Contenu",
19 | anchor: "#contenu"
20 | },
21 | {
22 | label: "Menu",
23 | anchor: "#header-navigation"
24 | },
25 | {
26 | label: "Recherche",
27 | anchor: "#header-search"
28 | },
29 | {
30 | label: "Pied de page",
31 | anchor: "#footer"
32 | }
33 | ],
34 | classes: {
35 | root: "fr-mt-9v" // Just to fix storybook preview toolbar overlapping
36 | }
37 | });
38 |
--------------------------------------------------------------------------------
/stories/Stepper.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Stepper } from "../dist/Stepper";
2 | import { sectionName } from "./sectionName";
3 | import { getStoryFactory } from "./getStory";
4 |
5 | const { meta, getStory } = getStoryFactory({
6 | sectionName,
7 | "wrappedComponent": { Stepper },
8 | "description": `
9 | - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/indicateur-d-etapes)
10 | - [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/Stepper.tsx)`
11 | });
12 |
13 | export default meta;
14 |
15 | export const Default = getStory({
16 | "stepCount": 3,
17 | "currentStep": 1,
18 | "title": "Titre de l’étape en cours",
19 | "nextTitle": "Titre de la prochaine étape"
20 | });
21 |
22 | export const StepperWithoutNext = getStory({
23 | "stepCount": 4,
24 | "currentStep": 4,
25 | "title": "Titre de la dernière étape en cours"
26 | });
27 |
--------------------------------------------------------------------------------
/stories/TagsGroup.stories.tsx:
--------------------------------------------------------------------------------
1 | import { TagsGroup, TagsGroupProps } from "../dist/TagsGroup";
2 | import { sectionName } from "./sectionName";
3 | import { getStoryFactory } from "./getStory";
4 | import { TagProps } from "../dist/Tag";
5 |
6 | const { meta, getStory } = getStoryFactory({
7 | sectionName,
8 | "wrappedComponent": { TagsGroup },
9 | "description": `
10 | - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/composants-et-modeles/composants/tag)
11 | - [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/TagsGroup.tsx)`,
12 | "argTypes": {
13 | "smallTags": {
14 | "description": `
15 | Default: false, if true, the tags will be smaller.
16 | `,
17 | "control": { "type": "boolean" }
18 | },
19 | "tags": {
20 | "description": `An array of TagProps (at least 1, max recommended: 6).`,
21 | "control": { "type": null }
22 | }
23 | },
24 | "disabledProps": ["lang"],
25 | "defaultContainerWidth": 800
26 | });
27 |
28 | export default meta;
29 |
30 | const tagsWithProps = (props?: Omit) =>
31 | Array.from(
32 | { "length": 6 },
33 | (_, i) =>
34 | ({
35 | ...props,
36 | "children": `Libellé tag ${i + 1}`
37 | } as TagProps)
38 | ) as TagsGroupProps["tags"];
39 |
40 | export const Default = getStory({
41 | "tags": tagsWithProps()
42 | });
43 |
44 | export const SmallTags = getStory({
45 | "tags": tagsWithProps(),
46 | "smallTags": true
47 | });
48 |
49 | export const TagsAsAnchor = getStory({
50 | "tags": tagsWithProps({ "linkProps": { "href": "#" } })
51 | });
52 |
53 | export const SmallTagsAsAnchor = getStory({
54 | "tags": tagsWithProps({ "linkProps": { "href": "#" } }),
55 | "smallTags": true
56 | });
57 |
58 | export const TagsPressed = getStory({
59 | "tags": tagsWithProps({ "pressed": true })
60 | });
61 |
62 | export const SmallTagsPressed = getStory({
63 | "tags": tagsWithProps({ "pressed": true }),
64 | "smallTags": true
65 | });
66 |
67 | export const TagsDismissable = getStory({
68 | "tags": tagsWithProps({ "dismissible": true })
69 | });
70 |
71 | export const SmallTagsDismissable = getStory({
72 | "tags": tagsWithProps({ "dismissible": true }),
73 | "smallTags": true
74 | });
75 |
--------------------------------------------------------------------------------
/stories/Tooltip.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { assert, Equals } from "tsafe/assert";
3 |
4 | import { Tooltip, type TooltipProps } from "../src/Tooltip";
5 |
6 | import { sectionName } from "./sectionName";
7 | import { getStoryFactory } from "./getStory";
8 |
9 | const { meta, getStory } = getStoryFactory({
10 | sectionName,
11 | "wrappedComponent": { Tooltip },
12 | "description": `
13 | - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/infobulle)
14 | - [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/Tooltip.tsx)`,
15 | "argTypes": {
16 | "id": {
17 | "control": { "type": "text" },
18 | "description":
19 | "Optional: tootlip Id, which is also use as aria-describedby for hovered/clicked element"
20 | },
21 | "className": {
22 | "control": { "type": "text" },
23 | "description": "Optional"
24 | },
25 | "kind": {
26 | "control": { "type": "select" },
27 | "options": (() => {
28 | const options = ["hover", "click"] as const;
29 |
30 | assert>();
31 |
32 | return options;
33 | })(),
34 | "description": "Optional."
35 | },
36 | "title": {
37 | "control": { "type": "text" }
38 | },
39 | "children": {
40 | "control": { "type": "text" }
41 | }
42 | }
43 | });
44 |
45 | export default meta;
46 |
47 | const defaultOnHoverProps: TooltipProps.WithHoverAction = {
48 | "kind": "hover",
49 | "title": "lorem ipsum"
50 | };
51 |
52 | export const Default = getStory(defaultOnHoverProps);
53 |
54 | export const TooltipOnHover = getStory(defaultOnHoverProps);
55 |
56 | export const TooltipOnHoverWithChild = getStory({
57 | ...defaultOnHoverProps,
58 | children: Some link
59 | });
60 |
61 | export const TooltipOnClick = getStory({
62 | "kind": "click",
63 | "title": "lorem ipsum"
64 | });
65 |
--------------------------------------------------------------------------------
/stories/assets/logo-in.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codegouvfr/react-dsfr/320fd6280a0ffd343c28e051e1a1a1ec3db9af6f/stories/assets/logo-in.png
--------------------------------------------------------------------------------
/stories/assets/logo-out.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codegouvfr/react-dsfr/320fd6280a0ffd343c28e051e1a1a1ec3db9af6f/stories/assets/logo-out.png
--------------------------------------------------------------------------------
/stories/assets/placeholder.16x9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codegouvfr/react-dsfr/320fd6280a0ffd343c28e051e1a1a1ec3db9af6f/stories/assets/placeholder.16x9.png
--------------------------------------------------------------------------------
/stories/assets/placeholder.9x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codegouvfr/react-dsfr/320fd6280a0ffd343c28e051e1a1a1ec3db9af6f/stories/assets/placeholder.9x16.png
--------------------------------------------------------------------------------
/stories/blocks/PasswordInput.stories.tsx:
--------------------------------------------------------------------------------
1 | import PasswordInput from "../../dist/blocks/PasswordInput";
2 | import { getStoryFactory } from "../getStory";
3 | import { sectionName } from "./sectionName";
4 |
5 | const { meta, getStory } = getStoryFactory({
6 | sectionName,
7 | "wrappedComponent": { PasswordInput },
8 | "description": `\`import { PasswordInput } from "@codegouvfr/react-dsfr/blocks/PasswordInput"\`
9 |
10 | - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/mot-de-passe/)
11 | - [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/blocks/PasswordInput.tsx) `,
12 | "argTypes": {
13 | "disabled": {
14 | "control": { "type": "boolean" }
15 | },
16 | "nativeInputProps": {
17 | "description": `An object that is forwarded as props to te underlying native \` \` element.
18 | This is where you pass the \`name\` prop or \`onChange\` for example.`,
19 | "control": { "type": null }
20 | },
21 | "messagesHint": {
22 | "description": `The text that is displayed before the list of messages.
23 | Default to "Your password must contain:" (internationalized).
24 | If you pass an empty string, the hint block wont be displayed.`
25 | }
26 | },
27 | "doHideImportInstruction": true
28 | });
29 |
30 | export default meta;
31 |
32 | export const Default = getStory({
33 | "label": "Mot de passe"
34 | });
35 |
36 | export const WithHint = getStory({
37 | "label": "Mot de passe",
38 | /* spell-checker: disable */
39 | "hintText": "Texte de description additionnel"
40 | /* spell-checker: english */
41 | });
42 |
43 | export const WithMessagesGroup = getStory({
44 | "label": "Mot de passe",
45 | "messages": [
46 | /* spell-checker: disable */
47 | {
48 | "message": "12 caractères minimum",
49 | "severity": "info"
50 | },
51 | {
52 | "message": "1 caractère spécial minimum",
53 | "severity": "valid"
54 | },
55 | {
56 | "message": "1 chiffre minimum",
57 | "severity": "error"
58 | }
59 | /* spell-checker: enabled */
60 | ]
61 | });
62 |
63 | export const Disabled = getStory({
64 | "label": "Mot de passe",
65 | "disabled": true
66 | });
67 |
--------------------------------------------------------------------------------
/stories/blocks/sectionName.ts:
--------------------------------------------------------------------------------
1 | export const sectionName = "blocks";
2 |
--------------------------------------------------------------------------------
/stories/charts/GaugeChart.stories.tsx:
--------------------------------------------------------------------------------
1 | import { GaugeChart, type GaugeChartProps } from "../../dist/Chart/GaugeChart";
2 | import { getStoryFactory } from "../getStory";
3 | import { sectionName } from "./sectionName";
4 |
5 | const { meta, getStory } = getStoryFactory({
6 | sectionName: sectionName,
7 | "wrappedComponent": { GaugeChart },
8 | "description": `
9 | - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/composants-et-modeles/composants-beta/graphiques-charts/)
10 | - [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/Chart/BarChart.tsx)
11 |
12 | To use this component you need to add \`@gouvfr/dsfr-chart\` to your dependencies.
13 |
14 | Note for Next users: Chart components are not SSR compatible. You need to import them dynamically with [\`next/dynamic\`](https://nextjs.org/docs/pages/building-your-application/optimizing/lazy-loading#nextdynamic).
15 | You can find an example [here](https://github.com/codegouvfr/react-dsfr/blob/bc2c2be290b09684711c53176b7a379cebed08a8/test/integration/next-appdir/app/dsfr-chart/page.tsx#L8-L9).
16 |
17 | `,
18 | "argTypes": {
19 | "value": {
20 | "description": "Current value"
21 | },
22 | "init": {
23 | "description": "Base value"
24 | },
25 | "target": {
26 | "description": "Target value"
27 | },
28 | "color": {
29 | "description": "Color"
30 | }
31 | },
32 | "disabledProps": ["lang"],
33 | isChartComponent: true
34 | });
35 |
36 | export default meta;
37 |
38 | export const Default = getStory({
39 | value: 16,
40 | init: 10,
41 | target: 20,
42 | color: "blue-france"
43 | });
44 |
--------------------------------------------------------------------------------
/stories/charts/LineChart.stories.tsx:
--------------------------------------------------------------------------------
1 | import { LineChart, type LineChartProps } from "../../dist/Chart/LineChart";
2 | import { getStoryFactory } from "../getStory";
3 | import { sectionName } from "./sectionName";
4 |
5 | const { meta, getStory } = getStoryFactory({
6 | sectionName: sectionName,
7 | "wrappedComponent": { LineChart },
8 | "description": `
9 | - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/composants-et-modeles/composants-beta/graphiques-charts/)
10 | - [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/Chart/BarChart.tsx)
11 |
12 | To use this component you need to add \`@gouvfr/dsfr-chart\` to your dependencies.
13 |
14 | Note for Next users: Chart components are not SSR compatible. You need to import them dynamically with [\`next/dynamic\`](https://nextjs.org/docs/pages/building-your-application/optimizing/lazy-loading#nextdynamic).
15 | You can find an example [here](https://github.com/codegouvfr/react-dsfr/blob/bc2c2be290b09684711c53176b7a379cebed08a8/test/integration/next-appdir/app/dsfr-chart/page.tsx#L8-L9).
16 |
17 | `,
18 | "argTypes": {
19 | "x": {
20 | "description": "Array of value for the x axis"
21 | },
22 | "y": {
23 | "description": "Array of value for the y axis"
24 | },
25 | "name": { "description": "Array of name", control: "object" },
26 | "color": { "description": "Array of color", control: "object" },
27 | "hline": { "description": "Array of horizontal lines to add", control: "object" },
28 | "hlinename": { "description": "Name of the horizontal lines", control: "object" },
29 | "vline": { "description": "Array of vertical lines to add", control: "object" },
30 | "vlinename": { "description": "Name of the vertical lines", control: "object" },
31 | "vlinecolor": { "description": "Color of the horizontal lines", control: "object" },
32 | "hlinecolor": { "description": "Color of the vertical lines", control: "object" }
33 | },
34 | "disabledProps": ["lang"],
35 | isChartComponent: true
36 | });
37 |
38 | export default meta;
39 |
40 | export const Default = getStory({
41 | x: [1, 2, 3],
42 | y: [10, 20, 30]
43 | });
44 |
--------------------------------------------------------------------------------
/stories/charts/MultiLineChart.stories.tsx:
--------------------------------------------------------------------------------
1 | import { MultiLineChart, type MultiLineChartProps } from "../../dist/Chart/MultiLineChart";
2 | import { getStoryFactory } from "../getStory";
3 | import { sectionName } from "./sectionName";
4 |
5 | const { meta, getStory } = getStoryFactory({
6 | sectionName: sectionName,
7 | "wrappedComponent": { MultiLineChart },
8 | "description": `
9 | - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/composants-et-modeles/composants-beta/graphiques-charts/)
10 | - [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/Chart/BarChart.tsx)
11 |
12 | To use this component you need to add \`@gouvfr/dsfr-chart\` to your dependencies.
13 |
14 | Note for Next users: Chart components are not SSR compatible. You need to import them dynamically with [\`next/dynamic\`](https://nextjs.org/docs/pages/building-your-application/optimizing/lazy-loading#nextdynamic).
15 | You can find an example [here](https://github.com/codegouvfr/react-dsfr/blob/bc2c2be290b09684711c53176b7a379cebed08a8/test/integration/next-appdir/app/dsfr-chart/page.tsx#L8-L9).
16 |
17 | `,
18 | "argTypes": {
19 | "x": {
20 | "description": "Array of value for the x axis"
21 | },
22 | "y": {
23 | "description": "Array of value for the y axis"
24 | },
25 | "name": { "description": "Array of name", control: "object" },
26 | "color": { "description": "Array of color", control: "object" },
27 | "hline": { "description": "Array of horizontal lines to add", control: "object" },
28 | "hlinename": { "description": "Name of the horizontal lines", control: "object" },
29 | "vline": { "description": "Array of vertical lines to add", control: "object" },
30 | "vlinename": { "description": "Name of the vertical lines", control: "object" },
31 | "vlinecolor": { "description": "Color of the horizontal lines", control: "object" },
32 | "hlinecolor": { "description": "Color of the vertical lines", control: "object" }
33 | },
34 | "disabledProps": ["lang"],
35 | isChartComponent: true
36 | });
37 |
38 | export default meta;
39 |
40 | export const Default = getStory({
41 | x: [
42 | [1, 2, 3],
43 | [1, 2, 3]
44 | ],
45 | y: [
46 | [30, 10, 20],
47 | [10, 20, 30]
48 | ]
49 | });
50 |
--------------------------------------------------------------------------------
/stories/charts/PieChart.stories.tsx:
--------------------------------------------------------------------------------
1 | import { PieChart, type PieChartProps } from "../../dist/Chart/PieChart";
2 | import { getStoryFactory } from "../getStory";
3 | import { sectionName } from "./sectionName";
4 |
5 | const { meta, getStory } = getStoryFactory({
6 | sectionName: sectionName,
7 | "wrappedComponent": { PieChart },
8 | "description": `
9 | - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/composants-et-modeles/composants-beta/graphiques-charts/)
10 | - [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/Chart/BarChart.tsx)
11 |
12 | To use this component you need to add \`@gouvfr/dsfr-chart\` to your dependencies.
13 |
14 | Note for Next users: Chart components are not SSR compatible. You need to import them dynamically with [\`next/dynamic\`](https://nextjs.org/docs/pages/building-your-application/optimizing/lazy-loading#nextdynamic).
15 | You can find an example [here](https://github.com/codegouvfr/react-dsfr/blob/bc2c2be290b09684711c53176b7a379cebed08a8/test/integration/next-appdir/app/dsfr-chart/page.tsx#L8-L9).
16 |
17 | `,
18 | "argTypes": {
19 | "x": {
20 | "description": "Array of value for the x axis"
21 | },
22 | "y": {
23 | "description": "Array of value for the y axis"
24 | },
25 | "name": { "description": "Array of name" },
26 | "color": { "description": "Array of color" },
27 | "fill": { control: "boolean" }
28 | },
29 | "disabledProps": ["lang"],
30 | isChartComponent: true
31 | });
32 |
33 | export default meta;
34 |
35 | export const Default = getStory({
36 | x: [1, 2, 3],
37 | y: [10, 20, 30],
38 | name: ["Serie 1", "Serie 2", "Serie 3"],
39 | color: ["blue-france", "green-bourgeon", "blue-ecume"]
40 | });
41 |
--------------------------------------------------------------------------------
/stories/charts/RadarChart.stories.tsx:
--------------------------------------------------------------------------------
1 | import { RadarChart, type RadarChartProps } from "../../dist/Chart/RadarChart";
2 | import { getStoryFactory } from "../getStory";
3 | import { sectionName } from "./sectionName";
4 |
5 | const { meta, getStory } = getStoryFactory({
6 | sectionName: sectionName,
7 | "wrappedComponent": { RadarChart },
8 | "description": `
9 | - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/composants-et-modeles/composants-beta/graphiques-charts/)
10 | - [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/Chart/BarChart.tsx)
11 |
12 | To use this component you need to add \`@gouvfr/dsfr-chart\` to your dependencies.
13 |
14 | Note for Next users: Chart components are not SSR compatible. You need to import them dynamically with [\`next/dynamic\`](https://nextjs.org/docs/pages/building-your-application/optimizing/lazy-loading#nextdynamic).
15 | You can find an example [here](https://github.com/codegouvfr/react-dsfr/blob/bc2c2be290b09684711c53176b7a379cebed08a8/test/integration/next-appdir/app/dsfr-chart/page.tsx#L8-L9).
16 |
17 | `,
18 | "argTypes": {
19 | "x": {
20 | "description": "Array of value for the x axis"
21 | },
22 | "y": {
23 | "description": "Array of value for the y axis"
24 | },
25 | "name": { "description": "Array of name", control: "object" },
26 | "color": { "description": "Array of color", control: "object" }
27 | },
28 | "disabledProps": ["lang"],
29 | isChartComponent: true
30 | });
31 |
32 | export default meta;
33 |
34 | export const Default = getStory({
35 | x: [
36 | ["Eating", "Drinking", "Sleeping", "Designing", "Coding", "Cycling", "Running"],
37 | ["Eating", "Drinking", "Sleeping", "Designing", "Coding", "Cycling", "Running"],
38 | ["Eating", "Drinking", "Sleeping", "Designing", "Coding", "Cycling", "Running"]
39 | ],
40 | y: [
41 | [65, 59, 90, 81, 56, 55, 40],
42 | [28, 48, 40, 19, 96, 27, 100],
43 | [12, 12, 20, 23, 13, 14, 15]
44 | ]
45 | });
46 |
--------------------------------------------------------------------------------
/stories/charts/ScatterChart.stories.tsx:
--------------------------------------------------------------------------------
1 | import { ScatterChart, type ScatterChartProps } from "../../dist/Chart/ScatterChart";
2 | import { getStoryFactory } from "../getStory";
3 | import { sectionName } from "./sectionName";
4 |
5 | const { meta, getStory } = getStoryFactory({
6 | sectionName: sectionName,
7 | "wrappedComponent": { ScatterChart },
8 | "description": `
9 | - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/composants-et-modeles/composants-beta/graphiques-charts/)
10 | - [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/Chart/BarChart.tsx)
11 |
12 | To use this component you need to add \`@gouvfr/dsfr-chart\` to your dependencies.
13 |
14 | Note for Next users: Chart components are not SSR compatible. You need to import them dynamically with [\`next/dynamic\`](https://nextjs.org/docs/pages/building-your-application/optimizing/lazy-loading#nextdynamic).
15 | You can find an example [here](https://github.com/codegouvfr/react-dsfr/blob/bc2c2be290b09684711c53176b7a379cebed08a8/test/integration/next-appdir/app/dsfr-chart/page.tsx#L8-L9).
16 |
17 | `,
18 | "argTypes": {
19 | "x": {
20 | "description": "Array of value for the x axis"
21 | },
22 | "y": {
23 | "description": "Array of value for the y axis"
24 | },
25 | "name": { "description": "Array of name", control: "object" },
26 | "color": { "description": "Array of color", control: "object" },
27 | "hline": { "description": "Array of horizontal lines to add", control: "object" },
28 | "hlinename": { "description": "Name of the horizontal lines", control: "object" },
29 | "vline": { "description": "Array of vertical lines to add", control: "object" },
30 | "vlinename": { "description": "Name of the vertical lines", control: "object" },
31 | "vlinecolor": { "description": "Color of the horizontal lines", control: "object" },
32 | "hlinecolor": { "description": "Color of the vertical lines", control: "object" }
33 | },
34 | "disabledProps": ["lang"],
35 | isChartComponent: true
36 | });
37 |
38 | export default meta;
39 |
40 | export const Default = getStory({
41 | x: [
42 | [1, 5, 8],
43 | [1, 2, 15]
44 | ],
45 | y: [
46 | [30, 10, 20],
47 | [10, 20, 30]
48 | ]
49 | });
50 |
--------------------------------------------------------------------------------
/stories/charts/sectionName.ts:
--------------------------------------------------------------------------------
1 | export const sectionName = "charts";
2 |
--------------------------------------------------------------------------------
/stories/colorHelper.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta } from "@storybook/addon-docs";
2 | import { ColorHelper } from "./ColorHelper";
3 |
4 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/stories/global.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.png" {
2 | const src: string;
3 | export default src;
4 | }
5 |
6 | declare module "*.svg" {
7 | import * as React from "react";
8 |
9 | export const ReactComponent: React.FunctionComponent<
10 | React.SVGProps & { title?: string }
11 | >;
12 |
13 | const src: string;
14 | export default src;
15 | }
16 |
--------------------------------------------------------------------------------
/stories/intro.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta } from "@storybook/addon-docs";
2 | import { useDarkMode } from "storybook-dark-mode";
3 | import { RotatingLogo } from "./RotatingLogo";
4 |
5 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 🇫🇷
The French State Design System React toolkit 🇫🇷
24 |
25 |
26 | [Github](https://github.com/codegouvfr/react-dsfr) - [Documentation](https://react-dsfr.codegouv.studio/)
27 |
28 |
VIDEO
29 |
30 |
31 |
32 | This Storybook website serves as your daily companion for developing with React-DSFR.
33 | It not only provides visibility into the available components but also allows you to preview them in various states,
34 | enabling you to copy and paste the code directly into your project.
35 |
36 | Additionally, this website features a handy [color helper tool](/?path=/docs/%F0%9F%8E%A8-color-helper--page).
37 | This tool empowers you to navigate through the DSFR color palette and choose the most fitting shades for your specific use cases.
38 |
39 |
40 |
--------------------------------------------------------------------------------
/stories/sectionName.ts:
--------------------------------------------------------------------------------
1 | export const sectionName = "components";
2 |
--------------------------------------------------------------------------------
/stories/utils.css:
--------------------------------------------------------------------------------
1 |
2 | .margin-bottom-50px {
3 | margin-bottom: 50px;
4 | }
5 |
6 | .margin-bottom-300px {
7 | margin-bottom: 300px;
8 | }
9 |
10 | .margin-bottom-600px {
11 | margin-bottom: 600px;
12 | }
--------------------------------------------------------------------------------
/test/integration/README.md:
--------------------------------------------------------------------------------
1 | # integration tests
2 |
3 | In this directory we test the integration of the module against mainstream React frameworks.
4 |
--------------------------------------------------------------------------------
/test/integration/cra/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | /public/dsfr
26 |
27 | /public/dsfr/
28 |
--------------------------------------------------------------------------------
/test/integration/cra/README.md:
--------------------------------------------------------------------------------
1 | Run the App:
2 |
3 | ```bash
4 | git clone https://github.com/codegouvfr/react-dsfr
5 | cd react-dsfr
6 | yarn
7 | yarn start-cra
8 | ```
--------------------------------------------------------------------------------
/test/integration/cra/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-dsfr-test-app-cra",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "18.2.0",
7 | "react-dom": "18.2.0",
8 | "react-scripts": "5.0.1",
9 | "type-route": "^0.7.2",
10 | "@mui/material": "^5.14.18",
11 | "@emotion/react": "^11.11.0",
12 | "@emotion/styled": "^11.11.0",
13 | "@mui/icons-material": "^5.14.18",
14 | "@mui/x-date-pickers": "^6.18.2",
15 | "dayjs": "^1.11.6",
16 | "@codegouvfr/react-dsfr": "file:../../../dist"
17 | },
18 | "devDependencies": {
19 | "@types/node": "^16.7.13",
20 | "@types/react": "18.0.21",
21 | "@types/react-dom": "18.0.6",
22 | "typescript": "^4.8.3"
23 | },
24 | "scripts": {
25 | "start": "react-scripts start",
26 | "build": "react-scripts build",
27 | "test": "react-scripts test",
28 | "eject": "react-scripts eject",
29 | "copy-dsfr-to-public": "node node_modules/@codegouvfr/react-dsfr/bin/copy-dsfr-to-public.js",
30 | "only-include-used-icons": "node node_modules/@codegouvfr/react-dsfr/bin/only-include-used-icons.js",
31 | "prestart": "yarn copy-dsfr-to-public && yarn only-include-used-icons",
32 | "prebuild": "yarn copy-dsfr-to-public && yarn only-include-used-icons"
33 | },
34 | "eslintConfig": {
35 | "extends": [
36 | "react-app",
37 | "react-app/jest"
38 | ]
39 | },
40 | "browserslist": {
41 | "production": [
42 | ">0.2%",
43 | "not dead",
44 | "not op_mini all"
45 | ],
46 | "development": [
47 | "last 1 chrome version",
48 | "last 1 firefox version",
49 | "last 1 safari version"
50 | ]
51 | }
52 | }
--------------------------------------------------------------------------------
/test/integration/cra/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/test/integration/cra/src/Picto.tsx:
--------------------------------------------------------------------------------
1 | import { fr } from "@codegouvfr/react-dsfr";
2 |
3 | import * as Pictogrammes from '@codegouvfr/react-dsfr/picto';
4 |
5 | export function Picto() {
6 | return (
7 |
8 | {
9 | Object.entries(Pictogrammes).map(([name, Component]) => (
10 |
14 | ))
15 | }
16 |
17 | )
18 | }
--------------------------------------------------------------------------------
/test/integration/cra/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/test/integration/cra/src/router.ts:
--------------------------------------------------------------------------------
1 | import { createRouter, defineRoute } from "type-route";
2 |
3 | const routeDefs = {
4 | "home": defineRoute("/"),
5 | "mui": defineRoute("/mui"),
6 | "picto": defineRoute("/picto"),
7 | };
8 |
9 | export const { RouteProvider, useRoute, routes } = createRouter(routeDefs);
10 |
--------------------------------------------------------------------------------
/test/integration/cra/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"]
20 | }
21 |
--------------------------------------------------------------------------------
/test/integration/next-appdir/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["next", "next/core-web-vitals"]
3 | }
4 |
--------------------------------------------------------------------------------
/test/integration/next-appdir/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
36 |
--------------------------------------------------------------------------------
/test/integration/next-appdir/README.md:
--------------------------------------------------------------------------------
1 |
2 | Run the App:
3 |
4 | ```bash
5 | git clone https://github.com/codegouvfr/react-dsfr
6 | cd react-dsfr
7 | yarn
8 | yarn start-next-appdir
9 | ```
10 |
--------------------------------------------------------------------------------
/test/integration/next-appdir/app/DefaultTrustedPolicy.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useEffect } from "react";
4 |
5 | export function DefaultTrustedPolicy() {
6 | useEffect(() => {
7 | // You need to add a default trusted type to enable dsfr-chart to inject charts
8 | // Or you can disable require-trusted-types-for CSP
9 | if (
10 | window.trustedTypes &&
11 | window.trustedTypes.createPolicy &&
12 | !window.trustedTypes.defaultPolicy
13 | ) {
14 | window.trustedTypes.createPolicy("default", {
15 | createHTML: string => string
16 | });
17 | }
18 | });
19 |
20 | return null;
21 | }
22 |
--------------------------------------------------------------------------------
/test/integration/next-appdir/app/Follow.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Follow as BaseFollow } from "@codegouvfr/react-dsfr/Follow";
4 | import { useState } from 'react'
5 |
6 | export const Follow = () => {
7 | const [success, setSuccess] = useState(false)
8 | return setSuccess(true)
13 | },
14 | form: {
15 | formComponent: ({ children }) => ,
16 | success,
17 | }
18 | }}
19 | social= {{
20 | buttons: [
21 | {
22 | type: "facebook",
23 | linkProps: {
24 | href: "#facebook"
25 | }
26 | },
27 | {
28 | type: "twitter-x",
29 | linkProps: {
30 | href: "#twitter"
31 | }
32 | },
33 | {
34 | type: "linkedin",
35 | linkProps: {
36 | href: "#linkedin"
37 | }
38 | },
39 | {
40 | type: "instagram",
41 | linkProps: {
42 | href: "#instagram"
43 | }
44 | },
45 | {
46 | type: "youtube",
47 | linkProps: {
48 | href: "#youtube"
49 | }
50 | }
51 | ]
52 | }}
53 | />
54 | }
--------------------------------------------------------------------------------
/test/integration/next-appdir/app/Navigation.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { MainNavigation } from "@codegouvfr/react-dsfr/MainNavigation";
4 | import { useSelectedLayoutSegment } from "next/navigation";
5 |
6 | export function Navigation() {
7 | const segment = useSelectedLayoutSegment();
8 |
9 | return (
10 |
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/test/integration/next-appdir/app/StartDsfr.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { startReactDsfr } from "@codegouvfr/react-dsfr/next-appdir";
4 | import { defaultColorScheme } from "./defaultColorScheme";
5 | import { addAlertTranslations } from "@codegouvfr/react-dsfr/Alert";
6 | import Link from "next/link";
7 |
8 | declare module "@codegouvfr/react-dsfr/next-appdir" {
9 | interface RegisterLink {
10 | Link: typeof Link;
11 | }
12 | }
13 |
14 | startReactDsfr({
15 | defaultColorScheme,
16 | Link,
17 | "doCheckNonce": true
18 | });
19 |
20 | export function StartDsfr(){
21 | return null;
22 | }
23 |
24 | addAlertTranslations({
25 | "lang": "fr",
26 | "messages": {
27 | "hide message": "Masquer le message (modifié)",
28 | }
29 | });
30 |
--------------------------------------------------------------------------------
/test/integration/next-appdir/app/consentManagement.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { createConsentManagement } from "@codegouvfr/react-dsfr/consentManagement";
4 |
5 | export const {
6 | ConsentBannerAndConsentManagement,
7 | FooterConsentManagementItem,
8 | FooterPersonalDataPolicyItem,
9 | useConsent
10 | } = createConsentManagement({
11 | "finalityDescription": ({ lang }) => ({
12 | "advertising": {
13 | "title": "Publicité",
14 | "description": "Nous utilisons des cookies pour vous proposer des publicités adaptées à vos centres d’intérêts et mesurer leur efficacité."
15 | },
16 | "analytics": {
17 | "title": "Analyse",
18 | "description": "Nous utilisons des cookies pour mesurer l’audience de notre site et améliorer son contenu."
19 | },
20 | "personalization": {
21 | "title": "Personnalisation",
22 | "description": "Nous utilisons des cookies pour vous proposer des contenus adaptés à vos centres d’intérêts."
23 | },
24 | "statistics": {
25 | "title": "Statistiques",
26 | "description": "Nous utilisons des cookies pour mesurer l’audience de notre site et améliorer son contenu.",
27 | "subFinalities": {
28 | "deviceInfo": "Informations sur votre appareil",
29 | "traffic": "Informations sur votre navigation",
30 | }
31 | }
32 | }),
33 | "personalDataPolicyLinkProps": {
34 | "href": "/politique-de-confidentialite",
35 | },
36 | "consentCallback": async ({ finalityConsent, finalityConsent_prev })=> {
37 |
38 | if( finalityConsent_prev === undefined && !finalityConsent.isFullConsent ){
39 | location.reload();
40 | await new Promise(()=> {/*never*/});
41 | }
42 |
43 | console.log("callback from gdpr hook");
44 | }
45 | });
46 |
--------------------------------------------------------------------------------
/test/integration/next-appdir/app/defaultColorScheme.ts:
--------------------------------------------------------------------------------
1 | import type { DefaultColorScheme } from "@codegouvfr/react-dsfr/next-appdir";
2 |
3 | export const defaultColorScheme: DefaultColorScheme = "system";
--------------------------------------------------------------------------------
/test/integration/next-appdir/app/main.module.css:
--------------------------------------------------------------------------------
1 | .app, .container {
2 | flex: 1;
3 | margin: auto;
4 | max-width: 1000px;
5 | }
--------------------------------------------------------------------------------
/test/integration/next-appdir/middleware.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from "next/server";
2 |
3 | export function middleware(request: NextRequest) {
4 | const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
5 |
6 | const cspHeader = `
7 | default-src 'self';
8 | script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${
9 | process.env.NODE_ENV === "development" ? "'unsafe-eval'" : ""
10 | };
11 | style-src 'self' 'unsafe-inline';
12 | img-src 'self' blob: data:;
13 | font-src 'self';
14 | object-src 'none';
15 | base-uri 'self';
16 | form-action 'self';
17 | frame-ancestors 'none';
18 | block-all-mixed-content;
19 | upgrade-insecure-requests;
20 | require-trusted-types-for 'script';
21 | trusted-types default react-dsfr react-dsfr-asap nextjs#bundler;
22 | `;
23 |
24 | const requestHeaders = new Headers();
25 | requestHeaders.set("x-nonce", nonce);
26 | requestHeaders.set(
27 | "Content-Security-Policy",
28 | // Replace newline characters and spaces
29 | cspHeader.replace(/\s{2,}/g, " ").trim()
30 | );
31 |
32 | return NextResponse.next({
33 | headers: requestHeaders,
34 | request: {
35 | headers: requestHeaders
36 | }
37 | });
38 | }
39 |
40 | export const config = {
41 | matcher: [
42 | /*
43 | * Match all request paths except for the ones starting with:
44 | * - api (API routes)
45 | * - _next/static (static files)
46 | * - _next/image (image optimization files)
47 | * - favicon.ico (favicon file)
48 | */
49 | {
50 | source: "/((?!api|_next/static|_next/image|favicon.ico).*)",
51 | missing: [
52 | { type: "header", key: "next-router-prefetch" },
53 | { type: "header", key: "purpose", value: "prefetch" }
54 | ]
55 | }
56 | ]
57 | };
58 |
--------------------------------------------------------------------------------
/test/integration/next-appdir/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/test/integration/next-appdir/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: false, // Recommended for the `pages` directory, default in `app`.
4 | swcMinify: true,
5 | webpack: config => {
6 |
7 | config.module.rules.push({
8 | test: /\.woff2$/,
9 | type: "asset/resource"
10 | });
11 |
12 | return config;
13 | }
14 | };
15 |
16 | module.exports = nextConfig;
17 |
--------------------------------------------------------------------------------
/test/integration/next-appdir/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-appdir-integration-test-setup",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "only-include-used-icons": "node node_modules/@codegouvfr/react-dsfr/bin/only-include-used-icons.js",
11 | "predev": "yarn only-include-used-icons",
12 | "prebuild": "yarn only-include-used-icons"
13 | },
14 | "dependencies": {
15 | "@emotion/react": "11.10.5",
16 | "@emotion/server": "11.10.0",
17 | "@emotion/styled": "11.10.5",
18 | "@mui/icons-material": "^5.14.18",
19 | "@mui/material": "^5.14.18",
20 | "next": "13.5.1",
21 | "react": "18.2.0",
22 | "react-dom": "18.2.0",
23 | "tss-react": "^4.9.1",
24 | "tsafe": "^1.4.1",
25 | "@mui/x-date-pickers": "^6.18.2",
26 | "dayjs": "^1.11.7",
27 | "@mui/x-data-grid": "^6.18.2",
28 | "@gouvfr/dsfr-chart": "^1.0.0",
29 | "@codegouvfr/react-dsfr": "file:../../../dist"
30 | },
31 | "devDependencies": {
32 | "@types/node": "^18.8.2",
33 | "@types/react": "18.0.26",
34 | "@types/trusted-types": "^2.0.7",
35 | "eslint": "7.30.0",
36 | "eslint-config-next": "11.0.1",
37 | "typescript": "^4.9.4",
38 | "sass": "^1.62.1"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test/integration/next-appdir/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codegouvfr/react-dsfr/320fd6280a0ffd343c28e051e1a1a1ec3db9af6f/test/integration/next-appdir/public/favicon.ico
--------------------------------------------------------------------------------
/test/integration/next-appdir/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/test/integration/next-appdir/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "incremental": true,
21 | "paths": {
22 | "#/*": [
23 | "./*"
24 | ]
25 | },
26 | "plugins": [
27 | {
28 | "name": "next"
29 | }
30 | ]
31 | },
32 | "include": [
33 | "next-env.d.ts",
34 | "**/*.ts",
35 | "**/*.tsx",
36 | ".next/types/**/*.ts"
37 | ],
38 | "exclude": [
39 | "node_modules"
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/test/integration/next-appdir/ui/ClientComponent.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from 'react';
4 | import Stack from '@mui/material/Stack';
5 | import Button from '@mui/material/Button';
6 | import { useIsDark } from "@codegouvfr/react-dsfr/useIsDark";
7 | import { createModal } from "@codegouvfr/react-dsfr/Modal";
8 | import { useIsModalOpen } from "@codegouvfr/react-dsfr/Modal/useIsModalOpen";
9 | import { useIsHeaderMenuModalOpen } from "@codegouvfr/react-dsfr/Header/useIsHeaderMenuModalOpen";
10 |
11 | export function ClientComponent() {
12 |
13 | const { isDark } = useIsDark();
14 |
15 | const isModalOpen = useIsModalOpen(modal);
16 |
17 | const isHeaderMenuModalOpen = useIsHeaderMenuModalOpen();
18 |
19 | console.log(`Modal ${modal.id} is currently: ${isModalOpen ? "open" : "closed"}`);
20 | console.log(`Header Modal is currently: ${isHeaderMenuModalOpen ? "open" : "closed"}`);
21 |
22 | return (
23 | <>
24 |
25 | Text
26 | Contained
27 | Outlined
28 |
29 | Is dark? {isDark ? "yes" : "no"}
30 | Open client modal
31 |
32 | Client modal content
33 | Close
34 |
35 | >
36 | );
37 | }
38 |
39 | const modal = createModal({
40 | "isOpenedByDefault": false,
41 | "id": "client-modal"
42 | });
--------------------------------------------------------------------------------
/test/integration/next-appdir/ui/ClientFooterItem.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { FooterBottomItem } from "@codegouvfr/react-dsfr/Footer";
3 |
4 | export function ClientFooterItem() {
5 | return (
6 | {
11 |
12 | alert("Click on client item");
13 |
14 | }
15 | },
16 | text: "A client side bottom item",
17 |
18 | }}
19 | />
20 | );
21 | }
--------------------------------------------------------------------------------
/test/integration/next-appdir/ui/ClientHeaderQuickAccessItem.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { HeaderQuickAccessItem } from "@codegouvfr/react-dsfr/Header";
4 |
5 | export function ClientHeaderQuickAccessItem() {
6 |
7 | //NOTE: You can use hooks here
8 |
9 | return (
10 | {
15 |
16 | alert("Click on client item");
17 |
18 | }
19 | },
20 | text: "A client side item",
21 |
22 | }}
23 | />
24 | );
25 | }
--------------------------------------------------------------------------------
/test/integration/next-pagesdir/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["next", "next/core-web-vitals"],
3 | "rules": {
4 | "@next/next/no-document-import-in-page": "off"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/test/integration/next-pagesdir/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/test/integration/next-pagesdir/README.md:
--------------------------------------------------------------------------------
1 | Run the App:
2 |
3 | ```bash
4 | git clone https://github.com/codegouvfr/react-dsfr
5 | cd react-dsfr
6 | yarn
7 | yarn start-next-pagesdir
8 | ```
9 |
10 |
--------------------------------------------------------------------------------
/test/integration/next-pagesdir/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/test/integration/next-pagesdir/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | swcMinify: true,
5 | webpack: config => {
6 | config.module.rules.push({
7 | test: /\.woff2$/,
8 | type: "asset/resource"
9 | });
10 | return config;
11 | },
12 | //This option requires Next 13.1 or newer, if you can't update you can use this plugin instead: https://github.com/martpie/next-transpile-modules
13 | transpilePackages: ["@codegouvfr/react-dsfr", "tss-react"]
14 | };
15 |
16 | module.exports = nextConfig
--------------------------------------------------------------------------------
/test/integration/next-pagesdir/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-dsfr-test-app-next",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "only-include-used-icons": "node node_modules/@codegouvfr/react-dsfr/bin/only-include-used-icons.js",
11 | "predev": "yarn only-include-used-icons",
12 | "prebuild": "yarn only-include-used-icons"
13 | },
14 | "dependencies": {
15 | "next": "13.5.1",
16 | "react": "18.2.0",
17 | "react-dom": "18.2.0",
18 | "@emotion/react": "^11.10.5",
19 | "@emotion/server": "^11.10.0",
20 | "tss-react": "^4.9.1",
21 | "@mui/material": "^5.14.18",
22 | "@mui/icons-material": "^5.14.18",
23 | "@emotion/styled": "^11.10.5",
24 | "dayjs": "^1.11.6",
25 | "@mui/x-date-pickers": "^6.18.2",
26 | "@mui/x-data-grid": "^6.18.2",
27 | "@codegouvfr/react-dsfr": "file:../../../dist"
28 | },
29 | "devDependencies": {
30 | "@types/react": "18.0.21",
31 | "eslint": "7.30.0",
32 | "eslint-config-next": "11.0.1",
33 | "typescript": "^4.6.4"
34 | }
35 | }
--------------------------------------------------------------------------------
/test/integration/next-pagesdir/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import { Html, Head, Main, NextScript } from "next/document";
2 | import type { DocumentProps } from "next/document";
3 | import { dsfrDocumentApi, augmentDocumentWithEmotionCache } from "./_app";
4 |
5 | const { augmentDocumentForDsfr, getColorSchemeHtmlAttributes } = dsfrDocumentApi;
6 |
7 | export default function Document(props: DocumentProps) {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 | }
18 |
19 | augmentDocumentForDsfr(Document);
20 |
21 | augmentDocumentWithEmotionCache(Document);
22 |
--------------------------------------------------------------------------------
/test/integration/next-pagesdir/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true
17 | },
18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
19 | "exclude": ["node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------
/test/integration/vite/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | node_modules
3 | dist
4 | dist-ssr
5 | *.local
6 |
7 | # Editor directories and files
8 | .vscode/*
9 | !.vscode/extensions.json
10 | .idea
11 | .DS_Store
12 | *.suo
13 | *.ntvs*
14 | *.njsproj
15 | *.sln
16 | *.sw?
17 |
18 | /public/dsfr/
19 |
--------------------------------------------------------------------------------
/test/integration/vite/README.md:
--------------------------------------------------------------------------------
1 | Run the App:
2 |
3 | ```bash
4 | git clone https://github.com/codegouvfr/react-dsfr
5 | cd react-dsfr
6 | yarn
7 | yarn start-vite
8 | ```
--------------------------------------------------------------------------------
/test/integration/vite/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Vite + React + TS
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/test/integration/vite/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-dsfr-test-app-vite",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview",
10 | "copy-dsfr-to-public": "node node_modules/@codegouvfr/react-dsfr/bin/copy-dsfr-to-public.js",
11 | "only-include-used-icons": "node node_modules/@codegouvfr/react-dsfr/bin/only-include-used-icons.js",
12 | "predev": "yarn copy-dsfr-to-public && yarn only-include-used-icons",
13 | "prebuild": "yarn copy-dsfr-to-public && yarn only-include-used-icons"
14 | },
15 | "dependencies": {
16 | "react": "18.2.0",
17 | "react-dom": "18.2.0",
18 | "react-router-dom": "^6.8.1",
19 | "@mui/material": "^5.14.18",
20 | "@emotion/styled": "^11.11.0",
21 | "@emotion/react": "^11.11.0",
22 | "@mui/x-date-pickers": "^6.18.2",
23 | "@mui/icons-material": "^5.14.18",
24 | "dayjs": "^1.11.6",
25 | "@codegouvfr/react-dsfr": "file:../../../dist"
26 | },
27 | "devDependencies": {
28 | "@types/react": "18.0.21",
29 | "@types/react-dom": "18.0.6",
30 | "@vitejs/plugin-react": "^2.1.0",
31 | "typescript": "^4.6.4",
32 | "vite": "^3.1.0"
33 | }
34 | }
--------------------------------------------------------------------------------
/test/integration/vite/public/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codegouvfr/react-dsfr/320fd6280a0ffd343c28e051e1a1a1ec3db9af6f/test/integration/vite/public/.keep
--------------------------------------------------------------------------------
/test/integration/vite/src/Picto.tsx:
--------------------------------------------------------------------------------
1 | import { fr } from "@codegouvfr/react-dsfr";
2 |
3 | import * as Pictogrammes from '@codegouvfr/react-dsfr/picto';
4 |
5 | export function Picto() {
6 | return (
7 |
8 | {
9 | Object.entries(Pictogrammes).map(([name, Component]) => (
10 |
14 | ))
15 | }
16 |
17 | )
18 | }
--------------------------------------------------------------------------------
/test/integration/vite/src/consentManagement.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { createConsentManagement } from "@codegouvfr/react-dsfr/consentManagement";
4 |
5 | export const {
6 | ConsentBannerAndConsentManagement,
7 | FooterConsentManagementItem,
8 | FooterPersonalDataPolicyItem,
9 | useConsent
10 | } = createConsentManagement({
11 | "finalityDescription": ({ lang }) => ({
12 | "advertising": {
13 | "title": "Publicité",
14 | "description": "Nous utilisons des cookies pour vous proposer des publicités adaptées à vos centres d’intérêts et mesurer leur efficacité."
15 | },
16 | "analytics": {
17 | "title": "Analyse",
18 | "description": "Nous utilisons des cookies pour mesurer l’audience de notre site et améliorer son contenu."
19 | },
20 | "personalization": {
21 | "title": "Personnalisation",
22 | "description": "Nous utilisons des cookies pour vous proposer des contenus adaptés à vos centres d’intérêts."
23 | },
24 | "statistics": {
25 | "title": "Statistiques",
26 | "description": "Nous utilisons des cookies pour mesurer l’audience de notre site et améliorer son contenu.",
27 | "subFinalities": {
28 | "deviceInfo": "Informations sur votre appareil",
29 | "traffic": "Informations sur votre navigation",
30 | }
31 | }
32 | }),
33 | "personalDataPolicyLinkProps": {
34 | "to": "/politique-de-confidentialite",
35 | },
36 | "consentCallback": async ({ finalityConsent, finalityConsent_prev })=> {
37 |
38 | if( finalityConsent_prev === undefined && !finalityConsent.isFullConsent ){
39 | location.reload();
40 | await new Promise(()=> {/*never*/});
41 | }
42 |
43 | console.log("callback from gdpr hook");
44 | }
45 | });
46 |
--------------------------------------------------------------------------------
/test/integration/vite/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/test/integration/vite/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"],
20 | "references": [{ "path": "./tsconfig.node.json" }]
21 | }
22 |
--------------------------------------------------------------------------------
/test/integration/vite/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/test/integration/vite/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | publicDir: "public",
8 | server: {
9 | fs: {
10 | allow: ['../../..']
11 | }
12 | }
13 | });
14 |
--------------------------------------------------------------------------------
/test/runtime/README.md:
--------------------------------------------------------------------------------
1 | # runtime tests
2 |
3 | In the directory we write unit tests to make sure that, at runtime, things actually behave are expected to.
4 | This is classic unit testing.
5 |
--------------------------------------------------------------------------------
/test/runtime/lib/spacing.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from "vitest";
2 | import { spacing } from "../../../src/fr/spacing";
3 |
4 | describe("Testing the replacer function", () => {
5 | it("with one argument", () => {
6 | const got = spacing("1v");
7 |
8 | const expected = "0.25rem";
9 |
10 | expect(got).toBe(expected);
11 | });
12 |
13 | it("Works with margin topBottom", () => {
14 | const got = spacing("margin", { "topBottom": "13w" });
15 |
16 | const expected = {
17 | "marginTop": "6.5rem",
18 | "marginBottom": "6.5rem"
19 | };
20 |
21 | expect(got).toStrictEqual(expected);
22 | });
23 |
24 | it("Works with margin topBottom and left", () => {
25 | const got = spacing("margin", { "topBottom": "13w", "left": 3 });
26 |
27 | const expected = {
28 | "marginTop": "6.5rem",
29 | "marginBottom": "6.5rem",
30 | "marginLeft": 3
31 | };
32 |
33 | expect(got).toStrictEqual(expected);
34 | });
35 |
36 | it("Works with margin topBottom overwriting bottom", () => {
37 | const got = spacing("margin", { "topBottom": "13w", "bottom": 3 });
38 |
39 | const expected = {
40 | "marginTop": "6.5rem",
41 | "marginBottom": 3
42 | };
43 |
44 | expect(got).toStrictEqual(expected);
45 | });
46 |
47 | it("Works with padding and rightLeft", () => {
48 | const got = spacing("padding", { "rightLeft": "1v" });
49 |
50 | const expected = {
51 | "paddingRight": "0.25rem",
52 | "paddingLeft": "0.25rem"
53 | };
54 |
55 | expect(got).toStrictEqual(expected);
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/test/runtime/scripts/breakpoints/generateBreakpointsTsCode.test.ts:
--------------------------------------------------------------------------------
1 | import { it, expect } from "vitest";
2 | import { generateBreakpointsTsCode } from "../../../../scripts/build/cssToTs/breakpoints";
3 |
4 | it("Generation of TS code for breakpoints", () => {
5 | const input = `
6 | @media (min-width: 36em) {
7 | /*! media sm */
8 |
9 | /*! media sm */
10 | .fr-hidden-sm {
11 | display: none !important;
12 | }
13 | }
14 |
15 | @media (min-width: 48em) {
16 | /*! media md */
17 |
18 | /*! media md */
19 | h6 {
20 | font-size: 1.25rem;
21 | line-height: 1.75rem;
22 | }
23 | }
24 |
25 | @media (min-width: 78em) {
26 | /*! media xl */
27 |
28 | /*! media xl */
29 | .fr-hidden-xl {
30 | display: none !important;
31 | }
32 | }
33 |
34 | @media (min-width: 62em) {
35 | /*! media lg */
36 |
37 | /*! media lg */
38 | .fr-hidden-lg {
39 | display: none !important;
40 | }
41 | }
42 | `;
43 |
44 | const expected = `
45 | import { assert } from "tsafe/assert";
46 | import type { Extends } from "tsafe";
47 |
48 | export const breakpointsValuesUnit = "em";
49 |
50 | export const breakpointKeys = ["xs", "sm", "md", "lg", "xl"] as const;
51 |
52 | export type BreakpointKeys = typeof breakpointKeys[number];
53 |
54 | export const breakpointsValues = {
55 | "xs": 0,
56 | "sm": 36,
57 | "md": 48,
58 | "lg": 62,
59 | "xl": 78
60 | } as const;
61 |
62 | assert>>();
63 | `.replace(/^\n/, "");
64 |
65 | const got = generateBreakpointsTsCode(input);
66 |
67 | expect(got).toBe(expected);
68 | });
69 |
--------------------------------------------------------------------------------
/test/runtime/scripts/breakpoints/parseBreakpointsValues.test.ts:
--------------------------------------------------------------------------------
1 | import { it, expect } from "vitest";
2 | import { parseBreakpointsValues } from "../../../../scripts/build/cssToTs/breakpoints";
3 | import type {
4 | BreakpointsValues,
5 | MediaQueryByBreakpoint
6 | } from "../../../../scripts/build/cssToTs/breakpoints";
7 | //import { assert } from "tsafe/assert";
8 | //import { same } from "evt/tools/inDepth/same";
9 | import { id } from "tsafe";
10 |
11 | it("Parsing of breakpoint", () => {
12 | const input = `
13 | @media (min-width: 36em) {
14 | /*! media sm */
15 |
16 | /*! media sm */
17 | .fr-hidden-sm {
18 | display: none !important;
19 | }
20 | }
21 |
22 | @media (min-width: 48em) {
23 | /*! media md */
24 |
25 | /*! media md */
26 | h6 {
27 | font-size: 1.25rem;
28 | line-height: 1.75rem;
29 | }
30 | }
31 |
32 | @media (min-width: 78em) {
33 | /*! media xl */
34 |
35 | /*! media xl */
36 | .fr-hidden-xl {
37 | display: none !important;
38 | }
39 | }
40 |
41 | @media (min-width: 62em) {
42 | /*! media lg */
43 |
44 | /*! media lg */
45 | .fr-hidden-lg {
46 | display: none !important;
47 | }
48 | }
49 | `;
50 |
51 | const expected = {
52 | "breakpointsValues": id({
53 | "unit": "em",
54 | "sm": 36,
55 | "md": 48,
56 | "lg": 62,
57 | "xl": 78
58 | }),
59 | "mediaQueryByBreakpoint": id({
60 | "sm": "(min-width: 36em)",
61 | "md": "(min-width: 48em)",
62 | "lg": "(min-width: 62em)",
63 | "xl": "(min-width: 78em)"
64 | })
65 | };
66 |
67 | const got = parseBreakpointsValues(input);
68 |
69 | expect(got).toStrictEqual(expected);
70 | });
71 |
--------------------------------------------------------------------------------
/test/runtime/scripts/classNames/generateClassNamesTsCode.test.ts:
--------------------------------------------------------------------------------
1 | import { it, expect } from "vitest";
2 | import { generateClassNamesTsCode } from "../../../../scripts/build/cssToTs/classNames";
3 |
4 | it("Generation of TS code for fr class names", () => {
5 | const input = `
6 | .fr-text--light {
7 | font-weight: 300 !important;
8 | }
9 |
10 | .fr-text--xl,
11 | .fr-text--lead {
12 | font-size: 1.25rem !important;
13 | line-height: 2rem !important;
14 | margin: var(--text-spacing);
15 | }
16 |
17 | .fr-grid-row--gutters > [class^=fr-col-],
18 | .fr-grid-row--gutters > [class*=" fr-col-"],
19 | .fr-grid-row--gutters > .fr-col {
20 | padding: 0.5rem;
21 | }
22 |
23 | @media (min-width: 36em) { }
24 | @media (min-width: 48em) { }
25 | @media (min-width: 62em) { }
26 | @media (min-width: 78em) { }
27 |
28 | `;
29 |
30 | const expected = `
31 | export const frCoreClassNames= [
32 | "fr-text--light",
33 | "fr-text--xl",
34 | "fr-text--lead",
35 | "fr-grid-row--gutters",
36 | "fr-col"
37 | ] as const;
38 |
39 | export type FrCoreClassName = typeof frCoreClassNames[number];
40 |
41 | export const frIconClassNames= [
42 | "fr-icon-ancient-gate-fill"
43 | ] as const;
44 |
45 | export type FrIconClassName = typeof frIconClassNames[number];
46 |
47 | export const riIconClassNames= [
48 | "ri-airplay-fill",
49 | "ri-airplay-line"
50 | ] as const;
51 |
52 | export type RiIconClassName = typeof riIconClassNames[number];
53 |
54 | export type FrClassName = FrCoreClassName | FrIconClassName | RiIconClassName;
55 | `.replace(/^\n/, "");
56 |
57 | const got = generateClassNamesTsCode({
58 | "rawCssCode": input,
59 | "dsfrIconClassNames": ["fr-icon-ancient-gate-fill"],
60 | "remixiconClassNames": ["ri-airplay-fill", "ri-airplay-line"]
61 | });
62 |
63 | expect(got).toBe(expected);
64 | });
65 |
--------------------------------------------------------------------------------
/test/runtime/scripts/classNames/parseClassNames.test.ts:
--------------------------------------------------------------------------------
1 | /* eslint no-useless-escape: 0 */
2 | import { it, expect } from "vitest";
3 | import { parseClassNames } from "../../../../scripts/build/cssToTs/classNames";
4 |
5 | it("Parsing of fr classnames", () => {
6 | const input = `
7 | .fr-text--light {
8 | font-weight: 300 !important;
9 | }
10 |
11 | .fr-text--xl,
12 | .fr-text--lead {
13 | font-size: 1.25rem !important;
14 | line-height: 2rem !important;
15 | margin: var(--text-spacing);
16 | }
17 |
18 | .fr-grid-row--gutters > [class^=fr-col-],
19 | .fr-grid-row--gutters > [class*=" fr-col-"],
20 | .fr-grid-row--gutters > .fr-col {
21 | padding: 0.5rem;
22 | }
23 |
24 | .fr-header__navbar .fr-btn--menu {
25 | color: #3a3a3a;
26 | }
27 |
28 | .fr-tile--vertical\@md {
29 | display: flex;
30 | flex-direction: column;
31 | }
32 |
33 | @supports (aspect-ratio: 16/9) {
34 | .fr-ratio-32x9 {
35 | aspect-ratio: 3.5555555556 !important;
36 | }
37 |
38 | .fr-ratio-16x9 {
39 | aspect-ratio: 1.7777777778 !important;
40 | }
41 | }
42 |
43 | @media (min-width: 36em) { }
44 | @media (min-width: 48em) { }
45 | @media (min-width: 62em) { }
46 | @media (min-width: 78em) { }
47 |
48 | `;
49 |
50 | const expected = [
51 | "fr-text--light",
52 | "fr-text--xl",
53 | "fr-text--lead",
54 | "fr-grid-row--gutters",
55 | "fr-col",
56 | "fr-header__navbar",
57 | "fr-btn--menu",
58 | "fr-tile--vertical@md",
59 | "fr-ratio-32x9",
60 | "fr-ratio-16x9"
61 | ];
62 |
63 | const got = parseClassNames(input);
64 |
65 | expect(got).toStrictEqual(expected);
66 | });
67 |
--------------------------------------------------------------------------------
/test/runtime/scripts/colorDecisions/generateGetColorDecisionsTsCode.test.ts:
--------------------------------------------------------------------------------
1 | import { it, expect } from "vitest";
2 | import { generateGetColorDecisionsHexTsCode } from "../../../../scripts/build/cssToTs/colorDecisions";
3 |
4 | it("Generates the correct TS code for breakpoints", () => {
5 | const input = `
6 | :root {
7 | --grey-1000-50-hover: #ffffff;
8 | --grey-1000-50: #ffffff;
9 | --orange-terre-battue-850-200: #ffffff;
10 | --grey-975-100-hover: #ffffff;
11 | --grey-950-150: #ffffff;
12 |
13 | --background-default-grey-hover: var(--grey-1000-50-hover);
14 | --background-default-grey: var(--grey-1000-50);
15 | --border-action-low-orange-terre-battue: var(--orange-terre-battue-850-200);
16 | --background-alt-raised-grey-hover: var(--grey-975-100-hover);
17 | --background-contrast-overlap-grey: var(--grey-950-150);
18 | }
19 |
20 | :root[data-fr-theme=dark] {
21 | --grey-1000-50-hover: #000000;
22 | --grey-1000-50: #000000;
23 | --orange-terre-battue-850-200: #000000;
24 | --grey-975-100-hover: #000000;
25 | --grey-950-150: #000000;
26 | }
27 |
28 | @media (min-width: 36em) { }
29 |
30 | @media (min-width: 48em) { }
31 |
32 | @media (min-width: 62em) { }
33 |
34 | @media (min-width: 78em) { }
35 | `;
36 |
37 | const expected = `
38 | export function getColorDecisionsHex(
39 | params: {
40 | colorOptions: ColorOptions<"hex">;
41 | }
42 | ) {
43 |
44 | const { colorOptions } = params;
45 |
46 | return {
47 | "background": {
48 | "default": {
49 | "grey": {
50 | "hover": colorOptions.grey._1000_50.hover,
51 | "default": colorOptions.grey._1000_50.default
52 | }
53 | },
54 | "altRaised": {
55 | "grey": {
56 | "hover": colorOptions.grey._975_100.hover
57 | }
58 | },
59 | "contrastOverlap": {
60 | "grey": {
61 | "default": colorOptions.grey._950_150.default
62 | }
63 | }
64 | },
65 | "border": {
66 | "actionLow": {
67 | "orangeTerreBattue": {
68 | "default": colorOptions.orangeTerreBattue._850_200.default
69 | }
70 | }
71 | }
72 | } as const;
73 | }`.replace(/^\n/, "");
74 |
75 | const got = generateGetColorDecisionsHexTsCode(input);
76 |
77 | expect(got).toBe(expected);
78 | });
79 |
--------------------------------------------------------------------------------
/test/runtime/scripts/spacing/generateSpacingTsCode.test.ts:
--------------------------------------------------------------------------------
1 | import { it, expect } from "vitest";
2 | import { generateSpacingTsCode } from "../../../../scripts/build/cssToTs/spacing";
3 |
4 | it("Generates spacing TS code", () => {
5 | const rawCssCode = `
6 | .fr-m-7v {
7 | margin: 1.75rem !important;
8 | }
9 |
10 | .fr-m-12v,
11 | .fr-m-6w {
12 | margin: 3rem !important;
13 | }
14 |
15 | @media (min-width: 36em) { }
16 |
17 | @media (min-width: 48em) { }
18 |
19 | @media (min-width: 62em) { }
20 |
21 | @media (min-width: 78em) { }
22 | `.replace(/^\n/, "");
23 |
24 | const got = generateSpacingTsCode(rawCssCode);
25 |
26 | const expected = `
27 | export const spacingTokenByValue= {
28 | "7v": "1.75rem",
29 | "12v": "3rem",
30 | "6w": "3rem"
31 | } as const;
32 |
33 | export type SpacingTokenByValue = typeof spacingTokenByValue;
34 |
35 | export type SpacingToken = keyof SpacingTokenByValue;
36 | `.replace(/^\n/, "");
37 |
38 | expect(got).toBe(expected);
39 | });
40 |
--------------------------------------------------------------------------------
/test/runtime/scripts/spacing/parseSpacing.test.ts:
--------------------------------------------------------------------------------
1 | import { it, expect } from "vitest";
2 | import { parseSpacing } from "../../../../scripts/build/cssToTs/spacing";
3 | import type { SpacingTokenAndValue } from "../../../../scripts/build/cssToTs/spacing";
4 |
5 | it("Parse spacing successfully", () => {
6 | const input = `
7 | .fr-m-7v {
8 | margin: 1.75rem !important;
9 | }
10 |
11 | .fr-m-12v,
12 | .fr-m-6w {
13 | margin: 3rem !important;
14 | }
15 |
16 | @media (min-width: 36em) { }
17 |
18 | @media (min-width: 48em) { }
19 |
20 | @media (min-width: 62em) { }
21 |
22 | @media (min-width: 78em) { }
23 | `;
24 |
25 | const expected: SpacingTokenAndValue[] = [
26 | {
27 | "token": "7v",
28 | "value": "1.75rem"
29 | },
30 | {
31 | "token": "12v",
32 | "value": "3rem"
33 | },
34 | {
35 | "token": "6w",
36 | "value": "3rem"
37 | }
38 | ];
39 |
40 | const got = parseSpacing(input);
41 |
42 | expect(got).toStrictEqual(expected);
43 | });
44 |
--------------------------------------------------------------------------------
/test/types/Checkbox.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Checkbox } from "../../src/Checkbox";
3 |
4 | {
5 | ;
24 | }
25 | {
26 |
45 | }
46 | ]}
47 | />;
48 | }
49 |
--------------------------------------------------------------------------------
/test/types/README.md:
--------------------------------------------------------------------------------
1 | # Types tests
2 |
3 | In this directory we test the type definition of the module API.
4 | Theses tests are not meant to be run. If they TypeScript says there's no error, they passes.
5 |
6 | See [tsafe](https://github.com/garronej/tsafe) for more info about type-lever testing.
7 |
--------------------------------------------------------------------------------
/test/types/Tag.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Tag } from "../../src/Tag";
3 |
4 | {
5 | Label ;
6 | }
7 | {
8 | Label ;
9 | }
10 | {
11 |
12 | Label
13 | ;
14 | }
15 | {
16 | console.log("clicked")}>
17 | Label
18 | ;
19 | }
20 | {
21 |
27 | Label
28 | ;
29 | }
30 | {
31 | console.log("clicked")}>Label ;
32 | }
33 | {
34 | console.log("clicked on my dismissible tag")}>
35 | Label
36 | ;
37 | }
38 | {
39 | console.log("clicked")}>
40 | Label
41 | ;
42 | }
43 | {
44 | //@ts-expect-error: children is required
45 | ;
46 | }
47 | {
48 | //@ts-expect-error: nativeSpanProps and onClick are mutually exclusive
49 | console.log("clicked")}
51 | nativeSpanProps={{
52 | "id": "foo"
53 | }}
54 | >
55 | Label
56 | ;
57 | }
58 | {
59 | //@ts-expect-error: linkProps and onClick are mutually exclusive
60 | console.log("clicked")}
65 | >
66 | Label
67 | ;
68 | }
69 | {
70 | //@ts-expect-error: we shouldn't use Tag component as a span if it's dismissible
71 |
77 | Label
78 | ;
79 | }
80 | {
81 | //@ts-expect-error: we shouldn't use Tag component as an anchor if it's dismissible
82 |
88 | Label
89 | ;
90 | }
91 |
--------------------------------------------------------------------------------
/test/types/Tooltip.tsx:
--------------------------------------------------------------------------------
1 | import { Tooltip } from "../../src/Tooltip";
2 |
3 | {
4 | Exemple ;
5 | }
6 | {
7 |
8 | Exemple
9 | ;
10 | }
11 | {
12 | ;
13 | }
14 | {
15 |
16 | Exemple
17 | ;
18 | }
19 |
--------------------------------------------------------------------------------
/test/types/i18n.ts:
--------------------------------------------------------------------------------
1 | import { createComponentI18nApi } from "../../src/lib/i18n";
2 | import { assert } from "tsafe/assert";
3 | import type { Equals } from "tsafe";
4 |
5 | const { useTranslation, addSomeDsfrComponentTranslations } = createComponentI18nApi({
6 | "componentName": "SomeDsfrComponent",
7 | "frMessages": {
8 | /* spell-checker: disable */
9 |
10 | "do something": ({ what }: { what: string }) => `Faire ${what}`,
11 | "the good": "le bien",
12 | "bar": "bar"
13 | /* spell-checker: enable */
14 | }
15 | });
16 |
17 | addSomeDsfrComponentTranslations({
18 | "lang": "en",
19 | "messages": {
20 | "do something": ({ what }) => {
21 | assert>();
22 | return `Do ${what}`;
23 | },
24 | "the good": "the good"
25 | }
26 | });
27 |
28 | const { t } = useTranslation();
29 |
30 | t("do something", { "what": t("the good") });
31 |
--------------------------------------------------------------------------------
/test/types/libBinSync.ts:
--------------------------------------------------------------------------------
1 | /*
2 | NOTE: We can't import between src/lib and src/bin
3 | so when we copy declaration values we make sure they stay in sync here
4 | */
5 | import { assert } from "tsafe/assert";
6 | import type { Equals } from "tsafe";
7 | import type { ColorScheme as ColorScheme_lib } from "../../src/useIsDark";
8 | import type { ColorScheme as ColorScheme_bin } from "../../scripts/build/cssToTs/colorOptions";
9 |
10 | assert>();
11 |
--------------------------------------------------------------------------------
/test/types/spacing.ts:
--------------------------------------------------------------------------------
1 | import { spacing } from "../../src/fr/spacing";
2 | import { assert } from "tsafe/assert";
3 | import { Equals } from "tsafe";
4 |
5 | {
6 | const got = spacing("margin", { "topBottom": 4 });
7 |
8 | type Expected = {
9 | marginTop: number;
10 | marginBottom: number;
11 | };
12 |
13 | assert>();
14 | }
15 |
16 | {
17 | const got = spacing("margin", { "topBottom": "1v" });
18 |
19 | type Expected = {
20 | marginTop: "0.25rem";
21 | marginBottom: "0.25rem";
22 | };
23 |
24 | assert>();
25 | }
26 |
27 | {
28 | const got = spacing("margin", { "topBottom": "1v", "rightLeft": 33 });
29 |
30 | type Expected = {
31 | marginTop: "0.25rem";
32 | marginBottom: "0.25rem";
33 | marginLeft: number;
34 | marginRight: number;
35 | };
36 |
37 | assert>();
38 | }
39 |
40 | {
41 | const got = spacing("margin", { "topBottom": "1v", "rightLeft": 33 });
42 |
43 | type Expected = {
44 | marginTop: "0.25rem";
45 | marginBottom: "0.25rem";
46 | marginLeft: number;
47 | marginRight: number;
48 | };
49 |
50 | assert>();
51 | }
52 |
53 | {
54 | const got = spacing("margin", { "bottom": "1v" });
55 |
56 | type Expected = {
57 | marginBottom: "0.25rem";
58 | };
59 |
60 | assert>();
61 | }
62 |
63 | {
64 | const got = spacing("padding", { "bottom": "1v", "rightLeft": 42 });
65 |
66 | type Expected = {
67 | paddingRight: number;
68 | paddingLeft: number;
69 | paddingBottom: "0.25rem";
70 | };
71 |
72 | assert>();
73 | }
74 |
75 | {
76 | const got = spacing("margin", { "rightLeft": "auto" });
77 |
78 | type Expected = {
79 | marginLeft: "auto";
80 | marginRight: "auto";
81 | };
82 |
83 | assert>();
84 | }
85 |
--------------------------------------------------------------------------------
/tsproject.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "sourceMap": true,
5 | "newLine": "LF",
6 | "noUnusedLocals": true,
7 | "noUnusedParameters": true,
8 | "incremental": true,
9 | "strict": true,
10 | "downlevelIteration": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "composite": true,
13 | "outDir": "./dist",
14 | "skipLibCheck": true
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/vitest.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 |
3 | export default defineConfig({
4 | "test": {
5 | // ref: https://vitest.dev/config/
6 | "include": ["test/runtime/**/*.test.ts"],
7 | "watch": false,
8 | "outputFile": "./vitest-report.json",
9 | "testTimeout": 43600
10 | }
11 | });
12 |
--------------------------------------------------------------------------------