Skip to main content

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

  1. Qu'est-ce que le Server-Driven UI ?
  2. Pourquoi cicaw-sdui ?
  3. Installation
  4. Architecture du package
  5. Concepts fondamentaux
  6. Composants — Atoms
  7. Composants — Layouts
  8. Actions
  9. Tokens de référence
  10. Exemples complets
  11. Référence du rendu JSON
  12. 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 None sont 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.MDp-md (16px)
Dimensionnement SizingToken SizingToken.FULLw-full
Couleurs ColorRole ColorRole.PRIMARYbg-primary
Bordures RadiusToken RadiusToken.LGrounded-lg
Élévation ElevationToken ElevationToken.LEVEL_2shadow-2
Typographie TextSize, TextWeight TextSize.XLtext-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 (h1h6, 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

  1. Forkez le dépôt et créez une branche : git checkout -b feat/mon-composant
  2. Ajoutez votre composant dans le fichier approprié (atoms.py, layouts.py ou actions.py)
  3. Déclarez le type dans ComponentType (ou ActionType) dans enums.py
  4. Ajoutez les tests correspondants
  5. 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] = None aux valeurs par défaut implicites pour tirer parti de exclude_defaults=True

cicaw-sdui est maintenu par l'équipe Cicaw.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cicaw_sdui-0.1.1.tar.gz (74.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cicaw_sdui-0.1.1-py3-none-any.whl (57.1 kB view details)

Uploaded Python 3

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

Hashes for cicaw_sdui-0.1.1.tar.gz
Algorithm Hash digest
SHA256 8ba409144f729c2d2cba035c714d6d393f166360b26e8542279db7a31baaa58a
MD5 282cd4c95bc1edea6b44fddf200f8e8d
BLAKE2b-256 e8dcfe164fe0bab5097e840676c236babb7b4dd1f623e9c5bd9e1bb47ebc37ca

See more details on using hashes here.

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

Hashes for cicaw_sdui-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8e7de5e67ed6815b15bb918bb59d3911c9102705baf91921441baefed26b3838
MD5 d1109f55bd9deb539acf61012526aac9
BLAKE2b-256 4da4390b5ed742225f027ed3cb08f909a4f9d5007dc4cac1efe8bcb6539e38ba

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page