@dsfrkit/config 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # @dsfrkit/config
2
+
3
+ Preset Tailwind CSS pour le Système de Design de l'État français (DSFR).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add -D @dsfrkit/config tailwindcss
9
+ pnpm add @dsfrkit/tokens
10
+ ```
11
+
12
+ > Note: `@dsfrkit/config` déclare `@dsfrkit/tokens` en tant que peerDependency — installez `@dsfrkit/tokens` dans votre projet pour pouvoir importer `@dsfrkit/tokens/theme.css`.
13
+
14
+ ## Usage
15
+
16
+ ### Configuration Tailwind
17
+
18
+ Ajoutez le preset dans votre `tailwind.config.js` :
19
+
20
+ ```js
21
+ import dsfrPreset from '@dsfrkit/config'
22
+
23
+ export default {
24
+ presets: [dsfrPreset],
25
+ content: [
26
+ './src/**/*.{js,jsx,ts,tsx}',
27
+ ],
28
+ // Vos personnalisations ici
29
+ }
30
+ ```
31
+
32
+ ### Utilisation des classes
33
+
34
+ ```tsx
35
+ // Couleurs DSFR
36
+ <button className="bg-blue-france-main text-white">
37
+ Bouton principal
38
+ </button>
39
+
40
+ <div className="bg-error-main text-white">
41
+ Message d'erreur
42
+ </div>
43
+
44
+ // Typographie Marianne
45
+ <h1 className="font-marianne text-4xl font-bold">
46
+ Titre principal
47
+ </h1>
48
+
49
+ // Container DSFR
50
+ <div className="fr-container">
51
+ <div className="fr-grid-row">
52
+ <div className="fr-col">
53
+ Contenu
54
+ </div>
55
+ </div>
56
+ </div>
57
+
58
+ // Utilities de décision
59
+ <p className="text-decision-default">Texte par défaut</p>
60
+ <div className="bg-decision-disabled">Arrière-plan désactivé</div>
61
+ ```
62
+
63
+ ## Couleurs disponibles
64
+
65
+ - `blue-france-*` - Bleu France (couleur principale)
66
+ - `red-marianne-*` - Rouge Marianne
67
+ - `grey-*` - Échelle de gris complète
68
+ - `info-*` - Couleurs d'information
69
+ - `success-*` - Couleurs de succès
70
+ - `warning-*` - Couleurs d'avertissement
71
+ - `error-*` - Couleurs d'erreur
72
+
73
+ ## Typographie
74
+
75
+ ### Polices Marianne et Spectral
76
+
77
+ Importez les polices officielles DSFR dans votre CSS principal :
78
+
79
+ ```css
80
+ /* src/index.css */
81
+ @import '@dsfrkit/config/fonts.css';
82
+
83
+ @tailwind base;
84
+ @tailwind components;
85
+ @tailwind utilities;
86
+
87
+ ```
88
+
89
+ Les polices sont chargées depuis le CDN officiel DSFR avec `font-display: swap` pour de meilleures performances.
90
+
91
+ - **Police principale** : Marianne (`font-marianne`)
92
+ - Regular (400)
93
+ - Medium (500)
94
+ - Bold (700)
95
+ - Italic variants
96
+
97
+ - **Police serif** : Spectral (`font-spectral`)
98
+ - Regular (400)
99
+ - ExtraBold (800)
@@ -0,0 +1,17 @@
1
+ export { borderRadius, boxShadow, colors, screens, spacing, typography } from '@dsfrkit/tokens';
2
+ import { Config } from 'tailwindcss';
3
+
4
+ /**
5
+ * Preset Tailwind CSS pour le DSFR
6
+ * Étend la configuration Tailwind avec les tokens du design system
7
+ * Supporte le mode clair/sombre via CSS variables
8
+ *
9
+ * @see https://www.systeme-de-design.gouv.fr/fondamentaux/couleurs-palette
10
+ * @see https://www.systeme-de-design.gouv.fr/fondamentaux/typographie
11
+ * @see https://www.systeme-de-design.gouv.fr/fondamentaux/espacement
12
+ * @see https://www.systeme-de-design.gouv.fr/fondamentaux/grille-et-points-de-rupture
13
+ * @see https://www.systeme-de-design.gouv.fr/fondamentaux/systeme-d-ombres-et-d-elevation
14
+ */
15
+ declare const dsfrPreset: Partial<Config>;
16
+
17
+ export { dsfrPreset as default };
@@ -0,0 +1,17 @@
1
+ export { borderRadius, boxShadow, colors, screens, spacing, typography } from '@dsfrkit/tokens';
2
+ import { Config } from 'tailwindcss';
3
+
4
+ /**
5
+ * Preset Tailwind CSS pour le DSFR
6
+ * Étend la configuration Tailwind avec les tokens du design system
7
+ * Supporte le mode clair/sombre via CSS variables
8
+ *
9
+ * @see https://www.systeme-de-design.gouv.fr/fondamentaux/couleurs-palette
10
+ * @see https://www.systeme-de-design.gouv.fr/fondamentaux/typographie
11
+ * @see https://www.systeme-de-design.gouv.fr/fondamentaux/espacement
12
+ * @see https://www.systeme-de-design.gouv.fr/fondamentaux/grille-et-points-de-rupture
13
+ * @see https://www.systeme-de-design.gouv.fr/fondamentaux/systeme-d-ombres-et-d-elevation
14
+ */
15
+ declare const dsfrPreset: Partial<Config>;
16
+
17
+ export { dsfrPreset as default };
package/dist/index.js ADDED
@@ -0,0 +1,245 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var tokens = require('@dsfrkit/tokens');
6
+ var plugin = require('tailwindcss/plugin');
7
+
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
10
+ var plugin__default = /*#__PURE__*/_interopDefault(plugin);
11
+
12
+ // src/index.ts
13
+ var toMutableArray = (arr) => [...arr];
14
+ var toMutableFontSize = (fontSize) => {
15
+ const result = {};
16
+ for (const [key, value] of Object.entries(fontSize)) {
17
+ result[key] = [
18
+ value[0],
19
+ { lineHeight: value[1].lineHeight }
20
+ ];
21
+ }
22
+ return result;
23
+ };
24
+ var dsfrPreset = {
25
+ // Mode sombre via classe 'dark' sur <html>, data-theme="dark" ou data-fr-theme="dark" (DSFR natif)
26
+ darkMode: ["selector", '&:is(.dark, [data-theme="dark"], [data-fr-theme="dark"])'],
27
+ theme: {
28
+ // Breakpoints DSFR (surcharge complète)
29
+ screens: tokens.screens,
30
+ extend: {
31
+ colors: {
32
+ // Couleurs principales (statiques)
33
+ "blue-france": tokens.colors["blue-france"],
34
+ "red-marianne": tokens.colors["red-marianne"],
35
+ // Couleurs sémantiques via CSS variables (thème-aware)
36
+ info: tokens.cssVariables.info,
37
+ success: tokens.cssVariables.success,
38
+ warning: tokens.cssVariables.warning,
39
+ error: tokens.cssVariables.destructive,
40
+ destructive: tokens.cssVariables.destructive,
41
+ grey: tokens.colors.grey,
42
+ // Palette illustrative DSFR
43
+ "green-tilleul-verveine": tokens.colors["green-tilleul-verveine"],
44
+ "green-bourgeon": tokens.colors["green-bourgeon"],
45
+ "green-emeraude": tokens.colors["green-emeraude"],
46
+ "green-menthe": tokens.colors["green-menthe"],
47
+ "green-archipel": tokens.colors["green-archipel"],
48
+ "blue-ecume": tokens.colors["blue-ecume"],
49
+ "blue-cumulus": tokens.colors["blue-cumulus"],
50
+ "purple-glycine": tokens.colors["purple-glycine"],
51
+ "pink-macaron": tokens.colors["pink-macaron"],
52
+ "pink-tuile": tokens.colors["pink-tuile"],
53
+ "yellow-tournesol": tokens.colors["yellow-tournesol"],
54
+ "yellow-moutarde": tokens.colors["yellow-moutarde"],
55
+ "orange-terre-battue": tokens.colors["orange-terre-battue"],
56
+ "brown-cafe-creme": tokens.colors["brown-cafe-creme"],
57
+ "brown-caramel": tokens.colors["brown-caramel"],
58
+ "brown-opera": tokens.colors["brown-opera"],
59
+ "beige-gris-galet": tokens.colors["beige-gris-galet"],
60
+ background: tokens.cssVariables.background,
61
+ foreground: tokens.cssVariables.foreground,
62
+ border: tokens.cssVariables.border.DEFAULT,
63
+ "border-contrast": tokens.cssVariables.border.contrast,
64
+ "border-active": tokens.cssVariables.border.active,
65
+ primary: tokens.cssVariables.primary,
66
+ secondary: tokens.cssVariables.secondary,
67
+ muted: {
68
+ DEFAULT: tokens.cssVariables.background.alt,
69
+ foreground: tokens.cssVariables.foreground.muted
70
+ },
71
+ accent: {
72
+ DEFAULT: tokens.cssVariables.background.contrast,
73
+ foreground: tokens.cssVariables.foreground.DEFAULT
74
+ },
75
+ ring: tokens.cssVariables.focus.DEFAULT,
76
+ card: tokens.cssVariables.card,
77
+ popover: tokens.cssVariables.popover,
78
+ input: tokens.cssVariables.input
79
+ },
80
+ fontFamily: {
81
+ sans: toMutableArray(tokens.typography.fontFamily.marianne),
82
+ serif: toMutableArray(tokens.typography.fontFamily.spectral),
83
+ marianne: toMutableArray(tokens.typography.fontFamily.marianne),
84
+ spectral: toMutableArray(tokens.typography.fontFamily.spectral)
85
+ },
86
+ fontSize: toMutableFontSize(tokens.typography.fontSize),
87
+ fontWeight: tokens.typography.fontWeight,
88
+ lineHeight: tokens.typography.lineHeight,
89
+ spacing: tokens.spacing,
90
+ borderRadius: tokens.borderRadius,
91
+ boxShadow: tokens.boxShadow,
92
+ // Container pour suivre les breakpoints DSFR
93
+ container: {
94
+ center: true,
95
+ padding: {
96
+ DEFAULT: "1rem",
97
+ sm: "2rem",
98
+ lg: "4rem",
99
+ xl: "5rem"
100
+ }
101
+ },
102
+ // Outline DSFR pour le focus
103
+ outlineOffset: {
104
+ focus: "2px"
105
+ },
106
+ outlineWidth: {
107
+ focus: "2px"
108
+ }
109
+ }
110
+ },
111
+ plugins: [
112
+ plugin__default.default(({ addBase, addComponents, addUtilities, theme }) => {
113
+ addBase({
114
+ // Focus visible DSFR — outline 2px + offset 2px
115
+ "*:focus-visible": {
116
+ outline: `2px solid ${theme("colors.ring")}`,
117
+ outlineOffset: "2px"
118
+ },
119
+ // Suppression du focus visible pour les clics souris
120
+ "*:focus:not(:focus-visible)": {
121
+ outline: "none"
122
+ }
123
+ });
124
+ addComponents({
125
+ ".fr-container": {
126
+ width: "100%",
127
+ marginLeft: "auto",
128
+ marginRight: "auto",
129
+ paddingLeft: theme("spacing.4"),
130
+ paddingRight: theme("spacing.4"),
131
+ "@screen sm": {
132
+ maxWidth: "540px"
133
+ },
134
+ "@screen md": {
135
+ maxWidth: "720px"
136
+ },
137
+ "@screen lg": {
138
+ maxWidth: "960px"
139
+ },
140
+ "@screen xl": {
141
+ maxWidth: "1140px"
142
+ }
143
+ },
144
+ ".fr-grid-row": {
145
+ display: "flex",
146
+ flexWrap: "wrap",
147
+ marginLeft: `-${theme("spacing.2")}`,
148
+ marginRight: `-${theme("spacing.2")}`
149
+ },
150
+ ".fr-col": {
151
+ flex: "0 0 100%",
152
+ maxWidth: "100%",
153
+ paddingLeft: theme("spacing.2"),
154
+ paddingRight: theme("spacing.2")
155
+ },
156
+ /* Utilitaires globaux DSFR (liens, boutons, textes) */
157
+ ".fr-link": {
158
+ color: theme("colors.primary.DEFAULT"),
159
+ textDecoration: "underline",
160
+ textDecorationSkipInk: "auto",
161
+ "&:hover": {
162
+ color: theme("colors.primary.hover")
163
+ }
164
+ },
165
+ ".fr-btn": {
166
+ display: "inline-flex",
167
+ alignItems: "center",
168
+ justifyContent: "center",
169
+ gap: theme("spacing.2"),
170
+ fontWeight: theme("fontWeight.medium"),
171
+ transitionProperty: "color, background-color, border-color, box-shadow",
172
+ transitionTimingFunction: "ease-in-out",
173
+ transitionDuration: "200ms",
174
+ cursor: "pointer",
175
+ borderRadius: "0",
176
+ "&:disabled": {
177
+ cursor: "not-allowed",
178
+ opacity: "0.6"
179
+ }
180
+ },
181
+ ".fr-text": {
182
+ color: theme("colors.foreground.DEFAULT")
183
+ }
184
+ });
185
+ addUtilities({
186
+ ".text-decision-default": {
187
+ color: tokens.colors.decision.text.default.grey
188
+ },
189
+ ".text-decision-disabled": {
190
+ color: tokens.colors.decision.text.disabled.grey
191
+ },
192
+ ".bg-decision-default": {
193
+ backgroundColor: tokens.colors.decision.background.default.grey
194
+ },
195
+ ".bg-decision-disabled": {
196
+ backgroundColor: tokens.colors.decision.background.disabled.grey
197
+ },
198
+ /* Text / border helpers (DSFR fundamentals) */
199
+ ".text-default": { color: theme("colors.foreground.DEFAULT") },
200
+ ".text-muted": { color: theme("colors.foreground.muted") },
201
+ ".text-inverted": { color: theme("colors.foreground.inverted") },
202
+ ".border-default": { borderColor: theme("colors.border") },
203
+ ".border-contrast": { borderColor: theme("colors.border-contrast") },
204
+ ".border-active": { borderColor: theme("colors.border-active") },
205
+ /* Élévation DSFR */
206
+ ".elevation-raised": { boxShadow: theme("boxShadow.raised") },
207
+ ".elevation-overlap": { boxShadow: theme("boxShadow.overlap") },
208
+ ".elevation-sticky": { boxShadow: theme("boxShadow.sticky") },
209
+ ".elevation-lifted": { boxShadow: theme("boxShadow.lifted") },
210
+ /* Focus DSFR */
211
+ ".focus-dsfr": {
212
+ outline: `2px solid ${theme("colors.ring")}`,
213
+ outlineOffset: "2px"
214
+ }
215
+ });
216
+ })
217
+ ]
218
+ };
219
+ var index_default = dsfrPreset;
220
+
221
+ Object.defineProperty(exports, "borderRadius", {
222
+ enumerable: true,
223
+ get: function () { return tokens.borderRadius; }
224
+ });
225
+ Object.defineProperty(exports, "boxShadow", {
226
+ enumerable: true,
227
+ get: function () { return tokens.boxShadow; }
228
+ });
229
+ Object.defineProperty(exports, "colors", {
230
+ enumerable: true,
231
+ get: function () { return tokens.colors; }
232
+ });
233
+ Object.defineProperty(exports, "screens", {
234
+ enumerable: true,
235
+ get: function () { return tokens.screens; }
236
+ });
237
+ Object.defineProperty(exports, "spacing", {
238
+ enumerable: true,
239
+ get: function () { return tokens.spacing; }
240
+ });
241
+ Object.defineProperty(exports, "typography", {
242
+ enumerable: true,
243
+ get: function () { return tokens.typography; }
244
+ });
245
+ exports.default = index_default;
package/dist/index.mjs ADDED
@@ -0,0 +1,214 @@
1
+ import { colors, typography, cssVariables, boxShadow, borderRadius, spacing, screens } from '@dsfrkit/tokens';
2
+ export { borderRadius, boxShadow, colors, screens, spacing, typography } from '@dsfrkit/tokens';
3
+ import plugin from 'tailwindcss/plugin';
4
+
5
+ // src/index.ts
6
+ var toMutableArray = (arr) => [...arr];
7
+ var toMutableFontSize = (fontSize) => {
8
+ const result = {};
9
+ for (const [key, value] of Object.entries(fontSize)) {
10
+ result[key] = [
11
+ value[0],
12
+ { lineHeight: value[1].lineHeight }
13
+ ];
14
+ }
15
+ return result;
16
+ };
17
+ var dsfrPreset = {
18
+ // Mode sombre via classe 'dark' sur <html>, data-theme="dark" ou data-fr-theme="dark" (DSFR natif)
19
+ darkMode: ["selector", '&:is(.dark, [data-theme="dark"], [data-fr-theme="dark"])'],
20
+ theme: {
21
+ // Breakpoints DSFR (surcharge complète)
22
+ screens,
23
+ extend: {
24
+ colors: {
25
+ // Couleurs principales (statiques)
26
+ "blue-france": colors["blue-france"],
27
+ "red-marianne": colors["red-marianne"],
28
+ // Couleurs sémantiques via CSS variables (thème-aware)
29
+ info: cssVariables.info,
30
+ success: cssVariables.success,
31
+ warning: cssVariables.warning,
32
+ error: cssVariables.destructive,
33
+ destructive: cssVariables.destructive,
34
+ grey: colors.grey,
35
+ // Palette illustrative DSFR
36
+ "green-tilleul-verveine": colors["green-tilleul-verveine"],
37
+ "green-bourgeon": colors["green-bourgeon"],
38
+ "green-emeraude": colors["green-emeraude"],
39
+ "green-menthe": colors["green-menthe"],
40
+ "green-archipel": colors["green-archipel"],
41
+ "blue-ecume": colors["blue-ecume"],
42
+ "blue-cumulus": colors["blue-cumulus"],
43
+ "purple-glycine": colors["purple-glycine"],
44
+ "pink-macaron": colors["pink-macaron"],
45
+ "pink-tuile": colors["pink-tuile"],
46
+ "yellow-tournesol": colors["yellow-tournesol"],
47
+ "yellow-moutarde": colors["yellow-moutarde"],
48
+ "orange-terre-battue": colors["orange-terre-battue"],
49
+ "brown-cafe-creme": colors["brown-cafe-creme"],
50
+ "brown-caramel": colors["brown-caramel"],
51
+ "brown-opera": colors["brown-opera"],
52
+ "beige-gris-galet": colors["beige-gris-galet"],
53
+ background: cssVariables.background,
54
+ foreground: cssVariables.foreground,
55
+ border: cssVariables.border.DEFAULT,
56
+ "border-contrast": cssVariables.border.contrast,
57
+ "border-active": cssVariables.border.active,
58
+ primary: cssVariables.primary,
59
+ secondary: cssVariables.secondary,
60
+ muted: {
61
+ DEFAULT: cssVariables.background.alt,
62
+ foreground: cssVariables.foreground.muted
63
+ },
64
+ accent: {
65
+ DEFAULT: cssVariables.background.contrast,
66
+ foreground: cssVariables.foreground.DEFAULT
67
+ },
68
+ ring: cssVariables.focus.DEFAULT,
69
+ card: cssVariables.card,
70
+ popover: cssVariables.popover,
71
+ input: cssVariables.input
72
+ },
73
+ fontFamily: {
74
+ sans: toMutableArray(typography.fontFamily.marianne),
75
+ serif: toMutableArray(typography.fontFamily.spectral),
76
+ marianne: toMutableArray(typography.fontFamily.marianne),
77
+ spectral: toMutableArray(typography.fontFamily.spectral)
78
+ },
79
+ fontSize: toMutableFontSize(typography.fontSize),
80
+ fontWeight: typography.fontWeight,
81
+ lineHeight: typography.lineHeight,
82
+ spacing,
83
+ borderRadius,
84
+ boxShadow,
85
+ // Container pour suivre les breakpoints DSFR
86
+ container: {
87
+ center: true,
88
+ padding: {
89
+ DEFAULT: "1rem",
90
+ sm: "2rem",
91
+ lg: "4rem",
92
+ xl: "5rem"
93
+ }
94
+ },
95
+ // Outline DSFR pour le focus
96
+ outlineOffset: {
97
+ focus: "2px"
98
+ },
99
+ outlineWidth: {
100
+ focus: "2px"
101
+ }
102
+ }
103
+ },
104
+ plugins: [
105
+ plugin(({ addBase, addComponents, addUtilities, theme }) => {
106
+ addBase({
107
+ // Focus visible DSFR — outline 2px + offset 2px
108
+ "*:focus-visible": {
109
+ outline: `2px solid ${theme("colors.ring")}`,
110
+ outlineOffset: "2px"
111
+ },
112
+ // Suppression du focus visible pour les clics souris
113
+ "*:focus:not(:focus-visible)": {
114
+ outline: "none"
115
+ }
116
+ });
117
+ addComponents({
118
+ ".fr-container": {
119
+ width: "100%",
120
+ marginLeft: "auto",
121
+ marginRight: "auto",
122
+ paddingLeft: theme("spacing.4"),
123
+ paddingRight: theme("spacing.4"),
124
+ "@screen sm": {
125
+ maxWidth: "540px"
126
+ },
127
+ "@screen md": {
128
+ maxWidth: "720px"
129
+ },
130
+ "@screen lg": {
131
+ maxWidth: "960px"
132
+ },
133
+ "@screen xl": {
134
+ maxWidth: "1140px"
135
+ }
136
+ },
137
+ ".fr-grid-row": {
138
+ display: "flex",
139
+ flexWrap: "wrap",
140
+ marginLeft: `-${theme("spacing.2")}`,
141
+ marginRight: `-${theme("spacing.2")}`
142
+ },
143
+ ".fr-col": {
144
+ flex: "0 0 100%",
145
+ maxWidth: "100%",
146
+ paddingLeft: theme("spacing.2"),
147
+ paddingRight: theme("spacing.2")
148
+ },
149
+ /* Utilitaires globaux DSFR (liens, boutons, textes) */
150
+ ".fr-link": {
151
+ color: theme("colors.primary.DEFAULT"),
152
+ textDecoration: "underline",
153
+ textDecorationSkipInk: "auto",
154
+ "&:hover": {
155
+ color: theme("colors.primary.hover")
156
+ }
157
+ },
158
+ ".fr-btn": {
159
+ display: "inline-flex",
160
+ alignItems: "center",
161
+ justifyContent: "center",
162
+ gap: theme("spacing.2"),
163
+ fontWeight: theme("fontWeight.medium"),
164
+ transitionProperty: "color, background-color, border-color, box-shadow",
165
+ transitionTimingFunction: "ease-in-out",
166
+ transitionDuration: "200ms",
167
+ cursor: "pointer",
168
+ borderRadius: "0",
169
+ "&:disabled": {
170
+ cursor: "not-allowed",
171
+ opacity: "0.6"
172
+ }
173
+ },
174
+ ".fr-text": {
175
+ color: theme("colors.foreground.DEFAULT")
176
+ }
177
+ });
178
+ addUtilities({
179
+ ".text-decision-default": {
180
+ color: colors.decision.text.default.grey
181
+ },
182
+ ".text-decision-disabled": {
183
+ color: colors.decision.text.disabled.grey
184
+ },
185
+ ".bg-decision-default": {
186
+ backgroundColor: colors.decision.background.default.grey
187
+ },
188
+ ".bg-decision-disabled": {
189
+ backgroundColor: colors.decision.background.disabled.grey
190
+ },
191
+ /* Text / border helpers (DSFR fundamentals) */
192
+ ".text-default": { color: theme("colors.foreground.DEFAULT") },
193
+ ".text-muted": { color: theme("colors.foreground.muted") },
194
+ ".text-inverted": { color: theme("colors.foreground.inverted") },
195
+ ".border-default": { borderColor: theme("colors.border") },
196
+ ".border-contrast": { borderColor: theme("colors.border-contrast") },
197
+ ".border-active": { borderColor: theme("colors.border-active") },
198
+ /* Élévation DSFR */
199
+ ".elevation-raised": { boxShadow: theme("boxShadow.raised") },
200
+ ".elevation-overlap": { boxShadow: theme("boxShadow.overlap") },
201
+ ".elevation-sticky": { boxShadow: theme("boxShadow.sticky") },
202
+ ".elevation-lifted": { boxShadow: theme("boxShadow.lifted") },
203
+ /* Focus DSFR */
204
+ ".focus-dsfr": {
205
+ outline: `2px solid ${theme("colors.ring")}`,
206
+ outlineOffset: "2px"
207
+ }
208
+ });
209
+ })
210
+ ]
211
+ };
212
+ var index_default = dsfrPreset;
213
+
214
+ export { index_default as default };
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@dsfrkit/config",
3
+ "version": "0.1.0",
4
+ "description": "Preset Tailwind CSS pour le Système de Design de l'État français (DSFR)",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "src"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
24
+ "typecheck": "tsc --noEmit",
25
+ "clean": "rm -rf dist"
26
+ },
27
+ "keywords": [
28
+ "dsfr",
29
+ "tailwindcss",
30
+ "preset",
31
+ "design-system",
32
+ "france"
33
+ ],
34
+ "license": "ETALAB-2.0",
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "peerDependencies": {
39
+ "@dsfrkit/tokens": "workspace:*",
40
+ "tailwindcss": ">=3.4.0"
41
+ },
42
+ "devDependencies": {
43
+ "@dsfrkit/tokens": "workspace:*",
44
+ "tailwindcss": "^3.4.17",
45
+ "tsup": "^8.3.5",
46
+ "typescript": "^5.7.2"
47
+ }
48
+ }
package/src/index.ts ADDED
@@ -0,0 +1,256 @@
1
+ import {
2
+ borderRadius,
3
+ boxShadow,
4
+ colors,
5
+ cssVariables,
6
+ screens,
7
+ spacing,
8
+ typography,
9
+ } from '@dsfrkit/tokens'
10
+ import type { Config } from 'tailwindcss'
11
+ import plugin from 'tailwindcss/plugin'
12
+
13
+ // Convertit les tableaux readonly en tableaux mutables pour Tailwind
14
+ const toMutableArray = (arr: readonly string[]): string[] => [...arr]
15
+
16
+ // Convertit fontSize readonly en format mutable
17
+ const toMutableFontSize = (fontSize: typeof typography.fontSize) => {
18
+ const result: Record<string, [string, { lineHeight: string }]> = {}
19
+ for (const [key, value] of Object.entries(fontSize)) {
20
+ result[key] = [
21
+ value[0] as string,
22
+ { lineHeight: (value[1] as { lineHeight: string }).lineHeight },
23
+ ]
24
+ }
25
+ return result
26
+ }
27
+
28
+ /**
29
+ * Preset Tailwind CSS pour le DSFR
30
+ * Étend la configuration Tailwind avec les tokens du design system
31
+ * Supporte le mode clair/sombre via CSS variables
32
+ *
33
+ * @see https://www.systeme-de-design.gouv.fr/fondamentaux/couleurs-palette
34
+ * @see https://www.systeme-de-design.gouv.fr/fondamentaux/typographie
35
+ * @see https://www.systeme-de-design.gouv.fr/fondamentaux/espacement
36
+ * @see https://www.systeme-de-design.gouv.fr/fondamentaux/grille-et-points-de-rupture
37
+ * @see https://www.systeme-de-design.gouv.fr/fondamentaux/systeme-d-ombres-et-d-elevation
38
+ */
39
+ const dsfrPreset: Partial<Config> = {
40
+ // Mode sombre via classe 'dark' sur <html>, data-theme="dark" ou data-fr-theme="dark" (DSFR natif)
41
+ darkMode: ['selector', '&:is(.dark, [data-theme="dark"], [data-fr-theme="dark"])'],
42
+ theme: {
43
+ // Breakpoints DSFR (surcharge complète)
44
+ screens,
45
+
46
+ extend: {
47
+ colors: {
48
+ // Couleurs principales (statiques)
49
+ 'blue-france': colors['blue-france'],
50
+ 'red-marianne': colors['red-marianne'],
51
+
52
+ // Couleurs sémantiques via CSS variables (thème-aware)
53
+ info: cssVariables.info,
54
+ success: cssVariables.success,
55
+ warning: cssVariables.warning,
56
+ error: cssVariables.destructive,
57
+ destructive: cssVariables.destructive,
58
+ grey: colors.grey,
59
+ // Palette illustrative DSFR
60
+ 'green-tilleul-verveine': colors['green-tilleul-verveine'],
61
+ 'green-bourgeon': colors['green-bourgeon'],
62
+ 'green-emeraude': colors['green-emeraude'],
63
+ 'green-menthe': colors['green-menthe'],
64
+ 'green-archipel': colors['green-archipel'],
65
+ 'blue-ecume': colors['blue-ecume'],
66
+ 'blue-cumulus': colors['blue-cumulus'],
67
+ 'purple-glycine': colors['purple-glycine'],
68
+ 'pink-macaron': colors['pink-macaron'],
69
+ 'pink-tuile': colors['pink-tuile'],
70
+ 'yellow-tournesol': colors['yellow-tournesol'],
71
+ 'yellow-moutarde': colors['yellow-moutarde'],
72
+ 'orange-terre-battue': colors['orange-terre-battue'],
73
+ 'brown-cafe-creme': colors['brown-cafe-creme'],
74
+ 'brown-caramel': colors['brown-caramel'],
75
+ 'brown-opera': colors['brown-opera'],
76
+ 'beige-gris-galet': colors['beige-gris-galet'],
77
+
78
+ background: cssVariables.background,
79
+ foreground: cssVariables.foreground,
80
+ border: cssVariables.border.DEFAULT,
81
+ 'border-contrast': cssVariables.border.contrast,
82
+ 'border-active': cssVariables.border.active,
83
+ primary: cssVariables.primary,
84
+ secondary: cssVariables.secondary,
85
+ muted: {
86
+ DEFAULT: cssVariables.background.alt,
87
+ foreground: cssVariables.foreground.muted,
88
+ },
89
+ accent: {
90
+ DEFAULT: cssVariables.background.contrast,
91
+ foreground: cssVariables.foreground.DEFAULT,
92
+ },
93
+ ring: cssVariables.focus.DEFAULT,
94
+ card: cssVariables.card,
95
+ popover: cssVariables.popover,
96
+ input: cssVariables.input,
97
+ },
98
+
99
+ fontFamily: {
100
+ sans: toMutableArray(typography.fontFamily.marianne),
101
+ serif: toMutableArray(typography.fontFamily.spectral),
102
+ marianne: toMutableArray(typography.fontFamily.marianne),
103
+ spectral: toMutableArray(typography.fontFamily.spectral),
104
+ },
105
+
106
+ fontSize: toMutableFontSize(typography.fontSize),
107
+ fontWeight: typography.fontWeight,
108
+ lineHeight: typography.lineHeight,
109
+
110
+ spacing,
111
+ borderRadius,
112
+ boxShadow,
113
+
114
+ // Container pour suivre les breakpoints DSFR
115
+ container: {
116
+ center: true,
117
+ padding: {
118
+ DEFAULT: '1rem',
119
+ sm: '2rem',
120
+ lg: '4rem',
121
+ xl: '5rem',
122
+ },
123
+ },
124
+
125
+ // Outline DSFR pour le focus
126
+ outlineOffset: {
127
+ focus: '2px',
128
+ },
129
+ outlineWidth: {
130
+ focus: '2px',
131
+ },
132
+ },
133
+ },
134
+
135
+ plugins: [
136
+ plugin(({ addBase, addComponents, addUtilities, theme }) => {
137
+ // Styles de base DSFR
138
+ addBase({
139
+ // Focus visible DSFR — outline 2px + offset 2px
140
+ '*:focus-visible': {
141
+ outline: `2px solid ${theme('colors.ring')}`,
142
+ outlineOffset: '2px',
143
+ },
144
+ // Suppression du focus visible pour les clics souris
145
+ '*:focus:not(:focus-visible)': {
146
+ outline: 'none',
147
+ },
148
+ })
149
+
150
+ // Composants utilitaires DSFR
151
+ addComponents({
152
+ '.fr-container': {
153
+ width: '100%',
154
+ marginLeft: 'auto',
155
+ marginRight: 'auto',
156
+ paddingLeft: theme('spacing.4'),
157
+ paddingRight: theme('spacing.4'),
158
+ '@screen sm': {
159
+ maxWidth: '540px',
160
+ },
161
+ '@screen md': {
162
+ maxWidth: '720px',
163
+ },
164
+ '@screen lg': {
165
+ maxWidth: '960px',
166
+ },
167
+ '@screen xl': {
168
+ maxWidth: '1140px',
169
+ },
170
+ },
171
+ '.fr-grid-row': {
172
+ display: 'flex',
173
+ flexWrap: 'wrap',
174
+ marginLeft: `-${theme('spacing.2')}`,
175
+ marginRight: `-${theme('spacing.2')}`,
176
+ },
177
+ '.fr-col': {
178
+ flex: '0 0 100%',
179
+ maxWidth: '100%',
180
+ paddingLeft: theme('spacing.2'),
181
+ paddingRight: theme('spacing.2'),
182
+ },
183
+ /* Utilitaires globaux DSFR (liens, boutons, textes) */
184
+ '.fr-link': {
185
+ color: theme('colors.primary.DEFAULT'),
186
+ textDecoration: 'underline',
187
+ textDecorationSkipInk: 'auto',
188
+ '&:hover': {
189
+ color: theme('colors.primary.hover'),
190
+ },
191
+ },
192
+ '.fr-btn': {
193
+ display: 'inline-flex',
194
+ alignItems: 'center',
195
+ justifyContent: 'center',
196
+ gap: theme('spacing.2'),
197
+ fontWeight: theme('fontWeight.medium'),
198
+ transitionProperty: 'color, background-color, border-color, box-shadow',
199
+ transitionTimingFunction: 'ease-in-out',
200
+ transitionDuration: '200ms',
201
+ cursor: 'pointer',
202
+ borderRadius: '0',
203
+ '&:disabled': {
204
+ cursor: 'not-allowed',
205
+ opacity: '0.6',
206
+ },
207
+ },
208
+ '.fr-text': {
209
+ color: theme('colors.foreground.DEFAULT'),
210
+ },
211
+ })
212
+
213
+ // Utilities pour les couleurs de décision DSFR
214
+ addUtilities({
215
+ '.text-decision-default': {
216
+ color: colors.decision.text.default.grey,
217
+ },
218
+ '.text-decision-disabled': {
219
+ color: colors.decision.text.disabled.grey,
220
+ },
221
+ '.bg-decision-default': {
222
+ backgroundColor: colors.decision.background.default.grey,
223
+ },
224
+ '.bg-decision-disabled': {
225
+ backgroundColor: colors.decision.background.disabled.grey,
226
+ },
227
+
228
+ /* Text / border helpers (DSFR fundamentals) */
229
+ '.text-default': { color: theme('colors.foreground.DEFAULT') },
230
+ '.text-muted': { color: theme('colors.foreground.muted') },
231
+ '.text-inverted': { color: theme('colors.foreground.inverted') },
232
+
233
+ '.border-default': { borderColor: theme('colors.border') },
234
+ '.border-contrast': { borderColor: theme('colors.border-contrast') },
235
+ '.border-active': { borderColor: theme('colors.border-active') },
236
+
237
+ /* Élévation DSFR */
238
+ '.elevation-raised': { boxShadow: theme('boxShadow.raised') },
239
+ '.elevation-overlap': { boxShadow: theme('boxShadow.overlap') },
240
+ '.elevation-sticky': { boxShadow: theme('boxShadow.sticky') },
241
+ '.elevation-lifted': { boxShadow: theme('boxShadow.lifted') },
242
+
243
+ /* Focus DSFR */
244
+ '.focus-dsfr': {
245
+ outline: `2px solid ${theme('colors.ring')}`,
246
+ outlineOffset: '2px',
247
+ },
248
+ })
249
+ }),
250
+ ],
251
+ }
252
+
253
+ export default dsfrPreset
254
+
255
+ // Export individuel pour usage avancé
256
+ export { borderRadius, boxShadow, colors, screens, spacing, typography }
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import dsfrPreset, { colors, spacing, typography } from './index'
3
+
4
+ describe('DSFR Tailwind Preset', () => {
5
+ it('should export preset configuration object', () => {
6
+ expect(dsfrPreset).toBeDefined()
7
+ expect(dsfrPreset.theme).toBeDefined()
8
+ expect(dsfrPreset.plugins).toBeDefined()
9
+ })
10
+
11
+ it('should map core DSFR colors to the Tailwind extend theme', () => {
12
+ const extendColors = dsfrPreset.theme?.extend?.colors as any
13
+ expect(extendColors).toBeDefined()
14
+
15
+ // Check direct static assignments
16
+ expect(extendColors['blue-france']).toEqual(colors['blue-france'])
17
+ expect(extendColors['red-marianne']).toEqual(colors['red-marianne'])
18
+
19
+ // Check semantic variables assignments
20
+ expect(extendColors.primary).toBeDefined()
21
+ expect(extendColors.background).toBeDefined()
22
+ expect(extendColors.foreground).toBeDefined()
23
+ })
24
+
25
+ it('should map typography and spacing', () => {
26
+ const extendTheme = dsfrPreset.theme?.extend as any
27
+ expect(extendTheme.fontFamily.marianne).toBeDefined()
28
+ expect(extendTheme.spacing).toEqual(spacing)
29
+ expect(extendTheme.fontWeight).toEqual(typography.fontWeight)
30
+ })
31
+
32
+ it('should contain the DSFR container configuration', () => {
33
+ const container = dsfrPreset.theme?.extend?.container as any
34
+ expect(container).toBeDefined()
35
+ expect(container.center).toBe(true)
36
+ expect(container.padding).toBeDefined()
37
+ })
38
+
39
+ it('should inject plugins', () => {
40
+ expect(Array.isArray(dsfrPreset.plugins)).toBe(true)
41
+ expect(dsfrPreset.plugins?.length).toBeGreaterThan(0)
42
+ })
43
+ })