@situaction/traquiste-docs-kit 0.0.0-stage → 1.0.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/CHANGELOG.md +18 -0
- package/README.md +252 -2
- package/client.d.ts +7 -0
- package/dist/app/ColorModeContext.d.ts +15 -0
- package/dist/app/ColorModeContext.d.ts.map +1 -0
- package/dist/app/ColorModeContext.js +58 -0
- package/dist/app/config.d.ts +69 -0
- package/dist/app/config.d.ts.map +1 -0
- package/dist/app/config.js +35 -0
- package/dist/app/createDocsApp.d.ts +9 -0
- package/dist/app/createDocsApp.d.ts.map +1 -0
- package/dist/app/createDocsApp.js +53 -0
- package/dist/components/Callout.d.ts +11 -0
- package/dist/components/Callout.d.ts.map +1 -0
- package/dist/components/Callout.js +28 -0
- package/dist/components/CodeBlock.d.ts +12 -0
- package/dist/components/CodeBlock.d.ts.map +1 -0
- package/dist/components/CodeBlock.js +60 -0
- package/dist/components/DeviceFrame.d.ts +17 -0
- package/dist/components/DeviceFrame.d.ts.map +1 -0
- package/dist/components/DeviceFrame.js +74 -0
- package/dist/components/LiveEditor.d.ts +10 -0
- package/dist/components/LiveEditor.d.ts.map +1 -0
- package/dist/components/LiveEditor.js +231 -0
- package/dist/components/PropsTable.d.ts +16 -0
- package/dist/components/PropsTable.d.ts.map +1 -0
- package/dist/components/PropsTable.js +60 -0
- package/dist/components/layout/ComponentSearch.d.ts +2 -0
- package/dist/components/layout/ComponentSearch.d.ts.map +1 -0
- package/dist/components/layout/ComponentSearch.js +156 -0
- package/dist/components/layout/Layout.d.ts +8 -0
- package/dist/components/layout/Layout.d.ts.map +1 -0
- package/dist/components/layout/Layout.js +109 -0
- package/dist/components/layout/NotFound.d.ts +2 -0
- package/dist/components/layout/NotFound.d.ts.map +1 -0
- package/dist/components/layout/NotFound.js +36 -0
- package/dist/components/layout/Sidebar.d.ts +2 -0
- package/dist/components/layout/Sidebar.d.ts.map +1 -0
- package/dist/components/layout/Sidebar.js +88 -0
- package/dist/components/layout/TableOfContents.d.ts +10 -0
- package/dist/components/layout/TableOfContents.d.ts.map +1 -0
- package/dist/components/layout/TableOfContents.js +48 -0
- package/dist/hooks/useCopy.d.ts +5 -0
- package/dist/hooks/useCopy.d.ts.map +1 -0
- package/dist/hooks/useCopy.js +19 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/pages/DocPage.d.ts +26 -0
- package/dist/pages/DocPage.d.ts.map +1 -0
- package/dist/pages/DocPage.js +23 -0
- package/dist/pages/createComponentPage.d.ts +32 -0
- package/dist/pages/createComponentPage.d.ts.map +1 -0
- package/dist/pages/createComponentPage.js +149 -0
- package/dist/pages/createScreenPage.d.ts +33 -0
- package/dist/pages/createScreenPage.d.ts.map +1 -0
- package/dist/pages/createScreenPage.js +124 -0
- package/dist/styles/docs.css +177 -0
- package/dist/styles/pageStyles.d.ts +45 -0
- package/dist/styles/pageStyles.d.ts.map +1 -0
- package/dist/styles/pageStyles.js +46 -0
- package/dist/vite/extractProps.d.ts +8 -0
- package/dist/vite/extractProps.d.ts.map +1 -0
- package/dist/vite/extractProps.js +79 -0
- package/dist/vite/index.d.ts +18 -0
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +73 -0
- package/package.json +111 -4
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
2
|
+
import type { DeviceType } from '../components/DeviceFrame';
|
|
3
|
+
export interface ScreenConfig {
|
|
4
|
+
/** Tab label */
|
|
5
|
+
label: string;
|
|
6
|
+
/** Id used in the URL (`?screen=…`) — defaults to the slugified label */
|
|
7
|
+
id?: string;
|
|
8
|
+
/** What the screen shows and when it appears */
|
|
9
|
+
description?: ReactNode;
|
|
10
|
+
/** Library components used — linked to their docs page when one has this label */
|
|
11
|
+
components?: string[];
|
|
12
|
+
/** Implementation notes */
|
|
13
|
+
notes?: ReactNode;
|
|
14
|
+
/** Address shown in the browser bar — overrides the page's `url` */
|
|
15
|
+
url?: string;
|
|
16
|
+
/** The screen itself, rendered inside the device and the library's theme */
|
|
17
|
+
component: ComponentType;
|
|
18
|
+
}
|
|
19
|
+
export interface ScreenPageConfig {
|
|
20
|
+
title: string;
|
|
21
|
+
description?: ReactNode;
|
|
22
|
+
/** 'phone' for the mobile library, 'browser' for the web library (default 'phone') */
|
|
23
|
+
device?: DeviceType;
|
|
24
|
+
/** Screen size in px — phone 390 × 760, browser 1024 × 640 by default */
|
|
25
|
+
width?: number;
|
|
26
|
+
height?: number;
|
|
27
|
+
/** Address shown in the browser bar */
|
|
28
|
+
url?: string;
|
|
29
|
+
screens: ScreenConfig[];
|
|
30
|
+
}
|
|
31
|
+
/** Generates a page showing a flow of full screens, one tab per screen */
|
|
32
|
+
export declare function createScreenPage(config: ScreenPageConfig): () => import("react").JSX.Element;
|
|
33
|
+
//# sourceMappingURL=createScreenPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createScreenPage.d.ts","sourceRoot":"","sources":["../../src/pages/createScreenPage.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAKpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AAG3D,MAAM,WAAW,YAAY;IAC3B,gBAAgB;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,gDAAgD;IAChD,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,4EAA4E;IAC5E,SAAS,EAAE,aAAa,CAAA;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,sFAAsF;IACtF,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,uCAAuC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,YAAY,EAAE,CAAA;CACxB;AAoFD,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,qCA8HxD"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/** Factory for full-screen pages: a flow of screens (login, dashboard…) shown in a device frame */
|
|
3
|
+
import { useRef } from 'react';
|
|
4
|
+
import { Link, useSearchParams } from 'react-router-dom';
|
|
5
|
+
import { useDocsConfig } from '../app/config';
|
|
6
|
+
import { useColorMode } from '../app/ColorModeContext';
|
|
7
|
+
import { DeviceFrame } from '../components/DeviceFrame';
|
|
8
|
+
import { pageStyles, slugify } from '../styles/pageStyles';
|
|
9
|
+
const DEFAULT_HEIGHT = { phone: 760, browser: 640 };
|
|
10
|
+
const S = {
|
|
11
|
+
...pageStyles,
|
|
12
|
+
tabs: {
|
|
13
|
+
display: 'flex',
|
|
14
|
+
flexWrap: 'wrap',
|
|
15
|
+
gap: 6,
|
|
16
|
+
padding: 4,
|
|
17
|
+
marginBottom: 32,
|
|
18
|
+
borderRadius: 10,
|
|
19
|
+
border: '1px solid var(--docs-border)',
|
|
20
|
+
backgroundColor: 'var(--docs-surface)',
|
|
21
|
+
width: 'fit-content',
|
|
22
|
+
maxWidth: '100%',
|
|
23
|
+
},
|
|
24
|
+
tab: (active) => ({
|
|
25
|
+
fontSize: 13,
|
|
26
|
+
fontWeight: active ? 600 : 500,
|
|
27
|
+
fontFamily: 'inherit',
|
|
28
|
+
padding: '6px 14px',
|
|
29
|
+
borderRadius: 7,
|
|
30
|
+
border: 'none',
|
|
31
|
+
cursor: 'pointer',
|
|
32
|
+
color: active ? 'var(--docs-text-primary)' : 'var(--docs-text-muted)',
|
|
33
|
+
backgroundColor: active ? 'var(--docs-bg)' : 'transparent',
|
|
34
|
+
boxShadow: active ? 'var(--docs-shadow-sm)' : 'none',
|
|
35
|
+
}),
|
|
36
|
+
body: (device) => ({
|
|
37
|
+
display: 'flex',
|
|
38
|
+
// Phones sit next to their description; wide browser windows go above it
|
|
39
|
+
flexDirection: device === 'phone' ? 'row' : 'column',
|
|
40
|
+
flexWrap: 'wrap',
|
|
41
|
+
alignItems: 'flex-start',
|
|
42
|
+
gap: 40,
|
|
43
|
+
}),
|
|
44
|
+
panel: {
|
|
45
|
+
flex: '1 1 280px',
|
|
46
|
+
minWidth: 0,
|
|
47
|
+
maxWidth: 520,
|
|
48
|
+
display: 'flex',
|
|
49
|
+
flexDirection: 'column',
|
|
50
|
+
gap: 16,
|
|
51
|
+
},
|
|
52
|
+
step: { fontSize: 12, fontWeight: 600, letterSpacing: '0.06em', textTransform: 'uppercase', color: 'var(--docs-text-muted)' },
|
|
53
|
+
screenTitle: { fontSize: 20, fontWeight: 600, lineHeight: 1.4, color: 'var(--docs-text-primary)' },
|
|
54
|
+
text: { fontSize: 15, lineHeight: 1.65, color: 'var(--docs-text-secondary)' },
|
|
55
|
+
box: {
|
|
56
|
+
fontSize: 14,
|
|
57
|
+
lineHeight: 1.6,
|
|
58
|
+
color: 'var(--docs-text-secondary)',
|
|
59
|
+
padding: '12px 16px',
|
|
60
|
+
borderRadius: 8,
|
|
61
|
+
border: '1px solid var(--docs-border)',
|
|
62
|
+
backgroundColor: 'var(--docs-surface)',
|
|
63
|
+
},
|
|
64
|
+
boxTitle: { display: 'block', fontWeight: 600, color: 'var(--docs-text-primary)', marginBottom: 8 },
|
|
65
|
+
chips: { display: 'flex', flexWrap: 'wrap', gap: 6 },
|
|
66
|
+
chip: {
|
|
67
|
+
fontFamily: 'ui-monospace, Consolas, monospace',
|
|
68
|
+
fontSize: 12,
|
|
69
|
+
padding: '2px 8px',
|
|
70
|
+
borderRadius: 999,
|
|
71
|
+
border: '1px solid var(--docs-border)',
|
|
72
|
+
backgroundColor: 'var(--docs-bg)',
|
|
73
|
+
color: 'var(--docs-text-primary)',
|
|
74
|
+
textDecoration: 'none',
|
|
75
|
+
},
|
|
76
|
+
nav: { display: 'flex', justifyContent: 'space-between', gap: 8 },
|
|
77
|
+
navBtn: (disabled) => ({
|
|
78
|
+
fontSize: 13,
|
|
79
|
+
fontWeight: 500,
|
|
80
|
+
fontFamily: 'inherit',
|
|
81
|
+
padding: '6px 12px',
|
|
82
|
+
borderRadius: 7,
|
|
83
|
+
border: '1px solid var(--docs-border)',
|
|
84
|
+
backgroundColor: 'var(--docs-bg)',
|
|
85
|
+
color: disabled ? 'var(--docs-text-disabled)' : 'var(--docs-text-primary)',
|
|
86
|
+
cursor: disabled ? 'default' : 'pointer',
|
|
87
|
+
}),
|
|
88
|
+
};
|
|
89
|
+
/** Generates a page showing a flow of full screens, one tab per screen */
|
|
90
|
+
export function createScreenPage(config) {
|
|
91
|
+
const device = config.device ?? 'phone';
|
|
92
|
+
const screens = config.screens.map((screen) => ({ ...screen, id: screen.id ?? slugify(screen.label) }));
|
|
93
|
+
return function ScreenPage() {
|
|
94
|
+
const [params, setParams] = useSearchParams();
|
|
95
|
+
const { wrapper: Wrapper, pages } = useDocsConfig();
|
|
96
|
+
const { mode } = useColorMode();
|
|
97
|
+
const tabRefs = useRef([]);
|
|
98
|
+
// The active screen lives in the URL, so a screen can be linked to directly
|
|
99
|
+
const index = Math.max(0, screens.findIndex((s) => s.id === params.get('screen')));
|
|
100
|
+
const screen = screens[index];
|
|
101
|
+
const Screen = screen.component;
|
|
102
|
+
const select = (i) => setParams((prev) => {
|
|
103
|
+
const next = new URLSearchParams(prev);
|
|
104
|
+
next.set('screen', screens[i].id);
|
|
105
|
+
return next;
|
|
106
|
+
}, { replace: true });
|
|
107
|
+
// Arrow keys move between tabs, as expected from a tablist
|
|
108
|
+
function onTabKeyDown(e) {
|
|
109
|
+
const delta = e.key === 'ArrowRight' ? 1 : e.key === 'ArrowLeft' ? -1 : 0;
|
|
110
|
+
if (!delta)
|
|
111
|
+
return;
|
|
112
|
+
const next = (index + delta + screens.length) % screens.length;
|
|
113
|
+
select(next);
|
|
114
|
+
tabRefs.current[next]?.focus();
|
|
115
|
+
}
|
|
116
|
+
const pageFor = (name) => pages.find((p) => p.label === name);
|
|
117
|
+
return (_jsxs("div", { children: [_jsx("h1", { style: S.title, children: config.title }), config.description && _jsx("p", { style: S.description, children: config.description }), _jsx("div", { role: "tablist", "aria-label": "\u00C9crans", style: S.tabs, onKeyDown: onTabKeyDown, children: screens.map((s, i) => (_jsx("button", { ref: (el) => {
|
|
118
|
+
tabRefs.current[i] = el;
|
|
119
|
+
}, type: "button", role: "tab", id: `screen-tab-${s.id}`, "aria-selected": i === index, "aria-controls": "screen-panel", tabIndex: i === index ? 0 : -1, style: S.tab(i === index), onClick: () => select(i), children: s.label }, s.id))) }), _jsxs("div", { id: "screen-panel", role: "tabpanel", "aria-labelledby": `screen-tab-${screen.id}`, style: S.body(device), children: [_jsx(DeviceFrame, { device: device, width: config.width, height: config.height ?? DEFAULT_HEIGHT[device], url: screen.url ?? config.url, children: _jsx("div", { className: "docs-screen docs-demo", children: _jsx(Wrapper, { mode: mode, children: _jsx(Screen, {}, screen.id) }) }) }), _jsxs("aside", { style: S.panel, children: [_jsxs("span", { style: S.step, children: ["\u00C9cran ", index + 1, " / ", screens.length] }), _jsx("h2", { style: S.screenTitle, children: screen.label }), screen.description && _jsx("div", { style: S.text, children: screen.description }), screen.components && screen.components.length > 0 && (_jsxs("div", { style: S.box, children: [_jsx("strong", { style: S.boxTitle, children: "Composants utilis\u00E9s" }), _jsx("div", { style: S.chips, children: screen.components.map((name) => {
|
|
120
|
+
const page = pageFor(name);
|
|
121
|
+
return page ? (_jsx(Link, { to: page.path, style: S.chip, children: name }, name)) : (_jsx("code", { style: S.chip, children: name }, name));
|
|
122
|
+
}) })] })), screen.notes && _jsx("div", { style: S.box, children: screen.notes }), screens.length > 1 && (_jsxs("div", { style: S.nav, children: [_jsx("button", { type: "button", style: S.navBtn(index === 0), disabled: index === 0, onClick: () => select(index - 1), children: "\u2190 Pr\u00E9c\u00E9dent" }), _jsx("button", { type: "button", style: S.navBtn(index === screens.length - 1), disabled: index === screens.length - 1, onClick: () => select(index + 1), children: "Suivant \u2192" })] }))] })] })] }));
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
*, *::before, *::after {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
margin: 0;
|
|
4
|
+
padding: 0;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/* ===== Light theme (default) ===== */
|
|
8
|
+
:root {
|
|
9
|
+
--docs-bg: #ffffff;
|
|
10
|
+
--docs-bg-rgb: 255, 255, 255;
|
|
11
|
+
--docs-bg-subtle: #f9fafb;
|
|
12
|
+
--docs-surface: #f4f4f5;
|
|
13
|
+
--docs-border: #e4e4e7;
|
|
14
|
+
|
|
15
|
+
--docs-text-primary: #18181b;
|
|
16
|
+
--docs-text-secondary: #52525b;
|
|
17
|
+
--docs-text-muted: #a1a1aa;
|
|
18
|
+
--docs-text-disabled: #d4d4d8;
|
|
19
|
+
|
|
20
|
+
--docs-accent: #0072E5;
|
|
21
|
+
--docs-accent-subtle: rgba(0, 114, 229, 0.08);
|
|
22
|
+
|
|
23
|
+
--docs-shadow-sm: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.04);
|
|
24
|
+
--docs-shadow-md: 0 4px 12px rgba(0,0,0,0.08), 0 2px 4px rgba(0,0,0,0.04);
|
|
25
|
+
|
|
26
|
+
--docs-badge-name-color: #dc2626;
|
|
27
|
+
--docs-badge-name-bg: #fef2f2;
|
|
28
|
+
--docs-badge-type-color: #2563eb;
|
|
29
|
+
--docs-badge-type-bg: #eff6ff;
|
|
30
|
+
--docs-badge-default-color: #16a34a;
|
|
31
|
+
--docs-badge-default-bg: #f0fdf4;
|
|
32
|
+
/* Callouts (guide pages) */
|
|
33
|
+
--docs-callout-info-bg: #eff6ff;
|
|
34
|
+
--docs-callout-info-border: #bfdbfe;
|
|
35
|
+
--docs-callout-info-accent: #2563eb;
|
|
36
|
+
--docs-callout-tip-bg: #f0fdf4;
|
|
37
|
+
--docs-callout-tip-border: #bbf7d0;
|
|
38
|
+
--docs-callout-tip-accent: #16a34a;
|
|
39
|
+
--docs-callout-warning-bg: #fffbeb;
|
|
40
|
+
--docs-callout-warning-border:#fde68a;
|
|
41
|
+
--docs-callout-warning-accent:#d97706;
|
|
42
|
+
--docs-callout-danger-bg: #fef2f2;
|
|
43
|
+
--docs-callout-danger-border: #fecaca;
|
|
44
|
+
--docs-callout-danger-accent: #dc2626;
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* ===== Dark theme ===== */
|
|
49
|
+
[data-theme="dark"] {
|
|
50
|
+
--docs-bg: #09090b;
|
|
51
|
+
--docs-bg-rgb: 9, 9, 11;
|
|
52
|
+
--docs-bg-subtle: #18181b;
|
|
53
|
+
--docs-surface: #27272a;
|
|
54
|
+
--docs-border: #3f3f46;
|
|
55
|
+
|
|
56
|
+
--docs-text-primary: #fafafa;
|
|
57
|
+
--docs-text-secondary: #a1a1aa;
|
|
58
|
+
--docs-text-muted: #71717a;
|
|
59
|
+
--docs-text-disabled: #52525b;
|
|
60
|
+
|
|
61
|
+
--docs-accent: #60a5fa;
|
|
62
|
+
--docs-accent-subtle: rgba(96, 165, 250, 0.1);
|
|
63
|
+
|
|
64
|
+
--docs-shadow-sm: 0 1px 3px rgba(0,0,0,0.4), 0 1px 2px rgba(0,0,0,0.3);
|
|
65
|
+
--docs-shadow-md: 0 4px 12px rgba(0,0,0,0.4), 0 2px 4px rgba(0,0,0,0.3);
|
|
66
|
+
|
|
67
|
+
--docs-badge-name-color: #fca5a5;
|
|
68
|
+
--docs-badge-name-bg: #450a0a;
|
|
69
|
+
--docs-badge-type-color: #93c5fd;
|
|
70
|
+
--docs-badge-type-bg: #0c1a2e;
|
|
71
|
+
--docs-badge-default-color: #86efac;
|
|
72
|
+
--docs-badge-default-bg: #052e16;
|
|
73
|
+
--docs-callout-info-bg: rgba(37, 99, 235, 0.12);
|
|
74
|
+
--docs-callout-info-border: rgba(96, 165, 250, 0.35);
|
|
75
|
+
--docs-callout-info-accent: #60a5fa;
|
|
76
|
+
--docs-callout-tip-bg: rgba(22, 163, 74, 0.12);
|
|
77
|
+
--docs-callout-tip-border: rgba(74, 222, 128, 0.3);
|
|
78
|
+
--docs-callout-tip-accent: #4ade80;
|
|
79
|
+
--docs-callout-warning-bg: rgba(217, 119, 6, 0.12);
|
|
80
|
+
--docs-callout-warning-border:rgba(251, 191, 36, 0.3);
|
|
81
|
+
--docs-callout-warning-accent:#fbbf24;
|
|
82
|
+
--docs-callout-danger-bg: rgba(220, 38, 38, 0.12);
|
|
83
|
+
--docs-callout-danger-border: rgba(248, 113, 113, 0.3);
|
|
84
|
+
--docs-callout-danger-accent: #f87171;
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
html {
|
|
89
|
+
scroll-behavior: smooth;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
body {
|
|
93
|
+
font-family: var(--docs-font-family, 'Urbanist'), -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
94
|
+
font-size: 15px;
|
|
95
|
+
line-height: 1.6;
|
|
96
|
+
-webkit-font-smoothing: antialiased;
|
|
97
|
+
-moz-osx-font-smoothing: grayscale;
|
|
98
|
+
background: var(--docs-bg);
|
|
99
|
+
color: var(--docs-text-primary);
|
|
100
|
+
transition: background 0.2s, color 0.2s;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* A documented library may size html/body globally (e.g. `:root, body { width: 100vw }`),
|
|
104
|
+
which adds a horizontal scrollbar. Higher specificity wins whatever the load order. */
|
|
105
|
+
html:root,
|
|
106
|
+
html body {
|
|
107
|
+
width: auto;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
#root {
|
|
111
|
+
display: contents;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* ===== Guide pages (DocPage) =====
|
|
115
|
+
Plain p / ul / code / a written in a guide. `:not(.docs-demo *)` leaves live demos untouched:
|
|
116
|
+
they must render like in an app, with the library's own styles. */
|
|
117
|
+
.docs-prose p:not(.docs-demo *) {
|
|
118
|
+
margin: 0 0 16px;
|
|
119
|
+
line-height: 1.7;
|
|
120
|
+
color: var(--docs-text-secondary);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.docs-prose h3:not(.docs-demo *) {
|
|
124
|
+
font-size: 17px;
|
|
125
|
+
font-weight: 600;
|
|
126
|
+
line-height: 1.4;
|
|
127
|
+
margin: 28px 0 10px;
|
|
128
|
+
color: var(--docs-text-primary);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.docs-prose ul:not(.docs-demo *),
|
|
132
|
+
.docs-prose ol:not(.docs-demo *) {
|
|
133
|
+
margin: 0 0 16px;
|
|
134
|
+
padding-left: 22px;
|
|
135
|
+
line-height: 1.7;
|
|
136
|
+
color: var(--docs-text-secondary);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.docs-prose ul:not(.docs-demo *) { list-style: disc; }
|
|
140
|
+
.docs-prose ol:not(.docs-demo *) { list-style: decimal; }
|
|
141
|
+
.docs-prose li:not(.docs-demo *) { margin-bottom: 4px; }
|
|
142
|
+
|
|
143
|
+
.docs-prose a:not(.docs-demo *) {
|
|
144
|
+
color: var(--docs-accent);
|
|
145
|
+
text-decoration: underline;
|
|
146
|
+
text-underline-offset: 2px;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.docs-prose strong:not(.docs-demo *) {
|
|
150
|
+
font-weight: 600;
|
|
151
|
+
color: var(--docs-text-primary);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.docs-prose :not(pre) > code:not(.docs-demo *) {
|
|
155
|
+
font-family: ui-monospace, Consolas, monospace;
|
|
156
|
+
font-size: 0.88em;
|
|
157
|
+
padding: 1px 5px;
|
|
158
|
+
border: 1px solid var(--docs-border);
|
|
159
|
+
border-radius: 4px;
|
|
160
|
+
background: var(--docs-surface);
|
|
161
|
+
color: var(--docs-text-primary);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* Callout text sits on a tinted background: primary color, no trailing gap */
|
|
165
|
+
.docs-callout-body p:not(.docs-demo *) { color: inherit; margin-bottom: 8px; }
|
|
166
|
+
.docs-callout-body > :last-child { margin-bottom: 0; }
|
|
167
|
+
|
|
168
|
+
/* ===== Full screens (createScreenPage) =====
|
|
169
|
+
The screen fills the device height, also through a wrapper that renders its own element
|
|
170
|
+
(e.g. a ThemeProvider <div>): a screen root with `flex: 1` stretches to the bottom. */
|
|
171
|
+
.docs-screen,
|
|
172
|
+
.docs-screen > * {
|
|
173
|
+
flex: 1 1 auto;
|
|
174
|
+
display: flex;
|
|
175
|
+
flex-direction: column;
|
|
176
|
+
min-height: 0;
|
|
177
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export declare const pageStyles: {
|
|
2
|
+
layout: {
|
|
3
|
+
display: "flex";
|
|
4
|
+
gap: number;
|
|
5
|
+
alignItems: "flex-start";
|
|
6
|
+
};
|
|
7
|
+
content: {
|
|
8
|
+
flex: number;
|
|
9
|
+
minWidth: number;
|
|
10
|
+
};
|
|
11
|
+
title: {
|
|
12
|
+
fontSize: number;
|
|
13
|
+
fontWeight: number;
|
|
14
|
+
lineHeight: number;
|
|
15
|
+
marginBottom: number;
|
|
16
|
+
color: "var(--docs-text-primary)";
|
|
17
|
+
letterSpacing: string;
|
|
18
|
+
};
|
|
19
|
+
description: {
|
|
20
|
+
fontSize: number;
|
|
21
|
+
color: "var(--docs-text-secondary)";
|
|
22
|
+
marginBottom: number;
|
|
23
|
+
lineHeight: number;
|
|
24
|
+
};
|
|
25
|
+
sectionHeading: {
|
|
26
|
+
fontSize: number;
|
|
27
|
+
fontWeight: number;
|
|
28
|
+
lineHeight: number;
|
|
29
|
+
color: "var(--docs-text-primary)";
|
|
30
|
+
marginBottom: number;
|
|
31
|
+
marginTop: number;
|
|
32
|
+
paddingTop: number;
|
|
33
|
+
borderTop: string;
|
|
34
|
+
scrollMarginTop: number;
|
|
35
|
+
};
|
|
36
|
+
sectionDescription: {
|
|
37
|
+
fontSize: number;
|
|
38
|
+
color: "var(--docs-text-secondary)";
|
|
39
|
+
lineHeight: number;
|
|
40
|
+
marginBottom: number;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
/** Anchor id for a heading — accents folded so French titles keep readable ids ('États' → 'etats') */
|
|
44
|
+
export declare function slugify(text: string): string;
|
|
45
|
+
//# sourceMappingURL=pageStyles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pageStyles.d.ts","sourceRoot":"","sources":["../../src/styles/pageStyles.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCkB,CAAA;AAEzC,sGAAsG;AACtG,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQ5C"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const pageStyles = {
|
|
2
|
+
layout: { display: 'flex', gap: 48, alignItems: 'flex-start' },
|
|
3
|
+
content: { flex: 1, minWidth: 0 },
|
|
4
|
+
title: {
|
|
5
|
+
fontSize: 36,
|
|
6
|
+
fontWeight: 700,
|
|
7
|
+
// Explicit line-height: a library's global h1–h6 rules must not restyle the docs headings
|
|
8
|
+
lineHeight: 1.6,
|
|
9
|
+
marginBottom: 8,
|
|
10
|
+
color: 'var(--docs-text-primary)',
|
|
11
|
+
letterSpacing: '-0.02em',
|
|
12
|
+
},
|
|
13
|
+
description: {
|
|
14
|
+
fontSize: 16,
|
|
15
|
+
color: 'var(--docs-text-secondary)',
|
|
16
|
+
marginBottom: 40,
|
|
17
|
+
lineHeight: 1.6,
|
|
18
|
+
},
|
|
19
|
+
sectionHeading: {
|
|
20
|
+
fontSize: 22,
|
|
21
|
+
fontWeight: 600,
|
|
22
|
+
lineHeight: 1.6,
|
|
23
|
+
color: 'var(--docs-text-primary)',
|
|
24
|
+
marginBottom: 12,
|
|
25
|
+
marginTop: 40,
|
|
26
|
+
paddingTop: 24,
|
|
27
|
+
borderTop: '1px solid var(--docs-border)',
|
|
28
|
+
scrollMarginTop: 80,
|
|
29
|
+
},
|
|
30
|
+
sectionDescription: {
|
|
31
|
+
fontSize: 15,
|
|
32
|
+
color: 'var(--docs-text-secondary)',
|
|
33
|
+
lineHeight: 1.65,
|
|
34
|
+
marginBottom: 20,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
/** Anchor id for a heading — accents folded so French titles keep readable ids ('États' → 'etats') */
|
|
38
|
+
export function slugify(text) {
|
|
39
|
+
return text
|
|
40
|
+
.normalize('NFD')
|
|
41
|
+
.replace(/[̀-ͯ]/g, '')
|
|
42
|
+
.toLowerCase()
|
|
43
|
+
.trim()
|
|
44
|
+
.replace(/\s+/g, '-')
|
|
45
|
+
.replace(/[^a-z0-9-]/g, '');
|
|
46
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ComponentProps } from '../components/PropsTable.js';
|
|
2
|
+
export type { ComponentProps };
|
|
3
|
+
export interface ExtractOptions {
|
|
4
|
+
/** tsconfig used to type-check the components — defaults to sensible JSX compiler options */
|
|
5
|
+
tsconfig?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function createPropsExtractor({ tsconfig }?: ExtractOptions): (files: string[]) => ComponentProps;
|
|
8
|
+
//# sourceMappingURL=extractProps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extractProps.d.ts","sourceRoot":"","sources":["../../src/vite/extractProps.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAW,MAAM,6BAA6B,CAAA;AAE1E,YAAY,EAAE,cAAc,EAAE,CAAA;AAE9B,MAAM,WAAW,cAAc;IAC7B,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AA0ED,wBAAgB,oBAAoB,CAAC,EAAE,QAAQ,EAAE,GAAE,cAAmB,IAI5D,OAAO,MAAM,EAAE,KAAG,cAAc,CACzC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** Reads component props from TypeScript sources (interfaces, JSDoc, defaults) — Node only */
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
import { withCompilerOptions, withCustomConfig } from 'react-docgen-typescript';
|
|
4
|
+
const PARSER_OPTIONS = {
|
|
5
|
+
// Unions of literals are expanded to their values instead of a type alias name
|
|
6
|
+
shouldExtractLiteralValuesFromEnum: true,
|
|
7
|
+
shouldRemoveUndefinedFromOptional: true,
|
|
8
|
+
savePropValueAsString: true,
|
|
9
|
+
// Inherited DOM / React props (ButtonHTMLAttributes, ViewProps…) would bury the component's own API
|
|
10
|
+
propFilter: (prop) => !prop.parent?.fileName.includes('node_modules'),
|
|
11
|
+
};
|
|
12
|
+
// Single quotes, as in the libraries' code style: "primary" → 'primary'
|
|
13
|
+
const singleQuote = (literal) => literal.replace(/^"(.*)"$/, "'$1'");
|
|
14
|
+
/** Removes outer parentheses added around optional function types: `(() => void)` → `() => void` */
|
|
15
|
+
function unwrapParens(type) {
|
|
16
|
+
if (!type.startsWith('(') || !type.endsWith(')'))
|
|
17
|
+
return type;
|
|
18
|
+
let depth = 0;
|
|
19
|
+
for (let i = 0; i < type.length; i++) {
|
|
20
|
+
if (type[i] === '(')
|
|
21
|
+
depth++;
|
|
22
|
+
else if (type[i] === ')')
|
|
23
|
+
depth--;
|
|
24
|
+
// The first parenthesis closes before the end: they do not wrap the whole type
|
|
25
|
+
if (depth === 0 && i < type.length - 1)
|
|
26
|
+
return type;
|
|
27
|
+
}
|
|
28
|
+
return type.slice(1, -1);
|
|
29
|
+
}
|
|
30
|
+
function formatType(prop) {
|
|
31
|
+
const { type } = prop;
|
|
32
|
+
if (type.name === 'enum') {
|
|
33
|
+
const values = (type.value ?? []);
|
|
34
|
+
if (values.length)
|
|
35
|
+
return values.map((v) => singleQuote(v.value)).join(' | ');
|
|
36
|
+
return type.raw ?? type.name;
|
|
37
|
+
}
|
|
38
|
+
return unwrapParens(type.name)
|
|
39
|
+
// ReactElement<unknown, string | JSXElementConstructor<any>> → ReactElement
|
|
40
|
+
.replace(/ReactElement<[^<>]*(<[^<>]*>[^<>]*)*>/g, 'ReactElement');
|
|
41
|
+
}
|
|
42
|
+
function formatDefault(value, type) {
|
|
43
|
+
if (value === undefined || value === null || value === '')
|
|
44
|
+
return undefined;
|
|
45
|
+
const text = String(value);
|
|
46
|
+
// String defaults come without quotes: put them back when the prop holds strings
|
|
47
|
+
const isStringProp = type === 'string' || type.includes("'");
|
|
48
|
+
const isLiteral = /^(true|false|null|undefined|-?\d+(\.\d+)?|['"`].*)$/.test(text);
|
|
49
|
+
return isStringProp && !isLiteral ? `'${text}'` : text;
|
|
50
|
+
}
|
|
51
|
+
// JSDoc written as `/** text **/` leaves a trailing asterisk
|
|
52
|
+
const cleanDescription = (text) => text.replace(/\s*\*+\s*$/, '').trim();
|
|
53
|
+
function toPropDef(prop) {
|
|
54
|
+
const type = formatType(prop);
|
|
55
|
+
const def = formatDefault(prop.defaultValue?.value, type);
|
|
56
|
+
return {
|
|
57
|
+
name: prop.name,
|
|
58
|
+
type,
|
|
59
|
+
required: prop.required,
|
|
60
|
+
...(def !== undefined ? { default: def } : {}),
|
|
61
|
+
description: cleanDescription(prop.description),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// docgen also reports exported helpers and constants (getBearingSector, MARKER_HEIGHT): keep PascalCase only
|
|
65
|
+
const isComponentName = (name) => /^[A-Z]/.test(name) && !/^[A-Z0-9_]+$/.test(name);
|
|
66
|
+
function toComponentProps(docs) {
|
|
67
|
+
const result = {};
|
|
68
|
+
for (const doc of docs) {
|
|
69
|
+
if (isComponentName(doc.displayName))
|
|
70
|
+
result[doc.displayName] = Object.values(doc.props).map(toPropDef);
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
export function createPropsExtractor({ tsconfig } = {}) {
|
|
75
|
+
const parser = tsconfig
|
|
76
|
+
? withCustomConfig(tsconfig, PARSER_OPTIONS)
|
|
77
|
+
: withCompilerOptions({ jsx: ts.JsxEmit.ReactJSX, esModuleInterop: true, skipLibCheck: true }, PARSER_OPTIONS);
|
|
78
|
+
return (files) => (files.length ? toComponentProps(parser.parse(files)) : {});
|
|
79
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
export type { ComponentProps } from './extractProps.js';
|
|
3
|
+
export interface DocgenOptions {
|
|
4
|
+
/** Component source files, relative to the Vite root — e.g. ['../src/components/**\/*.tsx'] */
|
|
5
|
+
include: string[];
|
|
6
|
+
/** Files to skip — stories and tests are always skipped */
|
|
7
|
+
exclude?: string[];
|
|
8
|
+
/** tsconfig used to read the components, relative to the Vite root */
|
|
9
|
+
tsconfig?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Absolute paths of the files matching `include` minus `exclude` */
|
|
12
|
+
export declare function findComponentFiles(root: string, include: string[], exclude?: string[]): Promise<string[]>;
|
|
13
|
+
/**
|
|
14
|
+
* Exposes `virtual:traquiste-docs/props`, a `{ [componentName]: PropDef[] }` map to pass to
|
|
15
|
+
* `defineDocsConfig({ componentProps })`. Pages then use `props: 'auto'`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function docgen(options: DocgenOptions): Plugin;
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAkB,MAAM,MAAM,CAAA;AAIlD,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAEvD,MAAM,WAAW,aAAa;IAC5B,+FAA+F;IAC/F,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAQD,qEAAqE;AACrE,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAOnH;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAqDrD"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** Vite plugin: generates component props tables from TypeScript sources — import from '@situaction/traquiste-docs-kit/vite' */
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { glob } from 'tinyglobby';
|
|
4
|
+
import { createPropsExtractor } from './extractProps.js';
|
|
5
|
+
const VIRTUAL_ID = 'virtual:traquiste-docs/props';
|
|
6
|
+
const RESOLVED_ID = '\0' + VIRTUAL_ID;
|
|
7
|
+
// Matched on absolute paths: glob `ignore` patterns are relative to the cwd, and components live
|
|
8
|
+
// outside it (`../src/…`), so they would never apply
|
|
9
|
+
const ALWAYS_EXCLUDED = /(\.(stories|test|spec)\.[^/\\]+$)|([/\\]node_modules[/\\])/;
|
|
10
|
+
/** Absolute paths of the files matching `include` minus `exclude` */
|
|
11
|
+
export async function findComponentFiles(root, include, exclude = []) {
|
|
12
|
+
const [matched, excluded] = await Promise.all([
|
|
13
|
+
glob(include, { cwd: root, absolute: true }),
|
|
14
|
+
exclude.length ? glob(exclude, { cwd: root, absolute: true }) : Promise.resolve([]),
|
|
15
|
+
]);
|
|
16
|
+
const skip = new Set(excluded);
|
|
17
|
+
return matched.filter((file) => !skip.has(file) && !ALWAYS_EXCLUDED.test(file)).sort();
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Exposes `virtual:traquiste-docs/props`, a `{ [componentName]: PropDef[] }` map to pass to
|
|
21
|
+
* `defineDocsConfig({ componentProps })`. Pages then use `props: 'auto'`.
|
|
22
|
+
*/
|
|
23
|
+
export function docgen(options) {
|
|
24
|
+
let root = process.cwd();
|
|
25
|
+
let files = [];
|
|
26
|
+
let props = {};
|
|
27
|
+
let extract;
|
|
28
|
+
let ready = null;
|
|
29
|
+
async function scan() {
|
|
30
|
+
files = await findComponentFiles(root, options.include, options.exclude);
|
|
31
|
+
props = extract(files);
|
|
32
|
+
}
|
|
33
|
+
const isIncluded = (file) => files.includes(path.resolve(file));
|
|
34
|
+
return {
|
|
35
|
+
name: 'traquiste-docs:docgen',
|
|
36
|
+
configResolved(config) {
|
|
37
|
+
root = config.root;
|
|
38
|
+
extract = createPropsExtractor({
|
|
39
|
+
tsconfig: options.tsconfig ? path.resolve(root, options.tsconfig) : undefined,
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
resolveId(id) {
|
|
43
|
+
return id === VIRTUAL_ID ? RESOLVED_ID : null;
|
|
44
|
+
},
|
|
45
|
+
async load(id) {
|
|
46
|
+
if (id !== RESOLVED_ID)
|
|
47
|
+
return null;
|
|
48
|
+
// Parsed once, on first import: a TypeScript program over all components takes a few seconds
|
|
49
|
+
ready ??= scan();
|
|
50
|
+
await ready;
|
|
51
|
+
return `export default ${JSON.stringify(props)}`;
|
|
52
|
+
},
|
|
53
|
+
configureServer(server) {
|
|
54
|
+
// Edit a component → re-read it alone; add or remove one → rescan everything
|
|
55
|
+
const refresh = async (file, rescan) => {
|
|
56
|
+
if (rescan)
|
|
57
|
+
await scan();
|
|
58
|
+
else if (isIncluded(file))
|
|
59
|
+
Object.assign(props, extract([path.resolve(file)]));
|
|
60
|
+
else
|
|
61
|
+
return;
|
|
62
|
+
const mod = server.moduleGraph.getModuleById(RESOLVED_ID);
|
|
63
|
+
if (mod) {
|
|
64
|
+
server.moduleGraph.invalidateModule(mod);
|
|
65
|
+
server.ws.send({ type: 'full-reload' });
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
server.watcher.on('change', (file) => void refresh(file, false));
|
|
69
|
+
server.watcher.on('add', (file) => void refresh(file, true));
|
|
70
|
+
server.watcher.on('unlink', (file) => void refresh(file, true));
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|