Un système Server-Driven UI
Project description
cicaw-sdui
cicaw-sdui est un framework Python de Server-Driven UI (SDUI) qui permet de décrire et sérialiser des interfaces utilisateur côté serveur, sous forme de JSON consommé par un frontend React (ou toute autre cible compatible).
Table des matières
- Qu'est-ce que le Server-Driven UI ?
- Pourquoi cicaw-sdui ?
- Installation
- Architecture du package
- Concepts fondamentaux
- Composants — Atoms
- Composants — Layouts
- Actions
- Tokens de référence
- Exemples complets
- Référence du rendu JSON
- Contribuer
Qu'est-ce que le Server-Driven UI ?
Le Server-Driven UI (SDUI) est un paradigme dans lequel le serveur décide de la structure et du contenu de l'interface, et le client se contente de la rendre. Plutôt que d'avoir de la logique de navigation et de composition d'écrans dispersée dans le frontend, le backend retourne du JSON décrivant l'arbre de composants à afficher.
┌─────────────────────────────────────────────────────────────────┐
│ Client React │
│ │
│ GET /api/home ──────────────────► Backend Python (Django…) │
│ │ │
│ ◄──── JSON SDUI ──────────────────────────┘ │
│ │
│ SDUIRenderer({ │
│ type: "screen", │
│ data: { children: [...] } │
│ }) │
└─────────────────────────────────────────────────────────────────┘
Avantages :
- Déploiements sans mise à jour de l'app mobile (changement de textes, layouts, features flags…)
- Logique métier centralisée côté serveur
- A/B testing et personnalisation triviales
- UI identique sur toutes les plateformes (web, iOS, Android)
Pourquoi cicaw-sdui ?
cicaw-sdui fournit une couche Python typée (Pydantic v2) pour :
- Décrire des interfaces via des classes Python lisibles et auto-documentées
- Valider la structure au moment de la construction (typage strict, énumérations)
- Sérialiser en JSON compact (
exclude_none=True,exclude_defaults=True) prêt à être consommé par le renderer frontend - Composer des vues complexes grâce à l'API fluente et aux conteneurs récursifs
Installation
Depuis PyPI :
pip install cicaw-sdui
Depuis les sources :
git clone https://github.com/your-org/cicaw-sdui.git
cd cicaw-sdui
pip install -e .
Prérequis : Python ≥ 3.8, Pydantic ≥ 2.0.0
Architecture du package
cicaw_sdui/
├── actions.py # Actions client & serveur (navigation, formulaires, tracking…)
├── atoms.py # Composants atomiques (Text, Button, Image, Icon, Badge…)
├── base.py # Classes de base : UIComponent, Style, Spacing
├── enums.py # Tous les tokens du design system (couleurs, tailles, espacements…)
└── layouts.py # Conteneurs (Screen, Column, Row, Carousel, Form…)
Chaque fichier est indépendant et peut être importé séparément.
Concepts fondamentaux
UIComponent et rendu JSON
Tous les composants héritent de UIComponent. La méthode .render() produit le JSON final à retourner au client.
from cicaw_sdui.atoms import Text
from cicaw_sdui.enums import TextSize, ColorRole
text = Text(
text="Bienvenue",
text_size=TextSize.XXXXL,
text_color=ColorRole.TEXT_PRIMARY,
)
print(text.render())
# {
# "type": "text",
# "data": {
# "text": "Bienvenue",
# "text_size": "4xl",
# "text_color": "text-primary"
# }
# }
Comportement de .render() :
- Les champs
Nonesont exclus (exclude_none=True) - Les valeurs égales aux défauts sont exclues (
exclude_defaults=True) — réduit la taille du JSON d'un facteur ~10 - Le résultat est toujours
{"type": "<component_type>", "data": {...}}
Style
Chaque UIComponent possède un objet style: Style qui regroupe toutes les propriétés CSS de bas niveau : dimensions, couleurs, flexbox, grilles, bordures, effets, transitions, etc.
from cicaw_sdui.base import Style
from cicaw_sdui.enums import (
ColorRole, RadiusToken, ElevationToken,
SizingToken, SpacingToken
)
style = Style(
width=SizingToken.FULL,
background_color=ColorRole.SURFACE_RAISED,
border_radius=RadiusToken.LG,
elevation=ElevationToken.LEVEL_2,
padding=SpacingToken.MD,
)
Les composants de type Row et Column exposent leurs propriétés de style directement (sans passer par .style) pour plus de lisibilité :
from cicaw_sdui.layouts import Column
from cicaw_sdui.enums import GapToken, SpacingToken, ColorRole
column = Column(
gap=GapToken.MD,
padding_x=SpacingToken.LG,
bg=ColorRole.SURFACE_MAIN,
)
Spacing
Le modèle Spacing offre un contrôle fin des marges et paddings (par côté, par axe) indépendamment du Style :
from cicaw_sdui.base import Spacing
from cicaw_sdui.enums import SpacingToken
spacing = Spacing(
pt=SpacingToken.LG,
pb=SpacingToken.MD,
px=SpacingToken.GUTTER,
mb=SpacingToken.XL,
)
Tokens de design system
Tous les tokens sont des Enum string définis dans enums.py. Ils mappent directement sur les classes Tailwind / variables CSS du design system :
| Catégorie | Enum | Exemple |
|---|---|---|
| Espacement | SpacingToken |
SpacingToken.MD → p-md (16px) |
| Dimensionnement | SizingToken |
SizingToken.FULL → w-full |
| Couleurs | ColorRole |
ColorRole.PRIMARY → bg-primary |
| Bordures | RadiusToken |
RadiusToken.LG → rounded-lg |
| Élévation | ElevationToken |
ElevationToken.LEVEL_2 → shadow-2 |
| Typographie | TextSize, TextWeight |
TextSize.XL → text-xl |
| Animation | AnimationToken |
AnimationToken.FADE |
| Flex | MainAxisAlignment, CrossAxisAlignment |
MainAxisAlignment.CENTER |
Composants — Atoms
Text
Composant texte riche avec typographie complète.
from cicaw_sdui.atoms import Text
from cicaw_sdui.enums import TextSize, TextWeight, ColorRole, TextAlign
# Titre principal
h1 = Text(
text="Découvrez nos produits",
text_size=TextSize.XXXXL,
weight=TextWeight.BOLD,
text_color=ColorRole.TEXT_HEADING,
align=TextAlign.CENTER,
)
# Texte secondaire tronqué
caption = Text(
text="Description longue qui sera coupée après 2 lignes...",
text_size=TextSize.SM,
text_color=ColorRole.TEXT_SECONDARY,
max_lines=2,
)
# Texte animé (typewriter)
animated = Text(
text="Chargement en cours…",
animate=True,
typewriter_speed=40,
)
Propriétés clés :
| Prop | Type | Description |
|---|---|---|
text |
str |
Contenu textuel principal |
html |
str |
HTML brut (dangerouslySetInnerHTML) |
text_size |
TextSize |
xs / sm / md / lg / xl / 2xl / 3xl / 4xl |
weight |
TextWeight |
light / regular / medium / semibold / bold / black |
text_color |
ColorRole |
Token de couleur du design system |
align |
TextAlign |
start / center / end / justify |
max_lines |
int |
Troncature multi-lignes via -webkit-line-clamp |
truncate |
bool |
Troncature sur une seule ligne |
tag |
TextTag |
Force la balise HTML (h1…h6, p, span…) |
typewriter_speed |
int |
Délai entre caractères en ms |
Button
from cicaw_sdui.atoms import Button
from cicaw_sdui.enums import ButtonVariant, ButtonSize, ButtonState, IconName
from cicaw_sdui.actions import NavigateAction
btn = Button(
label="Voir le catalogue",
variant=ButtonVariant.FILLED,
size=ButtonSize.LG,
icon_right=IconName.ARROW_RIGHT,
on_press=NavigateAction(route="/catalogue"),
)
# Bouton en état de chargement
loading_btn = Button(
label="Connexion",
variant=ButtonVariant.FILLED,
state=ButtonState.LOADING,
loading_label="Connexion en cours…",
)
Variantes :
| Valeur | Rendu |
|---|---|
FILLED |
Fond coloré, texte blanc — action principale |
TONAL |
Fond clair, texte primaire — action secondaire |
OUTLINED |
Bordure seule |
GHOST |
Texte seul, hover avec fond léger |
LINK |
Texte souligné |
FAB |
Bouton circulaire flottant |
Image / ImageView
Image est un composant simple. ImageView est la version complète avec lazy-loading, aspect ratio, décoration et animation d'entrée.
from cicaw_sdui.atoms import ImageView
from cicaw_sdui.enums import (
AspectRatioToken, RadiusToken, ObjectFit, AnimationToken
)
# Image produit standard
product_img = ImageView(
src="https://cdn.example.com/product-123.jpg",
alt="Sneakers blanc taille 42",
aspect_ratio=AspectRatioToken.SQUARE,
object_fit=ObjectFit.COVER,
radius=RadiusToken.MD,
animate_in=AnimationToken.FADE,
)
# Hero section (above the fold, pas de lazy-load)
hero_img = ImageView(
src="https://cdn.example.com/hero.jpg",
alt="",
aspect_ratio=AspectRatioToken.VIDEO,
priority=True, # eager load, LCP optimisé
)
Icon
from cicaw_sdui.atoms import Icon
from cicaw_sdui.enums import IconName, SizingToken, ColorRole
icon = Icon(
name=IconName.HEART,
size=SizingToken.S_6, # 24px
color=ColorRole.ERROR,
stroke_width=1.5,
)
Icônes disponibles (sélection) :
SHOPPING_CART, SEARCH, USER, HEART, STAR, ARROW_RIGHT, CHEVRON_RIGHT, CHECK, X, TRASH_2, EYE, UPLOAD, MAIL, PHONE, MAP_PIN, CLOCK, TRUCK, CREDIT_CARD…
Badge
from cicaw_sdui.atoms import Badge
from cicaw_sdui.enums import ColorRole, IconName
# Badge texte
Badge(label="Nouveau", color=ColorRole.PRIMARY, tone="filled")
# Badge compteur
Badge(count=5, color=ColorRole.ERROR, tone="filled", max_count=99)
# Badge point (notification dot)
Badge(dot=True, color=ColorRole.SUCCESS)
# Badge avec icône
Badge(label="Pro", icon=IconName.STAR, tone="outlined", color=ColorRole.WARNING)
Avatar
from cicaw_sdui.atoms import Avatar
# Avec image
Avatar(src="https://cdn.example.com/user.jpg", size="lg")
# Avec initiales (fallback)
Avatar(initials="JD", size="md")
InputText
from cicaw_sdui.atoms import InputText
from cicaw_sdui.enums import InputType
InputText(
name="email",
label="Adresse e-mail",
type=InputType.TEXT,
placeholder="vous@exemple.com",
required=True,
help_text="Nous ne partageons jamais votre adresse.",
)
InputText(
name="password",
label="Mot de passe",
type=InputType.PASSWORD,
min_length=8,
max_length=128,
)
# Select (liste déroulante)
InputText(
name="country",
label="Pays",
widget="select",
choices=[
{"value": "SN", "label": "Sénégal"},
{"value": "CI", "label": "Côte d'Ivoire"},
{"value": "ML", "label": "Mali"},
],
)
Switch / Checkbox
from cicaw_sdui.atoms import Switch, Checkbox
Switch(name="notifications", label="Activer les notifications", value=True)
Checkbox(name="cgu", label="J'accepte les conditions générales", value=False)
Loader
from cicaw_sdui.atoms import Loader
Loader(size="md") # sm | md | lg
MarkdownText
Rendu de contenu Markdown riche (GFM : tableaux, listes, blocs de code…) de façon sécurisée.
from cicaw_sdui.atoms import MarkdownText
MarkdownText(
text="## Guide d'utilisation\n\nVoici les **étapes** pour commencer :\n\n1. Créez un compte\n2. Ajoutez vos produits\n3. Partagez votre boutique",
prose_size="base", # sm | base | lg
open_links_in_new_tab=True,
)
GradientText / TextHighlight / WaveSeparator
from cicaw_sdui.atoms import GradientText, TextHighlight, WaveSeparator
from cicaw_sdui.enums import (
GradientTextVariant, GradientDirection,
TextHighlightVariant, WaveSeparatorVariant, ColorRole
)
# Texte en dégradé
GradientText(
text="Bienvenue sur Cicaw",
variant=GradientTextVariant.LINEAR,
from_color=ColorRole.PRIMARY,
to_color=ColorRole.ACCENT,
direction=GradientDirection.TO_RIGHT,
tag="h1",
)
# Mise en valeur de mots-clés
TextHighlight(
text="Livraison gratuite et retours faciles.",
highlighted_words="gratuite,faciles",
variant=TextHighlightVariant.MARKER,
color=ColorRole.WARNING,
)
# Séparateur décoratif entre sections
WaveSeparator(
variant=WaveSeparatorVariant.WAVE,
color=ColorRole.SURFACE_MAIN,
height=80,
flip_y=True,
)
CartController / LoadingTrigger
from cicaw_sdui.atoms import CartController, LoadingTrigger
# Contrôleur d'ajout au panier (bouton +/- intégré)
CartController(
product_id=42,
initial_quantity=0,
add_label="Ajouter",
add_url="/api/cart/add",
remove_url="/api/cart/remove",
)
# Déclencheur de chargement (infinite scroll)
LoadingTrigger(
url="/api/products?page=2",
label="Charger plus",
)
Composants — Layouts
Screen
Conteneur racine de chaque page SDUI. Retourné par vos vues Django/FastAPI/Flask.
from cicaw_sdui.layouts import Screen
def home_view(request):
screen = Screen(
page_title="Accueil — Cicaw",
page_description="Découvrez nos produits",
track_page_view="home_viewed",
stale_time=60_000, # Cache React Query : 1 minute
cache_strategy="normal",
children=[...]
)
return screen.render()
Propriétés clés :
| Prop | Type | Description |
|---|---|---|
page_title |
str |
Titre de l'onglet |
page_description |
str |
Meta description |
stale_time |
int |
Cache React Query en ms |
cache_strategy |
str |
normal / aggressive / no-cache |
track_page_view |
str |
Nom d'event analytics au montage |
max_width |
int |
max-width du conteneur en px |
progress_bar |
bool |
Affiche NProgress pendant le chargement |
Column / Row
Les deux briques de layout les plus utilisées. Exposent toutes les propriétés flex + espacement + visuel directement.
from cicaw_sdui.layouts import Column, Row
from cicaw_sdui.enums import (
GapToken, SpacingToken, SizingToken,
CrossAxisAlignment, MainAxisAlignment, ColorRole, RadiusToken
)
# Colonne verticale centrée
Column(
gap=GapToken.MD,
padding_x=SpacingToken.GUTTER,
padding_y=SpacingToken.LG,
cross_axis_align=CrossAxisAlignment.CENTER,
width=SizingToken.FULL,
children=[...],
)
# Ligne horizontale avec espace entre les éléments
Row(
main_axis_align=MainAxisAlignment.SPACE_BETWEEN,
cross_axis_align=CrossAxisAlignment.CENTER,
padding_x=SpacingToken.MD,
bg=ColorRole.SURFACE_HEADER,
children=[logo, nav_actions],
)
API fluente :
col = Column(gap=GapToken.SM).justify_center().items_center()
col.with_children(text1, text2, text3)
Stack
Superpose les enfants sur l'axe Z (position relative/absolute).
from cicaw_sdui.layouts import Stack, Overlay
from cicaw_sdui.enums import OverlayPlacement
Stack(
children=[
image_component,
Overlay(
placement=OverlayPlacement.BOTTOM_LEFT,
children=[badge_promo],
),
]
)
Grid (via Style)
La grille CSS passe par Style.grid_cols sur un Column ou Row.
from cicaw_sdui.layouts import Row
from cicaw_sdui.base import Style
from cicaw_sdui.enums import GridCols, SpacingToken
Row(
style=Style(
grid_cols=GridCols.THREE,
gap=SpacingToken.MD,
),
children=[card1, card2, card3],
)
Carousel / GlideReel
from cicaw_sdui.layouts import Carousel, GlideReel
from cicaw_sdui.enums import CarouselEffect
# Carousel produits responsive
carousel = Carousel(
children=[slide1, slide2, slide3],
)
carousel.layout(per_view=1.2, space=16)
carousel.set_responsive(sm=1.2, md=2.2, lg=3.5)
carousel.navigation(dots=True, arrows=False)
carousel.set_autoplay(delay=4000)
# GlideReel avancé
reel = GlideReel(
slidesPerView="auto",
spaceBetween=16,
loop=True,
navigation=True,
pagination=GlidePagination.BULLETS,
autoplay=GlideAutoplayConfig(delay=3000, pauseOnHover=True),
children=[...],
)
Overlay
Positionne son contenu en absolu dans un Stack parent.
from cicaw_sdui.layouts import Overlay
from cicaw_sdui.enums import (
OverlayPlacement, OverlayBackdrop, AnimationToken
)
Overlay(
placement=OverlayPlacement.BOTTOM_CENTER,
backdrop=OverlayBackdrop.GRADIENT_BOTTOM,
animation=AnimationToken.SLIDE_UP,
children=[title_text, cta_button],
)
Link
from cicaw_sdui.layouts import Link
Link(
url="/blog/article-1",
target="_self",
children=[Text(text="Lire l'article")],
)
# Lien externe dans un nouvel onglet
Link(url="https://example.com").open_in_new_tab().with_children(icon)
Form
Conteneur de formulaire. Combiné à FormSubmitAction et FormScope.
from cicaw_sdui.layouts import Form, Column
from cicaw_sdui.atoms import InputText, Button
from cicaw_sdui.enums import InputType, ButtonVariant
from cicaw_sdui.actions import FormSubmitAction, FieldRule
form = Form(
form_id="register_form",
children=[
Column(
gap=GapToken.MD,
children=[
InputText(name="email", label="E-mail", type=InputType.TEXT),
InputText(name="password", label="Mot de passe", type=InputType.PASSWORD),
Button(
label="Créer mon compte",
variant=ButtonVariant.FILLED,
on_press=FormSubmitAction(
form_id="register_form",
endpoint="/api/auth/register",
rules=[
FieldRule(field="email", required=True, pattern=r".+@.+\..+"),
FieldRule(field="password", required=True, min_length=8),
],
),
),
],
)
],
)
Animated
Enveloppe n'importe quel composant ou sous-arbre avec une animation CSS/JS.
from cicaw_sdui.layouts import Animated
from cicaw_sdui.enums import AnimationVariant, AnimationTrigger
# Animation déclenchée à l'entrée dans le viewport
Animated(
variant=AnimationVariant.FADE_UP,
trigger=AnimationTrigger.VISIBLE,
delay_ms=100,
duration_ms=600,
children=[my_card],
)
# Animer une liste avec stagger (enfants animés un par un)
Animated(
variant=AnimationVariant.STAGGER_UP,
stagger_ms=80,
trigger=AnimationTrigger.VISIBLE,
children=[item1, item2, item3, item4],
)
Variantes d'entrée (sélection) :
FADE_IN, FADE_UP, FADE_DOWN, ZOOM_IN, SLIDE_UP, BOUNCE_IN, BLUR_IN, REVEAL_CLIP
Variantes en boucle :
PULSE, FLOAT, BREATHE, SPIN, GLOW, SHIMMER_LOOP
Variantes stagger :
STAGGER_UP, STAGGER_WAVE, STAGGER_CASCADE, STAGGER_FADE
GradientBackground / BackgroundImageContainer
from cicaw_sdui.layouts import GradientBackground, BackgroundImageContainer
from cicaw_sdui.enums import (
GradientVariant, GradientDirection, ColorRole,
BgVariant, BgPosition, BgSize
)
# Section avec fond dégradé
GradientBackground(
variant=GradientVariant.LINEAR,
from_color=ColorRole.PRIMARY,
to_color=ColorRole.SECONDARY,
direction=GradientDirection.TO_BOTTOM,
children=[hero_content],
)
# Section hero avec image de fond
BackgroundImageContainer(
src="https://cdn.example.com/hero.jpg",
alt="",
position=BgPosition.CENTER,
size=BgSize.COVER,
variant=BgVariant.OVERLAY_GRADIENT_BOTTOM,
lazy=False,
children=[title, subtitle, cta],
)
Transparent
Fragment sans wrapper DOM — retourne ses enfants directement.
from cicaw_sdui.layouts import Transparent
# Utile pour les fragments de données (DataSource retournant plusieurs frères)
Transparent(children=[section_a, section_b, section_c])
Actions
Les actions décrivent ce qui se passe quand l'utilisateur interagit (clic, soumission, etc.) ou quand le serveur répond. Elles sont passées aux props on_press, on_click, on_submit ou dans les réponses API.
Actions client (immédiates)
Exécutées localement sans appel réseau.
from cicaw_sdui.actions import (
NavigateAction, ToastAction, OpenUrlAction
)
# Navigation interne
NavigateAction(route="/profil")
NavigateAction(route="/home", replace=True) # Remplace l'historique
# Toast / notification
ToastAction(message="Produit ajouté au panier !", level="success", duration=3000)
ToastAction(message="Erreur réseau", title="Oops", level="error", dismissible=True)
# Lien externe
OpenUrlAction(url="https://wa.me/221XXXXXXXXX")
Actions serveur
Envoient une requête au backend et exécutent la réponse (une nouvelle ActionUnion).
from cicaw_sdui.actions import (
Post, ServerEventAction, RefreshScreenAction,
RemoveComponent, UpdateComponentAction,
InsertComponentAction, FetchInsertAction, FetchAction
)
# POST vers un endpoint
Post(pathname="/api/products/42/like", show_loader=True)
# Rafraîchir la page entière
RefreshScreenAction(show_loader=True)
# Supprimer un composant de l'arbre (ex: supprimer un item de liste)
RemoveComponent(target_id="product-card-42")
# Modifier partiellement un composant (optimistic UI)
UpdateComponentAction(
target_id="like-btn-42",
data_patch={"icon": "heart", "color": "error"},
)
# Insérer du SDUI dynamiquement (infinite scroll, pagination)
FetchInsertAction(
url="/api/products?page=2",
target_id="product-list",
mode="append",
remove_trigger=True,
)
# Charger et exécuter une action depuis le serveur
FetchAction(
url="/api/next-step",
method="post",
payload={"context": "onboarding"},
show_loader=True,
)
Actions formulaire
from cicaw_sdui.actions import (
FormSubmitAction, FormResetAction, FormSetErrorsAction, FieldRule
)
# Soumettre un formulaire avec validation côté client
FormSubmitAction(
form_id="login_form",
endpoint="/api/auth/login",
method="post",
rules=[
FieldRule(field="email", required=True, pattern=r".+@.+\..+", message="Email invalide"),
FieldRule(field="password", required=True, min_length=8),
],
show_loader=True,
reset_on_success=False,
)
# Réinitialiser un formulaire
FormResetAction(form_id="login_form")
# Injecter des erreurs serveur dans le formulaire (422 / logique métier)
FormSetErrorsAction(
form_id="register_form",
errors={"email": "Cette adresse est déjà utilisée."},
)
Chaînage d'actions
Toute action peut enchaîner une autre via on_success, on_error, next_action, ou être groupée dans un BatchAction.
from cicaw_sdui.actions import (
Post, ToastAction, NavigateAction,
TrackAction, BatchAction, RemoveComponent
)
# Après suppression : toast + suppression du composant
Post(
pathname="/api/products/42/delete",
on_success=BatchAction(
mode="sequence",
actions=[
ToastAction(message="Produit supprimé", level="success"),
RemoveComponent(target_id="product-card-42"),
],
),
on_error=ToastAction(message="Erreur lors de la suppression", level="error"),
)
# Tracking + navigation
TrackAction(
event_name="cta_clicked",
properties={"source": "home_banner"},
next_action=NavigateAction(route="/catalogue"),
)
Tokens de référence
SpacingToken (padding, margin, gap)
| Token | Valeur CSS |
|---|---|
XXXS |
2px |
XXS |
4px |
XS |
8px |
SM |
12px |
MD |
16px |
LG |
24px |
XL |
32px |
XXL |
48px |
XXXL |
64px |
GUTTER |
20px |
TextSize
| Token | Taille |
|---|---|
XS |
12px |
SM |
14px |
MD |
16px (défaut) |
LG |
18px |
XL |
20px |
XXL |
24px |
XXXL |
30px |
XXXXL |
36px |
ColorRole (sélection)
| Token | Usage |
|---|---|
SURFACE_MAIN |
Fond principal de la page |
SURFACE_RAISED |
Fond de carte légèrement élevée |
TEXT_PRIMARY |
Texte principal |
TEXT_SECONDARY |
Texte secondaire (moins visible) |
TEXT_DISABLED |
Texte désactivé |
PRIMARY |
Couleur de marque principale |
SUCCESS |
Validation, succès |
ERROR |
Erreurs, états destructifs |
WARNING |
Avertissements |
BORDER_DEFAULT |
Bordure standard |
ALWAYS_WHITE |
Blanc forcé (même en Dark Mode) |
Exemples complets
Page d'accueil simple
from cicaw_sdui.layouts import Screen, Column, Row
from cicaw_sdui.atoms import Text, Button, ImageView
from cicaw_sdui.actions import NavigateAction
from cicaw_sdui.enums import (
TextSize, TextWeight, ColorRole, ButtonVariant,
GapToken, SpacingToken, SizingToken, AspectRatioToken
)
def home_screen():
return Screen(
page_title="Accueil",
track_page_view="home_viewed",
children=[
Column(
gap=GapToken.LG,
padding_x=SpacingToken.GUTTER,
padding_y=SpacingToken.XL,
children=[
Text(
text="Bienvenue sur Cicaw",
text_size=TextSize.XXXXL,
weight=TextWeight.BOLD,
),
Text(
text="Découvrez des milliers de produits.",
text_size=TextSize.LG,
text_color=ColorRole.TEXT_SECONDARY,
),
Button(
label="Explorer le catalogue",
variant=ButtonVariant.FILLED,
on_press=NavigateAction(route="/catalogue"),
),
],
)
],
).render()
Formulaire de connexion
from cicaw_sdui.layouts import Screen, Column, Form
from cicaw_sdui.atoms import Text, InputText, Button
from cicaw_sdui.actions import (
AuthLoginAction, FormSubmitAction, FieldRule, NavigateAction, ToastAction
)
from cicaw_sdui.enums import (
InputType, ButtonVariant, TextSize, TextWeight,
GapToken, SpacingToken, SizingToken
)
def login_screen():
return Screen(
page_title="Connexion",
children=[
Column(
gap=GapToken.LG,
padding=SpacingToken.XL,
max_width=SizingToken.MD,
margin_x=SpacingToken.AUTO,
children=[
Text(
text="Connectez-vous",
text_size=TextSize.XXXL,
weight=TextWeight.BOLD,
),
Form(
form_id="login_form",
children=[
Column(
gap=GapToken.MD,
children=[
InputText(
name="email",
label="Adresse e-mail",
type=InputType.TEXT,
required=True,
),
InputText(
name="password",
label="Mot de passe",
type=InputType.PASSWORD,
required=True,
),
Button(
label="Se connecter",
variant=ButtonVariant.FILLED,
on_press=FormSubmitAction(
form_id="login_form",
endpoint="/api/auth/login",
rules=[
FieldRule(field="email", required=True),
FieldRule(field="password", required=True, min_length=6),
],
on_success=NavigateAction(route="/dashboard"),
on_error=ToastAction(
message="Identifiants incorrects",
level="error",
),
),
),
],
)
],
),
],
)
],
).render()
Card produit avec panier
from cicaw_sdui.layouts import Column, Row, Stack, Overlay
from cicaw_sdui.atoms import (
ImageView, Text, Badge, CartController
)
from cicaw_sdui.enums import (
AspectRatioToken, RadiusToken, ColorRole,
TextSize, TextWeight, GapToken, SpacingToken,
ElevationToken, OverlayPlacement
)
def product_card(product: dict) -> dict:
return Stack(
style=Style(
border_radius=RadiusToken.LG,
elevation=ElevationToken.LEVEL_1,
overflow=Overflow.HIDDEN,
),
children=[
ImageView(
src=product["image"],
alt=product["name"],
aspect_ratio=AspectRatioToken.SQUARE,
),
# Badge promo en superposition
Overlay(
placement=OverlayPlacement.TOP_LEFT,
children=[
Badge(
label=f"-{product['discount']}%",
color=ColorRole.ERROR,
tone="filled",
)
] if product.get("discount") else [],
),
# Infos produit
Column(
gap=GapToken.XS,
padding=SpacingToken.MD,
children=[
Text(
text=product["name"],
text_size=TextSize.SM,
weight=TextWeight.SEMIBOLD,
max_lines=2,
),
Row(
main_axis_align=MainAxisAlignment.SPACE_BETWEEN,
cross_axis_align=CrossAxisAlignment.CENTER,
children=[
Text(
text=f"{product['price']} FCFA",
text_size=TextSize.MD,
weight=TextWeight.BOLD,
text_color=ColorRole.PRIMARY,
),
CartController(
product_id=product["id"],
add_url="/api/cart/add",
remove_url="/api/cart/remove",
),
],
),
],
),
],
).render()
Référence du rendu JSON
Chaque UIComponent.render() produit un objet de la forme :
{
"type": "<component_type>",
"data": {
"id": "optional-id",
"prop1": "value1",
"...": "...",
"children": [
{
"type": "<child_type>",
"data": { "...": "..." }
}
]
}
}
Les actions sont sérialisées avec leur type directement dans l'objet :
{
"type": "navigate",
"route": "/catalogue",
"replace": false
}
Optimisation : .render() utilise exclude_none=True et exclude_defaults=True. Un composant minimal comme Text(text="Hello") ne produira que les champs explicitement définis, réduisant drastiquement la taille du payload.
Contribuer
- Forkez le dépôt et créez une branche :
git checkout -b feat/mon-composant - Ajoutez votre composant dans le fichier approprié (
atoms.py,layouts.pyouactions.py) - Déclarez le type dans
ComponentType(ouActionType) dansenums.py - Ajoutez les tests correspondants
- Ouvrez une Pull Request
Conventions :
- Les classes Python sont en
PascalCase - Les valeurs d'énumérations sont en
snake_case(pour correspondre aux classes Tailwind/CSS) - Toutes les propriétés doivent être typées et documentées via des docstrings ou des
Field(description=...) - Préférer
Optional[X] = Noneaux valeurs par défaut implicites pour tirer parti deexclude_defaults=True
cicaw-sdui est maintenu par l'équipe Cicaw.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cicaw_sdui-0.1.1.tar.gz.
File metadata
- Download URL: cicaw_sdui-0.1.1.tar.gz
- Upload date:
- Size: 74.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8ba409144f729c2d2cba035c714d6d393f166360b26e8542279db7a31baaa58a
|
|
| MD5 |
282cd4c95bc1edea6b44fddf200f8e8d
|
|
| BLAKE2b-256 |
e8dcfe164fe0bab5097e840676c236babb7b4dd1f623e9c5bd9e1bb47ebc37ca
|
File details
Details for the file cicaw_sdui-0.1.1-py3-none-any.whl.
File metadata
- Download URL: cicaw_sdui-0.1.1-py3-none-any.whl
- Upload date:
- Size: 57.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8e7de5e67ed6815b15bb918bb59d3911c9102705baf91921441baefed26b3838
|
|
| MD5 |
d1109f55bd9deb539acf61012526aac9
|
|
| BLAKE2b-256 |
4da4390b5ed742225f027ed3cb08f909a4f9d5007dc4cac1efe8bcb6539e38ba
|