// UI Kit Vasco : la moitié bureau du catalogue.
//
// Décision de Sfar du 14/09/2026 : Web bureau, Web mobile et mobile natif sont
// trois cibles séparées. `ui-kit.jsx` reste donc le catalogue de la cible
// téléphone, identique à son contenu du gel ; ce fichier-ci ajoute, au-delà du
// seuil seulement, la démonstration des primitives qui n'existent qu'au bureau.
//
// Il ne recopie rien du catalogue téléphone : il appelle l'écran existant, pose
// la coquille réelle autour, et ajoute ses sections à la suite des siennes. Les
// trois commandes qui ouvrent les surfaces modales sont celles du catalogue,
// reprises par leur libellé dans l'arbre qu'il vient de rendre : il n'y a qu'un
// seul jeu d'états de démonstration.
const VASCO_UI_KIT_LEGACY_SCREEN = VascoUiKitScreen;
// Doublure de démonstration : elle reproduit la forme exacte des feuilles de la
// référence figée, un cadre plein écran, un voile cliquable et un panneau ancré
// en bas avec sa propre animation. L'hôte la recadre sans la connaître.
function VascoUiKitLegacySurface({ onClose, tokens }) {
return
Feuille héritée
Ce contenu et cette croix appartiennent à la feuille. L’hôte ne les remplace pas : il
ne change que la place et la mesure du panneau.
Fermer
;
}
// Une commande du catalogue, reprise dans l'arbre qu'il vient de rendre. On la
// repère par son libellé, qui est le texte que le catalogue affiche : rien n'est
// recréé, et les états de démonstration restent les siens.
function vascoUiKitCommand(node, label, depth = 0) {
if (depth > 10 || node === null || node === undefined || typeof node === 'boolean') return null;
if (Array.isArray(node)) {
for (const child of node) {
const found = vascoUiKitCommand(child, label, depth);
if (found) return found;
}
return null;
}
if (!React.isValidElement(node)) return null;
if (node.props.children === label && typeof node.props.onClick === 'function') {
return node.props.onClick;
}
return vascoUiKitCommand(node.props.children, label, depth + 1);
}
function VascoUiKitDesktopSections({ navTab, onNavTab, legacyOpen, onLegacyOpen, onLegacyClose,
openForm, openBottom, openConfirm }) {
const { tokens, Button, InlineAction, InteractiveRow, StatusNotice,
SideNav, PageHeader, PageGrid, PageColumn, Panel, SplitView, TimeGrid, CalendarEvent,
LegacySurfaceFrame, useColumns } = window.VascoUI;
const columns = useColumns();
return <>
L’entrée active porte aria-current="page", la teinte
d’accent et sa couleur. Le survol n’éclaire une entrée inactive que sur un pointeur
précis ; la tabulation révèle un contour de focus, et Entrée comme Espace changent
d’onglet. Entrée sélectionnée : {navTab}.
Une page de bureau se compose d’un PageHeader, d’une
PageGrid de {tokens.layout.grid.columns} colonnes et de
Panel. Au-dessous de {tokens.layout.grid.singleColumnMaxWidth} px,
la grille repasse en colonne unique. Grille actuelle :
{columns === 'split' ? 'deux colonnes' : 'colonne unique'}.
Les lignes réutilisent InteractiveRow : clavier, focus et cible identiques.
{}} />
Une liste à gauche, le détail de l’élément choisi à droite. Chaque volet défile pour
son compte et porte son étiquette. La liste mesure {tokens.layout.split.listWidth} px,
et {tokens.layout.split.narrowListWidth} px sous le seuil de scission.
Le détail de l’élément sélectionné vit ici. Changer de sélection ne fait pas bouger
la liste : son défilement, sa recherche et son filtre restent en place.
} />
Un axe des heures et des colonnes de jours. La primitive ne calcule ni horaire ni
chevauchement : elle reçoit une géométrie, des heures et des blocs déjà placés. Une
heure mesure {tokens.layout.agenda.hourHeight} px.
({
minutes, offset: index * tokens.layout.agenda.hourHeight,
label: `${String(Math.floor(minutes / 60)).padStart(2, '0')}:00`,
}))}
nowOffset={tokens.layout.agenda.hourHeight * 1.5}
columns={[
{ key: 'lun', header: Lundi, children: [
{}} />,
{}} />,
] },
{ key: 'mar', header: Mardi, children: [
{}} />,
] },
{ key: 'mer', header: Mercredi,
muted: true, children: [] },
// Une journée de congé : grisée en tête, hachurée sous ses blocs, qui
// restent lisibles. Jamais rouge – partir en congé n’est pas un danger.
{ key: 'jeu', header: Jeudi,
absent: true, children: [
{}} />,
] },
]} />
Les feuilles de la référence figée gardent leur contenu et leur propre croix. Au bureau,
un hôte unique recadre leur cadre, leur voile et leur panneau : dialogue centré de
{' '}{tokens.layout.dialog.wideMaxWidth} px, quatre coins arrondis, pas de poignée,
fermeture par Échap et par le fond. Sous le seuil, elles glissent du bas, inchangées.
{legacyOpen && }
>;
}
// Au-delà du seuil, le catalogue s'affiche dans la coquille réelle : la
// navigation latérale et la colonne de contenu se relisent avec les mêmes
// primitives que l'application. Sous le seuil, il est rendu tel quel.
VascoUiKitScreen = function VascoUiKitScreenForLayout(props) {
const desktop = window.VascoUI.useLayout() === 'desktop';
const [navTab, setNavTab] = React.useState('PAT');
const [legacyOpen, setLegacyOpen] = React.useState(false);
// Appel inconditionnel : les états de démonstration du catalogue appartiennent
// alors à ce composant et y restent stables d'un rendu à l'autre.
const tree = VASCO_UI_KIT_LEGACY_SCREEN(props);
if (!desktop || !React.isValidElement(tree)) return tree;
const children = React.Children.toArray(tree.props.children);
const scroll = children.find((child) => React.isValidElement(child)
&& child.props.className === 'vasco-scroll');
if (!scroll) return tree;
const sections = setLegacyOpen(true)}
onLegacyClose={() => setLegacyOpen(false)}
openForm={vascoUiKitCommand(scroll, 'Ouvrir l’exemple')}
openBottom={vascoUiKitCommand(scroll, 'Ouvrir depuis le bas')}
openConfirm={vascoUiKitCommand(scroll, 'Ouvrir la confirmation')} />;
const rebuilt = children.map((child) => (child === scroll
? React.cloneElement(child, undefined,
...React.Children.toArray(child.props.children), sections)
: child));
return }>
{React.cloneElement(tree, undefined, ...rebuilt)}
;
};
window.VascoUiKitScreen = VascoUiKitScreen;