@oremis/prisme
v0.12.0
Published
Design system Vue 3 officiel des applications web OREMIS.
Readme
Prisme
Prisme est la bibliotheque UI Vue 3 officielle des applications web OREMIS.
Elle fournit des composants, des styles et des tokens de design pour construire des interfaces coherentes dans l'ecosysteme OREMIS.
Requirements
- Node 24 ou plus recent.
- Vue 3.5 ou plus recent.
- Un navigateur moderne compatible avec Tailwind CSS v4.
Typographie
Le token --pr-font-sans (utilisé par tous les composants via reset.css) declare Roboto en premier, pour rester coherent avec les applications OREMIS existantes (data, formation). Prisme ne charge pas la police lui-meme : sans action de votre part, les navigateurs retombent silencieusement sur la police systeme (ui-sans-serif/system-ui). Ajoutez, comme le fait deja data/formation (resources/views/layouts/app.blade.php), un lien Google Fonts dans le <head> de votre application :
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">Usage
- Installez le package.
npm install @oremis/prisme- Importez les styles.
import '@oremis/prisme/styles.css'- Utilisez les composants.
<script setup lang="ts">
import { PrButton } from '@oremis/prisme'
</script>
<template>
<PrButton>Continuer</PrButton>
</template>- Consultez la documentation Storybook.
https://associationoremis.github.io/prisme/Composants controles (v-model)
Chaque composant a etat ouvert/coche reprend le nom de prop de la primitive reka-ui qu'il enveloppe, plutot que d'imposer une convention modelValue uniforme. Un v-model nu ne fonctionne donc que sur PrAccordion :
| Composant(s) | Prop | v-model |
| --- | --- | --- |
| PrAccordion | modelValue | v-model="valeur" |
| PrCheckbox, PrSwitch | checked | v-model:checked="valeur" |
| PrToggle | pressed | v-model:pressed="valeur" |
| PrCollapsible, PrDialog, PrSheet, PrPopover, PrToast, PrAlertDialog, PrDropdownMenu | open | v-model:open="valeur" |
Chacun de ces composants accepte aussi un default-xxx (defaultValue, defaultChecked, defaultPressed, defaultOpen) pour un usage non controle, sans avoir a gerer l'etat cote consommateur.
PrRichTextEditor
Editeur de texte riche (Vue 3 + tiptap), avec titres, listes, tableaux, images, video YouTube, blocs de code colores, sections repliables et encadres Callout (info/succes/avertissement/danger).
<script setup lang="ts">
import { ref } from 'vue'
import { PrRichTextEditor } from '@oremis/prisme'
const content = ref('<p>Contenu initial</p>')
async function uploadImage(file: File, onProgress?: (percent: number) => void) {
const body = new FormData()
body.append('file', file)
const response = await fetch('/upload', { method: 'POST', body })
// Adaptez selon votre backend : pas de progression reelle possible avec
// `fetch`, un client HTTP capable de suivre l'upload (axios, XHR) permet
// d'appeler `onProgress(percent)` pendant l'envoi.
onProgress?.(100)
const { url } = await response.json()
return url
}
</script>
<template>
<PrRichTextEditor v-model="content" label="Contenu" :upload-image="uploadImage" />
</template>Ses dependances tiptap (@tiptap/*, lowlight, tiptap-extension-resize-image) sont des peerDependencies optionnelles — installez-les explicitement dans l'application consommatrice (memes versions que celles listees dans package.json#peerDependencies de Prisme) :
npm install @tiptap/[email protected] @tiptap/[email protected] @tiptap/[email protected] # ... voir peerDependenciesLe node tiptap Callout utilise par l'editeur est aussi exporte separement, pour une app qui monterait son propre editeur tiptap sans passer par PrRichTextEditor :
import { Callout } from '@oremis/prisme/tiptap/callout'Le rendu du contenu (tableaux, Callout, blocs de code, sections repliables) est stylise par @oremis/prisme/styles/editor-content.css, importable seul par une vue de lecture seule qui n'a pas besoin du reste de Prisme :
import '@oremis/prisme/styles/editor-content.css'<div class="pr-editor-content" v-html="content" />Comme PrCombobox/PrTagInput, le contenu de l'editeur n'est pas un <textarea> natif : pour une soumission de formulaire native (<form method="POST"> sans JS de soumission), passez un name — un <input type="hidden"> synchronise avec modelValue est rendu automatiquement.
<PrRichTextEditor v-model="content" name="body" />Empiler plusieurs composants Prisme verticalement
PrDataTable et les autres composants larges (formulaires avec beaucoup de champs) utilisent flex flex-col en interne, pas display: grid. Un wrapper consommateur en display: grid sans grid-template-columns explicite autour d'un tel composant produit un debordement (CSS Grid blowout) : la piste implicite se dimensionne sur le contenu le plus large, meme si le conteneur a min-width: 0 (qui ne protege que sa propre boite, pas sa piste interne).
Pour empiler plusieurs composants Prisme verticalement (DataTable, formulaire, etc.), utilisez flex flex-col (ou display: block) plutot qu'une grille nue :
<!-- A eviter : grille sans colonnes explicites -->
<div class="grid">
<PrDataTable ... />
</div>
<!-- Recommande -->
<div class="flex flex-col">
<PrDataTable ... />
</div>Laravel Blade
Dans une application Laravel avec Vite, Prisme peut etre monte dans une vue Blade via une petite application Vue.
- Installez le package dans l'application Laravel.
npm install @oremis/prisme- Importez les styles et montez Vue dans
resources/js/app.ts.
import { createApp } from 'vue'
import { PrButton } from '@oremis/prisme'
import '@oremis/prisme/styles.css'
const element = document.getElementById('prisme-app')
if (element) {
createApp({
components: { PrButton },
template: '<PrButton>{{ label }}</PrButton>',
data: () => ({
label: element.dataset.label ?? 'Continuer',
}),
}).mount(element)
}- Ajoutez le point de montage dans une vue Blade.
@vite('resources/js/app.ts')
<div id="prisme-app" data-label="Enregistrer"></div>Pour plusieurs composants ou des ecrans plus complets, creez un composant Vue dedie puis montez-le depuis app.ts.
Enregistrement global avec app.use(Prisme)
Vous pouvez aussi ecrire les composants Prisme directement dans le Blade, tant qu'ils sont dans le noeud monte par Vue. Dans ce cas, installez le plugin Prisme avec app.use(Prisme) : il enregistre globalement tous les composants publics de la bibliotheque, utilisables ensuite en HTML/Blade sous leur forme kebab-case.
import { createApp } from 'vue'
import Prisme from '@oremis/prisme'
import '@oremis/prisme/styles.css'
const app = createApp({})
app.use(Prisme)
app.mount('#prisme-app')@vite('resources/js/app.ts')
<div id="prisme-app">
<pr-button>Enregistrer</pr-button>
<pr-badge>Actif</pr-badge>
<pr-input placeholder="Nom"></pr-input>
<pr-data-table></pr-data-table>
</div>app.use(Prisme) ou imports nommes ?
app.use(Prisme)enregistre en une seule fois tous les composants Prisme comme composants globaux de l'application. C'est le plus adapte quand les composants sont utilises directement dans du HTML/Blade (pas de<script setup>pour les declarer), au prix d'inclure l'integralite de la bibliotheque dans le bundle.import { PrButton } from '@oremis/prisme'importe uniquement les composants reellement utilises et beneficie du tree-shaking. C'est le choix recommande dans des composants Vue (SFC) ou seule une partie de Prisme est necessaire.
Les deux approches sont interchangeables et peuvent cohabiter dans la meme application.
Eviter le flash de theme (FOUC) en Blade
Dans une SPA, usePrTheme() applique le theme des le montage de Vue. En Blade, le HTML est deja affiche par le navigateur avant que le bundle Vue ne s'execute : sans intervention, la page s'affiche brievement dans le theme par defaut avant de basculer vers le theme reellement choisi (sombre par ex.).
Pour l'eviter, il faut qu'un <script> synchrone, non-module, s'execute dans le <head> du layout Blade avant le premier paint — donc avant meme @vite(...), dont les scripts sont charges en type="module" (differe par le navigateur). getPrThemeInitScript() fournit justement ce script : une chaine de JS vanilla, sans dependance, qui lit la preference stockee et pose data-pr-theme/color-scheme sur <html> immediatement.
Le plus simple est de l'ecrire une fois dans un fichier statique servi tel quel (pas de build Vite dessus), a partir d'un petit script Node execute a la racine du projet :
// scripts/write-theme-init-script.mjs
import { writeFileSync } from 'node:fs'
import { getPrThemeInitScript } from '@oremis/prisme'
writeFileSync('public/prisme-theme-init.js', getPrThemeInitScript())<head>
<script src="{{ asset('prisme-theme-init.js') }}"></script>
@vite(['resources/css/app.css', 'resources/js/app.ts'])
</head>Sans cette etape, getPrThemeInitScript() — bien que fournie a cet effet — n'a aucun effet : le flash de theme qu'elle est censee eviter se produira quand meme, puisque usePrTheme()/setPrTheme() ne s'executent qu'apres l'hydratation du bundle Vue, donc apres le premier paint.
Isoler Prisme dans une page qui utilise un autre framework CSS (Bootstrap, etc.)
@oremis/prisme/styles.css inclut un reset Tailwind non prefixe (.flex, .p-4, .hidden, .grid, etc. sans espace de nom). Importe normalement (import global ou @vite(...)) dans une page Blade qui charge deja un autre framework CSS non-Tailwind (Bootstrap par exemple), ce reset s'applique a toute la page, pas seulement au composant monte : il peut silencieusement casser l'apparence ou le comportement d'elements totalement sans rapport ailleurs sur la meme page. Incident reel : la sidebar d'une app consommatrice s'est retrouvee bloquee en position repliee des qu'un composant Prisme etait monte sur la meme page.
Pour une app Blade deja construite sur un autre framework CSS, montez le composant dans un shadow DOM plutot que dans le document courant, avec mountPrismeIsolated() : le style de Prisme reste alors entierement confine a l'interieur, sans jamais pouvoir affecter le reste de la page (et inversement, le CSS de l'hote ne peut pas polluer l'interieur du composant).
import { mountPrismeIsolated } from '@oremis/prisme'
import prismeStyles from '@oremis/prisme/styles.css?inline' // Vite: recupere le CSS en chaine, sans l'injecter dans <head>
import MyPrismeComponent from './MyPrismeComponent.vue'
const host = document.getElementById('prisme-app')
if (host) {
mountPrismeIsolated(MyPrismeComponent, host, { styles: prismeStyles })
}mountPrismeIsolated() renvoie { app, shadowRoot, unmount } si vous avez besoin d'aller plus loin (demonter le composant, inspecter le shadow root, etc.).
Attention avec PrRichTextEditor (et tout autre composant qui accepte un prop name pour rendre un <input> cache destine a une soumission de formulaire native) : un element place dans un shadow DOM n'est pas inclus dans la soumission native du <form> ancetre. Ne passez pas name dans ce cas : gardez un vrai <input type="hidden"> dans le DOM normal (hors du shadow root), et synchronisez-le vous-meme via v-model/un callback plutot que de compter sur l'auto-rendu du composant.
Development
npm install
npm run storybookBuild de la librairie :
npm run buildBuild du Storybook statique :
npm run build-storybookLicense
Prisme est distribue sous licence MIT. Voir LICENSE.
