├── .github └── workflows │ ├── front-dependencies-installation.yml │ └── main.yml ├── .gitignore ├── .storybook ├── main.ts └── preview.tsx ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── common.d.ts ├── cunningham.ts ├── eslint.config.js ├── package.json ├── public ├── storybook │ ├── hero-image.png │ └── logo-fichiers.svg └── vite.svg ├── src ├── assets │ ├── fonts │ │ └── Marianne │ │ │ ├── Marianne-Bold.woff │ │ │ ├── Marianne-Bold.woff2 │ │ │ ├── Marianne-Bold_Italic.woff │ │ │ ├── Marianne-Bold_Italic.woff2 │ │ │ ├── Marianne-ExtraBold.woff │ │ │ ├── Marianne-ExtraBold.woff2 │ │ │ ├── Marianne-ExtraBold_Italic.woff │ │ │ ├── Marianne-ExtraBold_Italic.woff2 │ │ │ ├── Marianne-Light.woff │ │ │ ├── Marianne-Light.woff2 │ │ │ ├── Marianne-Light_Italic.woff │ │ │ ├── Marianne-Light_Italic.woff2 │ │ │ ├── Marianne-Medium.woff │ │ │ ├── Marianne-Medium.woff2 │ │ │ ├── Marianne-Medium_Italic.woff │ │ │ ├── Marianne-Medium_Italic.woff2 │ │ │ ├── Marianne-Regular.woff │ │ │ ├── Marianne-Regular.woff2 │ │ │ ├── Marianne-Regular_Italic.woff │ │ │ ├── Marianne-Regular_Italic.woff2 │ │ │ ├── Marianne-Thin.woff │ │ │ ├── Marianne-Thin.woff2 │ │ │ ├── Marianne-Thin_Italic.woff │ │ │ ├── Marianne-Thin_Italic.woff2 │ │ │ └── Marianne-font.css │ ├── logo-gouv.svg │ ├── proconnect-content-disabled.svg │ ├── proconnect-content.svg │ └── react.svg ├── components │ ├── Provider │ │ └── Provider.tsx │ ├── badge │ │ ├── badge.stories.tsx │ │ ├── badge.tsx │ │ ├── index.scss │ │ ├── index.tsx │ │ └── types.ts │ ├── button │ │ ├── ProConnectButton.tsx │ │ ├── button.stories.tsx │ │ ├── index.scss │ │ └── index.tsx │ ├── datagrid │ │ ├── datagrid.stories.tsx │ │ ├── index.scss │ │ └── resources │ │ │ └── databaseCars.json │ ├── dnd │ │ ├── Draggable.tsx │ │ └── Droppable.tsx │ ├── dropdown-menu │ │ ├── DropdownMenu.tsx │ │ ├── dropdown.stories.tsx │ │ ├── index.scss │ │ ├── index.tsx │ │ ├── types.ts │ │ └── useDropdownMenu.tsx │ ├── filter │ │ ├── Filter.stories.tsx │ │ ├── Filter.tsx │ │ └── index.scss │ ├── footer │ │ ├── Footer.stories.tsx │ │ ├── Footer.tsx │ │ ├── assets │ │ │ └── external-link.svg │ │ └── index.scss │ ├── form │ │ ├── checkbox │ │ │ ├── checkbox.stories.tsx │ │ │ └── index.scss │ │ ├── form.stories.tsx │ │ ├── index.tsx │ │ ├── input │ │ │ ├── index.scss │ │ │ └── input.stories.tsx │ │ ├── label │ │ │ ├── WithLabel.tsx │ │ │ ├── index.scss │ │ │ ├── index.tsx │ │ │ ├── label.stories.tsx │ │ │ ├── label.tsx │ │ │ └── with-label.stories.tsx │ │ ├── radio │ │ │ ├── index.scss │ │ │ └── radio.stories.tsx │ │ ├── select │ │ │ ├── index.scss │ │ │ └── select.stories.tsx │ │ ├── switch │ │ │ ├── index.scss │ │ │ └── switch.stories.tsx │ │ └── textarea │ │ │ ├── index.scss │ │ │ └── textarea.stories.tsx │ ├── hero │ │ ├── Hero.stories.tsx │ │ ├── Hero.tsx │ │ └── index.scss │ ├── icon │ │ ├── Icon.stories.tsx │ │ ├── Icon.tsx │ │ ├── index.scss │ │ ├── index.tsx │ │ ├── types.ts │ │ └── utils.ts │ ├── la-gaufre │ │ ├── LaGaufre.stories.tsx │ │ ├── LaGaufre.tsx │ │ └── index.scss │ ├── language │ │ ├── index.scss │ │ ├── index.tsx │ │ └── language-picker.tsx │ ├── layout │ │ ├── MainLayout.tsx │ │ ├── header │ │ │ ├── Header.tsx │ │ │ ├── index.scss │ │ │ └── logo-exemple.svg │ │ ├── index.scss │ │ ├── index.tsx │ │ ├── layout.stories.tsx │ │ ├── left-panel │ │ │ ├── LeftPanel.tsx │ │ │ └── index.scss │ │ ├── right-panel │ │ │ ├── RightPanel.tsx │ │ │ └── index.scss │ │ └── utils.ts │ ├── loader │ │ ├── Spinner.tsx │ │ ├── index.scss │ │ ├── index.tsx │ │ └── spinner.stories.tsx │ ├── modal │ │ ├── index.scss │ │ └── modal.stories.tsx │ ├── quick-search │ │ ├── QuickSearch.tsx │ │ ├── QuickSearchGroup.tsx │ │ ├── QuickSearchInput.tsx │ │ ├── QuickSearchItem.tsx │ │ ├── QuickSearchItemTemplate.tsx │ │ ├── index.scss │ │ ├── index.tsx │ │ ├── quick-search-global-style.scss │ │ ├── quick-search.stories.tsx │ │ └── types.tsx │ ├── separator │ │ ├── HorizontalSeparator.tsx │ │ ├── index.scss │ │ ├── index.tsx │ │ └── separator.stories.tsx │ ├── share │ │ ├── access │ │ │ ├── AccessRoleDropdown.tsx │ │ │ ├── access-role-dropdown.stories.tsx │ │ │ ├── index.scss │ │ │ └── index.tsx │ │ ├── index.ts │ │ ├── modal │ │ │ ├── ShareModal.tsx │ │ │ ├── index.tsx │ │ │ ├── items │ │ │ │ ├── SearchUserItem.tsx │ │ │ │ ├── ShareInvitationItem.tsx │ │ │ │ ├── ShareMemberItem.tsx │ │ │ │ ├── index.scss │ │ │ │ ├── index.tsx │ │ │ │ └── share-items.stories.tsx │ │ │ ├── share-modal.scss │ │ │ └── stories │ │ │ │ ├── ShareModalExample.tsx │ │ │ │ └── share-modal.stories.tsx │ │ ├── types.ts │ │ ├── users-invitation │ │ │ ├── InvitationUserSelectorList.tsx │ │ │ └── index.scss │ │ └── utils │ │ │ ├── ShareModalCopyLinkFooter.tsx │ │ │ └── index.tsx │ ├── tabs │ │ ├── Tabs.tsx │ │ ├── index.scss │ │ ├── index.tsx │ │ ├── tabs.stories.tsx │ │ └── types.ts │ ├── tooltip │ │ ├── index.scss │ │ └── index.stories.tsx │ ├── tree-view │ │ ├── TreeView.tsx │ │ ├── TreeViewItem.tsx │ │ ├── TreeViewSeparator.tsx │ │ ├── index.scss │ │ ├── index.tsx │ │ ├── providers │ │ │ ├── TreeContext.tsx │ │ │ └── index.tsx │ │ ├── stories │ │ │ ├── data.ts │ │ │ ├── logo-exemple.svg │ │ │ ├── tree-view-exemple.scss │ │ │ ├── tree-view-exemple.tsx │ │ │ ├── tree-view-item-exemple.tsx │ │ │ └── tree.stories.tsx │ │ ├── types.ts │ │ ├── useTree.tsx │ │ └── utils.tsx │ └── users │ │ ├── avatar │ │ ├── UserAvatar.tsx │ │ ├── avatar.stories.tsx │ │ ├── index.scss │ │ ├── index.tsx │ │ ├── utils.test.ts │ │ └── utils.ts │ │ ├── index.ts │ │ └── rows │ │ ├── UserRow.tsx │ │ ├── index.scss │ │ └── user-row.stories.tsx ├── cunningham-custom-style.scss ├── cunningham-tokens-sass.scss ├── cunningham-tokens.css ├── hooks │ ├── useControllableState.ts │ └── useResponsive.tsx ├── index.scss ├── index.ts ├── library.scss ├── locales │ ├── Locale.tsx │ ├── en-US.json │ ├── fr-FR.json │ └── originals │ │ ├── en-US.json │ │ └── fr-FR.json ├── style-stories.scss ├── styles │ └── _variables.scss └── utils │ ├── children.tsx │ └── objects.ts ├── tsconfig.json ├── vite.config.ts └── yarn.lock /.github/workflows/front-dependencies-installation.yml: -------------------------------------------------------------------------------- 1 | name: Install frontend installation reusable workflow 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | node_version: 7 | required: false 8 | default: "20.x" 9 | type: string 10 | 11 | jobs: 12 | front-dependencies-installation: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | - name: Restore the frontend cache 18 | uses: actions/cache@v4 19 | id: front-node_modules 20 | with: 21 | path: "node_modules" 22 | key: front-node_modules-${{ hashFiles('yarn.lock') }} 23 | - name: Setup Node.js 24 | if: steps.front-node_modules.outputs.cache-hit != 'true' 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: ${{ inputs.node_version }} 28 | - name: Install dependencies 29 | if: steps.front-node_modules.outputs.cache-hit != 'true' 30 | run: yarn install --frozen-lockfile 31 | - name: Cache install frontend 32 | if: steps.front-node_modules.outputs.cache-hit != 'true' 33 | uses: actions/cache@v4 34 | with: 35 | path: "node_modules" 36 | key: front-node_modules-${{ hashFiles('yarn.lock') }} 37 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Frontend CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - "*" 10 | 11 | jobs: 12 | install-front: 13 | uses: ./.github/workflows/front-dependencies-installation.yml 14 | with: 15 | node_version: "20.x" 16 | 17 | lint-git: 18 | runs-on: ubuntu-latest 19 | if: github.event_name == 'pull_request' # Makes sense only for pull requests 20 | steps: 21 | - name: Checkout repository 22 | uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | - name: show 26 | run: git log 27 | - name: Enforce absence of print statements in code 28 | run: | 29 | ! git diff origin/${{ github.event.pull_request.base.ref }}..HEAD -- . ':(exclude)**/main.yml' | grep "print(" 30 | - name: Check absence of fixup commits 31 | run: | 32 | ! git log | grep 'fixup!' 33 | - name: Install gitlint 34 | run: pip install --user requests gitlint 35 | - name: Lint commit messages added to main 36 | run: ~/.local/bin/gitlint --commits origin/${{ github.event.pull_request.base.ref }}..HEAD 37 | 38 | lint: 39 | runs-on: ubuntu-latest 40 | needs: install-front 41 | steps: 42 | - name: Checkout repository 43 | uses: actions/checkout@v4 44 | - name: Setup Node.js 45 | uses: actions/setup-node@v4 46 | with: 47 | node-version: "20.x" 48 | - name: Restore the frontend cache 49 | uses: actions/cache@v4 50 | with: 51 | path: "node_modules" 52 | key: front-node_modules-${{ hashFiles('yarn.lock') }} 53 | fail-on-cache-miss: true 54 | - name: Check linting 55 | run: yarn lint 56 | 57 | test: 58 | runs-on: ubuntu-latest 59 | needs: install-front 60 | steps: 61 | - name: Checkout repository 62 | uses: actions/checkout@v4 63 | - name: Setup Node.js 64 | uses: actions/setup-node@v4 65 | with: 66 | node-version: "20.x" 67 | - name: Restore the frontend cache 68 | uses: actions/cache@v4 69 | with: 70 | path: "node_modules" 71 | key: front-node_modules-${{ hashFiles('yarn.lock') }} 72 | fail-on-cache-miss: true 73 | - name: Run tests 74 | run: yarn test 75 | 76 | build: 77 | runs-on: ubuntu-latest 78 | needs: install-front 79 | steps: 80 | - name: Checkout repository 81 | uses: actions/checkout@v4 82 | - name: Setup Node.js 83 | uses: actions/setup-node@v4 84 | with: 85 | node-version: "20.x" 86 | - name: Restore the frontend cache 87 | uses: actions/cache@v4 88 | with: 89 | path: "node_modules" 90 | key: front-node_modules-${{ hashFiles('yarn.lock') }} 91 | fail-on-cache-miss: true 92 | - name: Build 93 | run: yarn build 94 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Distribution / packaging 2 | .Python 3 | build/ 4 | develop-eggs/ 5 | dist/ 6 | downloads/ 7 | eggs/ 8 | .eggs/ 9 | lib/ 10 | lib64/ 11 | parts/ 12 | sdist/ 13 | var/ 14 | wheels/ 15 | pip-wheel-metadata/ 16 | share/python-wheels/ 17 | *.egg-info/ 18 | .installed.cfg 19 | *.egg 20 | MANIFEST 21 | .DS_Store 22 | .next/ 23 | 24 | # Environments 25 | .venv 26 | env/ 27 | venv/ 28 | ENV/ 29 | env.bak/ 30 | venv.bak/ 31 | env.d/development/* 32 | !env.d/development/*.dist 33 | env.d/terraform 34 | 35 | # npm 36 | node_modules 37 | 38 | 39 | # Logs 40 | *.log 41 | 42 | 43 | 44 | # Test & lint 45 | .coverage 46 | .pylint.d 47 | .pytest_cache 48 | db.sqlite3 49 | .mypy_cache 50 | 51 | # Site media 52 | /data/ 53 | 54 | # IDEs 55 | .idea/ 56 | .vscode/ 57 | *.iml 58 | .devcontainer 59 | node_modules 60 | .changeset -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from "@storybook/react-vite"; 2 | 3 | const config: StorybookConfig = { 4 | stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"], 5 | addons: [ 6 | "@storybook/addon-onboarding", 7 | "@storybook/addon-essentials", 8 | "@chromatic-com/storybook", 9 | "@storybook/addon-interactions", 10 | "@storybook/addon-a11y", 11 | ], 12 | 13 | framework: { 14 | name: "@storybook/react-vite", 15 | options: {}, 16 | }, 17 | }; 18 | export default config; 19 | -------------------------------------------------------------------------------- /.storybook/preview.tsx: -------------------------------------------------------------------------------- 1 | import { CunninghamProvider } from "../src/components/Provider/Provider"; 2 | import "./../src/index.scss"; 3 | import "./../src/style-stories.scss"; 4 | import type { Preview } from "@storybook/react"; 5 | import React from "react"; 6 | 7 | const preview: Preview = { 8 | decorators: [ 9 | (Story) => ( 10 | 11 |
12 | 13 |
14 |
15 | ), 16 | ], 17 | parameters: { 18 | backgrounds: { 19 | values: [ 20 | // 👇 Add your own 21 | { name: "Gray", value: "#F7F9F2" }, 22 | ], 23 | // 👇 Specify which background is shown by default 24 | default: "light", 25 | }, 26 | controls: { 27 | matchers: { 28 | color: /(background|color)$/i, 29 | date: /Date$/i, 30 | }, 31 | }, 32 | }, 33 | }; 34 | 35 | export default preview; 36 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @gouvfr-lasuite/ui-kit 2 | 3 | ## 0.8.0 4 | 5 | ### Minor Changes 6 | 7 | - add icon component 8 | - add material filled icons 9 | 10 | ## 0.7.0 11 | 12 | ### Minor Changes 13 | 14 | - export avatar utils 15 | - Do not show remove access option if the deleteAccess prop is not provided 16 | 17 | ### Patch Changes 18 | 19 | - Fix the keyDown event propagation in TreeViewItem 20 | 21 | ## 0.6.0 22 | 23 | ### Minor Changes 24 | 25 | - add separator for the filter component 26 | 27 | ### Patch Changes 28 | 29 | - fix the cunningham file because the refs were not used correctly 30 | - fix ShareModal 31 | 32 | ## 0.5.0 33 | 34 | ### Minor Changes 35 | 36 | - add Badge component 37 | - add controlled left-panel 38 | 39 | ### Patch Changes 40 | 41 | - update react-aria deps 42 | 43 | ## 0.4.1 44 | 45 | ### Patch Changes 46 | 47 | - hide the user list and enter in the Share Modal component if we can't update 48 | 49 | ## 0.4.0 50 | 51 | ### Minor Changes 52 | 53 | - add filter component 54 | 55 | ## 0.3.0 56 | 57 | ### Minor Changes 58 | 59 | - add ShareModal component and utils components 60 | - add topMessage for DropdownMenu 61 | - add Avatar component 62 | - add UserRow commponent 63 | 64 | ### Patch changes 65 | 66 | - fix useTree hook 67 | 68 | ## 0.2.0 69 | 70 | ### Minor Changes 71 | 72 | - add Hero component 73 | - add Footer component 74 | - add La Gaufre component 75 | - add hideLeftPanelOnDesktop on MainLayout 76 | 77 | ### Patch changes 78 | 79 | - remove unused tokens 80 | - fix locales format, frFR to fr-FR 81 | 82 | ## 0.1.10 83 | 84 | ### Patch Changes 85 | 86 | - (tree-view) update node values when the tree has changed 87 | 88 | ## 0.1.9 89 | 90 | ### Patch Changes 91 | 92 | - (tree-view) fix event propagation 93 | 94 | ## 0.1.8 95 | 96 | ### Patch Changes 97 | 98 | - (tree-view) fixed the incessant row re-rendering 99 | 100 | ## 0.1.7 101 | 102 | ### Patch Changes 103 | 104 | - (tree-view) fix updateNode method 105 | 106 | ## 0.1.6 107 | 108 | ### Patch Changes 109 | 110 | - (tree-view) add getAncestors to the useTree hook 111 | 112 | ## 0.1.5 113 | 114 | ### Patch Changes 115 | 116 | - (tree-view) Add getNode to the useTree hook 117 | 118 | ## 0.1.4 119 | 120 | ### Patch Changes 121 | 122 | - Add RightPannel component 123 | - Add TreeProvider and useTreeContext 124 | 125 | ## 0.1.3 126 | 127 | ### Patch Changes 128 | 129 | - Adjusted the position of the separator in the TreeView component 130 | - Added logic to update child elements when a parent element is updated in the useTree hook 131 | 132 | ## 0.1.2 133 | 134 | ### Patch Changes 135 | 136 | - Updated logic of the TreeView component's onMove method 137 | 138 | ## 0.1.1 139 | 140 | ### Patch Changes 141 | 142 | - Improve accessibility for DropdownMenu 143 | - Fixed addChild method for useTree hook 144 | - Fixed onMove method for TreeView 145 | 146 | ## 0.1.0 147 | 148 | ### Minor Changes 149 | 150 | - Add resetTree method to useTree hook 151 | - Add default opened node to TreeView 152 | 153 | ## 0.0.2 154 | 155 | ### Patch Changes 156 | 157 | - Update react to 19.0.0 158 | - Update cunningham to 3.0.0 159 | 160 | ## 0.0.1 161 | 162 | ### Major Changes 163 | 164 | - Add basic components: button, datagrid, dropdown-menu, modal, layouts, provider, quick-search, tabs, tree-veiw 165 | - Add storybook 166 | - Add custom cunningham.ts file 167 | - Still a WIP version 168 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Release 4 | 5 | 1. Run `yarn build` 6 | 7 | 2. Update CHANGELOG.md according to the Major / Minor / Patch semver convention. 8 | 9 | 3. Based on semver upgrade the package version in `package.json`. 10 | 11 | 4. Commit the changes and create a PR named "🔖(release) version package". 12 | 13 | 5. Ask for approval, once the PR is approved, merge it. 14 | 15 | 6. Once merged, run `npx @changesets/cli publish`. It will publish the new version of the package to NPM and create a git tag. 16 | 17 | 7. Run `git push origin ` 18 | 19 | 8. Tell everyone 🎉 ! 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 La Suite numérique 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # design-system -------------------------------------------------------------------------------- /common.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.svg" { 2 | const content: string; 3 | export default content; 4 | } -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | }, 23 | }, 24 | ) 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gouvfr-lasuite/ui-kit", 3 | "private": false, 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "version": "0.8.0", 8 | "type": "module", 9 | "main": "./dist/index.cjs", 10 | "module": "./dist/index.js", 11 | "types": "./dist/index.d.ts", 12 | "files": [ 13 | "dist" 14 | ], 15 | "exports": { 16 | ".": { 17 | "types": "./dist/index.d.ts", 18 | "import": "./dist/index.js", 19 | "require": "./dist/index.cjs" 20 | }, 21 | "./style": "./dist/style.css" 22 | }, 23 | "scripts": { 24 | "dev": "vite", 25 | "build": "yarn run import-locales && tsc && vite build && cp cunningham.ts dist/cunningham.ts", 26 | "lint": "eslint . --report-unused-disable-directives --max-warnings 0", 27 | "preview": "vite preview", 28 | "test": "vitest run", 29 | "build-theme": "cunningham -g css,scss -o src && mv src/cunningham-tokens.scss src/cunningham-tokens-sass.scss", 30 | "storybook": "storybook dev -p 6006", 31 | "build-storybook": "storybook build", 32 | "import-locales": "cp -R node_modules/@openfun/cunningham-react/dist/locales/ src/locales/originals/" 33 | }, 34 | "dependencies": { 35 | "@dnd-kit/core": "6.3.1", 36 | "@dnd-kit/modifiers": "9.0.0", 37 | "@dnd-kit/sortable": "10.0.0", 38 | "@fontsource/material-icons": "5.2.5", 39 | "@gouvfr-lasuite/integration": "1.0.2", 40 | "@openfun/cunningham-react": "3.0.0", 41 | "@types/node": "22.10.7", 42 | "clsx": "2.1.1", 43 | "cmdk": "1.0.4", 44 | "react": "19.0.0", 45 | "react-arborist": "3.4.3", 46 | "react-aria-components": "1.8.0", 47 | "react-dom": "19.0.0", 48 | "react-resizable-panels": "2.1.7", 49 | "react-stately": "3.37.0" 50 | }, 51 | "devDependencies": { 52 | "@babel/core": "7.26.0", 53 | "@babel/plugin-proposal-decorators": "7.25.9", 54 | "@babel/plugin-proposal-export-default-from": "7.25.9", 55 | "@babel/preset-typescript": "7.26.0", 56 | "@chromatic-com/storybook": "3.2.4", 57 | "@eslint/js": "9.17.0", 58 | "@storybook/addon-a11y": "8.5.2", 59 | "@storybook/addon-essentials": "8.5.2", 60 | "@storybook/addon-interactions": "8.5.2", 61 | "@storybook/addon-onboarding": "8.5.2", 62 | "@storybook/blocks": "8.5.2", 63 | "@storybook/react": "8.5.2", 64 | "@storybook/react-vite": "8.5.2", 65 | "@storybook/test": "8.5.2", 66 | "@types/react": "19.0.0", 67 | "@types/react-dom": "19.0.0", 68 | "@vitejs/plugin-react": "4.3.4", 69 | "@vitest/coverage-c8": "0.33.0", 70 | "@vitest/ui": "2.1.8", 71 | "babel-loader": "9.2.1", 72 | "css-loader": "7.1.2", 73 | "eslint": "9.17.0", 74 | "eslint-plugin-react-hooks": "5.0.0", 75 | "eslint-plugin-react-refresh": "0.4.16", 76 | "eslint-plugin-storybook": "0.11.2", 77 | "glob": "11.0.0", 78 | "globals": "15.14.0", 79 | "jsdom": "25.0.1", 80 | "react-hook-form": "7.54.2", 81 | "remark-gfm": "4.0.0", 82 | "sass": "1.83.1", 83 | "sass-loader": "16.0.4", 84 | "storybook": "8.5.2", 85 | "style-loader": "4.0.0", 86 | "typescript": "5.6.2", 87 | "typescript-eslint": "8.18.2", 88 | "use-resize-observer": "9.1.0", 89 | "vite": "6.0.5", 90 | "vite-plugin-dts": "4.5.0", 91 | "vite-tsconfig-paths": "5.1.4", 92 | "vitest": "2.1.8", 93 | "vitest-fetch-mock": "0.4.3", 94 | "yup": "1.6.1" 95 | }, 96 | "eslintConfig": { 97 | "extends": [ 98 | "plugin:storybook/recommended" 99 | ] 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /public/storybook/hero-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/public/storybook/hero-image.png -------------------------------------------------------------------------------- /public/storybook/logo-fichiers.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-Bold.woff -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-Bold.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-Bold_Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-Bold_Italic.woff -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-Bold_Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-Bold_Italic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-ExtraBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-ExtraBold.woff -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-ExtraBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-ExtraBold.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-ExtraBold_Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-ExtraBold_Italic.woff -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-ExtraBold_Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-ExtraBold_Italic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-Light.woff -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-Light.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-Light_Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-Light_Italic.woff -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-Light_Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-Light_Italic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-Medium.woff -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-Medium.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-Medium_Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-Medium_Italic.woff -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-Medium_Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-Medium_Italic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-Regular.woff -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-Regular.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-Regular_Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-Regular_Italic.woff -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-Regular_Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-Regular_Italic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-Thin.woff -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-Thin.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-Thin_Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-Thin_Italic.woff -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-Thin_Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suitenumerique/ui-kit/97343879f3fc681b202590a36a35ed21bf6c7685/src/assets/fonts/Marianne/Marianne-Thin_Italic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Marianne/Marianne-font.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: Marianne; 3 | src: 4 | url(':/assets/fonts/Marianne/Marianne-Thin.woff2') format('woff2'), 5 | url(':/assets/fonts/Marianne/Marianne-Thin.woff') format('woff'); 6 | font-weight: 100; 7 | } 8 | 9 | 10 | @font-face { 11 | font-family: Marianne; 12 | src: 13 | url(':/assets/fonts/Marianne/Marianne-Thin_Italic.woff2') format('woff2'), 14 | url(':/assets/fonts/Marianne/Marianne-Thin_Italic.woff') format('woff'); 15 | font-weight: 100; 16 | font-style: italic; 17 | } 18 | 19 | @font-face { 20 | font-family: Marianne; 21 | src: 22 | url(':/assets/fonts/Marianne/Marianne-Light.woff2') format('woff2'), 23 | url(':/assets/fonts/Marianne/Marianne-Light.woff') format('woff'); 24 | font-weight: 300; 25 | } 26 | 27 | @font-face { 28 | font-family: Marianne; 29 | src: 30 | url(':/assets/fonts/Marianne/Marianne-Light_Italic.woff2') format('woff2'), 31 | url(':/assets/fonts/Marianne/Marianne-Light_Italic.woff') format('woff'); 32 | font-weight: 300; 33 | font-style: italic; 34 | } 35 | 36 | @font-face { 37 | font-family: Marianne; 38 | src: 39 | url(':/assets/fonts/Marianne/Marianne-Regular.woff2') format('woff2'), 40 | url(':/assets/fonts/Marianne/Marianne-Regular.woff') format('woff'); 41 | font-weight: 400; 42 | } 43 | 44 | @font-face { 45 | font-family: Marianne; 46 | src: 47 | url(':/assets/fonts/Marianne/Marianne-Regular_Italic.woff2') format('woff2'), 48 | url(':/assets/fonts/Marianne/Marianne-Regular_Italic.woff') format('woff'); 49 | font-weight: 400; 50 | font-style: italic; 51 | } 52 | 53 | @font-face { 54 | font-family: Marianne; 55 | src: 56 | url(':/assets/fonts/Marianne/Marianne-Medium.woff2') format('woff2'), 57 | url(':/assets/fonts/Marianne/Marianne-Medium.woff') format('woff'); 58 | font-weight: 500; 59 | } 60 | 61 | @font-face { 62 | font-family: Marianne; 63 | src: 64 | url(':/assets/fonts/Marianne/Marianne-Medium_Italic.woff2') format('woff2'), 65 | url(':/assets/fonts/Marianne/Marianne-Medium_Italic.woff') format('woff'); 66 | font-weight: 500; 67 | font-style: italic; 68 | } 69 | 70 | @font-face { 71 | font-family: Marianne; 72 | src: 73 | url(':/assets/fonts/Marianne/Marianne-Bold.woff2') format('woff2'), 74 | url(':/assets/fonts/Marianne/Marianne-Bold.woff') format('woff'); 75 | font-weight: 700; 76 | } 77 | 78 | @font-face { 79 | font-family: Marianne; 80 | src: 81 | url(':/assets/fonts/Marianne/Marianne-Bold_Italic.woff2') format('woff2'), 82 | url(':/assets/fonts/Marianne/Marianne-Bold_Italic.woff') format('woff'); 83 | font-weight: 700; 84 | font-style: italic; 85 | } 86 | 87 | @font-face { 88 | font-family: Marianne; 89 | src: 90 | url(':/assets/fonts/Marianne/Marianne-ExtraBold.woff2') format('woff2'), 91 | url(':/assets/fonts/Marianne/Marianne-ExtraBold.woff') format('woff'); 92 | font-weight: 800; 93 | } 94 | 95 | @font-face { 96 | font-family: Marianne; 97 | src: 98 | url(':/assets/fonts/Marianne/Marianne-ExtraBold_Italic.woff2') format('woff2'), 99 | url(':/assets/fonts/Marianne/Marianne-ExtraBold_Italic.woff') format('woff'); 100 | font-weight: 800; 101 | font-style: italic; 102 | } 103 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Provider/Provider.tsx: -------------------------------------------------------------------------------- 1 | import { getLocales } from ":/locales/Locale"; 2 | import { CunninghamProvider as OriginalProvider } from "@openfun/cunningham-react"; 3 | 4 | export const CunninghamProvider = ( 5 | props: Parameters[0] 6 | ) => { 7 | const locales = getLocales(); 8 | return ; 9 | }; 10 | -------------------------------------------------------------------------------- /src/components/badge/badge.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react'; 2 | import Badge from './badge'; 3 | 4 | const meta: Meta = { 5 | title: 'Components/Badge', 6 | component: Badge, 7 | tags: ['autodocs'], 8 | }; 9 | 10 | export default meta; 11 | type Story = StoryObj; 12 | 13 | export const Default: Story = { 14 | args: { 15 | children: 'Badge', 16 | }, 17 | }; 18 | 19 | export const All: Story = { 20 | render: () => ( 21 |
22 | Accent 23 | Neutral 24 | Info 25 | Success 26 | Warning 27 | Danger 28 |
29 | ) 30 | } 31 | 32 | export const WithNumber: Story = { 33 | args: { 34 | children: '42', 35 | }, 36 | }; 37 | 38 | export const Uppercased: Story = { 39 | args: { 40 | children: 'new', 41 | uppercased: true, 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /src/components/badge/badge.tsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | import { HTMLAttributes, PropsWithChildren } from "react"; 3 | import { BadgeType } from "./types"; 4 | 5 | type BadgeProps = HTMLAttributes & { 6 | uppercased?: boolean; 7 | type?: BadgeType; 8 | } 9 | 10 | const Badge = ({ children, uppercased = false, type = "accent", className, ...props }: PropsWithChildren) => { 11 | return ( 12 |
16 | {children} 17 |
18 | ); 19 | }; 20 | 21 | export default Badge; 22 | -------------------------------------------------------------------------------- /src/components/badge/index.scss: -------------------------------------------------------------------------------- 1 | .c__badge { 2 | display: inline-flex; 3 | text-align: center; 4 | line-height: 1em; 5 | font-size: var(--c--components--badge--font-size); 6 | padding-inline: var(--c--components--badge--padding-inline); 7 | padding-block: var(--c--components--badge--padding-block); 8 | border-radius: var(--c--components--badge--border-radius); 9 | font-weight: 600; 10 | font-variant-numeric: tabular-nums; 11 | } 12 | 13 | .c__badge--uppercased { 14 | text-transform: uppercase; 15 | letter-spacing: 0.05em; 16 | } 17 | 18 | .c__badge--accent { 19 | background-color: var(--c--components--badge--accent--background-color); 20 | color: var(--c--components--badge--accent--color); 21 | } 22 | 23 | .c__badge--neutral { 24 | background-color: var(--c--components--badge--neutral--background-color); 25 | color: var(--c--components--badge--neutral--color); 26 | } 27 | 28 | .c__badge--danger { 29 | background-color: var(--c--components--badge--danger--background-color); 30 | color: var(--c--components--badge--danger--color); 31 | } 32 | 33 | .c__badge--success { 34 | background-color: var(--c--components--badge--success--background-color); 35 | color: var(--c--components--badge--success--color); 36 | } 37 | 38 | .c__badge--warning { 39 | background-color: var(--c--components--badge--warning--background-color); 40 | color: var(--c--components--badge--warning--color); 41 | } 42 | 43 | .c__badge--info { 44 | background-color: var(--c--components--badge--info--background-color); 45 | color: var(--c--components--badge--info--color); 46 | } 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/components/badge/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./badge"; -------------------------------------------------------------------------------- /src/components/badge/types.ts: -------------------------------------------------------------------------------- 1 | export type BadgeType = 2 | | "accent" 3 | | "neutral" 4 | | "danger" 5 | | "success" 6 | | "warning" 7 | | "info"; -------------------------------------------------------------------------------- /src/components/button/ProConnectButton.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@openfun/cunningham-react"; 2 | 3 | export type ProConnectButtonProps = { 4 | disabled?: boolean; 5 | onClick?: () => void; 6 | }; 7 | export const ProConnectButton = ({ 8 | disabled, 9 | onClick, 10 | }: ProConnectButtonProps) => { 11 | return ( 12 | 51 | } 52 | /> 53 | ); 54 | }; 55 | 56 | export const ClientSideWithPagination = () => { 57 | return ( 58 | <> 59 | 87 | 88 | ); 89 | }; 90 | -------------------------------------------------------------------------------- /src/components/datagrid/index.scss: -------------------------------------------------------------------------------- 1 | .c__datagrid__table__container { 2 | overflow: auto; 3 | } 4 | 5 | .c__datagrid__table__container > table th { 6 | height: 24px; 7 | } 8 | 9 | .c__datagrid__table__container > table td { 10 | height: 40px; 11 | } 12 | 13 | .c__datagrid__table__container > table th .c__datagrid__header { 14 | color: var(--c--components--datagrid--header--color); 15 | font-weight: var(--c--components--datagrid--header--weight); 16 | font-size: var(--c--components--datagrid--header--size); 17 | text-transform: none; 18 | } 19 | 20 | .c__datagrid__table__container > table tbody tr { 21 | border: none; 22 | } 23 | 24 | .c__datagrid__table__container > table tbody tr:hover { 25 | background-color: var( 26 | --c--components--datagrid--body--background-color-hover 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/components/datagrid/resources/databaseCars.json: -------------------------------------------------------------------------------- 1 | [{"id":"42e59801-eaf0-4cec-a9bb-8222788cd779","carName":"Hackett - Dickinson","year":2023,"price":5003},{"id":"92874aea-0984-473e-bb06-2e48f920099e","carName":"Brakus Group","year":2024,"price":5002},{"id":"5a29eb9c-2f43-498a-ad72-a2d9c933d8b5","carName":"Schamberger Inc","year":2023,"price":5005},{"id":"1c075a61-809b-4c32-bdf4-8679fe60957a","carName":"Schinner, Kertzmann and Mitchell","year":2023,"price":5003},{"id":"24091aba-94f2-4e0e-8ee1-5d83f80ad6a0","carName":"Ratke, Morissette and Beatty","year":2023,"price":5001},{"id":"f56b9138-8cc3-4175-86b4-4db860e3839f","carName":"Price LLC","year":2024,"price":5000},{"id":"d437fb53-a10b-46f9-8975-73568aa10635","carName":"Balistreri, Von and Wintheiser","year":2024,"price":5000},{"id":"fe4e25ef-2cd1-4ddd-9de9-b9cd4d51d3f4","carName":"Heathcote, Herzog and Casper","year":2023,"price":5005},{"id":"e41a2d28-6681-4bb2-988a-c4aac01a6d6b","carName":"Bins, Brown and Hirthe","year":2023,"price":5002},{"id":"f7acb144-40a5-4584-b5f2-9018c55bf98e","carName":"Welch Group","year":2023,"price":5000},{"id":"0d977b79-c762-478e-bed8-43d6faf1f7e7","carName":"Stroman and Sons","year":2024,"price":5000},{"id":"066aeb90-ff34-4f24-8d23-2df7dd4749ce","carName":"Padberg - Raynor","year":2023,"price":5002},{"id":"b242524e-1cc8-4d72-be79-430e881682f5","carName":"Emmerich, Breitenberg and Hand","year":2023,"price":5001},{"id":"88ea6672-ef61-468e-a0a0-cbe78ab4c9ca","carName":"Ratke, Abernathy and Macejkovic","year":2023,"price":5004},{"id":"5acfbdd8-0d1a-47a2-9a27-8cd998ce6f06","carName":"Ernser - Bartoletti","year":2023,"price":5005},{"id":"743eb166-853b-4501-ad26-3f5debcaaa98","carName":"Glover LLC","year":2024,"price":5000},{"id":"86508544-d335-4274-82e8-7b116a522651","carName":"Barton, Hodkiewicz and Harris","year":2023,"price":5002},{"id":"69c7b16a-124e-4c21-a906-efdacf7d305e","carName":"Dare and Sons","year":2024,"price":5005},{"id":"f1942cae-eb45-4540-9580-1276e84e3fa0","carName":"Ryan Inc","year":2023,"price":5005},{"id":"889763ec-881a-4bc6-af18-0d31ff46c000","carName":"Ziemann Inc","year":2023,"price":5005},{"id":"f9d65110-5be4-48e7-b80e-36d217b98a65","carName":"Borer, Miller and Watsica","year":2023,"price":5002},{"id":"81981644-07e7-45f4-af0b-ccd0503462be","carName":"Runolfsdottir Group","year":2023,"price":5000},{"id":"a27df970-ea88-4a24-91b4-a162ca3c7c43","carName":"White Inc","year":2023,"price":5000}] 2 | -------------------------------------------------------------------------------- /src/components/dnd/Draggable.tsx: -------------------------------------------------------------------------------- 1 | import { Data, useDraggable } from "@dnd-kit/core"; 2 | 3 | type DraggableProps = { 4 | id: string; 5 | data?: Data; 6 | children: React.ReactNode; 7 | }; 8 | 9 | export const Draggable = (props: DraggableProps) => { 10 | const { attributes, listeners, setNodeRef, isDragging } = useDraggable({ 11 | id: props.id, 12 | data: props.data, 13 | }); 14 | 15 | const style = { 16 | opacity: isDragging ? 0.5 : 1, 17 | }; 18 | 19 | return ( 20 |
21 | {props.children} 22 |
23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /src/components/dnd/Droppable.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import { Data, useDroppable } from "@dnd-kit/core"; 3 | import { useEffect } from "react"; 4 | 5 | type DroppableProps = { 6 | id: string; 7 | onOver?: (isOver: boolean, data?: Data) => void; 8 | data?: Data; 9 | children: React.ReactNode; 10 | }; 11 | 12 | export const Droppable = (props: DroppableProps) => { 13 | const { isOver, setNodeRef } = useDroppable({ 14 | id: props.id, 15 | data: props.data, 16 | }); 17 | 18 | useEffect(() => { 19 | props.onOver?.(isOver, props.data); 20 | }, [isOver, props.data, props.onOver]); 21 | 22 | return ( 23 |
24 | {props.children} 25 |
26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /src/components/dropdown-menu/DropdownMenu.tsx: -------------------------------------------------------------------------------- 1 | import { Menu, MenuItem, Popover, Separator } from "react-aria-components"; 2 | import { DropdownMenuOption } from "./types"; 3 | import { Fragment, PropsWithChildren, useId, useRef } from "react"; 4 | 5 | export type DropdownMenuProps = { 6 | options: DropdownMenuOption[]; 7 | onOpenChange?: (isOpen: boolean) => void; 8 | selectedValues?: string[]; 9 | onSelectValue?: (value: string) => void; 10 | isOpen?: boolean; 11 | topMessage?: string; 12 | shouldCloseOnInteractOutside?: (element: Element) => boolean; 13 | }; 14 | 15 | export const DropdownMenu = ({ 16 | options, 17 | isOpen = false, 18 | onOpenChange, 19 | children, 20 | selectedValues = [], 21 | onSelectValue, 22 | topMessage, 23 | shouldCloseOnInteractOutside, 24 | }: PropsWithChildren) => { 25 | const id = useId(); 26 | const onOpenChangeHandler = (isOpen: boolean) => { 27 | onOpenChange?.(isOpen); 28 | }; 29 | 30 | const triggerRef = useRef(null); 31 | return ( 32 | <> 33 |
{ 38 | e.stopPropagation(); 39 | e.preventDefault(); 40 | }} 41 | > 42 | {children} 43 |
44 | 45 | 54 | 55 | {topMessage && ( 56 | 57 | {topMessage} 58 | 59 | )} 60 | {options.map((option) => { 61 | if (option.isHidden) { 62 | return null; 63 | } 64 | return ( 65 | 66 | { 71 | if (option.value) { 72 | onSelectValue?.(option.value); 73 | } 74 | option.callback?.(); 75 | onOpenChangeHandler(false); 76 | }} 77 | isDisabled={option.isDisabled} 78 | > 79 | {option.icon} 80 |
84 | {option.label} 85 |
86 | {(option.isChecked || 87 | (option.value && 88 | selectedValues.includes(option.value))) && ( 89 | check 90 | )} 91 |
92 | {option.showSeparator && } 93 |
94 | ); 95 | })} 96 |
97 |
98 | 99 | ); 100 | }; 101 | -------------------------------------------------------------------------------- /src/components/dropdown-menu/index.scss: -------------------------------------------------------------------------------- 1 | .c__dropdown-menu-trigger { 2 | width: fit-content; 3 | display: flex; 4 | align-items: center; 5 | justify-content: center; 6 | } 7 | .c__dropdown-menu { 8 | background-color: var(--c--theme--colors--greyscale-000); 9 | z-index: 1000; 10 | max-width: 320px; 11 | max-height: inherit; 12 | box-sizing: border-box; 13 | overflow: auto; 14 | min-width: 150px; 15 | box-sizing: border-box; 16 | outline: none; 17 | border-radius: var(--c--theme--spacings--3xs, 4px); 18 | background-color: (--c--theme--colors--greyscale-000); 19 | border: 1px solid var(--c--theme--colors--greyscale-200); 20 | box-shadow: 0px 0px 6px 0px rgba(0, 0, 145, 0.1); 21 | 22 | .react-aria-Separator { 23 | height: 1px; 24 | background: var(--c--theme--colors--greyscale-200, #e0e0e0); 25 | } 26 | } 27 | 28 | .c__dropdown-menu-item-top-message { 29 | display: flex; 30 | align-items: center; 31 | 32 | padding: var(--c--theme--spacings--sm, 0.5rem); 33 | 34 | font-weight: 500; 35 | color: var(--c--theme--colors--greyscale-1000, #161616); 36 | font-size: var(--c--theme--font--sizes--xs, 0.75rem); 37 | } 38 | 39 | .c__dropdown-menu-item { 40 | display: flex; 41 | gap: var(--c--theme--spacings--base, 16px); 42 | padding: 8px 16px; 43 | border-bottom: 1px solid transparent; 44 | padding-right: 24px; 45 | outline: none; 46 | cursor: pointer; 47 | line-height: 19px; 48 | font-size: var(--c--theme--font--sizes--sm, 0.875rem); 49 | font-weight: 500; 50 | color: var(--c--theme--colors--greyscale-1000, #161616); 51 | align-items: center; 52 | forced-color-adjust: none; 53 | 54 | &__label { 55 | flex: 1; 56 | } 57 | 58 | &:hover { 59 | background: var(--c--theme--colors--greyscale-100); 60 | } 61 | 62 | &[data-focused] { 63 | background: var(--c--theme--colors--greyscale-100); 64 | color: black; 65 | } 66 | 67 | &[data-disabled] { 68 | color: var(--c--theme--colors--greyscale-400, #808080); 69 | cursor: not-allowed; 70 | 71 | &:hover { 72 | background: transparent; 73 | } 74 | } 75 | 76 | .material-icons { 77 | display: flex; 78 | align-items: center; 79 | justify-content: center; 80 | width: 24px; 81 | height: 24px; 82 | font-size: 20px; 83 | } 84 | 85 | .checked { 86 | margin-left: var(--c--theme--spacings--md, 1.5rem); 87 | } 88 | } 89 | 90 | .dropdown-menu-separator { 91 | background-color: var(--c--theme--colors--greyscale-200); 92 | height: 1px; 93 | width: 100%; 94 | } 95 | -------------------------------------------------------------------------------- /src/components/dropdown-menu/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./DropdownMenu"; 2 | export * from "./types"; 3 | export * from "./useDropdownMenu"; 4 | -------------------------------------------------------------------------------- /src/components/dropdown-menu/types.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | 3 | export type DropdownMenuOption = { 4 | label: string; 5 | icon?: ReactNode; 6 | callback?: () => void | Promise; 7 | isDisabled?: boolean; 8 | showSeparator?: boolean; 9 | isHidden?: boolean; 10 | isChecked?: boolean; 11 | testId?: string; 12 | value?: string; 13 | }; 14 | -------------------------------------------------------------------------------- /src/components/dropdown-menu/useDropdownMenu.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | export const useDropdownMenu = () => { 4 | const [isOpen, setIsOpen] = useState(false); 5 | return { isOpen, setIsOpen }; 6 | }; 7 | -------------------------------------------------------------------------------- /src/components/filter/Filter.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from "@storybook/react"; 2 | 3 | import { Filter, FilterOption } from "./Filter"; 4 | import { useState } from "react"; 5 | import { Key } from "react-aria-components"; 6 | import { Button } from "@openfun/cunningham-react"; 7 | 8 | const meta: Meta = { 9 | title: "Components/Filter", 10 | component: Filter, 11 | tags: ["autodocs"], 12 | parameters: { 13 | layout: "fullscreen", 14 | }, 15 | decorators: [ 16 | (Story) => ( 17 |
18 | {/* 👇 Decorators in Storybook also accept a function. Replace with Story() to enable it */} 19 | 20 |
21 | ), 22 | ], 23 | }; 24 | 25 | export default meta; 26 | type Story = StoryObj; 27 | 28 | const OPTIONS: FilterOption[] = [ 29 | { 30 | label: "All", 31 | value: "all", 32 | }, 33 | { 34 | label: "File", 35 | value: "file", 36 | }, 37 | { 38 | label: "Folder", 39 | value: "folder", 40 | }, 41 | ]; 42 | 43 | const OPTIONS_CUSTOM: FilterOption[] = [ 44 | { 45 | label: "File", 46 | render: () => ( 47 |
48 | file_present File 49 |
50 | ), 51 | value: "file", 52 | }, 53 | { 54 | label: "Folder", 55 | value: "folder", 56 | showSeparator: true, 57 | 58 | render: () => ( 59 |
60 | folder Folder 61 |
62 | ), 63 | }, 64 | { 65 | label: "Reset", 66 | render: () => ( 67 |
68 | all_inclusive All 69 |
70 | ), 71 | value: "all", 72 | }, 73 | ]; 74 | 75 | export const Uncontrolled: Story = { 76 | args: { 77 | label: "Type", 78 | options: OPTIONS, 79 | }, 80 | }; 81 | 82 | export const UncontrolledWithDefault: Story = { 83 | args: { 84 | label: "Type", 85 | defaultSelectedKey: "folder", 86 | options: OPTIONS, 87 | }, 88 | }; 89 | 90 | export const Controlled: Story = { 91 | args: { 92 | label: "Type", 93 | options: OPTIONS_CUSTOM, 94 | }, 95 | render: (args) => { 96 | // eslint-disable-next-line react-hooks/rules-of-hooks 97 | const [selected, setSelected] = useState("folder"); 98 | return ( 99 |
100 | { 104 | if (key === "all") { 105 | setSelected(null); 106 | } else { 107 | setSelected(key); 108 | } 109 | }} 110 | /> 111 |
112 | 115 | 127 |
128 |
129 | ); 130 | }, 131 | }; 132 | -------------------------------------------------------------------------------- /src/components/filter/Filter.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, useContext } from "react"; 2 | import { 3 | Button, 4 | Label, 5 | ListBox, 6 | ListBoxItem, 7 | Popover, 8 | Select, 9 | SelectProps, 10 | SelectStateContext, 11 | Separator, 12 | } from "react-aria-components"; 13 | 14 | import { Option } from "@openfun/cunningham-react"; 15 | import clsx from "clsx"; 16 | 17 | export type FilterOption = Option & { 18 | showSeparator?: boolean; 19 | isChecked?: boolean; 20 | }; 21 | 22 | export type FilterProps = { 23 | label: string; 24 | options: FilterOption[]; 25 | } & SelectProps; 26 | 27 | export const Filter = (props: FilterProps) => { 28 | return ( 29 | 32 | ); 33 | }; 34 | 35 | const FilterInner = (props: FilterProps) => { 36 | const state = useContext(SelectStateContext); 37 | 38 | const selectedOption = state?.selectedItem 39 | ? props.options.find((option) => option.value === state.selectedItem?.key) 40 | : null; 41 | 42 | return ( 43 | <> 44 | 69 | 70 | 71 | {props.options.map((option) => ( 72 | 73 | 78 |
79 | {option.render ? option.render() : option.label} 80 | {(props.selectedKey === option.value || option.isChecked) && ( 81 | check 82 | )} 83 |
84 |
85 | {option.showSeparator && } 86 |
87 | ))} 88 |
89 |
90 | 91 | ); 92 | }; 93 | -------------------------------------------------------------------------------- /src/components/filter/index.scss: -------------------------------------------------------------------------------- 1 | .c__filter { 2 | &__button { 3 | background-color: var(--c--theme--colors--greyscale-000); 4 | height: 32px; 5 | display: flex; 6 | align-items: center; 7 | border-radius: 4px; 8 | border: 1px solid var(--c--theme--colors--greyscale-200); 9 | font-size: 14px; 10 | font-weight: 400; 11 | font-family: var(--c--theme--font--families--base); 12 | gap: 6px; 13 | padding: 0 6px 0 8px; 14 | color: var(--c--theme--colors--greyscale-600); 15 | 16 | &__icon { 17 | font-size: 1.25rem; 18 | transition: all var(--c--theme--transitions--duration) 19 | var(--c--theme--transitions--ease-out); 20 | &.opened { 21 | transform: rotate(180deg); 22 | } 23 | } 24 | 25 | &:not(.c__filter__button--active) { 26 | &:hover, 27 | &:focus, 28 | &:focus-within { 29 | background-color: var(--c--theme--colors--greyscale-050); 30 | color: var(--c--theme--colors--greyscale-700); 31 | } 32 | } 33 | 34 | &--active { 35 | background-color: var(--c--theme--colors--primary-100); 36 | border-color: var(--c--theme--colors--primary-500); 37 | font-weight: 500; 38 | color: var(--c--theme--colors--primary-800); 39 | 40 | &:hover, 41 | &:focus, 42 | &:focus-within { 43 | background-color: var(--c--theme--colors--primary-200); 44 | } 45 | } 46 | } 47 | 48 | &__item { 49 | width: 100%; 50 | display: flex; 51 | align-items: center; 52 | gap: 6px; 53 | justify-content: space-between; 54 | } 55 | 56 | &__label { 57 | display: flex; 58 | align-items: center; 59 | } 60 | 61 | &__popover { 62 | background-color: var(--c--theme--colors--greyscale-000); 63 | border: 1px solid var(--c--theme--colors--greyscale-200); 64 | border-radius: 4px; 65 | padding: 8px; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/components/footer/Footer.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from "@storybook/react"; 2 | 3 | import { Footer } from "./Footer"; 4 | 5 | const meta = { 6 | title: "Components/Footer", 7 | component: Footer, 8 | tags: ["autodocs"], 9 | parameters: { 10 | layout: "fullscreen", 11 | }, 12 | } satisfies Meta; 13 | 14 | export default meta; 15 | type Story = StoryObj; 16 | 17 | export const Default: Story = {}; 18 | -------------------------------------------------------------------------------- /src/components/footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import IconLink from "./assets/external-link.svg"; 2 | import LogoGouv from ":/assets/logo-gouv.svg"; 3 | import { useCunningham } from "@openfun/cunningham-react"; 4 | 5 | // Improvements: 6 | // - Customize all links 7 | export const Footer = () => { 8 | const { t } = useCunningham(); 9 | 10 | return ( 11 |
12 |
13 |
14 |
15 |
16 |
17 | 26 |
27 |
28 |
29 | {[ 30 | { 31 | label: "legifrance.gouv.fr", 32 | href: "https://legifrance.gouv.fr/", 33 | }, 34 | { 35 | label: "info.gouv.fr", 36 | href: "https://info.gouv.fr/", 37 | }, 38 | { 39 | label: "service-public.fr", 40 | href: "https://service-public.fr/", 41 | }, 42 | { 43 | label: "data.gouv.fr", 44 | href: "https://data.gouv.fr/", 45 | }, 46 | ].map(({ label, href }) => ( 47 | 48 | {label} 49 | 56 | 57 | ))} 58 |
59 |
60 |
61 | {[ 62 | { 63 | label: t("components.footer.links.legal"), 64 | href: "/legal-notice", 65 | }, 66 | { 67 | label: t("components.footer.links.personal_data"), 68 | href: "/personal-data-cookies", 69 | }, 70 | { 71 | label: t("components.footer.links.accessibility"), 72 | href: "/accessibility", 73 | }, 74 | ].map(({ label, href }) => ( 75 | 76 | {label} 77 | 78 | ))} 79 |
80 |

81 | {t("components.footer.mention")}{" "} 82 | 86 | {t("components.footer.license")} 87 | 94 | 95 |

96 |
97 |
98 | ); 99 | }; 100 | -------------------------------------------------------------------------------- /src/components/footer/assets/external-link.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/footer/index.scss: -------------------------------------------------------------------------------- 1 | .c__footer { 2 | position: relative; 3 | 4 | &__stripe { 5 | position: absolute; 6 | height: 2px; 7 | width: 100%; 8 | background: var(--c--theme--colors--primary-600); 9 | top: 0; 10 | } 11 | 12 | &__logo { 13 | color: transparent; 14 | width: 220px; 15 | height: auto; 16 | } 17 | 18 | &__content { 19 | display: flex; 20 | flex-direction: column; 21 | padding: 3rem 1.625rem 1rem; 22 | 23 | &__top { 24 | display: flex; 25 | flex-direction: row; 26 | gap: 1.5rem; 27 | align-items: center; 28 | justify-content: space-between; 29 | flex-wrap: wrap; 30 | 31 | &__logo { 32 | display: flex; 33 | align-items: center; 34 | gap: 6rem; 35 | flex-direction: row; 36 | 37 | .c__logo-gouv { 38 | font-size: 1.3rem; 39 | } 40 | } 41 | 42 | &__links { 43 | display: flex; 44 | flex-flow: wrap; 45 | gap: 0.5rem 1.5rem; 46 | 47 | a { 48 | color: var(--c--theme--colors--greyscale-text); 49 | text-decoration: none; 50 | display: flex; 51 | gap: 0.2rem; 52 | transition: box-shadow 0.3s ease 0s; 53 | font-weight: bold; 54 | } 55 | } 56 | } 57 | 58 | &__middle { 59 | display: flex; 60 | flex-flow: wrap; 61 | margin-top: 1.625rem; 62 | padding-top: 0.5rem; 63 | border-top: 1px solid var(--c--theme--colors--greyscale-200); 64 | gap: 0.5rem 1rem; 65 | 66 | a { 67 | text-decoration: none; 68 | font-size: 0.8125rem; 69 | color: var(--c--theme--colors--greyscale-600); 70 | display: flex; 71 | padding-right: 1rem; 72 | box-shadow: inset -1px 0px 0px 0px var(--c--theme--colors--greyscale-200); 73 | } 74 | } 75 | 76 | &__mention { 77 | font-size: 0.8125rem; 78 | color: var(--c--theme--colors--greyscale-600); 79 | display: inline; 80 | margin-top: 1.625rem; 81 | 82 | a { 83 | color: var(--c--theme--colors--greyscale-600); 84 | text-decoration: none; 85 | display: inline-flex; 86 | box-shadow: 0px 1px 0 0 var(--c--theme--colors--greyscale-text); 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/components/form/checkbox/checkbox.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from "@storybook/react"; 2 | 3 | import { Checkbox } from "@openfun/cunningham-react"; 4 | 5 | // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export 6 | const meta = { 7 | title: "Components/Forms/Checkbox", 8 | component: Checkbox, 9 | tags: ["autodocs"], 10 | 11 | // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs 12 | } satisfies Meta; 13 | 14 | export default meta; 15 | type Story = StoryObj; 16 | 17 | export const Default: Story = {}; 18 | export const Disabled: Story = { 19 | args: { 20 | disabled: true, 21 | }, 22 | }; 23 | 24 | export const DisabledChecked: Story = { 25 | args: { 26 | disabled: true, 27 | checked: true, 28 | }, 29 | }; 30 | 31 | export const Indeterminate: Story = { 32 | args: { 33 | indeterminate: true, 34 | checked: true, 35 | }, 36 | }; 37 | 38 | export const IndeterminateDisable: Story = { 39 | args: { 40 | indeterminate: true, 41 | checked: true, 42 | disabled: true, 43 | }, 44 | }; 45 | 46 | export const WithLabel: Story = { 47 | args: { 48 | checked: true, 49 | label: "Label", 50 | text: "Description liée à ce label sur plusieurs lignes", 51 | }, 52 | }; 53 | -------------------------------------------------------------------------------- /src/components/form/checkbox/index.scss: -------------------------------------------------------------------------------- 1 | .c__checkbox { 2 | .c__checkbox__label { 3 | font-weight: var(--c--components--forms-checkbox--label--weight); 4 | font-size: var(--c--components--forms-checkbox--label--size); 5 | color: var(--c--components--forms-checkbox--label--color); 6 | } 7 | 8 | .c__field__text { 9 | font-weight: var(--c--components--forms-checkbox--text--weight); 10 | font-size: var(--c--components--forms-checkbox--text--size); 11 | color: var(--c--components--forms-checkbox--text--color); 12 | } 13 | .c__field__footer { 14 | padding: 0.25rem 0 0 2rem !important; 15 | } 16 | } 17 | 18 | .c__checkbox:hover, 19 | .c__checkbox:focus-within { 20 | background-color: var( 21 | --c--components--forms-checkbox--background-color--hover 22 | ); 23 | } 24 | 25 | .c__checkbox:has(input:not(:disabled)) { 26 | svg { 27 | color: var(--c--components--forms-checkbox--check--enable); 28 | } 29 | } 30 | 31 | .c__checkbox:has(input:checked):not(:has(input:disabled)), 32 | .c__checkbox:has(input:indeterminate):not(:has(input:disabled)) { 33 | &:not(.c__radio) { 34 | input { 35 | background-color: var(--c--theme--colors--primary-800); 36 | } 37 | } 38 | } 39 | 40 | .c__checkbox:has(input:disabled) { 41 | .c__checkbox__label { 42 | color: var(--c--components--forms-checkbox--label--color-disabled); 43 | } 44 | 45 | .c__field__text { 46 | color: var(--c--components--forms-checkbox--text--color-disabled); 47 | } 48 | input { 49 | border-color: var(--c--components--forms-checkbox--border--color-disabled); 50 | } 51 | 52 | &.c__radio { 53 | &:has(input:checked), 54 | &:has(input:indeterminate) { 55 | input { 56 | &:before { 57 | box-shadow: inset 1em 1em 58 | var(--c--components--forms-radio--accent-color-disabled); 59 | } 60 | } 61 | } 62 | } 63 | 64 | &:not(.c__radio) { 65 | &:has(input:checked), 66 | &:has(input:indeterminate) { 67 | svg { 68 | color: var(--c--components--forms-checkbox--check--disabled); 69 | } 70 | 71 | input { 72 | background-color: var( 73 | --c--components--forms-checkbox--background--disabled 74 | ); 75 | border-color: var( 76 | --c--components--forms-checkbox--border--color-disabled 77 | ); 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/components/form/form.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Checkbox, Radio, RadioGroup, Switch } from "@openfun/cunningham-react"; 2 | 3 | import { Button, Select, TextArea } from "@openfun/cunningham-react"; 4 | 5 | import { Input } from "@openfun/cunningham-react"; 6 | import { Meta } from "@storybook/react"; 7 | import { Label } from "./label/label"; 8 | 9 | export default { 10 | title: "Components/Forms/Examples", 11 | } as Meta; 12 | 13 | const CITIES = [ 14 | "Paris", 15 | "Marseille", 16 | "Lyon", 17 | "Toulouse", 18 | "Nice", 19 | "Nantes", 20 | "Strasbourg", 21 | "Montpellier", 22 | "Bordeaux", 23 | "Lille", 24 | ]; 25 | const OPTIONS = CITIES.map((city) => ({ 26 | label: city, 27 | value: city.toLowerCase(), 28 | })); 29 | 30 | export const Example = () => { 31 | return ( 32 |
40 | 41 |