Compare commits

..

No commits in common. "817e21567bf05f0959e5c365bab6545ee8299321" and "91d99d6ca47858721359c2dc7d052ca9316c5a9c" have entirely different histories.

143 changed files with 2361 additions and 6091 deletions

View file

@ -1,80 +0,0 @@
---
name: beads
description: Use when working in a repository that uses bd or Beads for durable project task tracking, issue dependencies, blocker management, multi-session handoff, or shared work memory. Trigger when the user asks to find ready work, claim or close tasks, create follow-up work, inspect blockers, recover project context, or choose between local planning and persistent project tracking.
---
# Beads
Use Beads as the shared project task system. Local plans, scratch files, and personal memories are useful, but they are not the durable source of truth for project work.
## First Step
Run:
```bash
bd prime
```
If that prints nothing, check whether the repository has an active Beads workspace:
```bash
bd where
```
## Preferred Route
Use the `bd` CLI when shell access is available. It is the most compact and direct Beads interface.
## Core CLI Workflow
1. Find work:
```bash
bd ready
bd list --status=open
bd list --status=in_progress
```
2. Inspect before editing:
```bash
bd show <id>
```
3. Claim work atomically:
```bash
bd update <id> --claim
```
4. Create durable follow-up work when implementation reveals new tasks:
```bash
bd create "Short title" --description="Why this exists and what needs to be done" --type=task --priority=2
```
5. Close completed work:
```bash
bd close <id> --reason="Completed"
```
## What Belongs In Beads
Use Beads for:
- shared project tasks
- blockers and dependencies
- discovered follow-up work
- work that must survive thread reset, compaction, or handoff
- status that another person or agent should be able to resume
Use agent-local planning tools only for the current turn's execution checklist. Do not treat them as shared project state.
## Rules
- Do not create markdown TODO files as the source of truth when Beads is available.
- Do not use `bd edit`; it opens an interactive editor. Use `bd update` flags instead.
- Prefer `--json` when parsing `bd` output programmatically.
- If hooks are installed, `bd prime` may already be injected. Run it manually when context is missing.
- Do not auto-close or mutate tasks unless the work is actually complete.

View file

@ -1,4 +0,0 @@
interface:
display_name: "Beads"
short_description: "Project task tracking with bd"
default_prompt: "Use $beads to inspect ready work and manage durable project tasks."

77
.beads/.gitignore vendored
View file

@ -1,77 +0,0 @@
# Dolt database (managed by Dolt, not git)
dolt/
embeddeddolt/
proxieddb/
# Runtime files
bd.sock
bd.sock.startlock
sync-state.json
last-touched
.exclusive-lock
# Daemon runtime (lock, log, pid)
daemon.*
# Push state (runtime, per-machine)
push-state.json
# Lock files (various runtime locks)
*.lock
# Credential key (encryption key for federation peer auth — never commit)
.beads-credential-key
# Local version tracking (prevents upgrade notification spam after git ops)
.local_version
proxied_server_client_info.json
# Worktree redirect file (contains relative path to main repo's .beads/)
# Must not be committed as paths would be wrong in other clones
redirect
# Sync state (local-only, per-machine)
# These files are machine-specific and should not be shared across clones
.sync.lock
export-state/
export-state.json
last_pull
# Ephemeral store (SQLite - wisps/molecules, intentionally not versioned)
ephemeral.sqlite3
ephemeral.sqlite3-journal
ephemeral.sqlite3-wal
ephemeral.sqlite3-shm
# Dolt server management (auto-started by bd)
dolt-server.pid
dolt-server.log
dolt-server.lock
dolt-server.port
dolt-server.activity
# Debug-mode pprof artifacts (written when dolt.debug: true in config.yaml)
dolt-pprof/
# Corrupt backup directories (created by bd doctor --fix recovery)
*.corrupt.backup/
# Backup data (auto-exported JSONL, local-only)
backup/
# Per-project environment file (Dolt connection config, GH#2520)
.env
# Legacy files (from pre-Dolt versions)
*.db
*.db?*
*.db-journal
*.db-wal
*.db-shm
db.sqlite
bd.db
# NOTE: Do NOT add negation patterns here.
# They would override fork protection in .git/info/exclude.
# Config files (metadata.json, config.yaml) are tracked by git by default
# since no pattern above ignores them.

View file

@ -1,81 +0,0 @@
# Beads - AI-Native Issue Tracking
Welcome to Beads! This repository uses **Beads** for issue tracking - a modern, AI-native tool designed to live directly in your codebase alongside your code.
## What is Beads?
Beads is issue tracking that lives in your repo, making it perfect for AI coding agents and developers who want their issues close to their code. No web UI required - everything works through the CLI and integrates seamlessly with git.
**Learn more:** [github.com/steveyegge/beads](https://github.com/steveyegge/beads)
## Quick Start
### Essential Commands
```bash
# Create new issues
bd create "Add user authentication"
# View all issues
bd list
# View issue details
bd show <issue-id>
# Update issue status
bd update <issue-id> --claim
bd update <issue-id> --status done
# Sync with Dolt remote
bd dolt push
```
### Working with Issues
Issues in Beads are:
- **Git-native**: Stored in Dolt database with version control and branching
- **AI-friendly**: CLI-first design works perfectly with AI coding agents
- **Branch-aware**: Issues can follow your branch workflow
- **Sync-ready**: Uses Dolt remotes for backup and team sharing
## Why Beads?
✨ **AI-Native Design**
- Built specifically for AI-assisted development workflows
- CLI-first interface works seamlessly with AI coding agents
- No context switching to web UIs
🚀 **Developer Focused**
- Issues live in your repo, right next to your code
- Works offline, syncs when you push
- Fast, lightweight, and stays out of your way
🔧 **Git Integration**
- Dolt-native sync via bd dolt push / bd dolt pull
- Branch-aware issue tracking
- Dolt-native three-way merge resolution
## Get Started with Beads
Try Beads in your own projects:
```bash
# Install Beads
curl -sSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash
# Initialize in your repo
bd init
# Create your first issue
bd create "Try out Beads"
```
## Learn More
- **Documentation**: [github.com/steveyegge/beads/docs](https://github.com/steveyegge/beads/tree/main/docs)
- **Quick Start Guide**: Run `bd quickstart`
- **Examples**: [github.com/steveyegge/beads/examples](https://github.com/steveyegge/beads/tree/main/examples)
---
*Beads: Issue tracking that moves at the speed of thought* ⚡

View file

@ -1,68 +0,0 @@
# Beads Configuration File
# This file configures default behavior for all bd commands in this repository
# All settings can also be set via environment variables (BD_* prefix)
# or overridden with command-line flags
# Issue prefix for this repository (used by bd init)
# If not set, bd init will auto-detect from directory name
# Example: issue-prefix: "myproject" creates issues like "myproject-1", "myproject-2", etc.
# issue-prefix: ""
# Use no-db mode: JSONL-only, no Dolt database
# When true, .beads/issues.jsonl is the only local store
# no-db: false
# Enable JSON output by default
# json: false
# Feedback title formatting for mutating commands (create/update/close/dep/edit)
# 0 = hide titles, N > 0 = truncate to N characters
# output:
# title-length: 255
# Default actor for audit trails (overridden by BEADS_ACTOR or --actor)
# actor: ""
# Export events (audit trail) to .beads/events.jsonl on each flush/sync
# When enabled, new events are appended incrementally using a high-water mark.
# Use 'bd export --events' to trigger manually regardless of this setting.
# events-export: false
# Multi-repo configuration (experimental - bd-307)
# Allows hydrating from multiple repositories and routing writes to the correct database
# repos:
# primary: "." # Primary repo (where this database lives)
# additional: # Additional repos to hydrate from (read-only)
# - ~/beads-planning # Personal planning repo
# - ~/work-planning # Work planning repo
# Dolt-native backup (periodic backup for off-machine recovery)
# This is full database backup only. Cross-machine sync uses Dolt remotes.
# backup:
# enabled: false # Disable auto-backup entirely
# interval: 15m # Minimum time between auto-backups
# git-push: false # Disable git push (backup locally only)
# git-repo: "" # Separate git repo for backups (default: project repo)
# Optional JSONL auto-export for viewers, interchange, and issue-level migration.
# Disabled by default; enable only when an integration needs fresh .beads/issues.jsonl.
# Use relative paths under .beads/ for JSONL import/export filenames.
# export:
# auto: false
# path: issues.jsonl
# interval: 60s
# git-add: false
# import:
# path: issues.jsonl
# Integration settings (access with 'bd config get/set')
# Non-secret keys (stored in the database):
# - jira.url, jira.project
# - linear.team_id
# - github.org, github.repo
#
# Secret keys (stored in this file but prefer env vars to avoid git exposure):
# - linear.api_key → use LINEAR_API_KEY env var instead
# - github.token → use GITHUB_TOKEN env var instead
sync.remote: "git+ssh://git@git.shinuwa.fr:2222/shinuwa/sokko-g.git"

View file

@ -1,33 +0,0 @@
#!/usr/bin/env sh
# --- BEGIN BEADS INTEGRATION v1.2.2 ---
# This section is managed by beads. Do not remove these markers.
if command -v bd >/dev/null 2>&1; then
export BD_GIT_HOOK=1
_bd_timeout=${BEADS_HOOK_TIMEOUT:-300}
_bd_used_perl=0
if command -v timeout >/dev/null 2>&1; then
timeout "$_bd_timeout" bd hooks run post-checkout "$@"
_bd_exit=$?
elif command -v gtimeout >/dev/null 2>&1; then
gtimeout "$_bd_timeout" bd hooks run post-checkout "$@"
_bd_exit=$?
elif command -v perl >/dev/null 2>&1; then
_bd_used_perl=1
perl -e 'alarm shift; exec @ARGV' "$_bd_timeout" bd hooks run post-checkout "$@"
_bd_exit=$?
else
echo >&2 "beads: hook 'post-checkout' running without timeout; install coreutils or perl to enable BEADS_HOOK_TIMEOUT"
bd hooks run post-checkout "$@"
_bd_exit=$?
fi
if [ $_bd_exit -eq 124 ] || { [ $_bd_used_perl -eq 1 ] && [ $_bd_exit -eq 142 ]; }; then
echo >&2 "beads: hook 'post-checkout' timed out after ${_bd_timeout}s — continuing without beads"
_bd_exit=0
fi
if [ $_bd_exit -eq 3 ]; then
echo >&2 "beads: database not initialized — skipping hook 'post-checkout'"
_bd_exit=0
fi
if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi
fi
# --- END BEADS INTEGRATION v1.2.2 ---

View file

@ -1,33 +0,0 @@
#!/usr/bin/env sh
# --- BEGIN BEADS INTEGRATION v1.2.2 ---
# This section is managed by beads. Do not remove these markers.
if command -v bd >/dev/null 2>&1; then
export BD_GIT_HOOK=1
_bd_timeout=${BEADS_HOOK_TIMEOUT:-300}
_bd_used_perl=0
if command -v timeout >/dev/null 2>&1; then
timeout "$_bd_timeout" bd hooks run post-merge "$@"
_bd_exit=$?
elif command -v gtimeout >/dev/null 2>&1; then
gtimeout "$_bd_timeout" bd hooks run post-merge "$@"
_bd_exit=$?
elif command -v perl >/dev/null 2>&1; then
_bd_used_perl=1
perl -e 'alarm shift; exec @ARGV' "$_bd_timeout" bd hooks run post-merge "$@"
_bd_exit=$?
else
echo >&2 "beads: hook 'post-merge' running without timeout; install coreutils or perl to enable BEADS_HOOK_TIMEOUT"
bd hooks run post-merge "$@"
_bd_exit=$?
fi
if [ $_bd_exit -eq 124 ] || { [ $_bd_used_perl -eq 1 ] && [ $_bd_exit -eq 142 ]; }; then
echo >&2 "beads: hook 'post-merge' timed out after ${_bd_timeout}s — continuing without beads"
_bd_exit=0
fi
if [ $_bd_exit -eq 3 ]; then
echo >&2 "beads: database not initialized — skipping hook 'post-merge'"
_bd_exit=0
fi
if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi
fi
# --- END BEADS INTEGRATION v1.2.2 ---

View file

@ -1,33 +0,0 @@
#!/usr/bin/env sh
# --- BEGIN BEADS INTEGRATION v1.2.2 ---
# This section is managed by beads. Do not remove these markers.
if command -v bd >/dev/null 2>&1; then
export BD_GIT_HOOK=1
_bd_timeout=${BEADS_HOOK_TIMEOUT:-300}
_bd_used_perl=0
if command -v timeout >/dev/null 2>&1; then
timeout "$_bd_timeout" bd hooks run pre-commit "$@"
_bd_exit=$?
elif command -v gtimeout >/dev/null 2>&1; then
gtimeout "$_bd_timeout" bd hooks run pre-commit "$@"
_bd_exit=$?
elif command -v perl >/dev/null 2>&1; then
_bd_used_perl=1
perl -e 'alarm shift; exec @ARGV' "$_bd_timeout" bd hooks run pre-commit "$@"
_bd_exit=$?
else
echo >&2 "beads: hook 'pre-commit' running without timeout; install coreutils or perl to enable BEADS_HOOK_TIMEOUT"
bd hooks run pre-commit "$@"
_bd_exit=$?
fi
if [ $_bd_exit -eq 124 ] || { [ $_bd_used_perl -eq 1 ] && [ $_bd_exit -eq 142 ]; }; then
echo >&2 "beads: hook 'pre-commit' timed out after ${_bd_timeout}s — continuing without beads"
_bd_exit=0
fi
if [ $_bd_exit -eq 3 ]; then
echo >&2 "beads: database not initialized — skipping hook 'pre-commit'"
_bd_exit=0
fi
if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi
fi
# --- END BEADS INTEGRATION v1.2.2 ---

View file

@ -1,33 +0,0 @@
#!/usr/bin/env sh
# --- BEGIN BEADS INTEGRATION v1.2.2 ---
# This section is managed by beads. Do not remove these markers.
if command -v bd >/dev/null 2>&1; then
export BD_GIT_HOOK=1
_bd_timeout=${BEADS_HOOK_TIMEOUT:-300}
_bd_used_perl=0
if command -v timeout >/dev/null 2>&1; then
timeout "$_bd_timeout" bd hooks run pre-push "$@"
_bd_exit=$?
elif command -v gtimeout >/dev/null 2>&1; then
gtimeout "$_bd_timeout" bd hooks run pre-push "$@"
_bd_exit=$?
elif command -v perl >/dev/null 2>&1; then
_bd_used_perl=1
perl -e 'alarm shift; exec @ARGV' "$_bd_timeout" bd hooks run pre-push "$@"
_bd_exit=$?
else
echo >&2 "beads: hook 'pre-push' running without timeout; install coreutils or perl to enable BEADS_HOOK_TIMEOUT"
bd hooks run pre-push "$@"
_bd_exit=$?
fi
if [ $_bd_exit -eq 124 ] || { [ $_bd_used_perl -eq 1 ] && [ $_bd_exit -eq 142 ]; }; then
echo >&2 "beads: hook 'pre-push' timed out after ${_bd_timeout}s — continuing without beads"
_bd_exit=0
fi
if [ $_bd_exit -eq 3 ]; then
echo >&2 "beads: database not initialized — skipping hook 'pre-push'"
_bd_exit=0
fi
if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi
fi
# --- END BEADS INTEGRATION v1.2.2 ---

View file

@ -1,33 +0,0 @@
#!/usr/bin/env sh
# --- BEGIN BEADS INTEGRATION v1.2.2 ---
# This section is managed by beads. Do not remove these markers.
if command -v bd >/dev/null 2>&1; then
export BD_GIT_HOOK=1
_bd_timeout=${BEADS_HOOK_TIMEOUT:-300}
_bd_used_perl=0
if command -v timeout >/dev/null 2>&1; then
timeout "$_bd_timeout" bd hooks run prepare-commit-msg "$@"
_bd_exit=$?
elif command -v gtimeout >/dev/null 2>&1; then
gtimeout "$_bd_timeout" bd hooks run prepare-commit-msg "$@"
_bd_exit=$?
elif command -v perl >/dev/null 2>&1; then
_bd_used_perl=1
perl -e 'alarm shift; exec @ARGV' "$_bd_timeout" bd hooks run prepare-commit-msg "$@"
_bd_exit=$?
else
echo >&2 "beads: hook 'prepare-commit-msg' running without timeout; install coreutils or perl to enable BEADS_HOOK_TIMEOUT"
bd hooks run prepare-commit-msg "$@"
_bd_exit=$?
fi
if [ $_bd_exit -eq 124 ] || { [ $_bd_used_perl -eq 1 ] && [ $_bd_exit -eq 142 ]; }; then
echo >&2 "beads: hook 'prepare-commit-msg' timed out after ${_bd_timeout}s — continuing without beads"
_bd_exit=0
fi
if [ $_bd_exit -eq 3 ]; then
echo >&2 "beads: database not initialized — skipping hook 'prepare-commit-msg'"
_bd_exit=0
fi
if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi
fi
# --- END BEADS INTEGRATION v1.2.2 ---

View file

@ -1,22 +0,0 @@
{"id":"int-8d5f8f5714677d91360a20177b1f5a9e","kind":"field_change","created_at":"2026-08-27T12:58:35.614943857Z","actor":"Shinuwa","issue_id":"sokko-g-lki","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed"}}
{"id":"int-206ccb97f9d09712e2b4136d115a0e43","kind":"field_change","created_at":"2026-08-27T13:09:45.211867088Z","actor":"Shinuwa","issue_id":"sokko-g-e7s","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed remaining toolbox control migration to NDS mixins and updated design-system catalogue examples."}}
{"id":"int-5131e4130f57a4e8a55f176a46240fad","kind":"field_change","created_at":"2026-08-27T16:19:44.570963534Z","actor":"Shinuwa","issue_id":"sokko-g-npt","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed NDS button variant cleanup and catalogue simplification."}}
{"id":"int-3db2dfcbabd4cbb63eaeedb1bea8ab47","kind":"field_change","created_at":"2026-08-27T16:43:20.612129607Z","actor":"Shinuwa","issue_id":"sokko-g-0o4","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed NDS data attribute API migration and validation."}}
{"id":"int-578a8a6aff782082d539f0e14bb0aa79","kind":"field_change","created_at":"2026-08-27T16:58:07.410812508Z","actor":"Shinuwa","issue_id":"sokko-g-alp","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Expanded /design-system into exhaustive NDS family attribute matrices."}}
{"id":"int-cc32f7330c12cf2d37b0d22650ccfb5e","kind":"field_change","created_at":"2026-08-27T17:13:59.871782552Z","actor":"Shinuwa","issue_id":"sokko-g-pp5","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Reworked /design-system into component cards with configurable attribute matrices."}}
{"id":"int-17da0005e87891c66734a766d8456459","kind":"field_change","created_at":"2026-08-27T17:23:07.045839669Z","actor":"Shinuwa","issue_id":"sokko-g-73i","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed"}}
{"id":"int-d0531629593c076b63281346c729ac3e","kind":"field_change","created_at":"2026-08-27T17:28:11.740302589Z","actor":"Shinuwa","issue_id":"sokko-g-wnh","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed"}}
{"id":"int-fb41489d0517f51fcda643c4230461fe","kind":"field_change","created_at":"2026-08-27T17:35:40.407737936Z","actor":"Shinuwa","issue_id":"sokko-g-s0a","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed"}}
{"id":"int-b7bafbc214af080356e07d2d97f1ffba","kind":"field_change","created_at":"2026-08-27T21:32:26.611249333Z","actor":"Shinuwa","issue_id":"sokko-g-2u9","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed"}}
{"id":"int-ea50d03c7c84b32ab2fce86f15bbe05f","kind":"field_change","created_at":"2026-08-27T21:43:00.775108889Z","actor":"Shinuwa","issue_id":"sokko-g-bh4","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Completed"}}
{"id":"int-4f7a574335bb2d616e9caae09e22e55b","kind":"field_change","created_at":"2026-08-27T21:45:18.547229481Z","actor":"Shinuwa","issue_id":"sokko-g-7jm","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed"}}
{"id":"int-ae5fb0bbe41a7958929560a1bbf65052","kind":"field_change","created_at":"2026-08-27T21:48:45.268794089Z","actor":"Shinuwa","issue_id":"sokko-g-z2t","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed"}}
{"id":"int-8e3b38de72d948a6c241b0d8b2f8077b","kind":"field_change","created_at":"2026-08-27T21:54:30.02159376Z","actor":"Shinuwa","issue_id":"sokko-g-coh","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Completed"}}
{"id":"int-0d86b94b9a04df5b68e2362e0824a37b","kind":"field_change","created_at":"2026-08-27T21:57:24.401323676Z","actor":"Shinuwa","issue_id":"sokko-g-hif","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed"}}
{"id":"int-3fcb6bc2b605db6210c0da18da07e902","kind":"field_change","created_at":"2026-08-27T22:01:35.180268661Z","actor":"Shinuwa","issue_id":"sokko-g-6fs","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Fixed NDS textarea min heights by size"}}
{"id":"int-611cb19bdd31f0e79cc8c2f2bb7a3dd7","kind":"field_change","created_at":"2026-08-28T07:13:54.812999765Z","actor":"Shinuwa","issue_id":"sokko-g-tvo","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Fixed NDS field hover cascade and replaced public date trigger with select trigger"}}
{"id":"int-0e1196655dabd3096c10beef1ba8aff2","kind":"field_change","created_at":"2026-08-28T07:19:28.131918475Z","actor":"Shinuwa","issue_id":"sokko-g-z4x","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Fixed select trigger hover chevron and focus-visible outline"}}
{"id":"int-9c27343c043121862608b8ed9013c306","kind":"field_change","created_at":"2026-08-28T07:25:53.259127006Z","actor":"Shinuwa","issue_id":"sokko-g-eln","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Fixed select trigger md hover/focus and disabled checkbox styling"}}
{"id":"int-da5413e82c18d19a8432288bec703dd7","kind":"field_change","created_at":"2026-08-28T07:50:07.033524644Z","actor":"Shinuwa","issue_id":"sokko-g-h7z","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed global NDS control migration and validation"}}
{"id":"int-42f39cb889dfab3bc998d4ce566d2339","kind":"field_change","created_at":"2026-08-28T08:09:50.738078383Z","actor":"Shinuwa","issue_id":"sokko-g-9p1","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Fixed filter radio group, sidebar storage buttons, memo controls, and textarea ghost variant"}}
{"id":"int-03ed1c823da90551f51a5a0d5691e440","kind":"field_change","created_at":"2026-08-28T12:35:29.257332022Z","actor":"Shinuwa","issue_id":"sokko-g-39h","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Applied NDS event-surface to calendar week and month events, documented data-color/data-size, and validated checks."}}

View file

@ -1,7 +0,0 @@
{
"database": "dolt",
"backend": "dolt",
"dolt_mode": "embedded",
"dolt_database": "sokko_g",
"project_id": "383fe8d1-bd71-460f-9ff5-e71f14df4827"
}

View file

@ -1,15 +0,0 @@
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"command": "bd prime --hook-json",
"type": "command"
}
],
"matcher": ""
}
]
}
}

View file

@ -1,2 +0,0 @@
[features]
hooks = true

View file

@ -1,51 +0,0 @@
{
"hooks": {
"PostCompact": [
{
"hooks": [
{
"command": "bd codex-hook PostCompact",
"statusMessage": "Scheduling Beads context refresh",
"type": "command"
}
],
"matcher": "manual|auto"
}
],
"PreCompact": [
{
"hooks": [
{
"command": "bd codex-hook PreCompact",
"statusMessage": "Checking Beads context",
"type": "command"
}
],
"matcher": "manual|auto"
}
],
"SessionStart": [
{
"hooks": [
{
"command": "bd codex-hook SessionStart",
"statusMessage": "Loading Beads context",
"type": "command"
}
],
"matcher": "startup|resume|clear"
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"command": "bd codex-hook UserPromptSubmit",
"statusMessage": "Refreshing Beads context",
"type": "command"
}
]
}
]
}
}

7
.gitignore vendored
View file

@ -4,10 +4,3 @@ playwright-report/
test-results/ test-results/
.env .env
.DS_Store .DS_Store
.local/
# Beads / Dolt files (added by bd init)
.dolt/
*.db
.beads-credential-key
.beads/proxieddb/

View file

@ -122,83 +122,3 @@ npm run check
Après lecture du diff pré-push, proposer un exemple de message de commit en anglais, concis et représentatif des changements réellement présents. Après lecture du diff pré-push, proposer un exemple de message de commit en anglais, concis et représentatif des changements réellement présents.
Ne pas faire de refactor large sans bénéfice clair. Garder les corrections pré-push ciblées, vérifiables et faciles à relire. Ne pas faire de refactor large sans bénéfice clair. Garder les corrections pré-push ciblées, vérifiables et faciles à relire.
<!-- BEGIN BEADS INTEGRATION v:1 profile:minimal hash:970c3bf2 -->
## Beads Issue Tracker
This project uses **bd (beads)** for issue tracking. Run `bd prime` to see full workflow context and commands.
### Quick Reference
```bash
bd ready # Find available work
bd show <id> # View issue details
bd update <id> --claim # Claim work
bd close <id> # Complete work
```
### Rules
- Use `bd` for ALL task tracking — do NOT use TodoWrite, TaskCreate, or markdown TODO lists
- Run `bd prime` for detailed command reference and session close protocol
- Use `bd remember` for persistent knowledge — do NOT use MEMORY.md files
**Architecture in one line:** issues live in a local Dolt DB; sync uses `refs/dolt/data` on your git remote; `.beads/issues.jsonl` is a passive export. See https://github.com/gastownhall/beads/blob/main/docs/SYNC_CONCEPTS.md for details and anti-patterns.
## Agent Context Profiles
The managed Beads block is task-tracking guidance, not permission to override repository, user, or orchestrator instructions.
- **Conservative (default)**: Use `bd` for task tracking. Do not run git commits, git pushes, or Dolt remote sync unless explicitly asked. At handoff, report changed files, validation, and suggested next commands.
- **Minimal**: Keep tool instruction files as pointers to `bd prime`; use the same conservative git policy unless active instructions say otherwise.
- **Team-maintainer**: Only when the repository explicitly opts in, agents may close beads, run quality gates, commit, and push as part of session close. A current "do not commit" or "do not push" instruction still wins.
## Session Completion
This protocol applies when ending a Beads implementation workflow. It is subordinate to explicit user, repository, and orchestrator instructions.
1. **File issues for remaining work** - Create beads for anything that needs follow-up
2. **Run quality gates** (if code changed) - Tests, linters, builds
3. **Update issue status** - Close finished work, update in-progress items
4. **Handle git/sync by active profile**:
```bash
# Conservative/minimal/default: report status and proposed commands; wait for approval.
git status
# Team-maintainer opt-in only, unless current instructions forbid it:
git pull --rebase
bd dolt push
git push
git status
```
5. **Hand off** - Summarize changes, validation, issue status, and any blocked sync/commit/push step
**Critical rules:**
- Explicit user or orchestrator instructions override this Beads block.
- Do not commit or push without clear authority from the active profile or the current user request.
- If a required sync or push is blocked, stop and report the exact command and error.
<!-- END BEADS INTEGRATION -->
<!-- BEGIN BEADS CODEX SETUP: generated by bd setup codex -->
## Beads Issue Tracker
Use Beads (`bd`) for durable task tracking in repositories that include it. Use the `beads` skill at `.agents/skills/beads/SKILL.md` (project install) or `~/.agents/skills/beads/SKILL.md` (global install) for Beads workflow guidance, then use the `bd` CLI for issue operations.
### Quick Reference
```bash
bd ready # Find available work
bd show <id> # View issue details
bd update <id> --claim # Claim work
bd close <id> # Complete work
bd prime # Refresh Beads context
```
### Rules
- Use `bd` for all task tracking; do not create markdown TODO lists.
- Run `bd prime` when Beads context is missing or stale. Codex 0.129.0+ can load Beads context automatically through native hooks; use `/hooks` to inspect or toggle them.
- Keep persistent project memory in Beads via `bd remember`; do not create ad hoc memory files.
**Architecture in one line:** issues live in a local Dolt DB; sync uses `refs/dolt/data` on your git remote; `.beads/issues.jsonl` is a passive export. See https://github.com/gastownhall/beads/blob/main/docs/SYNC_CONCEPTS.md for details and anti-patterns.
<!-- END BEADS CODEX SETUP -->

View file

@ -1,77 +0,0 @@
# Project Instructions for AI Agents
This file provides instructions and context for AI coding agents working on this project.
<!-- BEGIN BEADS INTEGRATION v:1 profile:minimal hash:6cd5cc61 -->
## Beads Issue Tracker
This project uses **bd (beads)** for issue tracking. Run `bd prime` to see full workflow context and commands.
### Quick Reference
```bash
bd ready # Find available work
bd show <id> # View issue details
bd update <id> --claim # Claim work
bd close <id> # Complete work
```
### Rules
- Use `bd` for ALL task tracking — do NOT use TodoWrite, TaskCreate, or markdown TODO lists
- Run `bd prime` for detailed command reference and session close protocol
- Use `bd remember` for persistent knowledge — do NOT use MEMORY.md files
**Architecture in one line:** issues live in a local Dolt DB; sync uses `refs/dolt/data` on your git remote; `.beads/issues.jsonl` is a passive export. See https://github.com/gastownhall/beads/blob/main/docs/SYNC_CONCEPTS.md for details and anti-patterns.
## Agent Context Profiles
The managed Beads block is task-tracking guidance, not permission to override repository, user, or orchestrator instructions.
- **Conservative (default)**: Use `bd` for task tracking. Do not run git commits, git pushes, or Dolt remote sync unless explicitly asked. At handoff, report changed files, validation, and suggested next commands.
- **Minimal**: Keep tool instruction files as pointers to `bd prime`; use the same conservative git policy unless active instructions say otherwise.
- **Team-maintainer**: Only when the repository explicitly opts in, agents may close beads, run quality gates, commit, and push as part of session close. A current "do not commit" or "do not push" instruction still wins.
## Session Completion
This protocol applies when ending a Beads implementation workflow. It is subordinate to explicit user, repository, and orchestrator instructions.
1. **File issues for remaining work** - Create beads for anything that needs follow-up
2. **Run quality gates** (if code changed) - Tests, linters, builds
3. **Update issue status** - Close finished work, update in-progress items
4. **Handle git/sync by active profile**:
```bash
# Conservative/minimal/default: report status and proposed commands; wait for approval.
git status
# Team-maintainer opt-in only, unless current instructions forbid it:
git pull --rebase
git push
git status
```
5. **Hand off** - Summarize changes, validation, issue status, and any blocked sync/commit/push step
**Critical rules:**
- Explicit user or orchestrator instructions override this Beads block.
- Do not commit or push without clear authority from the active profile or the current user request.
- If a required sync or push is blocked, stop and report the exact command and error.
<!-- END BEADS INTEGRATION -->
## Build & Test
_Add your build and test commands here_
```bash
# Example:
# npm install
# npm test
```
## Architecture Overview
_Add a brief overview of your project architecture_
## Conventions & Patterns
_Add your project-specific conventions here_

View file

@ -1,6 +1,6 @@
# Nebula Design System - Sokko G # Design System - Sokko G
Ce document est la reference visuelle du projet Sokko G. Toute nouvelle interface doit rester coherente avec le theme **Nebula Command** actuel et les controles du **Nebula Design System**. Ce document est la reference visuelle du projet Sokko G. Toute nouvelle interface doit rester coherente avec le theme **Nebula Command** actuel.
## 1. Vision ## 1. Vision
@ -63,36 +63,7 @@ Animations :
- `--duration-normal: 180ms` - `--duration-normal: 180ms`
- `--ease-standard: cubic-bezier(0.2, 0.8, 0.2, 1)` - `--ease-standard: cubic-bezier(0.2, 0.8, 0.2, 1)`
## 3. API Des Contrôles NDS ## 3. Fond Nebula
Le Nebula Design System expose ses contrôles publics avec une classe de famille et des attributs `data-*`.
Règles :
- la classe décrit uniquement la famille : `nds-button`, `nds-icon-button`, `nds-input`, `nds-select`, `nds-select-trigger`, `nds-time`, `nds-textarea`, `nds-inline-input`, `nds-switch`, `nds-radio-button-group`, `nds-checkbox`, `nds-quantity-stepper`, `nds-list-row`, `nds-large-icon-picker-trigger`, `nds-event-surface`, `nds-scrollbox` ;
- les variations passent par attributs : `data-size`, `data-variant`, `data-edge`, `data-border`, `data-axis` ;
- la page `/design-system` affiche aussi `data-state` comme axe de comparaison visuelle des états ;
- `data-size` accepte seulement les tailles prévues par la famille (`xs`, `sm`, `md`, `lg`, `xxs`, `micro` selon le contrôle) ; les checkboxes utilisent `md` 20 px, `sm` 18 px et `xs` 14 px ;
- `data-variant` couvre les intentions visuelles partagées : `primary`, `secondary`, `ghost`, `dashed`, `danger` et les variantes spécialisées documentées dans `/design-system`, dont `panel-toggle` pour les boutons icones accolés à un panneau et `icon-picker` pour les triggers de sélection compacts à icône ;
- `data-edge="left|middle|right|top|center|bottom|top-left|top-right|bottom-right|bottom-left"` décrit la position d'un bouton dans un groupe ou contre un bord, et ajuste uniquement les coins ;
- `data-border="joined"` masque les bordures de jonction déduites depuis `data-edge` ;
- `data-border="none"` masque toutes les bordures sans changer la taille, le fond, le hover ou l'état actif ;
- `data-axis="horizontal|vertical"` reste réservé aux composants dont l'axe est métier, comme `nds-scrollbox` ;
- les états fonctionnels restent portés par le HTML et l'application : `disabled`, `aria-pressed="true"`, `.active`, `.is-active`.
Exemples :
```html
<button class="nds-button" data-size="xs" data-variant="primary" data-edge="left" data-border="joined">Ajouter</button>
<button class="nds-icon-button" data-size="sm" data-variant="danger" aria-label="Supprimer"></button>
<button class="nds-icon-button" data-variant="panel-toggle" data-edge="right" data-border="joined" aria-pressed="true" aria-label="Pages"></button>
<select class="nds-select" data-size="sm"></select>
<button class="nds-select-trigger" data-size="sm" aria-pressed="false"></button>
```
Les anciennes classes de variation `nds-*--*` ne doivent plus etre ajoutees. Les anciennes classes globales `.button`, `.primary`, `.danger`, `.ghost` et `.import-button` ne sont plus des APIs visuelles publiques : utiliser `nds-button` / `nds-icon-button` avec `data-variant`. Dans les outils, les classes métier peuvent rester pour le layout, la grille, les largeurs, le positionnement et les états fonctionnels. Le style visuel d'un contrôle générique doit venir de `website/src/styles/design-system/_controls.scss` ou de l'API publique `website/src/styles/design-system/_attributes.scss`.
## 4. Fond Nebula
Le fond global est defini dans `website/src/styles/_nebula.scss`. Le fond global est defini dans `website/src/styles/_nebula.scss`.
@ -105,7 +76,7 @@ Regles :
Ne pas ajouter d'orbes decoratifs supplementaires. Si une section a besoin de profondeur, utiliser les fonds semi-transparents et les bordures existantes. Ne pas ajouter d'orbes decoratifs supplementaires. Si une section a besoin de profondeur, utiliser les fonds semi-transparents et les bordures existantes.
## 5. Typographie ## 4. Typographie
Police principale : Police principale :
@ -122,7 +93,7 @@ Regles :
- paragraphs limites visuellement, pas de longs blocs sans respiration ; - paragraphs limites visuellement, pas de longs blocs sans respiration ;
- les `<i>` doivent etre en italique, pas utilises comme gris decoratif. - les `<i>` doivent etre en italique, pas utilises comme gris decoratif.
## 6. Heroes ## 5. Heroes
Les heroes de la page d'accueil et des pages jeux utilisent le meme style via `.hero` et `.game-hero`. Les heroes de la page d'accueil et des pages jeux utilisent le meme style via `.hero` et `.game-hero`.
@ -137,7 +108,7 @@ Caracteristiques :
Les pages jeux doivent heriter du hero commun. Si un jeu a une image, elle doit etre utilisee dans les cards ou les covers, pas comme remplacement complet du hero commun sauf decision explicite. Les pages jeux doivent heriter du hero commun. Si un jeu a une image, elle doit etre utilisee dans les cards ou les covers, pas comme remplacement complet du hero commun sauf decision explicite.
## 7. Cards ## 6. Cards
Les cards representent les elements repetes : toolboxes, jeux, categories, monstres, faune. Les cards representent les elements repetes : toolboxes, jeux, categories, monstres, faune.
@ -153,7 +124,7 @@ La bordure haute/basse interne d'une cover doit rester doree quand elle separe u
Eviter les cards dans des cards. Les sections de page doivent rester des layouts ou panneaux, pas des empilements de cartes decoratives. Eviter les cards dans des cards. Les sections de page doivent rester des layouts ou panneaux, pas des empilements de cartes decoratives.
## 8. Modules Toolbox ## 7. Modules Toolbox
Les modules sont des panneaux de travail, pas des cards classiques. Les modules sont des panneaux de travail, pas des cards classiques.
@ -418,7 +389,7 @@ Regles :
### Selects Des Outils ### Selects Des Outils
Tous les `<select>` rendus dans le contenu d'un outil doivent utiliser le style factorise par `website/src/styles/design-system/_controls.scss`. Tous les `<select>` rendus dans le contenu d'un outil doivent utiliser le style factorise de `.module-content select`, defini dans `website/src/styles/toolboxes/_shared-controls.scss`.
Regles : Regles :
@ -600,180 +571,37 @@ Notifications internes :
- duree d'affichage cible : 2 minutes, fermeture manuelle possible ; - duree d'affichage cible : 2 minutes, fermeture manuelle possible ;
- style plus visible que les confirmations simples : bordure conique animee, pulse leger, arrivee depuis le cote configure. - style plus visible que les confirmations simples : bordure conique animee, pulse leger, arrivee depuis le cote configure.
## 17. Controles Interactifs ## 17. Scrollbars
Les champs, selects, boutons icones, switchs et choix compacts utilisent une base commune issue de `website/src/styles/design-system/_controls.scss`. Les fichiers d'outils peuvent choisir une taille ou poser une contrainte de layout, mais ne doivent pas redefinir localement les fonds, bordures, hovers ou etats `primary`/`danger`. Les scrollbars reprennent l'ancien rendu :
Inputs et textareas : ```css
::-webkit-scrollbar {
width: 6px;
height: 6px;
background-color: transparent;
}
- utiliser `field-control` pour les champs standards ; ::-webkit-scrollbar-track {
- la base globale cible uniquement les types texte explicites : `text`, `search`, `email`, `url`, `password` et `textarea` ; background-color: transparent;
- ne pas utiliser de selecteur global negatif comme `input:not([type="checkbox"])`, afin de ne pas capter les champs `number`, `time`, `file` ou les cellules specialisees ; box-shadow: none;
- tout nouvel `<input>` texte doit declarer son `type` explicitement ; }
- utiliser `time-control` pour les champs `input[type="time"]`, avec variante `sm` ou `xs` selon le contexte ;
- taille normale : `44px` minimum ;
- taille compacte `sm` : `36px` minimum ;
- taille dense `xs` : `32px` minimum ;
- utiliser `field-textarea` pour les textareas redimensionnables avec scrollbar stable : `md` `108px`, `sm` `88px`, `xs` `72px` minimum ;
- utiliser `data-variant="ghost"` sur `nds-textarea` pour les zones d'edition intégrées à une surface métier, comme les mémos ;
- placeholders via `field-placeholder` ;
- hover via `field-hover`, focus via `field-focus` ;
- disabled via `field-disabled` ;
- les variantes complexes comme les cellules de table et le champ timer `hh:mm:ss` utilisent des mixins dedies du meme fichier.
Edition inline : ::-webkit-scrollbar-thumb {
background-image: -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0.5, rgb(79, 26, 114)),
color-stop(1, rgb(44, 1, 135))
);
border-radius: 10px;
}
```
- utiliser `inline-field` pour les inputs transparents au repos ; Ne pas ajouter de rail noir visible ni de double bordure pres des panneaux.
- hover/focus via `inline-field-hover` ;
- utiliser `inline-textarea` pour les zones texte transparentes intégrées à une carte ;
- les outils peuvent changer la couleur texte selon le contexte, par exemple titre de section ou état terminé.
Selects : ## 18. Images Et Assets
- taille normale : `44px` minimum ;
- taille compacte toolbox : `36px` minimum ;
- taille dense `xs` : `32px` minimum ;
- fleche custom unique, jamais de chevrons natifs ou empiles ;
- fond sombre Nebula force via `background-color`, hover/focus violet avec chevron dore ;
- focus visible global dore conserve.
Select triggers :
- utiliser `select-trigger` pour les boutons qui ouvrent une liste, un picker ou un calendrier avec le langage visuel d'un select ;
- tailles disponibles : `md` `44px`, `sm` `36px`, `xs` `32px` ;
- le chevron est le meme asset CSS que `select-control`, pas une icone locale ;
- hover via `select-trigger-hover`, focus via `field-focus` et focus visible dore ;
- `date-picker-trigger` reste un alias de compatibilite pour les outils deja migrés.
Menus et palettes :
- utiliser `floating-control-menu` pour les menus flottants compacts ;
- utiliser `compact-menu-button` pour les options d'icones ou d'actions compactes ;
- utiliser `color-swatch`, `color-swatch-rainbow` et `color-swatch-active` pour les palettes couleur ;
- utiliser `large-icon-picker-trigger` pour les gros triggers de choix d'icone de toolbox ;
- les outils peuvent garder uniquement la position du menu, le nombre de colonnes et la variable de couleur métier.
Champs horaires :
- les champs `input[type="time"]` restent natifs ;
- utiliser `time-control` pour aligner le champ avec le theme Nebula ;
- la popup native du navigateur/OS n'est pas consideree comme stylable de facon fiable en CSS.
Boutons texte :
- utiliser `text-button` depuis `website/src/styles/design-system/_controls.scss` ;
- taille normale : hauteur minimale `42px` ;
- taille compacte `sm` : hauteur minimale `34px` ;
- taille dense `xs` : hauteur minimale `30px` ;
- le bouton standard sert de variant secondaire par defaut ;
- variants disponibles : `primary`, `secondary`, `ghost`, `dashed`, `danger` ;
- les boutons texte peuvent utiliser `data-edge` pour ajuster leurs coins et `data-border` pour gérer les bordures jointes ou masquées ;
- etat toggler via `.active` ou `.is-active`, avec le mixin `temporary-toggle-active` ;
- etat desactive via `control-disabled` ;
- les outils peuvent uniquement ajuster la largeur, l'alignement ou l'espacement environnant.
Boutons icones :
- taille normale : `40px`, icone `21px` ;
- taille compacte : `34px`, icone `19px` ;
- taille dense `xs` : `30px`, icone `17px` ;
- taille inline `xxs` : `26px`, icone `15px` ;
- tailles micro reservees aux noeuds denses : `24px`/`13px` et `22px`/`12px` ;
- fond compact sombre, rayon `--radius-md`, hover violet contenu ;
- variant `primary` dore/violet pour les actions principales ;
- variant `ghost` transparent pour les handles discrets ;
- variant `danger` rouge contenu pour les suppressions ;
- variant de forme `button-edge(left|middle|right|top|center|bottom|top-left|top-right|bottom-right|bottom-left)` pour ajuster uniquement les coins contre une bordure ou dans un groupe ;
- variant de bordure `button-joined-border(...)` pour masquer les bordures de jonction déduites depuis `data-edge` ;
- variant de bordure `button-borderless` pour masquer toutes les bordures ;
- variant visuel `panel-toggle` pour les boutons icones accolés à un panneau, comme le bouton pages ;
- etat toggler via `.active` ou `.is-active`, avec le mixin `temporary-toggle-active`.
- les barres d'outils compactes reutilisent la famille bouton icone ; `toolbar-button` reste un alias pratique pour les boutons courts du bloc-notes (`B`, `I`, `U`) et les boutons de dessin.
Boutons composes et toggles de bord :
- utiliser `button-edge` + `button-joined-border` pour les boutons groupés type ajout + chevron ;
- utiliser `button-edge` pour la forme des toggles accoles au bord d'un module, et `icon-button-panel-toggle` pour leur rendu visuel quand ils reprennent le pattern du bouton `pages` ;
- utiliser `list-row-button` pour les lignes selectionnables type pages du bloc-notes ;
- utiliser `text-button-dashed` pour les actions d'ajout contextuelles encadrees en pointilles ;
- `split-button-start`, `split-button-end`, `edge-toggle` et `dashed-action-button` restent des alias de compatibilite internes, pas des familles visuelles separees ;
- les outils peuvent garder uniquement la position absolue, la largeur contextuelle et les icones ;
- l'etat actif/hover doit rester dans le DS, sans fond/bordure locale.
Steppers de quantite :
- utiliser `quantity-stepper` depuis `website/src/styles/design-system/_controls.scss` ;
- variante normale : checklist, boutons `28px` autour de la valeur editable ;
- variante compacte : arbre d'objectifs, boutons `18px` ou `16px` selon la densite du noeud ;
- hover des boutons via `quantity-stepper-button-hover`, pas via `icon-button-hover` ;
- les outils peuvent adapter la grille environnante, mais pas redefinir les boutons `- / +`, le hover ou le champ de valeur.
Switchs binaires :
- utiliser `switch-control` depuis `website/src/styles/design-system/_controls.scss` ;
- taille `lg` : switchs de module, hauteur `40px`, rail `30 x 16px` ;
- taille normale : tool-split, hauteur `34px`, rail `34 x 18px` ;
- taille compacte `sm` : contrôles denses, hauteur `28px`, rail `24 x 14px` ;
- taille inline `xs` : timer inline, hauteur `26px`, rail `18 x 12px` ;
- les variantes natives avec `<label><input type="checkbox" />...` doivent réutiliser `native-switch-label`, `native-switch-input`, `switch-track` et `switch-thumb` ;
- les outils peuvent uniquement ajuster l'alignement ou la largeur environnante.
Radio button groups :
- la primitive commune est `radio-button-group` / `radio-button-group-option` depuis `website/src/styles/design-system/_controls.scss` ;
- tailles disponibles : `md`, `sm`, `xs` ;
- cette base couvre les tabs d'outils, switches de layout, choix d'alertes et répétitions calendrier ;
- le nombre d'options, le fait d'utiliser une icone, un texte ou les deux ne constitue pas un variant visuel ;
- les toggles compacts non natifs peuvent reutiliser ces mixins meme avec des `span`, tant que le comportement reste celui d'un choix exclusif ou d'un choix compact ;
- l'etat actif passe par `.active` ou `.is-active` avec le style toggler temporaire ;
- les outils peuvent definir la grille ou la largeur, mais pas redefinir localement les fonds, bordures, hovers ou actifs.
Controles composes :
- utiliser `compound-toggle-field` pour les cas qui combinent un choix compact et un champ, comme `Hold + number` ;
- le bouton interne utilise `compound-toggle-field-option`, base sur `radio-button-group-option(xs)` ;
- le champ interne utilise `compound-toggle-field-input`, base sur `field-control(xs)` et `number-spinner-reset` ;
- ces controles ne sont pas des variants du groupe de choix, car leur structure mélange action et saisie.
Checkboxes :
- utiliser `checkbox-control` depuis `website/src/styles/design-system/_controls.scss` ;
- taille normale globale : `20px`, format carre strict `1:1` ;
- taille compacte `xs` : `14px` pour les listes denses comme les prérequis de l'arbre d'objectifs ;
- etat coche avec gradient dore/violet et coche blanche ;
- les outils peuvent ajuster uniquement l'alignement ou l'espacement environnant.
Date picker et menus compacts :
- les tabs internes d'outils utilisent les primitives `radio-button-group-surface`, `radio-button-group-option`, `radio-button-group-option-hover` et `radio-button-group-option-active` ;
- les boutons du mini calendrier mensuel utilisent `date-picker-button` et `date-picker-button-hover` ;
- les boutons de menu/picker compacts utilisent `compact-menu-button` et `compact-menu-button-hover` ;
- les boutons transparents integres aux evenements calendrier utilisent `event-surface-button`, `event-surface-button-hover` et les contrastes `event-surface-contrast-default` / `event-surface-contrast-dark` ;
- `nds-event-surface[data-variant="default"]` cible un texte blanc/gris blanc sur fond sombre ;
- `nds-event-surface[data-variant="dark"]` cible un texte noir/gris noir sur fond clair ;
- `nds-event-surface[data-size="md"]` vise les surfaces à partir de `44px` et affiche le titre et la metadata secondaire, par exemple l'heure ;
- `nds-event-surface[data-size="sm"]` vise les surfaces autour de `22px` et masque la metadata secondaire ;
- `nds-event-surface[data-size="xs"]` vise les surfaces ultra-fines autour de `10px` et affiche uniquement le titre en taille réduite ;
- `nds-event-surface[data-color="#8b5cf6"]` expose la couleur métier de l'event ; la variable inline `--nds-event-color` applique cette couleur au fond et a la bordure ;
- le hover utilise une superposition sombre legere pour rester lisible sur une couleur dynamique inconnue du Sass ;
- les couleurs de fond des events restent métier et ne sont pas des variants NDS ;
- les fichiers d'outils peuvent conserver les dimensions de grille, le placement du menu et les états métier `disabled`.
## 18. Scrollbars
Les scrollbars utilisent `scrollbar-control` depuis `website/src/styles/design-system/_controls.scss`.
Regles :
- largeur/hauteur WebKit : `6px` ;
- `scrollbar-width: thin` pour Firefox ;
- track transparent ;
- thumb violet/indigo avec hover dore contenu ;
- coin transparent pour les zones scrollables dans les deux axes ;
- les textareas redimensionnables ajoutent `scrollbar-resizer`, sans fond noir ;
- ne pas ajouter de rail noir visible ni de double bordure pres des panneaux.
## 19. Images Et Assets
Regles : Regles :
@ -789,7 +617,7 @@ Regles :
- les categories de jeu utilisent des images representatives, entieres et bien cadrees ; - les categories de jeu utilisent des images representatives, entieres et bien cadrees ;
- eviter les images trop grandes dans les cards. - eviter les images trop grandes dans les cards.
## 20. Accessibilite ## 19. Accessibilite
Minimum attendu : Minimum attendu :
@ -801,7 +629,7 @@ Minimum attendu :
- modales avec `role="dialog"` et `aria-modal="true"` ; - modales avec `role="dialog"` et `aria-modal="true"` ;
- ne pas transmettre une information uniquement par couleur. - ne pas transmettre une information uniquement par couleur.
## 21. A Eviter ## 20. A Eviter
- accents dores partout ; - accents dores partout ;
- hover trop lumineux sur tous les elements ; - hover trop lumineux sur tous les elements ;

View file

@ -138,9 +138,9 @@ test("style entrypoint and theme layers are wired", async () => {
assert.match(styles, /@use "diablo4"/); assert.match(styles, /@use "diablo4"/);
assert.match(styleTokens, /--gradient-brand/); assert.match(styleTokens, /--gradient-brand/);
assert.match(styleBase, /:is\(/); assert.match(styleBase, /:is\(/);
assert.doesNotMatch(styleBase, /\.card-icon-button/); assert.match(styleBase, /\.card-icon-button/);
assert.doesNotMatch(styleBase, /\.drawer-action-button/); assert.match(styleBase, /\.drawer-action-button/);
assert.doesNotMatch(styleBase, /\.import-icon-button/); assert.match(styleBase, /\.import-icon-button/);
assert.match(styleBase, /themed-scrollbar/); assert.match(styleBase, /themed-scrollbar/);
assert.match(styleBase, /body\.is-modal-open/); assert.match(styleBase, /body\.is-modal-open/);
assert.match(styleNebula, /@function star-field/); assert.match(styleNebula, /@function star-field/);
@ -172,7 +172,7 @@ test("style entrypoint and theme layers are wired", async () => {
assert.match(styleToolboxCalendar, /\.calendar-event-form/); assert.match(styleToolboxCalendar, /\.calendar-event-form/);
assert.match(styleToolboxCalendar, /\.calendar-month-date-picker/); assert.match(styleToolboxCalendar, /\.calendar-month-date-picker/);
assert.match(styleToolboxCalendar, /\.calendar-alert-mode-switch/); assert.match(styleToolboxCalendar, /\.calendar-alert-mode-switch/);
assert.match(styles, /@use "design-system\/attributes"/); assert.match(styleToolboxSharedControls, /\.notepad-toolbar-button\.danger/);
assert.match(styleToolboxLayout, /\.toolbox-icon-picker/); assert.match(styleToolboxLayout, /\.toolbox-icon-picker/);
assert.match(styleToolboxLayout, /\.toolbox-game-icon/); assert.match(styleToolboxLayout, /\.toolbox-game-icon/);
assert.match(styleToolboxLayout, /\.toolbox-hero-link/); assert.match(styleToolboxLayout, /\.toolbox-hero-link/);

View file

@ -437,8 +437,7 @@ test("toolbox module registry and modules expose expected behavior", async () =>
assert.match(calendarBundle, /calendar-repeat-switch/); assert.match(calendarBundle, /calendar-repeat-switch/);
assert.match(calendarBundle, /layout-switch calendar-month-repeat-switch/); assert.match(calendarBundle, /layout-switch calendar-month-repeat-switch/);
assert.match(calendarBundle, /WEEK_TIME_INPUT_STEP_MINUTES \* 60/); assert.match(calendarBundle, /WEEK_TIME_INPUT_STEP_MINUTES \* 60/);
assert.match(calendarBundle, /naturalHeight/); assert.match(calendarBundle, /durationMinutes < 120/);
assert.match(calendarBundle, /data-size=\{eventSurfaceSize\}/);
assert.match(calendarBundle, /is-compact/); assert.match(calendarBundle, /is-compact/);
assert.match(calendarController, /handleKeyDown/); assert.match(calendarController, /handleKeyDown/);
assert.match(calendarContent, /TextExchangeActions/); assert.match(calendarContent, /TextExchangeActions/);
@ -535,7 +534,7 @@ test("toolbox module registry and modules expose expected behavior", async () =>
assert.match(checklistParts, /chevron-down/); assert.match(checklistParts, /chevron-down/);
assert.match(checklistParts, /chevron-up/); assert.match(checklistParts, /chevron-up/);
assert.match(checklistParts, /checklist-qty-current/); assert.match(checklistParts, /checklist-qty-current/);
assert.match(checklistParts, /checklist-delete-button nds-icon-button/); assert.match(checklistParts, /checklist-delete-button danger/);
assert.match(checklistModule, /useGroupedReorder/); assert.match(checklistModule, /useGroupedReorder/);
assert.match(checklistBundle, /getItemProps/); assert.match(checklistBundle, /getItemProps/);
assert.match(checklistParts, /getGroupProps/); assert.match(checklistParts, /getGroupProps/);

View file

@ -89,7 +89,7 @@ export function GuidedTooltipTour({ steps, labels = {}, targetAttribute = "data-
return createPortal( return createPortal(
<section ref={tooltipRef} className="toolbox-tutorial-tooltip" aria-live="polite" aria-labelledby="toolbox-tutorial-title" data-placement={placement} role="dialog" style={tooltipStyle}> <section ref={tooltipRef} className="toolbox-tutorial-tooltip" aria-live="polite" aria-labelledby="toolbox-tutorial-title" data-placement={placement} role="dialog" style={tooltipStyle}>
<button className="toolbox-tutorial-close nds-icon-button" data-size="xs" type="button" onClick={onDismiss} aria-label={labels.dismiss || "Fermer le tutoriel"} title={labels.dismiss || "Fermer"}> <button className="toolbox-tutorial-close" type="button" onClick={onDismiss} aria-label={labels.dismiss || "Fermer le tutoriel"} title={labels.dismiss || "Fermer"}>
<Icon name="close" /> <Icon name="close" />
</button> </button>
<span className="toolbox-tutorial-count">{stepIndex + 1}/{steps.length}</span> <span className="toolbox-tutorial-count">{stepIndex + 1}/{steps.length}</span>
@ -99,7 +99,7 @@ export function GuidedTooltipTour({ steps, labels = {}, targetAttribute = "data-
</div> </div>
<p>{currentStep.text}</p> <p>{currentStep.text}</p>
<footer> <footer>
<button className="nds-button" data-size="sm" data-variant="primary" type="button" onClick={isLastStep ? onDismiss : () => setStepIndex((index) => Math.min(index + 1, steps.length - 1))}> <button className="primary" type="button" onClick={isLastStep ? onDismiss : () => setStepIndex((index) => Math.min(index + 1, steps.length - 1))}>
{isLastStep ? labels.done || "Terminer" : labels.next || "Suivant"} {isLastStep ? labels.done || "Terminer" : labels.next || "Suivant"}
</button> </button>
</footer> </footer>

View file

@ -142,9 +142,9 @@ export function ImageViewer({ image, onClose, createMarkerId = () => globalThis.
{image.label && <strong className="image-viewer-title">{image.label}</strong>} {image.label && <strong className="image-viewer-title">{image.label}</strong>}
<div className="image-viewer-actions"> <div className="image-viewer-actions">
{!canAnnotate && !hasMarkers && ( {!canAnnotate && !hasMarkers && (
<button className="image-viewer-button nds-icon-button" data-size="sm" onClick={() => openImageInNewTab(image.dataUrl)} aria-label="Ouvrir l'image en taille réelle" title="Taille réelle"><Icon name="zoom" /></button> <button className="image-viewer-button" onClick={() => openImageInNewTab(image.dataUrl)} aria-label="Ouvrir l'image en taille réelle" title="Taille réelle"><Icon name="zoom" /></button>
)} )}
<button className="image-viewer-button nds-icon-button" data-size="sm" onClick={onClose} aria-label="Fermer" title="Fermer"><Icon name="close" /></button> <button className="image-viewer-button" onClick={onClose} aria-label="Fermer" title="Fermer"><Icon name="close" /></button>
</div> </div>
</header> </header>
<div className={`image-viewer-body ${canAnnotate ? "has-annotation-side" : ""}`}> <div className={`image-viewer-body ${canAnnotate ? "has-annotation-side" : ""}`}>
@ -211,15 +211,12 @@ export function ImageViewer({ image, onClose, createMarkerId = () => globalThis.
<li key={marker.id}> <li key={marker.id}>
<span>{index + 1}</span> <span>{index + 1}</span>
<input <input
className="nds-input"
data-size="sm"
type="text"
value={marker.label} value={marker.label}
placeholder={image.markerPlaceholder || "Libellé du marqueur"} placeholder={image.markerPlaceholder || "Libellé du marqueur"}
onChange={(event) => updateViewerMarker(marker.id, (item) => ({ ...item, label: event.target.value }))} onChange={(event) => updateViewerMarker(marker.id, (item) => ({ ...item, label: event.target.value }))}
aria-label={`Libellé du marqueur ${index + 1}`} aria-label={`Libellé du marqueur ${index + 1}`}
/> />
<button className="tool-split-action-button nds-icon-button" data-size="sm" data-variant="danger" type="button" onClick={() => removeViewerMarker(marker.id)} aria-label={`${image.deleteMarkerTitle || "Supprimer"} ${marker.label || `${image.markerPrefix || "Marqueur"} ${index + 1}`}`} title={image.deleteMarkerTitle || "Supprimer"}> <button className="tool-split-action-button danger" type="button" onClick={() => removeViewerMarker(marker.id)} aria-label={`${image.deleteMarkerTitle || "Supprimer"} ${marker.label || `${image.markerPrefix || "Marqueur"} ${index + 1}`}`} title={image.deleteMarkerTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</li> </li>

View file

@ -1,9 +1,9 @@
// Rôle : encapsule l'input fichier JSON derrière un bouton d'import réutilisable. // Rôle : encapsule l'input fichier JSON derrière un bouton d'import réutilisable.
import { Icon } from "./Icon.jsx"; import { Icon } from "./Icon.jsx";
export function ImportButton({ className, label, title, ariaLabel, icon, onFile, ...props }) { export function ImportButton({ className, label, title, ariaLabel, icon, onFile }) {
return ( return (
<label className={className} aria-label={ariaLabel} title={title} {...props}> <label className={className} aria-label={ariaLabel} title={title}>
{icon && <Icon name={icon} />} {icon && <Icon name={icon} />}
{label} {label}
<input type="file" accept="application/json" hidden onChange={async (event) => { <input type="file" accept="application/json" hidden onChange={async (event) => {

View file

@ -6,7 +6,7 @@ export function Shell({ route, content, games, toolboxes, links, actions, childr
const gameId = route.split("/")[1] === "games" ? route.split("/")[2] || "" : ""; const gameId = route.split("/")[1] === "games" ? route.split("/")[2] || "" : "";
const game = games.find((item) => item.id === gameId); const game = games.find((item) => item.id === gameId);
const linkedToolbox = game ? toolboxes.find((item) => item.id === links[game.id]) : null; const linkedToolbox = game ? toolboxes.find((item) => item.id === links[game.id]) : null;
const topbarLabel = route === "/design-system" && import.meta.env.DEV ? "Design system" : route.startsWith("/toolbox") ? content.topbar.toolbox : route.startsWith("/games") ? content.topbar.games : route === "/library" ? content.topbar.library : content.topbar.dashboard; const topbarLabel = route.startsWith("/toolbox") ? content.topbar.toolbox : route.startsWith("/games") ? content.topbar.games : route === "/library" ? content.topbar.library : content.topbar.dashboard;
const toolboxLabel = linkedToolbox ? `Ouvrir la toolbox ${linkedToolbox.name}` : "Associer une toolbox"; const toolboxLabel = linkedToolbox ? `Ouvrir la toolbox ${linkedToolbox.name}` : "Associer une toolbox";
return ( return (
@ -27,16 +27,13 @@ export function Shell({ route, content, games, toolboxes, links, actions, childr
<div className="sidebar-secondary-links"> <div className="sidebar-secondary-links">
<a className={`sidebar-about-link ${route === "/about" ? "active" : ""}`} href="#/about"><span className="sidebar-about-icon" aria-hidden="true">?</span><strong>{content.navigation.about}</strong></a> <a className={`sidebar-about-link ${route === "/about" ? "active" : ""}`} href="#/about"><span className="sidebar-about-icon" aria-hidden="true">?</span><strong>{content.navigation.about}</strong></a>
<a className={`sidebar-about-link ${route === "/library" ? "active" : ""}`} href="#/library"><span className="sidebar-about-icon" aria-hidden="true"><Icon name="book" /></span><strong>{content.library.eyebrow}</strong></a> <a className={`sidebar-about-link ${route === "/library" ? "active" : ""}`} href="#/library"><span className="sidebar-about-icon" aria-hidden="true"><Icon name="book" /></span><strong>{content.library.eyebrow}</strong></a>
{import.meta.env.DEV && (
<a className={`sidebar-about-link ${route === "/design-system" ? "active" : ""}`} href="#/design-system"><span className="sidebar-about-icon" aria-hidden="true"><Icon name="settings" /></span><strong>Design system</strong></a>
)}
</div> </div>
<div className="sidebar-note"> <div className="sidebar-note">
<span className="badge">{content.sidebar.badge}</span> <span className="badge">{content.sidebar.badge}</span>
<p>{content.sidebar.note}</p> <p>{content.sidebar.note}</p>
<div className="sidebar-storage-actions" aria-label="Actions globales de stockage"> <div className="sidebar-storage-actions" aria-label="Actions globales de stockage">
<button className="sidebar-action-button nds-icon-button" data-size="sm" data-variant="primary" onClick={actions.exportAllToolboxes} aria-label="Exporter toutes les toolboxes" data-toolbox-tutorial-target="exportAll" title="Exporter tout"><Icon name="export" /></button> <button className="sidebar-action-button primary" onClick={actions.exportAllToolboxes} aria-label="Exporter toutes les toolboxes" data-toolbox-tutorial-target="exportAll" title="Exporter tout"><Icon name="export" /></button>
<ImportButton className="sidebar-action-button nds-icon-button import-icon-button" data-size="sm" title="Importer tout" ariaLabel="Importer toutes les toolboxes" onFile={actions.importAllToolboxes} icon="import" /> <ImportButton className="sidebar-action-button import-icon-button" title="Importer tout" ariaLabel="Importer toutes les toolboxes" onFile={actions.importAllToolboxes} icon="import" />
</div> </div>
</div> </div>
</aside> </aside>
@ -45,7 +42,7 @@ export function Shell({ route, content, games, toolboxes, links, actions, childr
<div><strong>{topbarLabel}</strong></div> <div><strong>{topbarLabel}</strong></div>
{game && ( {game && (
<div className="topbar-actions"> <div className="topbar-actions">
<button className="drawer-button toolbox-icon-button nds-icon-button" data-size="sm" data-variant="primary" onClick={() => actions.setDrawerGameId(game.id)} aria-label={toolboxLabel} title={toolboxLabel}> <button className="drawer-button toolbox-icon-button primary" onClick={() => actions.setDrawerGameId(game.id)} aria-label={toolboxLabel} title={toolboxLabel}>
<img src="/static/icons/toolbox.svg" alt="" aria-hidden="true" /> <img src="/static/icons/toolbox.svg" alt="" aria-hidden="true" />
</button> </button>
</div> </div>
@ -56,7 +53,7 @@ export function Shell({ route, content, games, toolboxes, links, actions, childr
<nav className="mobile-nav" aria-label="Navigation mobile"> <nav className="mobile-nav" aria-label="Navigation mobile">
<a className={route === "/" ? "active" : ""} href="#/">{content.navigation.home}</a> <a className={route === "/" ? "active" : ""} href="#/">{content.navigation.home}</a>
<a className={route.startsWith("/toolboxes") || route.startsWith("/toolbox") ? "active" : ""} href="#/toolboxes">{content.navigation.toolboxes}</a> <a className={route.startsWith("/toolboxes") || route.startsWith("/toolbox") ? "active" : ""} href="#/toolboxes">{content.navigation.toolboxes}</a>
<button className="nds-button" data-size="sm" data-variant="primary" onClick={() => actions.setCreateModal({})}>+</button> <button className="primary" onClick={() => actions.setCreateModal({})}>+</button>
<a className={route === "/library" ? "active" : ""} href="#/library">{content.navigation.mobileLibrary}</a> <a className={route === "/library" ? "active" : ""} href="#/library">{content.navigation.mobileLibrary}</a>
<a className={route.startsWith("/games") ? "active" : ""} href="#/games">{content.navigation.mobileGames}</a> <a className={route.startsWith("/games") ? "active" : ""} href="#/games">{content.navigation.mobileGames}</a>
</nav> </nav>

View file

@ -1,7 +1,7 @@
// Rôle : fournit un contrôle d'onglets réutilisable avec rôles ARIA. // Rôle : fournit un contrôle d'onglets réutilisable avec rôles ARIA.
export function Tabs({ tabs, activeTab, onChange, className = "", iconOnly = false, size = "sm" }) { export function Tabs({ tabs, activeTab, onChange, className = "", iconOnly = false }) {
return ( return (
<div className={`tabs nds-radio-button-group ${className}`.trim()} data-size={size} role="tablist"> <div className={`tabs ${className}`.trim()} role="tablist">
{tabs.map((tab) => { {tabs.map((tab) => {
const active = tab.id === activeTab; const active = tab.id === activeTab;
return ( return (

View file

@ -4,34 +4,16 @@ import { Icon } from "./Icon.jsx";
export function ToastPositionSwitch({ position, labels, onChange }) { export function ToastPositionSwitch({ position, labels, onChange }) {
const normalizedPosition = position === "left" ? "left" : "right"; const normalizedPosition = position === "left" ? "left" : "right";
return ( return (
<div <button
className="toast-position-switch nds-radio-button-group" className="toast-position-switch"
data-size="xs" type="button"
role="radiogroup"
data-position={normalizedPosition} data-position={normalizedPosition}
onClick={() => onChange(normalizedPosition === "left" ? "right" : "left")}
aria-label={normalizedPosition === "left" ? labels.left : labels.right} aria-label={normalizedPosition === "left" ? labels.left : labels.right}
title={labels.title} title={labels.title}
> >
<button <span><Icon name="toast-left" /></span>
className={normalizedPosition === "left" ? "active" : ""} <span><Icon name="toast-right" /></span>
type="button"
role="radio"
aria-checked={normalizedPosition === "left"}
aria-label={labels.left}
onClick={() => onChange("left")}
>
<Icon name="toast-left" />
</button> </button>
<button
className={normalizedPosition === "right" ? "active" : ""}
type="button"
role="radio"
aria-checked={normalizedPosition === "right"}
aria-label={labels.right}
onClick={() => onChange("right")}
>
<Icon name="toast-right" />
</button>
</div>
); );
} }

View file

@ -16,8 +16,8 @@ export function ConfirmModal({ title, message, confirmLabel = "Confirmer", cance
<header><h2 id="confirm-title">{title}</h2></header> <header><h2 id="confirm-title">{title}</h2></header>
<p id="confirm-message">{message}</p> <p id="confirm-message">{message}</p>
<footer> <footer>
<button className="nds-button" data-size="sm" onClick={() => onClose(false)}>{cancelLabel}</button> <button onClick={() => onClose(false)}>{cancelLabel}</button>
<button className="confirm-danger nds-button" data-size="sm" data-variant={danger ? "danger" : "primary"} onClick={() => onClose(true)}>{confirmLabel}</button> <button className={danger ? "danger confirm-danger" : "primary"} onClick={() => onClose(true)}>{confirmLabel}</button>
</footer> </footer>
</section> </section>
</div> </div>
@ -33,7 +33,7 @@ export function NotificationToast({ id, message, durationMs = 120000, onClose })
return ( return (
<div className="notification-toast" role="status"> <div className="notification-toast" role="status">
<span>{message}</span> <span>{message}</span>
<button className="nds-icon-button" data-size="xs" type="button" onClick={() => onClose(id)} aria-label="Fermer l'alerte" title="Fermer"> <button type="button" onClick={() => onClose(id)} aria-label="Fermer l'alerte" title="Fermer">
<Icon name="close" /> <Icon name="close" />
</button> </button>
</div> </div>
@ -52,8 +52,8 @@ export function CreateToolboxModal({ gameId, initialName = "", onClose }) {
event.preventDefault(); event.preventDefault();
if (name.trim()) onClose(name.trim()); if (name.trim()) onClose(name.trim());
}}> }}>
<label className="field"><span>Nom</span><input className="nds-input" data-size="sm" name="name" type="text" autoComplete="off" placeholder="Nom de la toolbox" required value={name} onChange={(event) => setName(event.target.value)} autoFocus /></label> <label className="field"><span>Nom</span><input name="name" autoComplete="off" placeholder="Nom de la toolbox" required value={name} onChange={(event) => setName(event.target.value)} autoFocus /></label>
<footer><button className="nds-button" data-size="sm" type="button" onClick={() => onClose("")}>Annuler</button><button className="nds-button" data-size="sm" data-variant="primary" type="submit">Créer</button></footer> <footer><button type="button" onClick={() => onClose("")}>Annuler</button><button className="primary" type="submit">Créer</button></footer>
</form> </form>
</section> </section>
</div> </div>
@ -72,8 +72,8 @@ export function LinkToolboxModal({ toolboxes, selectedId, onClose }) {
event.preventDefault(); event.preventDefault();
onClose(toolboxId); onClose(toolboxId);
}}> }}>
<label className="field"><span>Toolbox associée</span><select className="nds-select" data-size="sm" value={toolboxId} onChange={(event) => setToolboxId(event.target.value)} autoFocus><option value="">Aucune</option>{toolboxes.map((toolbox) => <option key={toolbox.id} value={toolbox.id}>{toolbox.name}</option>)}</select></label> <label className="field"><span>Toolbox associée</span><select value={toolboxId} onChange={(event) => setToolboxId(event.target.value)} autoFocus><option value="">Aucune</option>{toolboxes.map((toolbox) => <option key={toolbox.id} value={toolbox.id}>{toolbox.name}</option>)}</select></label>
<footer><button className="nds-button" data-size="sm" type="button" onClick={() => onClose(null)}>Annuler</button><button className="nds-button" data-size="sm" data-variant="primary" type="submit">Valider</button></footer> <footer><button type="button" onClick={() => onClose(null)}>Annuler</button><button className="primary" type="submit">Valider</button></footer>
</form> </form>
</section> </section>
</div> </div>

View file

@ -57,8 +57,7 @@ export function CopyChecklistItemsButton({ labels, sections, title = "Copier pou
return ( return (
<button <button
className="filter-reset-button results-copy-button nds-icon-button" className="filter-reset-button results-copy-button"
data-size="xs"
type="button" type="button"
onClick={copyItems} onClick={copyItems}
disabled={!text} disabled={!text}

View file

@ -17,6 +17,8 @@ export function GameFiltersPanel({
onChange onChange
}) { }) {
const selected = new Set(filters[selectedKey] || []); const selected = new Set(filters[selectedKey] || []);
const logicLabel = filters.logic === "or" ? logicLabels.or : logicLabels.and;
function update(updater) { function update(updater) {
onChange(updater(filters)); onChange(updater(filters));
} }
@ -29,8 +31,7 @@ export function GameFiltersPanel({
<h2>{title}</h2> <h2>{title}</h2>
</div> </div>
<button <button
className="filter-reset-button nds-icon-button" className="filter-reset-button"
data-size="xs"
onClick={() => onChange({ name: "", [selectedKey]: [], logic: "and" })} onClick={() => onChange({ name: "", [selectedKey]: [], logic: "and" })}
aria-label={resetLabel} aria-label={resetLabel}
title={resetLabel} title={resetLabel}
@ -40,24 +41,11 @@ export function GameFiltersPanel({
</div> </div>
<label className="field compact"> <label className="field compact">
<span>{nameLabel}</span> <span>{nameLabel}</span>
<input className="nds-input" data-size="sm" type="search" value={filters.name} placeholder="Rechercher..." onChange={(event) => update((state) => ({ ...state, name: event.target.value }))} /> <input value={filters.name} placeholder="Rechercher..." onChange={(event) => update((state) => ({ ...state, name: event.target.value }))} />
</label> </label>
<div className="filter-logic"> <div className="filter-logic">
<span>{matchLabel}</span> <span>{matchLabel}</span>
<div className="filter-logic-group nds-radio-button-group" data-size="xs" role="radiogroup" aria-label={matchLabel}> <button onClick={() => update((state) => ({ ...state, logic: state.logic === "and" ? "or" : "and" }))}>{logicLabel}</button>
{["and", "or"].map((logic) => (
<button
key={logic}
className={filters.logic === logic ? "active" : ""}
type="button"
role="radio"
aria-checked={filters.logic === logic}
onClick={() => update((state) => ({ ...state, logic }))}
>
{logicLabels[logic]}
</button>
))}
</div>
</div> </div>
<div className="filter-options legacy-scrollbar"> <div className="filter-options legacy-scrollbar">
{options.map((option) => { {options.map((option) => {
@ -65,8 +53,6 @@ export function GameFiltersPanel({
return ( return (
<label className={`filter-chip ${getOptionClassName(option)} ${selected.has(optionId) ? "active" : ""}`} key={optionId}> <label className={`filter-chip ${getOptionClassName(option)} ${selected.has(optionId) ? "active" : ""}`} key={optionId}>
<input <input
className="nds-checkbox"
data-size="sm"
type="checkbox" type="checkbox"
checked={selected.has(optionId)} checked={selected.has(optionId)}
onChange={(event) => update((state) => { onChange={(event) => update((state) => {

View file

@ -30,7 +30,7 @@ export function GameListsPage({ game, lists, selectedListId, onSelectList, linke
<h1>Listes</h1> <h1>Listes</h1>
</div> </div>
<p>Sélectionnez une liste, puis copiez-la ou créez une checklist dans la toolbox associée.</p> <p>Sélectionnez une liste, puis copiez-la ou créez une checklist dans la toolbox associée.</p>
<a className="game-list-help-link nds-button" data-size="sm" href="#/about#contribuer">Proposer une nouvelle liste</a> <a className="game-list-help-link button" href="#/about#contribuer">Proposer une nouvelle liste</a>
</div> </div>
</section> </section>
{lists.length ? ( {lists.length ? (
@ -43,9 +43,7 @@ export function GameListsPage({ game, lists, selectedListId, onSelectList, linke
<small>{list.itemCount} éléments</small> <small>{list.itemCount} éléments</small>
</button> </button>
<button <button
className="game-list-create-button nds-icon-button" className="game-list-create-button primary"
data-size="sm"
data-variant="primary"
type="button" type="button"
disabled={!linkedToolboxId} disabled={!linkedToolboxId}
onClick={() => onCreateChecklist?.(list)} onClick={() => onCreateChecklist?.(list)}

View file

@ -32,7 +32,7 @@ export function GamesPage({ siteContent, games, gamesError }) {
<h2>{game.title}</h2> <h2>{game.title}</h2>
<p>{game.summary}</p> <p>{game.summary}</p>
<div className="card-actions"> <div className="card-actions">
<a className="nds-button" data-size="sm" data-variant="primary" href={`#/games/${game.id}`}>Ouvrir</a> <a className="button primary" href={`#/games/${game.id}`}>Ouvrir</a>
</div> </div>
</div> </div>
</article> </article>

View file

@ -30,8 +30,8 @@ export function MhwildsListing({ category, game, mhwilds, filters, setFilters, t
<p>{isMonsters ? "Filtrez les monstres par nom et faiblesses, puis consultez leurs dégâts détaillés." : "Filtrez la faune par nom et zones d'apparition."}</p> <p>{isMonsters ? "Filtrez les monstres par nom et faiblesses, puis consultez leurs dégâts détaillés." : "Filtrez la faune par nom et zones d'apparition."}</p>
</div> </div>
<div className="actions"> <div className="actions">
<a className="nds-button" data-size="sm" data-variant={isMonsters ? "primary" : "secondary"} href="#/games/mhwilds/monsters">Monstres</a> <a className={`button ${isMonsters ? "primary" : ""}`} href="#/games/mhwilds/monsters">Monstres</a>
<a className="nds-button" data-size="sm" data-variant={!isMonsters ? "primary" : "secondary"} href="#/games/mhwilds/endemic">Faune</a> <a className={`button ${!isMonsters ? "primary" : ""}`} href="#/games/mhwilds/endemic">Faune</a>
</div> </div>
</section> </section>
<section className="game-layout" data-mhwilds-category={category}> <section className="game-layout" data-mhwilds-category={category}>

View file

@ -34,9 +34,7 @@ export function ToolboxCard({ toolbox, game, actions, reorder }) {
return ( return (
<article className={className} {...reorder.getItemProps({ itemId: toolbox.id })}> <article className={className} {...reorder.getItemProps({ itemId: toolbox.id })}>
<button <button
className="toolbox-card-drag-handle nds-icon-button" className="toolbox-card-drag-handle"
data-size="md"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, toolbox.id)} onPointerDown={(event) => reorder.itemReorder.startDrag(event, toolbox.id)}
aria-label={`Déplacer ${toolbox.name}`} aria-label={`Déplacer ${toolbox.name}`}
@ -58,9 +56,9 @@ export function ToolboxCard({ toolbox, game, actions, reorder }) {
<h2>{toolbox.name}</h2> <h2>{toolbox.name}</h2>
<small>Modifiée le {formatDate(toolbox.updatedAt)}</small> <small>Modifiée le {formatDate(toolbox.updatedAt)}</small>
<div className="card-actions"> <div className="card-actions">
<a className="card-icon-button nds-icon-button" data-size="md" href={`#/toolbox/${toolbox.id}`} aria-label={`Ouvrir ${toolbox.name}`} title="Ouvrir"><Icon name="open" /></a> <a className="button card-icon-button" href={`#/toolbox/${toolbox.id}`} aria-label={`Ouvrir ${toolbox.name}`} title="Ouvrir"><Icon name="open" /></a>
<button className="card-icon-button nds-icon-button" data-size="md" onClick={() => actions.exportToolbox(toolbox.id)} aria-label={`Exporter ${toolbox.name}`} title="Exporter"><Icon name="export" /></button> <button className="card-icon-button" onClick={() => actions.exportToolbox(toolbox.id)} aria-label={`Exporter ${toolbox.name}`} title="Exporter"><Icon name="export" /></button>
<button className="card-icon-button nds-icon-button" data-size="md" data-variant="danger" onClick={() => actions.setConfirmModal({ <button className="card-icon-button danger" onClick={() => actions.setConfirmModal({
title: "Supprimer la toolbox", title: "Supprimer la toolbox",
message: `Supprimer "${toolbox.name}" et ses données locales ?`, message: `Supprimer "${toolbox.name}" et ses données locales ?`,
confirmLabel: "Supprimer", confirmLabel: "Supprimer",
@ -83,12 +81,11 @@ export function ToolboxIconPicker({ toolbox, onChange, tutorialTarget = "" }) {
label="Icônes de toolbox" label="Icônes de toolbox"
renderTrigger={({ open, toggle }) => ( renderTrigger={({ open, toggle }) => (
<button <button
className={`toolbox-icon-picker-button nds-large-icon-picker-trigger ${open ? "active" : ""}`} className="toolbox-icon-picker-button"
type="button" type="button"
onClick={toggle} onClick={toggle}
aria-label="Changer l'icône de la toolbox" aria-label="Changer l'icône de la toolbox"
aria-expanded={open} aria-expanded={open}
aria-pressed={open}
data-toolbox-tutorial-target={tutorialTarget || undefined} data-toolbox-tutorial-target={tutorialTarget || undefined}
title="Changer l'icône" title="Changer l'icône"
> >
@ -101,8 +98,7 @@ export function ToolboxIconPicker({ toolbox, onChange, tutorialTarget = "" }) {
{TOOLBOX_ICONS.map((path) => ( {TOOLBOX_ICONS.map((path) => (
<button <button
key={path} key={path}
className={`nds-icon-button ${path === icon ? "active" : ""}`} className={path === icon ? "active" : ""}
data-size="md"
type="button" type="button"
onClick={() => { onClick={() => {
onChange(path); onChange(path);

View file

@ -102,17 +102,17 @@ export function ToolboxesPage({ siteContent, toolboxes, getToolboxGame, actions,
<p className="eyebrow">{content.eyebrow}</p> <p className="eyebrow">{content.eyebrow}</p>
<h1>{content.title}</h1> <h1>{content.title}</h1>
<p>{content.summary}</p> <p>{content.summary}</p>
<a className="hero-library-button library-link-button nds-button" data-size="sm" href="#/library"><Icon name="book" />{content.libraryLink}</a> <a className="button hero-library-button library-link-button" href="#/library"><Icon name="book" />{content.libraryLink}</a>
</div> </div>
</section> </section>
<section className="toolbar toolbox-actions-row"> <section className="toolbar toolbox-actions-row">
<div> <div>
<button className="nds-button" data-size="sm" data-variant="primary" onClick={() => actions.setCreateModal({})}>{content.newButton}</button> <button className="primary" onClick={() => actions.setCreateModal({})}>{content.newButton}</button>
<ImportButton className="nds-button" data-size="sm" label={content.importOne} onFile={actions.importToolbox} /> <ImportButton className="import-button" label={content.importOne} onFile={actions.importToolbox} />
</div> </div>
<div> <div>
<button className="nds-button" data-size="sm" data-variant="primary" onClick={actions.exportAllToolboxes}>{content.exportAll}</button> <button className="primary" onClick={actions.exportAllToolboxes}>{content.exportAll}</button>
<ImportButton className="nds-button" data-size="sm" label={content.importAll} onFile={actions.importAllToolboxes} /> <ImportButton className="import-button" label={content.importAll} onFile={actions.importAllToolboxes} />
</div> </div>
</section> </section>
<section className="toolbox-preferences-row" aria-label={content.preferencesLabel}> <section className="toolbox-preferences-row" aria-label={content.preferencesLabel}>
@ -169,7 +169,7 @@ function StorageHelpCard({ help }) {
export function ToolboxPage(props) { export function ToolboxPage(props) {
const { toolboxId, toolboxes, getToolboxGame } = props; const { toolboxId, toolboxes, getToolboxGame } = props;
const toolbox = toolboxes.find((item) => item.id === toolboxId); const toolbox = toolboxes.find((item) => item.id === toolboxId);
if (!toolbox) return <div className="empty"><h1>Toolbox introuvable</h1><a className="nds-button" data-size="sm" href="#/toolboxes">Retour</a></div>; if (!toolbox) return <div className="empty"><h1>Toolbox introuvable</h1><a className="button" href="#/toolboxes">Retour</a></div>;
return <ToolboxView {...props} toolbox={toolbox} toolboxGame={getToolboxGame(toolbox)} embedded={false} />; return <ToolboxView {...props} toolbox={toolbox} toolboxGame={getToolboxGame(toolbox)} embedded={false} />;
} }
@ -265,8 +265,7 @@ function ToolboxView({ siteContent, toolbox, toolboxGame, embedded, actions, sto
</div> </div>
{toolboxGame && ( {toolboxGame && (
<a <a
className="toolbox-hero-link nds-button" className="button toolbox-hero-link"
data-size="sm"
href={embedded ? `#/toolbox/${toolbox.id}` : `#/games/${toolboxGame.id}`} href={embedded ? `#/toolbox/${toolbox.id}` : `#/games/${toolboxGame.id}`}
onClick={embedded ? () => actions.setDrawerGameId("") : undefined} onClick={embedded ? () => actions.setDrawerGameId("") : undefined}
> >
@ -300,7 +299,7 @@ function ToolboxView({ siteContent, toolbox, toolboxGame, embedded, actions, sto
)} )}
{!embedded && ( {!embedded && (
<section className="modules-toolbar" aria-label="Options d'affichage des outils"> <section className="modules-toolbar" aria-label="Options d'affichage des outils">
<div className="layout-switch nds-radio-button-group" data-size="sm" role="group" aria-label="Mode d'affichage des outils"> <div className="layout-switch" role="group" aria-label="Mode d'affichage des outils">
<button className={moduleColumns === 1 ? "active" : ""} onClick={() => updateToolbox({ ...toolbox, moduleColumns: 1 })} aria-pressed={moduleColumns === 1} aria-label="Afficher en lignes" title="Afficher en lignes"><Icon name="rows" /></button> <button className={moduleColumns === 1 ? "active" : ""} onClick={() => updateToolbox({ ...toolbox, moduleColumns: 1 })} aria-pressed={moduleColumns === 1} aria-label="Afficher en lignes" title="Afficher en lignes"><Icon name="rows" /></button>
<button className={moduleColumns === 2 ? "active" : ""} onClick={() => updateToolbox({ ...toolbox, moduleColumns: 2 })} aria-pressed={moduleColumns === 2} aria-label="Afficher en colonnes" title="Afficher en colonnes"><Icon name="columns" /></button> <button className={moduleColumns === 2 ? "active" : ""} onClick={() => updateToolbox({ ...toolbox, moduleColumns: 2 })} aria-pressed={moduleColumns === 2} aria-label="Afficher en colonnes" title="Afficher en colonnes"><Icon name="columns" /></button>
</div> </div>
@ -388,15 +387,15 @@ export function ToolboxDrawer({ gameId, game, siteContent, toolboxes, links, act
<div> <div>
<p className="eyebrow">Toolbox liée</p> <p className="eyebrow">Toolbox liée</p>
<div className="drawer-toolbox-actions" aria-label="Actions toolbox liée"> <div className="drawer-toolbox-actions" aria-label="Actions toolbox liée">
<button className="drawer-action-button nds-icon-button" data-size="sm" onClick={() => actions.setLinkModalGameId(gameId)} aria-label="Lier une toolbox" title="Lier"><Icon name="link" /></button> <button className="drawer-action-button" onClick={() => actions.setLinkModalGameId(gameId)} aria-label="Lier une toolbox" title="Lier"><Icon name="link" /></button>
<ImportButton className="drawer-action-button nds-icon-button import-icon-button" data-size="sm" ariaLabel="Importer une toolbox" title="Importer" icon="import" onFile={(file) => actions.importToolbox(file, gameId)} /> <ImportButton className="drawer-action-button import-icon-button" ariaLabel="Importer une toolbox" title="Importer" icon="import" onFile={(file) => actions.importToolbox(file, gameId)} />
<button className="drawer-action-button nds-icon-button" data-size="sm" onClick={() => actions.exportToolbox(selectedId)} disabled={!selectedId} aria-label="Exporter la toolbox liée" title="Exporter"><Icon name="export" /></button> <button className="drawer-action-button" onClick={() => actions.exportToolbox(selectedId)} disabled={!selectedId} aria-label="Exporter la toolbox liée" title="Exporter"><Icon name="export" /></button>
</div> </div>
</div> </div>
<button className="drawer-close-button nds-icon-button" data-size="sm" onClick={() => actions.setDrawerGameId("")} aria-label="Fermer" title="Fermer"><Icon name="close" /></button> <button className="drawer-close-button" onClick={() => actions.setDrawerGameId("")} aria-label="Fermer" title="Fermer"><Icon name="close" /></button>
</header> </header>
{toolbox ? <ToolboxView siteContent={siteContent} toolbox={toolbox} toolboxGame={game} embedded actions={actions} storageUsage={storageUsage} storageReady={storageReady} getModuleData={getModuleData} updateToolbox={updateToolbox} updateModuleData={updateModuleData} addImageFiles={addImageFiles} /> : ( {toolbox ? <ToolboxView siteContent={siteContent} toolbox={toolbox} toolboxGame={game} embedded actions={actions} storageUsage={storageUsage} storageReady={storageReady} getModuleData={getModuleData} updateToolbox={updateToolbox} updateModuleData={updateModuleData} addImageFiles={addImageFiles} /> : (
<div className="empty"><p>Aucune toolbox associée à cette page jeu.</p><button className="nds-button" data-size="sm" data-variant="primary" onClick={() => actions.setCreateModal({ gameId })}>Créer et associer</button></div> <div className="empty"><p>Aucune toolbox associée à cette page jeu.</p><button className="primary" onClick={() => actions.setCreateModal({ gameId })}>Créer et associer</button></div>
)} )}
<StorageQuota usage={storageUsage} /> <StorageQuota usage={storageUsage} />
</div> </div>

View file

@ -27,11 +27,7 @@ export function AddToolControls({ onAdd }) {
aria-label="Ajouter un outil" aria-label="Ajouter un outil"
> >
<button <button
className="tool-add-modal-button nds-button" className="primary tool-add-modal-button"
data-size="sm"
data-variant="primary"
data-edge="left"
data-border="joined"
type="button" type="button"
onClick={() => setOpen(true)} onClick={() => setOpen(true)}
data-toolbox-tutorial-target="addTool" data-toolbox-tutorial-target="addTool"
@ -42,13 +38,11 @@ export function AddToolControls({ onAdd }) {
className="tool-add-quick-dropdown" className="tool-add-quick-dropdown"
menuClassName="tool-quick-add" menuClassName="tool-quick-add"
label="Ajout rapide" label="Ajout rapide"
openOnHover
closeOnMouseLeave
renderTrigger={({ open: quickOpen, toggle }) => ( renderTrigger={({ open: quickOpen, toggle }) => (
<button <button
className={`tool-add-quick-toggle nds-icon-button ${quickOpen ? "active" : ""}`} className={`primary tool-add-quick-toggle ${quickOpen ? "active" : ""}`}
data-size="sm"
data-variant="primary"
data-edge="right"
data-border="joined"
type="button" type="button"
onClick={toggle} onClick={toggle}
aria-label="Afficher l'ajout rapide" aria-label="Afficher l'ajout rapide"
@ -64,8 +58,7 @@ export function AddToolControls({ onAdd }) {
{modules.map(([type, module]) => ( {modules.map(([type, module]) => (
<button <button
key={type} key={type}
className="tool-quick-add-button nds-icon-button" className="tool-quick-add-button"
data-size="sm"
type="button" type="button"
onClick={() => { onClick={() => {
onAdd(type); onAdd(type);
@ -89,7 +82,7 @@ export function AddToolControls({ onAdd }) {
<section className="confirm-modal tool-add-modal"> <section className="confirm-modal tool-add-modal">
<header> <header>
<h2 id="tool-add-modal-title">Ajouter un outil</h2> <h2 id="tool-add-modal-title">Ajouter un outil</h2>
<button className="drawer-close-button nds-icon-button" data-size="sm" type="button" onClick={() => setOpen(false)} aria-label="Fermer" title="Fermer"> <button className="drawer-close-button" type="button" onClick={() => setOpen(false)} aria-label="Fermer" title="Fermer">
<Icon name="close" /> <Icon name="close" />
</button> </button>
</header> </header>

View file

@ -41,9 +41,7 @@ export function CalculatorEntries({ entries, parentId, getScopedParentId, active
<li className={className} key={entry.id} {...reorder.getItemProps({ itemId: entry.id, parentId: getScopedParentId(parentId) })}> <li className={className} key={entry.id} {...reorder.getItemProps({ itemId: entry.id, parentId: getScopedParentId(parentId) })}>
<div className="tool-split-entry calculator-entry has-drag-handle"> <div className="tool-split-entry calculator-entry has-drag-handle">
<button <button
className="calculator-drag-handle nds-icon-button" className="calculator-drag-handle"
data-size="xs"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, entry.id)} onPointerDown={(event) => reorder.itemReorder.startDrag(event, entry.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${entry.label || formatResult(entry.value)}`} aria-label={`${textContent.reorderTitle || "Déplacer"} ${entry.label || formatResult(entry.value)}`}
@ -60,8 +58,6 @@ export function CalculatorEntries({ entries, parentId, getScopedParentId, active
</span> </span>
</button> </button>
<button <button
className="nds-icon-button"
data-size="xs"
type="button" type="button"
onClick={() => onAddChild(entry)} onClick={() => onAddChild(entry)}
aria-label={`${textContent.addChildTitle || "Ajouter un calcul enfant"} ${entry.label || formatResult(entry.value)}`} aria-label={`${textContent.addChildTitle || "Ajouter un calcul enfant"} ${entry.label || formatResult(entry.value)}`}
@ -69,7 +65,7 @@ export function CalculatorEntries({ entries, parentId, getScopedParentId, active
> >
<Icon name="add" /> <Icon name="add" />
</button> </button>
<button type="button" className="nds-icon-button" data-size="xs" data-variant="danger" onClick={() => onDelete(entry.id)} aria-label={`${textContent.deleteTitle || "Supprimer"} ${entry.label}`} title={textContent.deleteTitle || "Supprimer"}> <button type="button" className="danger" onClick={() => onDelete(entry.id)} aria-label={`${textContent.deleteTitle || "Supprimer"} ${entry.label}`} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</div> </div>

View file

@ -228,10 +228,8 @@ export function CalculatorModule({ toolboxId, moduleId, context, textExchangeOpe
/> />
) : ( ) : (
<input <input
type="text"
aria-label={textContent.expressionLabel || "Calcul"} aria-label={textContent.expressionLabel || "Calcul"}
className="calculator-expression-input nds-input" className="calculator-expression-input"
data-size="sm"
ref={expressionInputRef} ref={expressionInputRef}
onChange={(event) => setExpression(event.target.value)} onChange={(event) => setExpression(event.target.value)}
onKeyDown={(event) => { onKeyDown={(event) => {
@ -253,9 +251,9 @@ export function CalculatorModule({ toolboxId, moduleId, context, textExchangeOpe
</div> </div>
<label> <label>
<span>{textContent.labelLabel || "Libellé"}</span> <span>{textContent.labelLabel || "Libellé"}</span>
<input className="nds-input" data-size="sm" type="text" value={label} onChange={(event) => setLabel(event.target.value)} placeholder={textContent.labelPlaceholder || "Lingots de fer"} /> <input value={label} onChange={(event) => setLabel(event.target.value)} placeholder={textContent.labelPlaceholder || "Lingots de fer"} />
</label> </label>
<button className="nds-button" data-size="sm" data-variant="primary" disabled={result == null}>{formMode === "edit" ? textContent.updateButton || "Mettre à jour" : textContent.saveButton || "Enregistrer"}</button> <button className="primary" disabled={result == null}>{formMode === "edit" ? textContent.updateButton || "Mettre à jour" : textContent.saveButton || "Enregistrer"}</button>
</div> </div>
</form> </form>
<div <div
@ -264,19 +262,18 @@ export function CalculatorModule({ toolboxId, moduleId, context, textExchangeOpe
> >
<div className="tool-split-actions"> <div className="tool-split-actions">
{(formulaParent || editingEntry) && ( {(formulaParent || editingEntry) && (
<button className="tool-split-root-button nds-button" data-size="xs" type="button" onClick={returnToRoot}> <button className="tool-split-root-button" type="button" onClick={returnToRoot}>
{editingEntry ? textContent.newButton || "Nouveau calcul" : textContent.rootButton || "Revenir à la racine"} {editingEntry ? textContent.newButton || "Nouveau calcul" : textContent.rootButton || "Revenir à la racine"}
</button> </button>
)} )}
<button className="tool-split-action-button nds-icon-button" data-size="sm" data-variant="danger" type="button" onClick={resetCalculator} aria-label={textContent.resetTitle || "Réinitialiser"} title={textContent.resetTitle || "Réinitialiser"}> <button className="tool-split-action-button danger" type="button" onClick={resetCalculator} aria-label={textContent.resetTitle || "Réinitialiser"} title={textContent.resetTitle || "Réinitialiser"}>
<Icon name="rubber" /> <Icon name="rubber" />
</button> </button>
<button className="tool-split-action-button nds-icon-button" data-size="sm" type="button" onClick={copyChecklistImport} disabled={!data.entries.length} aria-label={textContent.copyTitle || "Copier pour checklist"} title={copied ? textContent.copiedTitle || "Copié" : textContent.copyTitle || "Copier pour checklist"}> <button className="tool-split-action-button" type="button" onClick={copyChecklistImport} disabled={!data.entries.length} aria-label={textContent.copyTitle || "Copier pour checklist"} title={copied ? textContent.copiedTitle || "Copié" : textContent.copyTitle || "Copier pour checklist"}>
<Icon name="copy" /> <Icon name="copy" />
</button> </button>
<button <button
className={`tool-split-scroll-toggle nds-switch ${data.scrollResults ? "active" : ""}`} className={`tool-split-scroll-toggle ${data.scrollResults ? "active" : ""}`}
data-size="md"
type="button" type="button"
onClick={() => setScrollResults(!data.scrollResults)} onClick={() => setScrollResults(!data.scrollResults)}
aria-pressed={data.scrollResults} aria-pressed={data.scrollResults}

View file

@ -130,8 +130,7 @@ function CalendarWeekOptions({ textContent, hideEmptyHours, onToggle }) {
<div className="calendar-week-options"> <div className="calendar-week-options">
<span>{textContent.hideEmptyHoursLabel || "Masquer les heures vides"}</span> <span>{textContent.hideEmptyHoursLabel || "Masquer les heures vides"}</span>
<button <button
className={`tool-split-scroll-toggle calendar-hide-hours-switch nds-switch ${hideEmptyHours ? "active" : ""}`} className={`tool-split-scroll-toggle calendar-hide-hours-switch ${hideEmptyHours ? "active" : ""}`}
data-size="md"
type="button" type="button"
onClick={onToggle} onClick={onToggle}
aria-pressed={hideEmptyHours} aria-pressed={hideEmptyHours}

View file

@ -2,11 +2,19 @@
import { Icon } from "../../../components/Icon.jsx"; import { Icon } from "../../../components/Icon.jsx";
import { import {
CALENDAR_ALERT_OFFSETS, CALENDAR_ALERT_OFFSETS,
CALENDAR_COLORS, CALENDAR_COLORS
CALENDAR_COLOR_VALUES
} from "./calendarUtils.js"; } from "./calendarUtils.js";
import { ALERT_MODES } from "./timerOptions.js"; import { ALERT_MODES } from "./timerOptions.js";
const CALENDAR_COLOR_VALUES = {
violet: "#8b5cf6",
gold: "#f6c453",
cyan: "#22d3ee",
pink: "#d946ef",
green: "#22c55e",
gray: "#7d879f"
};
export function MonthlyRepeatSwitch({ value, textContent, onChange }) { export function MonthlyRepeatSwitch({ value, textContent, onChange }) {
const options = [ const options = [
{ value: "monthDay", icon: "day-number", label: textContent.monthlyRepeatMonthDay || "Numéro du jour" }, { value: "monthDay", icon: "day-number", label: textContent.monthlyRepeatMonthDay || "Numéro du jour" },
@ -14,7 +22,7 @@ export function MonthlyRepeatSwitch({ value, textContent, onChange }) {
]; ];
return ( return (
<div className="layout-switch calendar-month-repeat-switch nds-radio-button-group" data-size="sm" role="group" aria-label={textContent.monthlyRepeatLabel || "Règle mensuelle"}> <div className="layout-switch calendar-month-repeat-switch" role="group" aria-label={textContent.monthlyRepeatLabel || "Règle mensuelle"}>
{options.map((option) => ( {options.map((option) => (
<button <button
key={option.value} key={option.value}
@ -41,7 +49,7 @@ export function MonthEventRepeatSwitch({ value, monthlyRepeatBy, textContent, on
const safeValue = monthlyRepeatBy === "monthDay" && value === "weekly" ? "none" : value; const safeValue = monthlyRepeatBy === "monthDay" && value === "weekly" ? "none" : value;
return ( return (
<div className="calendar-month-event-repeat-switch nds-radio-button-group" data-size="sm" role="group" aria-label={textContent.repeatLabel || "Répétition"}> <div className="calendar-month-event-repeat-switch" role="group" aria-label={textContent.repeatLabel || "Répétition"}>
{options.map((option) => ( {options.map((option) => (
<button <button
key={option.value} key={option.value}
@ -72,7 +80,7 @@ export function CalendarAlertOffsets({ selectedOffsets, textContent, onToggle })
<legend>{textContent.alertOffsetLabel || "Alerte"}</legend> <legend>{textContent.alertOffsetLabel || "Alerte"}</legend>
{CALENDAR_ALERT_OFFSETS.map((offset) => ( {CALENDAR_ALERT_OFFSETS.map((offset) => (
<label key={offset}> <label key={offset}>
<input className="nds-checkbox" data-size="sm" type="checkbox" checked={selectedOffsets.includes(offset)} onChange={() => onToggle(offset)} /> <input type="checkbox" checked={selectedOffsets.includes(offset)} onChange={() => onToggle(offset)} />
<span>{labels[offset]}</span> <span>{labels[offset]}</span>
</label> </label>
))} ))}
@ -86,7 +94,7 @@ export function CalendarColorPicker({ value, textContent, onChange }) {
<div className="calendar-color-picker notepad-color-dropdown"> <div className="calendar-color-picker notepad-color-dropdown">
<span>{label}</span> <span>{label}</span>
<button <button
className="calendar-color-toggle notepad-color-toggle nds-swatch" className="calendar-color-toggle notepad-color-toggle"
type="button" type="button"
title={label} title={label}
aria-label={label} aria-label={label}
@ -96,7 +104,7 @@ export function CalendarColorPicker({ value, textContent, onChange }) {
{CALENDAR_COLORS.map((color) => ( {CALENDAR_COLORS.map((color) => (
<button <button
key={color} key={color}
className={`notepad-swatch nds-swatch ${value === color ? "active" : ""}`} className={`notepad-swatch ${value === color ? "active" : ""}`}
type="button" type="button"
title={textContent[`color${color.charAt(0).toUpperCase()}${color.slice(1)}`] || color} title={textContent[`color${color.charAt(0).toUpperCase()}${color.slice(1)}`] || color}
aria-label={textContent[`color${color.charAt(0).toUpperCase()}${color.slice(1)}`] || color} aria-label={textContent[`color${color.charAt(0).toUpperCase()}${color.slice(1)}`] || color}
@ -118,7 +126,7 @@ export function CalendarAlertModeSwitch({ mode, textContent, onChange }) {
}; };
return ( return (
<div className="timer-alert-mode-switch calendar-alert-mode-switch nds-radio-button-group" data-size="xs" role="group" aria-label={textContent.alertModeTitle || "Mode d'alerte"}> <div className="timer-alert-mode-switch calendar-alert-mode-switch" role="group" aria-label={textContent.alertModeTitle || "Mode d'alerte"}>
{ALERT_MODES.map((option) => ( {ALERT_MODES.map((option) => (
<button <button
key={option.mode} key={option.mode}

View file

@ -29,14 +29,14 @@ export function CalendarEventForm({
<div className="calendar-form-row calendar-form-main-row"> <div className="calendar-form-row calendar-form-main-row">
<label className="calendar-title-field"> <label className="calendar-title-field">
<span>{textContent.titleLabel || "Titre"}</span> <span>{textContent.titleLabel || "Titre"}</span>
<input className="nds-input" data-size="sm" type="text" value={draft.title} onChange={(event) => onDraftChange({ title: event.target.value })} placeholder={textContent.titlePlaceholder || "Raid, reset, event..."} /> <input value={draft.title} onChange={(event) => onDraftChange({ title: event.target.value })} placeholder={textContent.titlePlaceholder || "Raid, reset, event..."} />
</label> </label>
<CalendarColorPicker value={draft.color} textContent={textContent} onChange={(color) => onDraftChange({ color })} /> <CalendarColorPicker value={draft.color} textContent={textContent} onChange={(color) => onDraftChange({ color })} />
{draft.id && <div className="calendar-delete-actions"> {draft.id && <div className="calendar-delete-actions">
{data.viewMode === "month" && draft.repeat !== "none" && activeOccurrenceDate && ( {data.viewMode === "month" && draft.repeat !== "none" && activeOccurrenceDate && (
<button className="calendar-form-delete-button nds-icon-button" data-size="sm" data-variant="danger" type="button" onClick={() => onDeleteOccurrence(draft.id)} aria-label={textContent.deleteOccurrenceTitle || "Supprimer cette occurrence"} title={textContent.deleteOccurrenceTitle || "Supprimer cette occurrence"}><Icon name="trashcan-one" /></button> <button className="danger calendar-form-delete-button" type="button" onClick={() => onDeleteOccurrence(draft.id)} aria-label={textContent.deleteOccurrenceTitle || "Supprimer cette occurrence"} title={textContent.deleteOccurrenceTitle || "Supprimer cette occurrence"}><Icon name="trashcan-one" /></button>
)} )}
<button className="calendar-form-delete-button nds-icon-button" data-size="sm" data-variant="danger" type="button" onClick={() => onDeleteEvent(draft.id)} aria-label={data.viewMode === "month" && draft.repeat !== "none" ? textContent.deleteSeriesTitle || "Supprimer toutes les répétitions" : textContent.deleteTitle || "Supprimer"} title={data.viewMode === "month" && draft.repeat !== "none" ? textContent.deleteSeriesTitle || "Supprimer toutes les répétitions" : textContent.deleteTitle || "Supprimer"}><Icon name="trash" /></button> <button className="danger calendar-form-delete-button" type="button" onClick={() => onDeleteEvent(draft.id)} aria-label={data.viewMode === "month" && draft.repeat !== "none" ? textContent.deleteSeriesTitle || "Supprimer toutes les répétitions" : textContent.deleteTitle || "Supprimer"} title={data.viewMode === "month" && draft.repeat !== "none" ? textContent.deleteSeriesTitle || "Supprimer toutes les répétitions" : textContent.deleteTitle || "Supprimer"}><Icon name="trash" /></button>
</div>} </div>}
</div> </div>
<div className={`calendar-form-row ${data.viewMode === "month" ? "calendar-form-month-row" : "calendar-form-time-row"}`}> <div className={`calendar-form-row ${data.viewMode === "month" ? "calendar-form-month-row" : "calendar-form-time-row"}`}>
@ -67,8 +67,6 @@ export function CalendarEventForm({
/> />
) : ( ) : (
<select <select
className="nds-select"
data-size="sm"
value={draft.dayIndex} value={draft.dayIndex}
onChange={(event) => { onChange={(event) => {
const value = Number(event.target.value); const value = Number(event.target.value);
@ -99,11 +97,11 @@ export function CalendarEventForm({
<> <>
<label className="calendar-time-field"> <label className="calendar-time-field">
<span>{textContent.startLabel || "Début"}</span> <span>{textContent.startLabel || "Début"}</span>
<input className="nds-time" data-size="xs" type="time" step={WEEK_TIME_INPUT_STEP_MINUTES * 60} value={draft.startTime} onChange={(event) => onDraftChange({ startTime: event.target.value })} /> <input type="time" step={WEEK_TIME_INPUT_STEP_MINUTES * 60} value={draft.startTime} onChange={(event) => onDraftChange({ startTime: event.target.value })} />
</label> </label>
<label className="calendar-time-field"> <label className="calendar-time-field">
<span>{textContent.endLabel || "Fin"}</span> <span>{textContent.endLabel || "Fin"}</span>
<input className="nds-time" data-size="xs" type="time" step={WEEK_TIME_INPUT_STEP_MINUTES * 60} value={draft.endTime} onChange={(event) => onDraftChange({ endTime: event.target.value })} /> <input type="time" step={WEEK_TIME_INPUT_STEP_MINUTES * 60} value={draft.endTime} onChange={(event) => onDraftChange({ endTime: event.target.value })} />
</label> </label>
</> </>
)} )}
@ -115,8 +113,7 @@ export function CalendarEventForm({
) : <div className="calendar-repeat-field"> ) : <div className="calendar-repeat-field">
<span>{textContent.repeatableTitle || "Répétable"}</span> <span>{textContent.repeatableTitle || "Répétable"}</span>
<button <button
className={`tool-split-scroll-toggle calendar-repeat-switch nds-switch ${draft.repeatable !== false ? "active" : ""}`} className={`tool-split-scroll-toggle calendar-repeat-switch ${draft.repeatable !== false ? "active" : ""}`}
data-size="lg"
type="button" type="button"
onClick={() => onDraftChange({ repeatable: draft.repeatable === false })} onClick={() => onDraftChange({ repeatable: draft.repeatable === false })}
aria-pressed={draft.repeatable !== false} aria-pressed={draft.repeatable !== false}
@ -134,8 +131,8 @@ export function CalendarEventForm({
<CalendarAlertModeSwitch mode={draft.alertMode} textContent={textContent} onChange={onAlertModeChange} /> <CalendarAlertModeSwitch mode={draft.alertMode} textContent={textContent} onChange={onAlertModeChange} />
</div> </div>
<div className="calendar-form-actions"> <div className="calendar-form-actions">
<button className="nds-button" data-size="sm" data-variant="primary" type="submit">{draft.id ? textContent.saveButton || "Enregistrer" : textContent.addButton || "Ajouter"}</button> <button className="primary" type="submit">{draft.id ? textContent.saveButton || "Enregistrer" : textContent.addButton || "Ajouter"}</button>
<button className="nds-button" data-size="sm" type="button" onClick={onClose}>{textContent.cancelButton || "Annuler"}</button> <button type="button" onClick={onClose}>{textContent.cancelButton || "Annuler"}</button>
</div> </div>
</div> </div>
{error && <p className="timer-form-error calendar-form-error" role="alert"><Icon name="warning" /><span>{error}</span></p>} {error && <p className="timer-form-error calendar-form-error" role="alert"><Icon name="warning" /><span>{error}</span></p>}

View file

@ -48,7 +48,7 @@ export function MonthDateDropdown({ value, displayedMonth, minDate = "", mode, t
const safeValue = normalizeDraftDateKey(value) || formatLocalDateKey(displayedMonth); const safeValue = normalizeDraftDateKey(value) || formatLocalDateKey(displayedMonth);
return ( return (
<div className={`calendar-month-date-dropdown ${align === "end" ? "is-aligned-end" : ""}`}> <div className={`calendar-month-date-dropdown ${align === "end" ? "is-aligned-end" : ""}`}>
<button className="calendar-month-date-trigger nds-date-trigger" data-size="sm" type="button" aria-label={label}> <button className="calendar-month-date-trigger" type="button" aria-label={label}>
<Icon name={mode === "weekday" ? "day-name" : "day-number"} /> <Icon name={mode === "weekday" ? "day-name" : "day-number"} />
<span>{formatMonthDateLabel(safeValue, mode)}</span> <span>{formatMonthDateLabel(safeValue, mode)}</span>
<Icon name="chevron-down" /> <Icon name="chevron-down" />

View file

@ -4,8 +4,6 @@ import {
WEEK_SLOT_MINUTES, WEEK_SLOT_MINUTES,
addDays, addDays,
formatLocalDateKey, formatLocalDateKey,
getCalendarColorValue,
getCalendarEventSurfaceVariant,
minutesToTimeValue, minutesToTimeValue,
pad2 pad2
} from "./calendarUtils.js"; } from "./calendarUtils.js";
@ -139,26 +137,19 @@ function CalendarOccurrence({ occurrence, layout, active, onEdit }) {
const durationMinutes = Math.max(WEEK_SLOT_MINUTES, (occurrence.endMs - occurrence.startMs) / 60000 || WEEK_SLOT_MINUTES); const durationMinutes = Math.max(WEEK_SLOT_MINUTES, (occurrence.endMs - occurrence.startMs) / 60000 || WEEK_SLOT_MINUTES);
const startMinutes = occurrence.event.startMinutes; const startMinutes = occurrence.event.startMinutes;
const timeLabel = `${minutesToTimeValue(occurrence.event.startMinutes)}${occurrence.event.endMinutes ? ` - ${minutesToTimeValue(occurrence.event.endMinutes)}` : ""}`; const timeLabel = `${minutesToTimeValue(occurrence.event.startMinutes)}${occurrence.event.endMinutes ? ` - ${minutesToTimeValue(occurrence.event.endMinutes)}` : ""}`;
const compact = durationMinutes < 120;
const top = getWeekMinutePosition(layout, startMinutes); const top = getWeekMinutePosition(layout, startMinutes);
const bottom = getWeekMinutePosition(layout, Math.min(24 * 60, startMinutes + durationMinutes)); const bottom = getWeekMinutePosition(layout, Math.min(24 * 60, startMinutes + durationMinutes));
const naturalHeight = bottom - top; const height = compact ? WEEK_COMPACT_EVENT_HEIGHT : Math.max(WEEK_COMPACT_EVENT_HEIGHT, bottom - top);
const eventSurfaceSize = naturalHeight < WEEK_COMPACT_EVENT_HEIGHT ? "xs" : naturalHeight < 44 ? "sm" : "md";
const height = eventSurfaceSize === "xs" ? 10 : eventSurfaceSize === "sm" ? WEEK_COMPACT_EVENT_HEIGHT : Math.max(44, naturalHeight);
const color = occurrence.event.color || "violet";
return ( return (
<article <article
className={`calendar-event nds-event-surface ${active ? "is-active" : ""} ${eventSurfaceSize !== "md" ? "is-compact" : ""}`} className={`calendar-event calendar-event-${occurrence.event.color || "violet"} ${active ? "is-active" : ""} ${compact ? "is-compact" : ""}`}
data-color={getCalendarColorValue(color)}
data-size={eventSurfaceSize}
data-variant={getCalendarEventSurfaceVariant(color)}
style={{ style={{
top: `${top}px`, top: `${top}px`,
width: "calc((100% / var(--calendar-event-lane-count, 1)) - 2px)",
height: `${height}px`, height: `${height}px`,
"--calendar-event-lane": occurrence.lane || 0, "--calendar-event-lane": occurrence.lane || 0,
"--calendar-event-lane-count": occurrence.laneCount || 1, "--calendar-event-lane-count": occurrence.laneCount || 1
"--nds-event-color": getCalendarColorValue(color)
}} }}
> >
<Tooltip <Tooltip
@ -170,7 +161,7 @@ function CalendarOccurrence({ occurrence, layout, active, onEdit }) {
onClick={onEdit} onClick={onEdit}
> >
<strong>{occurrence.event.title}</strong> <strong>{occurrence.event.title}</strong>
<span>{timeLabel}</span> {!compact && <span>{timeLabel}</span>}
</Tooltip> </Tooltip>
</article> </article>
); );
@ -197,10 +188,10 @@ export function CalendarMonthView({
return ( return (
<> <>
<div className="calendar-month-toolbar"> <div className="calendar-month-toolbar">
<button className="nds-button" data-size="xs" type="button" onClick={onPreviousMonth} title={textContent.previousMonthTitle || "Mois précédent"} aria-label={textContent.previousMonthTitle || "Mois précédent"}>{textContent.previousShortLabel || "Prec."}</button> <button type="button" onClick={onPreviousMonth} title={textContent.previousMonthTitle || "Mois précédent"} aria-label={textContent.previousMonthTitle || "Mois précédent"}>{textContent.previousShortLabel || "Prec."}</button>
<strong>{formatMonthTitle(monthStart)}</strong> <strong>{formatMonthTitle(monthStart)}</strong>
<button className="nds-button" data-size="xs" type="button" onClick={onCurrentMonth}>{textContent.currentMonthTitle || "Mois courant"}</button> <button type="button" onClick={onCurrentMonth}>{textContent.currentMonthTitle || "Mois courant"}</button>
<button className="nds-button" data-size="xs" type="button" onClick={onNextMonth} title={textContent.nextMonthTitle || "Mois suivant"} aria-label={textContent.nextMonthTitle || "Mois suivant"}>{textContent.nextShortLabel || "Suiv."}</button> <button type="button" onClick={onNextMonth} title={textContent.nextMonthTitle || "Mois suivant"} aria-label={textContent.nextMonthTitle || "Mois suivant"}>{textContent.nextShortLabel || "Suiv."}</button>
</div> </div>
<CalendarMonthGrid <CalendarMonthGrid
days={monthGridDays} days={monthGridDays}
@ -277,29 +268,22 @@ function CalendarMonthBar({ segment, active, onEdit }) {
const startColumn = segment.startIndex % WEEKDAY_LABELS.length + 1; const startColumn = segment.startIndex % WEEKDAY_LABELS.length + 1;
const endColumn = segment.endIndex % WEEKDAY_LABELS.length + 2; const endColumn = segment.endIndex % WEEKDAY_LABELS.length + 2;
const laneCount = Math.max(1, Math.min(3, segment.laneCount || 1)); const laneCount = Math.max(1, Math.min(3, segment.laneCount || 1));
const barHeight = laneCount <= 2 ? 22 : 10; const barHeight = laneCount === 1 ? 28 : laneCount === 2 ? 23 : 18;
const barGap = laneCount === 3 ? 2 : 4; const barGap = laneCount === 3 ? 3 : 4;
const eventSurfaceSize = barHeight <= 10 ? "xs" : barHeight <= 22 ? "sm" : "md";
const startLabel = new Date(segment.occurrence.startMs).toLocaleDateString(undefined, { day: "2-digit", month: "short", year: "numeric" }); const startLabel = new Date(segment.occurrence.startMs).toLocaleDateString(undefined, { day: "2-digit", month: "short", year: "numeric" });
const endDate = addDays(new Date(segment.occurrence.endMs), -1); const endDate = addDays(new Date(segment.occurrence.endMs), -1);
const endLabel = endDate.toLocaleDateString(undefined, { day: "2-digit", month: "short", year: "numeric" }); const endLabel = endDate.toLocaleDateString(undefined, { day: "2-digit", month: "short", year: "numeric" });
const dateLabel = startLabel === endLabel ? startLabel : `${startLabel} - ${endLabel}`; const dateLabel = startLabel === endLabel ? startLabel : `${startLabel} - ${endLabel}`;
const color = segment.occurrence.event.color || "violet";
return ( return (
<article <article
className={`calendar-month-bar nds-event-surface ${active ? "is-active" : ""}`} className={`calendar-month-bar calendar-event-${segment.occurrence.event.color || "violet"} ${active ? "is-active" : ""}`}
data-color={getCalendarColorValue(color)}
data-size={eventSurfaceSize}
data-variant={getCalendarEventSurfaceVariant(color)}
style={{ style={{
gridColumn: `${startColumn} / ${endColumn}`, gridColumn: `${startColumn} / ${endColumn}`,
gridRow, gridRow,
width: "auto",
"--calendar-event-lane": segment.lane || 0, "--calendar-event-lane": segment.lane || 0,
"--calendar-month-bar-height": `${barHeight}px`, "--calendar-month-bar-height": `${barHeight}px`,
"--calendar-month-bar-gap": `${barGap}px`, "--calendar-month-bar-gap": `${barGap}px`
"--nds-event-color": getCalendarColorValue(color)
}} }}
> >
<Tooltip <Tooltip

View file

@ -113,10 +113,10 @@ export function ChecklistModule({ toolboxId, moduleId, context, editing, textExc
{editing && ( {editing && (
<div className="module-add-panel"> <div className="module-add-panel">
<form className="inline-form checklist-add-form" onSubmit={addItem}> <form className="inline-form checklist-add-form" onSubmit={addItem}>
<input className="nds-input" data-size="sm" name="section" type="text" placeholder={textContent.sectionPlaceholder || "Catégorie"} value={sectionTitle} onChange={(event) => setSectionTitle(event.target.value)} /> <input name="section" placeholder={textContent.sectionPlaceholder || "Catégorie"} value={sectionTitle} onChange={(event) => setSectionTitle(event.target.value)} />
<input className="nds-input" data-size="sm" name="label" type="text" placeholder={textContent.itemPlaceholder || "Nouvel item"} value={label} onChange={(event) => setLabel(event.target.value)} /> <input name="label" placeholder={textContent.itemPlaceholder || "Nouvel item"} value={label} onChange={(event) => setLabel(event.target.value)} />
<input className="checklist-qty-input nds-input" data-size="sm" name="qty" type="number" min="1" value={qty} onChange={(event) => setQty(event.target.value)} aria-label={textContent.quantityLabel || "Quantité cible"} /> <input className="checklist-qty-input" name="qty" type="number" min="1" value={qty} onChange={(event) => setQty(event.target.value)} aria-label={textContent.quantityLabel || "Quantité cible"} />
<button className="nds-button" data-size="sm" data-variant="primary">{textContent.addButton || "Ajouter"}</button> <button className="primary">{textContent.addButton || "Ajouter"}</button>
</form> </form>
</div> </div>
)} )}
@ -126,8 +126,7 @@ export function ChecklistModule({ toolboxId, moduleId, context, editing, textExc
</div> </div>
)} )}
{namedSections.length > 0 && ( {namedSections.length > 0 && (
<div className="checklist-toolbar"> <div className="checklist-toolbar" role="radiogroup" aria-label={textContent.completedSectionsModeTitle || "Affichage des catégories terminées"}>
<div className="checklist-complete-mode-group nds-radio-button-group" data-size="xs" role="radiogroup" aria-label={textContent.completedSectionsModeTitle || "Affichage des catégories terminées"}>
{[ {[
{ mode: "visible", label: textContent.completedSectionsVisibleLabel || "Visible", icon: "eye-open" }, { mode: "visible", label: textContent.completedSectionsVisibleLabel || "Visible", icon: "eye-open" },
{ mode: "reduced", label: textContent.completedSectionsReducedLabel || "Réduit", icon: "hide" }, { mode: "reduced", label: textContent.completedSectionsReducedLabel || "Réduit", icon: "hide" },
@ -147,7 +146,6 @@ export function ChecklistModule({ toolboxId, moduleId, context, editing, textExc
</button> </button>
))} ))}
</div> </div>
</div>
)} )}
<ul className="checklist"> <ul className="checklist">
{visibleSections.map((section, index) => { {visibleSections.map((section, index) => {

View file

@ -67,9 +67,7 @@ export function ChecklistSection({ section, context, data, reorder, onRenameSect
{showTitle && ( {showTitle && (
<div className="checklist-section-header"> <div className="checklist-section-header">
<button <button
className="checklist-drag-handle nds-icon-button" className="checklist-drag-handle"
data-size="xs"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.groupReorder.startDrag(event, { groupId: section.id })} onPointerDown={(event) => reorder.groupReorder.startDrag(event, { groupId: section.id })}
aria-label={`${textContent.reorderSectionTitle || "Déplacer la catégorie"} ${section.title}`} aria-label={`${textContent.reorderSectionTitle || "Déplacer la catégorie"} ${section.title}`}
@ -81,7 +79,6 @@ export function ChecklistSection({ section, context, data, reorder, onRenameSect
<input <input
{...titleEdit.getInputProps({ {...titleEdit.getInputProps({
className: "checklist-section-title-input", className: "checklist-section-title-input",
type: "text",
title: textContent.sectionEditTitle || "Renommer la catégorie", title: textContent.sectionEditTitle || "Renommer la catégorie",
"aria-label": textContent.sectionEditTitle || "Renommer la catégorie" "aria-label": textContent.sectionEditTitle || "Renommer la catégorie"
})} })}
@ -90,8 +87,7 @@ export function ChecklistSection({ section, context, data, reorder, onRenameSect
<div className="checklist-section-actions"> <div className="checklist-section-actions">
<span>{section.items.filter((item) => context.clampQty(item.qtyCurrent, item.qtyTarget) >= item.qtyTarget).length} / {section.items.length}</span> <span>{section.items.filter((item) => context.clampQty(item.qtyCurrent, item.qtyTarget) >= item.qtyTarget).length} / {section.items.length}</span>
<button <button
className="checklist-section-collapse-button nds-icon-button" className="checklist-section-collapse-button"
data-size="xs"
type="button" type="button"
onClick={toggleSectionCollapsed} onClick={toggleSectionCollapsed}
aria-expanded={!isCollapsed} aria-expanded={!isCollapsed}
@ -164,9 +160,7 @@ function ChecklistItem({ item, section, context, reorder, items, save, onUpdateI
<li className={className} {...reorder.getItemProps({ itemId: item.id, groupId: section.id })}> <li className={className} {...reorder.getItemProps({ itemId: item.id, groupId: section.id })}>
<div className={`checklist-item-main ${editing ? "is-editing" : ""}`}> <div className={`checklist-item-main ${editing ? "is-editing" : ""}`}>
<button <button
className="checklist-drag-handle nds-icon-button" className="checklist-drag-handle"
data-size="xs"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, item.id)} onPointerDown={(event) => reorder.itemReorder.startDrag(event, item.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${item.label}`} aria-label={`${textContent.reorderTitle || "Déplacer"} ${item.label}`}
@ -176,20 +170,17 @@ function ChecklistItem({ item, section, context, reorder, items, save, onUpdateI
</button> </button>
{item.qtyTarget === 1 ? ( {item.qtyTarget === 1 ? (
<input <input
className="nds-checkbox"
data-size="sm"
type="checkbox" type="checkbox"
checked={done} checked={done}
onChange={(event) => updateItem((entry) => ({ ...entry, qtyTarget: 1, qtyCurrent: event.target.checked ? 1 : 0 }))} onChange={(event) => updateItem((entry) => ({ ...entry, qtyTarget: 1, qtyCurrent: event.target.checked ? 1 : 0 }))}
aria-label={`Terminer ${item.label}`} aria-label={`Terminer ${item.label}`}
/> />
) : ( ) : (
<div className="checklist-qty-controls nds-quantity-stepper" data-size="md" aria-label={`Quantité ${item.label}`}> <div className="checklist-qty-controls" aria-label={`Quantité ${item.label}`}>
<button type="button" onClick={() => updateItem((entry) => ({ ...entry, qtyCurrent: context.clampQty(entry.qtyCurrent - 1, entry.qtyTarget) }))} aria-label={textContent.decrementLabel || "Retirer une quantité"}>-</button> <button type="button" onClick={() => updateItem((entry) => ({ ...entry, qtyCurrent: context.clampQty(entry.qtyCurrent - 1, entry.qtyTarget) }))} aria-label={textContent.decrementLabel || "Retirer une quantité"}>-</button>
<label className="checklist-qty-current"> <label className="checklist-qty-current">
<input <input
type="number" type="number"
className="nds-quantity-stepper-input"
max={item.qtyTarget} max={item.qtyTarget}
value={context.clampQty(item.qtyCurrent, item.qtyTarget)} value={context.clampQty(item.qtyCurrent, item.qtyTarget)}
style={{ "--qty-current-digits": String(context.clampQty(item.qtyCurrent, item.qtyTarget)).length }} style={{ "--qty-current-digits": String(context.clampQty(item.qtyCurrent, item.qtyTarget)).length }}
@ -205,15 +196,13 @@ function ChecklistItem({ item, section, context, reorder, items, save, onUpdateI
<> <>
<input <input
{...labelEdit.getInputProps({ {...labelEdit.getInputProps({
className: "checklist-label-input nds-inline-input", className: "checklist-label-input",
type: "text",
"aria-label": textContent.itemEditLabel || "Libellé" "aria-label": textContent.itemEditLabel || "Libellé"
})} })}
/> />
<input <input
{...categoryEdit.getInputProps({ {...categoryEdit.getInputProps({
className: "checklist-category-input nds-inline-input", className: "checklist-category-input",
type: "text",
placeholder: textContent.sectionPlaceholder || "Catégorie", placeholder: textContent.sectionPlaceholder || "Catégorie",
"aria-label": textContent.categoryEditLabel || "Catégorie" "aria-label": textContent.categoryEditLabel || "Catégorie"
})} })}
@ -225,10 +214,10 @@ function ChecklistItem({ item, section, context, reorder, items, save, onUpdateI
</button> </button>
)} )}
</div> </div>
<button type="button" className="checklist-edit-button nds-icon-button" data-size="sm" onClick={onToggleEditing} aria-label={`${textContent.editTitle || "Modifier"} ${item.label}`} title={textContent.editTitle || "Modifier"}> <button type="button" className="checklist-edit-button" onClick={onToggleEditing} aria-label={`${textContent.editTitle || "Modifier"} ${item.label}`} title={textContent.editTitle || "Modifier"}>
<Icon name="edit" /> <Icon name="edit" />
</button> </button>
<button className="checklist-delete-button nds-icon-button" data-size="sm" data-variant="danger" onClick={() => onDeleteItem(item.id)} aria-label={`${textContent.deleteTitle || "Supprimer"} ${item.label}`} title={textContent.deleteTitle || "Supprimer"}> <button className="checklist-delete-button danger" onClick={() => onDeleteItem(item.id)} aria-label={`${textContent.deleteTitle || "Supprimer"} ${item.label}`} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</li> </li>

View file

@ -48,14 +48,14 @@ export function CombosEditor({
<div className="combos-device-row"> <div className="combos-device-row">
<label> <label>
<span>{textContent.deviceLabel || "Périphérique"}</span> <span>{textContent.deviceLabel || "Périphérique"}</span>
<select className="nds-select" data-size="sm" value={data.device} onChange={(event) => onDeviceChange(event.target.value)}> <select value={data.device} onChange={(event) => onDeviceChange(event.target.value)}>
{DEVICE_OPTIONS.map((device) => ( {DEVICE_OPTIONS.map((device) => (
<option key={device.value} value={device.value}>{device.label}</option> <option key={device.value} value={device.value}>{device.label}</option>
))} ))}
</select> </select>
</label> </label>
{data.device === "keyboardMouse" && ( {data.device === "keyboardMouse" && (
<div className="combos-keyboard-layout-switch nds-radio-button-group" data-size="sm" role="group" aria-label={textContent.keyboardLayoutLabel || "Disposition clavier"}> <div className="combos-keyboard-layout-switch" role="group" aria-label={textContent.keyboardLayoutLabel || "Disposition clavier"}>
{[ {[
{ value: "azerty", label: "AZERTY" }, { value: "azerty", label: "AZERTY" },
{ value: "qwerty", label: "QWERTY" } { value: "qwerty", label: "QWERTY" }
@ -78,11 +78,11 @@ export function CombosEditor({
<div className="combos-form-grid"> <div className="combos-form-grid">
<label> <label>
<span>{textContent.targetCategoryLabel || "Catégorie"}</span> <span>{textContent.targetCategoryLabel || "Catégorie"}</span>
<input className="nds-input" data-size="sm" type="text" value={comboCategory} onChange={(event) => onComboCategoryChange(event.target.value)} placeholder={textContent.noCategoryLabel || "Sans catégorie"} /> <input value={comboCategory} onChange={(event) => onComboCategoryChange(event.target.value)} placeholder={textContent.noCategoryLabel || "Sans catégorie"} />
</label> </label>
<label> <label>
<span>{textContent.comboNameLabel || "Nom"}</span> <span>{textContent.comboNameLabel || "Nom"}</span>
<input className="nds-input" data-size="sm" type="text" value={comboName} onChange={(event) => onComboNameChange(event.target.value)} placeholder={textContent.comboNamePlaceholder || "Nom du combo"} /> <input value={comboName} onChange={(event) => onComboNameChange(event.target.value)} placeholder={textContent.comboNamePlaceholder || "Nom du combo"} />
</label> </label>
</div> </div>
<div className="combos-palette" aria-label={textContent.paletteLabel || "Palette de touches"}> <div className="combos-palette" aria-label={textContent.paletteLabel || "Palette de touches"}>
@ -115,8 +115,8 @@ export function CombosEditor({
{data.device === "keyboardMouse" && ( {data.device === "keyboardMouse" && (
<div className="inline-form combos-custom-key-form"> <div className="inline-form combos-custom-key-form">
<input className="nds-input" data-size="sm" type="text" value={customKey} onChange={(event) => onCustomKeyChange(event.target.value)} placeholder={textContent.customKeyPlaceholder || "Touche personnalisée"} /> <input value={customKey} onChange={(event) => onCustomKeyChange(event.target.value)} placeholder={textContent.customKeyPlaceholder || "Touche personnalisée"} />
<button className="nds-button" data-size="sm" type="button" onClick={onCustomKeyAdd}>{textContent.addCustomKeyButton || "Ajouter touche"}</button> <button type="button" onClick={onCustomKeyAdd}>{textContent.addCustomKeyButton || "Ajouter touche"}</button>
</div> </div>
)} )}
@ -135,7 +135,7 @@ export function CombosEditor({
/> />
<div className="combo-draft-actions"> <div className="combo-draft-actions">
<label className="combo-hold-field"> <label className="combo-hold-field">
<span className="combo-hold-controls nds-compound-toggle-field"> <span className="combo-hold-controls">
<button <button
type="button" type="button"
className={selectedDraftInput?.hold || selectedDraftInput?.holdMs ? "active" : ""} className={selectedDraftInput?.hold || selectedDraftInput?.holdMs ? "active" : ""}
@ -146,7 +146,6 @@ export function CombosEditor({
{textContent.holdToggleLabel || "Hold"} {textContent.holdToggleLabel || "Hold"}
</button> </button>
<input <input
className="nds-compound-toggle-field-input"
type="number" type="number"
min="0" min="0"
max="99" max="99"
@ -162,8 +161,7 @@ export function CombosEditor({
</label> </label>
<button <button
type="button" type="button"
className={`combos-simultaneous-toggle nds-radio-button-group ${simultaneousMode ? "active" : ""}`} className={`combos-simultaneous-toggle ${simultaneousMode ? "active" : ""}`}
data-size="xs"
onClick={onSimultaneousModeToggle} onClick={onSimultaneousModeToggle}
aria-pressed={simultaneousMode} aria-pressed={simultaneousMode}
title={textContent.simultaneousModeTitle || "Ajout simultané"} title={textContent.simultaneousModeTitle || "Ajout simultané"}
@ -172,19 +170,19 @@ export function CombosEditor({
<span className={!simultaneousMode ? "active" : ""}>{textContent.successiveModeLabel || "A › B"}</span> <span className={!simultaneousMode ? "active" : ""}>{textContent.successiveModeLabel || "A › B"}</span>
<span className={simultaneousMode ? "active" : ""}>{textContent.simultaneousModeLabel || "A + B"}</span> <span className={simultaneousMode ? "active" : ""}>{textContent.simultaneousModeLabel || "A + B"}</span>
</button> </button>
<button type="button" className="notepad-toolbar-button nds-icon-button" data-size="sm" onClick={onLastInputRemove} disabled={!cleanDraftInputs.length} title={textContent.removeLastInputTitle || "Retirer la dernière touche"} aria-label={textContent.removeLastInputTitle || "Retirer la dernière touche"}> <button type="button" className="notepad-toolbar-button" onClick={onLastInputRemove} disabled={!cleanDraftInputs.length} title={textContent.removeLastInputTitle || "Retirer la dernière touche"} aria-label={textContent.removeLastInputTitle || "Retirer la dernière touche"}>
<Icon name="back" /> <Icon name="back" />
</button> </button>
<button type="button" className="notepad-toolbar-button nds-icon-button" data-size="sm" data-variant="danger" onClick={onActiveStepRemove} disabled={draftInputs.length <= 1} title={textContent.removeStepTitle || "Supprimer l'étape"} aria-label={textContent.removeStepTitle || "Supprimer l'étape"}> <button type="button" className="notepad-toolbar-button danger" onClick={onActiveStepRemove} disabled={draftInputs.length <= 1} title={textContent.removeStepTitle || "Supprimer l'étape"} aria-label={textContent.removeStepTitle || "Supprimer l'étape"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
<div className="combo-save-actions"> <div className="combo-save-actions">
{editingComboId && ( {editingComboId && (
<button type="button" className="combo-cancel-button nds-button" data-size="sm" onClick={onComboEditCancel}> <button type="button" className="combo-cancel-button" onClick={onComboEditCancel}>
{textContent.cancelEditButton || "Annuler"} {textContent.cancelEditButton || "Annuler"}
</button> </button>
)} )}
<button className="nds-button" data-size="sm" data-variant="primary" disabled={!comboName.trim() && !cleanDraftInputs.length}> <button className="primary" disabled={!comboName.trim() && !cleanDraftInputs.length}>
{editingComboId ? textContent.saveComboButton || "Enregistrer" : textContent.addComboButton || "Ajouter le combo"} {editingComboId ? textContent.saveComboButton || "Enregistrer" : textContent.addComboButton || "Ajouter le combo"}
</button> </button>
</div> </div>

View file

@ -27,9 +27,7 @@ export function CombosList({
return ( return (
<article className={comboClassName} key={combo.id} {...reorder.getItemProps({ itemId: combo.id, groupId: category })}> <article className={comboClassName} key={combo.id} {...reorder.getItemProps({ itemId: combo.id, groupId: category })}>
<button <button
className="task-planner-drag-handle combo-drag-handle nds-icon-button" className="task-planner-drag-handle combo-drag-handle"
data-size="xs"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, combo.id)} onPointerDown={(event) => reorder.itemReorder.startDrag(event, combo.id)}
aria-label={`${textContent.reorderComboTitle || "Déplacer le combo"} ${combo.name}`} aria-label={`${textContent.reorderComboTitle || "Déplacer le combo"} ${combo.name}`}
@ -47,10 +45,10 @@ export function CombosList({
<ComboSequence inputs={combo.inputs} device={combo.device || data.device} emptyLabel={textContent.emptySequence || "Combo sans touches"} /> <ComboSequence inputs={combo.inputs} device={combo.device || data.device} emptyLabel={textContent.emptySequence || "Combo sans touches"} />
</div> </div>
<div className="combo-card-actions"> <div className="combo-card-actions">
<button type="button" className="checklist-delete-button nds-icon-button" data-size="sm" onClick={() => onComboEdit(combo)} aria-label={`${textContent.editComboTitle || "Modifier le combo"} ${combo.name}`} title={textContent.editComboTitle || "Modifier le combo"}> <button type="button" className="checklist-delete-button" onClick={() => onComboEdit(combo)} aria-label={`${textContent.editComboTitle || "Modifier le combo"} ${combo.name}`} title={textContent.editComboTitle || "Modifier le combo"}>
<Icon name="edit" /> <Icon name="edit" />
</button> </button>
<button type="button" className="checklist-delete-button nds-icon-button" data-size="sm" data-variant="danger" onClick={() => onComboDelete(combo.id)} aria-label={`${textContent.deleteComboTitle || "Supprimer le combo"} ${combo.name}`} title={textContent.deleteComboTitle || "Supprimer le combo"}> <button type="button" className="checklist-delete-button danger" onClick={() => onComboDelete(combo.id)} aria-label={`${textContent.deleteComboTitle || "Supprimer le combo"} ${combo.name}`} title={textContent.deleteComboTitle || "Supprimer le combo"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</div> </div>
@ -90,9 +88,7 @@ export function CombosList({
<div className="checklist-section-header combos-category-header"> <div className="checklist-section-header combos-category-header">
<div className="combos-category-title"> <div className="combos-category-title">
<button <button
className="task-planner-category-drag-handle nds-icon-button" className="task-planner-category-drag-handle"
data-size="xs"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.groupReorder.startDrag(event, { groupId: category })} onPointerDown={(event) => reorder.groupReorder.startDrag(event, { groupId: category })}
aria-label={`${textContent.reorderCategoryTitle || "Déplacer la catégorie"} ${category}`} aria-label={`${textContent.reorderCategoryTitle || "Déplacer la catégorie"} ${category}`}
@ -105,8 +101,7 @@ export function CombosList({
<div className="checklist-section-actions"> <div className="checklist-section-actions">
<span>{group.combos.length}</span> <span>{group.combos.length}</span>
<button <button
className="checklist-section-collapse-button nds-icon-button" className="checklist-section-collapse-button"
data-size="xs"
type="button" type="button"
onClick={() => onCategoryToggle(category)} onClick={() => onCategoryToggle(category)}
aria-expanded={!isCollapsed} aria-expanded={!isCollapsed}

View file

@ -83,8 +83,7 @@ export function InlineTextInput({ value, onCommit, className, ariaLabel, placeho
return ( return (
<input <input
{...inlineEdit.getInputProps({ {...inlineEdit.getInputProps({
className: `${className} nds-inline-input`.trim(), className,
type: "text",
placeholder, placeholder,
"aria-label": ariaLabel "aria-label": ariaLabel
})} })}

View file

@ -47,8 +47,8 @@ export function CountersModule({ toolboxId, moduleId, context, editing, textExch
{editing && ( {editing && (
<div className="module-add-panel"> <div className="module-add-panel">
<form className="inline-form counters-add-form" onSubmit={addCounter}> <form className="inline-form counters-add-form" onSubmit={addCounter}>
<input {...counterDraft.getFieldProps("label", { className: "nds-input", "data-size": "sm", type: "text", placeholder: textContent.labelPlaceholder || "Nom du compteur" })} /> <input {...counterDraft.getFieldProps("label", { placeholder: textContent.labelPlaceholder || "Nom du compteur" })} />
<button className="nds-button" data-size="sm" data-variant="primary">{textContent.addButton || "Ajouter"}</button> <button className="primary">{textContent.addButton || "Ajouter"}</button>
</form> </form>
</div> </div>
)} )}
@ -69,9 +69,7 @@ export function CountersModule({ toolboxId, moduleId, context, editing, textExch
return ( return (
<article className={className} key={counter.id} {...getItemProps({ itemId: counter.id, parentId: moduleId })}> <article className={className} key={counter.id} {...getItemProps({ itemId: counter.id, parentId: moduleId })}>
<button <button
className="counter-drag-handle nds-icon-button" className="counter-drag-handle"
data-size="sm"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => itemReorder.startDrag(event, counter.id)} onPointerDown={(event) => itemReorder.startDrag(event, counter.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${counter.label}`} aria-label={`${textContent.reorderTitle || "Déplacer"} ${counter.label}`}
@ -84,12 +82,12 @@ export function CountersModule({ toolboxId, moduleId, context, editing, textExch
<span>{counter.label}</span> <span>{counter.label}</span>
</div> </div>
<div className="counter-actions"> <div className="counter-actions">
<button className="nds-icon-button" data-size="sm" type="button" onClick={() => updateCounter(counter.id, (item) => ({ ...item, value: item.value - 1 }))} aria-label={`${textContent.decrementLabel || "Décrémenter"} ${counter.label}`}>-</button> <button type="button" onClick={() => updateCounter(counter.id, (item) => ({ ...item, value: item.value - 1 }))} aria-label={`${textContent.decrementLabel || "Décrémenter"} ${counter.label}`}>-</button>
<button className="nds-icon-button" data-size="sm" type="button" onClick={() => updateCounter(counter.id, (item) => ({ ...item, value: item.value + 1 }))} aria-label={`${textContent.incrementLabel || "Incrémenter"} ${counter.label}`}>+</button> <button type="button" onClick={() => updateCounter(counter.id, (item) => ({ ...item, value: item.value + 1 }))} aria-label={`${textContent.incrementLabel || "Incrémenter"} ${counter.label}`}>+</button>
<button className="nds-icon-button" data-size="sm" type="button" onClick={() => updateCounter(counter.id, (item) => ({ ...item, value: 0 }))} aria-label={`${textContent.resetTitle || "Réinitialiser"} ${counter.label}`} title={textContent.resetTitle || "Réinitialiser"}> <button type="button" onClick={() => updateCounter(counter.id, (item) => ({ ...item, value: 0 }))} aria-label={`${textContent.resetTitle || "Réinitialiser"} ${counter.label}`} title={textContent.resetTitle || "Réinitialiser"}>
<Icon name="rubber" /> <Icon name="rubber" />
</button> </button>
<button type="button" className="nds-icon-button" data-size="sm" data-variant="danger" onClick={() => save(data.counters.filter((item) => item.id !== counter.id))} aria-label={`${textContent.deleteTitle || "Supprimer"} ${counter.label}`} title={textContent.deleteTitle || "Supprimer"}> <button type="button" className="danger" onClick={() => save(data.counters.filter((item) => item.id !== counter.id))} aria-label={`${textContent.deleteTitle || "Supprimer"} ${counter.label}`} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</div> </div>

View file

@ -7,14 +7,13 @@ const DEFAULT_WIDTHS = [2, 4, 8, 12];
export function DrawingControls({ textContent = {}, active, onToggleActive, eraseActive, onToggleErase, color, colors, onColorChange, width, widths = DEFAULT_WIDTHS, onWidthChange, onUndo, onClear, storageMode, onToggleStorageMode }) { export function DrawingControls({ textContent = {}, active, onToggleActive, eraseActive, onToggleErase, color, colors, onColorChange, width, widths = DEFAULT_WIDTHS, onWidthChange, onUndo, onClear, storageMode, onToggleStorageMode }) {
return ( return (
<div className={`drawing-controls-panel ${active ? "is-open" : ""}`} aria-label={textContent.drawingLabel || "Dessin"}> <div className={`drawing-controls-panel ${active ? "is-open" : ""}`} aria-label={textContent.drawingLabel || "Dessin"}>
<button className={`drawing-control-button nds-icon-button ${active ? "active" : ""}`} data-size="sm" type="button" aria-pressed={active} title={textContent.drawTitle || "Dessiner"} onClick={onToggleActive}> <button className={`drawing-control-button ${active ? "active" : ""}`} type="button" aria-pressed={active} title={textContent.drawTitle || "Dessiner"} onClick={onToggleActive}>
<Icon name="edit" /> <Icon name="edit" />
</button> </button>
{active && ( {active && (
<> <>
<button <button
className={`drawing-control-button nds-icon-button ${eraseActive ? "active" : ""}`} className={`drawing-control-button ${eraseActive ? "active" : ""}`}
data-size="sm"
type="button" type="button"
aria-pressed={eraseActive} aria-pressed={eraseActive}
title={textContent.eraseTitle || "Gommer"} title={textContent.eraseTitle || "Gommer"}
@ -34,16 +33,15 @@ export function DrawingControls({ textContent = {}, active, onToggleActive, eras
value={width} value={width}
onSelect={onWidthChange} onSelect={onWidthChange}
/> />
<button className="drawing-control-button nds-icon-button" data-size="sm" type="button" title={textContent.undoDrawingTitle || "Annuler le dernier trait"} onClick={onUndo}> <button className="drawing-control-button" type="button" title={textContent.undoDrawingTitle || "Annuler le dernier trait"} onClick={onUndo}>
<Icon name="back" /> <Icon name="back" />
</button> </button>
<button className="drawing-control-button nds-icon-button" data-size="sm" data-variant="danger" type="button" title={textContent.clearDrawingTitle || "Effacer les dessins"} onClick={onClear}> <button className="drawing-control-button danger" type="button" title={textContent.clearDrawingTitle || "Effacer les dessins"} onClick={onClear}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
{onToggleStorageMode && ( {onToggleStorageMode && (
<button <button
className={`module-scroll-button nds-switch drawing-storage-switch ${storageMode === "permanent" ? "active" : ""}`} className={`module-scroll-button drawing-storage-switch ${storageMode === "permanent" ? "active" : ""}`}
data-size="lg"
type="button" type="button"
aria-pressed={storageMode === "permanent"} aria-pressed={storageMode === "permanent"}
aria-label={storageMode === "permanent" ? textContent.permanentTitle || "Annotations permanentes" : textContent.temporaryTitle || "Annotations temporaires"} aria-label={storageMode === "permanent" ? textContent.permanentTitle || "Annotations permanentes" : textContent.temporaryTitle || "Annotations temporaires"}
@ -69,7 +67,7 @@ export function FormattingColorDropdown({ label, sections }) {
closeOnMouseLeave closeOnMouseLeave
preventMouseDown preventMouseDown
renderTrigger={({ toggle }) => ( renderTrigger={({ toggle }) => (
<button className="notepad-color-toggle nds-swatch is-rainbow" data-variant="rainbow" type="button" title={label} aria-label={label} onClick={toggle} /> <button className="notepad-color-toggle is-rainbow" type="button" title={label} aria-label={label} onClick={toggle} />
)} )}
> >
{({ close }) => ( {({ close }) => (
@ -81,7 +79,7 @@ export function FormattingColorDropdown({ label, sections }) {
{section.colors.map((color) => ( {section.colors.map((color) => (
<button <button
key={`${section.label}-${color}`} key={`${section.label}-${color}`}
className={`notepad-swatch nds-swatch ${section.value === color ? "active" : ""}`} className={`notepad-swatch ${section.value === color ? "active" : ""}`}
type="button" type="button"
title={`${section.label} ${color}`} title={`${section.label} ${color}`}
aria-label={`${section.label} ${color}`} aria-label={`${section.label} ${color}`}
@ -112,8 +110,7 @@ export function DrawingColorDropdown({ label, colors, value, onSelect }) {
preventMouseDown preventMouseDown
renderTrigger={({ toggle }) => ( renderTrigger={({ toggle }) => (
<button <button
className="notepad-color-toggle nds-swatch is-rainbow" className="notepad-color-toggle is-rainbow"
data-variant="rainbow"
type="button" type="button"
title={label} title={label}
aria-label={label} aria-label={label}
@ -127,7 +124,7 @@ export function DrawingColorDropdown({ label, colors, value, onSelect }) {
{colors.map((color) => ( {colors.map((color) => (
<button <button
key={color} key={color}
className={`notepad-swatch nds-swatch ${value === color ? "active" : ""}`} className={`notepad-swatch ${value === color ? "active" : ""}`}
type="button" type="button"
title={`${label} ${color}`} title={`${label} ${color}`}
aria-label={`${label} ${color}`} aria-label={`${label} ${color}`}
@ -154,7 +151,7 @@ export function DrawingWidthDropdown({ label, widths, value, onSelect }) {
closeOnMouseLeave closeOnMouseLeave
preventMouseDown preventMouseDown
renderTrigger={({ toggle }) => ( renderTrigger={({ toggle }) => (
<button className="drawing-width-toggle nds-icon-button" data-size="sm" type="button" title={label} aria-label={label} onClick={toggle}> <button className="drawing-width-toggle" type="button" title={label} aria-label={label} onClick={toggle}>
<span style={{ "--line-width": `${value}px` }} /> <span style={{ "--line-width": `${value}px` }} />
</button> </button>
)} )}
@ -164,8 +161,7 @@ export function DrawingWidthDropdown({ label, widths, value, onSelect }) {
{widths.map((lineWidth) => ( {widths.map((lineWidth) => (
<button <button
key={lineWidth} key={lineWidth}
className={`drawing-width-button nds-icon-button ${value === lineWidth ? "active" : ""}`} className={`drawing-width-button ${value === lineWidth ? "active" : ""}`}
data-size="sm"
type="button" type="button"
aria-pressed={value === lineWidth} aria-pressed={value === lineWidth}
title={`${label} ${lineWidth}`} title={`${label} ${lineWidth}`}

View file

@ -81,10 +81,10 @@ export function CharacteristicsEditor({ equipment, textContent, editing, onUpdat
options={TRAIT_ICONS.map((icon) => ({ icon, label: icon }))} options={TRAIT_ICONS.map((icon) => ({ icon, label: icon }))}
onChange={(icon) => setDraft((current) => ({ ...current, icon }))} onChange={(icon) => setDraft((current) => ({ ...current, icon }))}
/> />
<input className="nds-input" data-size="sm" type="text" value={draft.category} onChange={(event) => setDraft((current) => ({ ...current, category: event.target.value }))} placeholder={textContent.categoryPlaceholder || "Catégorie"} /> <input value={draft.category} onChange={(event) => setDraft((current) => ({ ...current, category: event.target.value }))} placeholder={textContent.categoryPlaceholder || "Catégorie"} />
<input className="nds-input" data-size="sm" type="text" value={draft.name} onChange={(event) => setDraft((current) => ({ ...current, name: event.target.value }))} placeholder={textContent.traitNamePlaceholder || "Nom"} /> <input value={draft.name} onChange={(event) => setDraft((current) => ({ ...current, name: event.target.value }))} placeholder={textContent.traitNamePlaceholder || "Nom"} />
<input className="nds-input" data-size="sm" type="text" value={draft.value} onChange={(event) => setDraft((current) => ({ ...current, value: event.target.value }))} placeholder={textContent.traitValuePlaceholder || "Valeur"} /> <input value={draft.value} onChange={(event) => setDraft((current) => ({ ...current, value: event.target.value }))} placeholder={textContent.traitValuePlaceholder || "Valeur"} />
<button className="nds-button" data-size="sm" data-variant="primary">{textContent.addTraitButton || "Ajouter"}</button> <button>{textContent.addTraitButton || "Ajouter"}</button>
</form> </form>
)} )}
<TraitCategoryList <TraitCategoryList
@ -145,12 +145,12 @@ function TraitCategoryList({ traits, categories, collapsedCategories, reorder, t
return ( return (
<section className={className} key={category} {...reorder.getGroupProps({ groupId: category })}> <section className={className} key={category} {...reorder.getGroupProps({ groupId: category })}>
<div className="equipment-planner-trait-category-header"> <div className="equipment-planner-trait-category-header">
<button type="button" className="task-planner-category-drag-handle nds-icon-button" data-size="xs" data-variant="ghost" onPointerDown={(event) => reorder.groupReorder.startDrag(event, { groupId: category })} title={textContent.reorderCategoryTitle || "Déplacer la catégorie"}> <button type="button" className="task-planner-category-drag-handle" onPointerDown={(event) => reorder.groupReorder.startDrag(event, { groupId: category })} title={textContent.reorderCategoryTitle || "Déplacer la catégorie"}>
<Icon name="drag" /> <Icon name="drag" />
</button> </button>
<strong>{category}</strong> <strong>{category}</strong>
<span>{categoryTraits.length}</span> <span>{categoryTraits.length}</span>
<button type="button" className="checklist-section-collapse-button nds-icon-button" data-size="xs" onClick={() => onToggleCategory(category)} aria-expanded={!collapsed}> <button type="button" className="checklist-section-collapse-button" onClick={() => onToggleCategory(category)} aria-expanded={!collapsed}>
<Icon name={collapsed ? "chevron-down" : "chevron-up"} /> <Icon name={collapsed ? "chevron-down" : "chevron-up"} />
</button> </button>
</div> </div>
@ -182,7 +182,7 @@ function EquipmentTraitRow({ trait, className, itemProps, editing, textContent,
<li className={className} {...itemProps}> <li className={className} {...itemProps}>
{editing ? ( {editing ? (
<> <>
<button type="button" className="task-planner-drag-handle nds-icon-button" data-size="xs" data-variant="ghost" onPointerDown={onStartDrag} title={textContent.reorderTraitTitle || "Déplacer"}> <button type="button" className="task-planner-drag-handle" onPointerDown={onStartDrag} title={textContent.reorderTraitTitle || "Déplacer"}>
<Icon name="drag" /> <Icon name="drag" />
</button> </button>
<IconChoiceDropdown <IconChoiceDropdown
@ -191,10 +191,10 @@ function EquipmentTraitRow({ trait, className, itemProps, editing, textContent,
options={TRAIT_ICONS.map((icon) => ({ icon, label: icon }))} options={TRAIT_ICONS.map((icon) => ({ icon, label: icon }))}
onChange={(icon) => onUpdateTrait(trait.id, (current) => ({ ...current, icon }))} onChange={(icon) => onUpdateTrait(trait.id, (current) => ({ ...current, icon }))}
/> />
<input {...categoryEdit.getInputProps({ className: "nds-inline-input", type: "text", placeholder: textContent.categoryPlaceholder || "Catégorie" })} /> <input {...categoryEdit.getInputProps({ placeholder: textContent.categoryPlaceholder || "Catégorie" })} />
<input {...nameEdit.getInputProps({ className: "nds-inline-input", type: "text", placeholder: textContent.traitNamePlaceholder || "Nom" })} /> <input {...nameEdit.getInputProps({ placeholder: textContent.traitNamePlaceholder || "Nom" })} />
<input {...valueEdit.getInputProps({ className: "nds-inline-input", type: "text", placeholder: textContent.traitValuePlaceholder || "Valeur" })} /> <input {...valueEdit.getInputProps({ placeholder: textContent.traitValuePlaceholder || "Valeur" })} />
<button type="button" className="task-planner-delete-button nds-icon-button" data-size="xs" data-variant="danger" onClick={() => onUpdateTrait(trait.id, () => ({ ...trait, name: "" }))} title={textContent.deleteTraitTitle || "Supprimer"}> <button type="button" className="task-planner-delete-button danger" onClick={() => onUpdateTrait(trait.id, () => ({ ...trait, name: "" }))} title={textContent.deleteTraitTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</> </>

View file

@ -30,9 +30,7 @@ export function EquipmentItem({ equipment, reorder, textContent, onUpdate, onDel
<li className={className} {...reorder.getItemProps({ itemId: equipment.id })}> <li className={className} {...reorder.getItemProps({ itemId: equipment.id })}>
<div className="equipment-planner-line"> <div className="equipment-planner-line">
<button <button
className="task-planner-drag-handle nds-icon-button" className="task-planner-drag-handle"
data-size="sm"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, equipment.id)} onPointerDown={(event) => reorder.itemReorder.startDrag(event, equipment.id)}
aria-label={`${textContent.reorderEquipmentTitle || "Déplacer"} ${equipment.name}`} aria-label={`${textContent.reorderEquipmentTitle || "Déplacer"} ${equipment.name}`}
@ -44,9 +42,9 @@ export function EquipmentItem({ equipment, reorder, textContent, onUpdate, onDel
<span className="equipment-planner-type-icon" aria-hidden="true"> <span className="equipment-planner-type-icon" aria-hidden="true">
<Icon name={equipment.icon || "shield"} /> <Icon name={equipment.icon || "shield"} />
</span> </span>
<strong {...nameEdit.getContentEditableProps({ className: "equipment-planner-name nds-inline-input", title: textContent.renameEquipmentTitle || "Renommer" })}>{equipment.name}</strong> <strong {...nameEdit.getContentEditableProps({ className: "equipment-planner-name", title: textContent.renameEquipmentTitle || "Renommer" })}>{equipment.name}</strong>
</div> </div>
<label className="equipment-planner-include-switch nds-native-switch"> <label className="equipment-planner-include-switch">
<input <input
type="checkbox" type="checkbox"
checked={equipment.active} checked={equipment.active}
@ -57,22 +55,21 @@ export function EquipmentItem({ equipment, reorder, textContent, onUpdate, onDel
</label> </label>
<button <button
type="button" type="button"
className={`task-planner-settings-button equipment-planner-row-edit-button nds-icon-button ${detailEditing ? "active" : ""}`} className={`task-planner-settings-button equipment-planner-row-edit-button ${detailEditing ? "active" : ""}`}
data-size="sm"
onClick={() => setDetailEditing((value) => !value)} onClick={() => setDetailEditing((value) => !value)}
aria-pressed={detailEditing} aria-pressed={detailEditing}
title={detailEditing ? textContent.viewModeTitle || "Mode affichage" : textContent.editModeTitle || "Mode édition"} title={detailEditing ? textContent.viewModeTitle || "Mode affichage" : textContent.editModeTitle || "Mode édition"}
> >
<Icon name="edit" /> <Icon name="edit" />
</button> </button>
<button type="button" className="task-planner-delete-button nds-icon-button" data-size="sm" data-variant="danger" onClick={onDelete} aria-label={`${textContent.deleteEquipmentTitle || "Supprimer"} ${equipment.name}`} title={textContent.deleteEquipmentTitle || "Supprimer"}> <button type="button" className="task-planner-delete-button danger" onClick={onDelete} aria-label={`${textContent.deleteEquipmentTitle || "Supprimer"} ${equipment.name}`} title={textContent.deleteEquipmentTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</div> </div>
<div className="equipment-planner-details"> <div className="equipment-planner-details">
{detailEditing ? ( {detailEditing ? (
<> <>
<div className="equipment-planner-tabs nds-radio-button-group" data-size="sm" role="tablist" aria-label={textContent.tabsLabel || "Ajout de caractéristiques"}> <div className="equipment-planner-tabs" role="tablist" aria-label={textContent.tabsLabel || "Ajout de caractéristiques"}>
{DETAIL_TABS.map((tab) => ( {DETAIL_TABS.map((tab) => (
<button <button
key={tab} key={tab}
@ -94,7 +91,7 @@ export function EquipmentItem({ equipment, reorder, textContent, onUpdate, onDel
<section className="equipment-planner-craft-tab"> <section className="equipment-planner-craft-tab">
<label className="equipment-planner-field"> <label className="equipment-planner-field">
<span>{textContent.obtainLabel || "Obtention"}</span> <span>{textContent.obtainLabel || "Obtention"}</span>
<textarea {...obtainEdit.getInputProps({ className: "nds-textarea", "data-size": "sm", placeholder: textContent.obtainPlaceholder || "Comment obtenir ou créer cet équipement" })} /> <textarea {...obtainEdit.getInputProps({ placeholder: textContent.obtainPlaceholder || "Comment obtenir ou créer cet équipement" })} />
</label> </label>
<MaterialsEditor equipment={equipment} textContent={textContent} editing onUpdate={onUpdate} /> <MaterialsEditor equipment={equipment} textContent={textContent} editing onUpdate={onUpdate} />
</section> </section>

View file

@ -23,9 +23,9 @@ export function MaterialsEditor({ equipment, textContent, editing, onUpdate }) {
<h4>{textContent.materialsTitle || "Matériaux"}</h4> <h4>{textContent.materialsTitle || "Matériaux"}</h4>
{editing && ( {editing && (
<form className="inline-form equipment-planner-material-form" onSubmit={addMaterial}> <form className="inline-form equipment-planner-material-form" onSubmit={addMaterial}>
<input className="nds-input" data-size="sm" type="text" value={draft.name} onChange={(event) => setDraft((current) => ({ ...current, name: event.target.value }))} placeholder={textContent.materialNamePlaceholder || "Matériau"} /> <input value={draft.name} onChange={(event) => setDraft((current) => ({ ...current, name: event.target.value }))} placeholder={textContent.materialNamePlaceholder || "Matériau"} />
<input className="nds-input" data-size="sm" type="number" min="1" value={draft.qty} onChange={(event) => setDraft((current) => ({ ...current, qty: event.target.value }))} aria-label={textContent.materialQtyLabel || "Quantité"} /> <input type="number" min="1" value={draft.qty} onChange={(event) => setDraft((current) => ({ ...current, qty: event.target.value }))} aria-label={textContent.materialQtyLabel || "Quantité"} />
<button className="nds-button" data-size="sm" data-variant="primary">{textContent.addMaterialButton || "Ajouter"}</button> <button>{textContent.addMaterialButton || "Ajouter"}</button>
</form> </form>
)} )}
<ul className="equipment-planner-materials"> <ul className="equipment-planner-materials">
@ -54,9 +54,9 @@ function MaterialRow({ material, editing, textContent, onUpdate }) {
<li> <li>
{editing ? ( {editing ? (
<> <>
<input {...nameEdit.getInputProps({ className: "nds-inline-input", type: "text" })} /> <input {...nameEdit.getInputProps()} />
<input className="nds-input" data-size="xs" type="number" min="1" value={material.qty} onChange={(event) => onUpdate((current) => ({ ...current, materials: current.materials.map((item) => item.id === material.id ? { ...item, qty: Math.max(1, Number.parseInt(event.target.value, 10) || 1) } : item) }))} /> <input type="number" min="1" value={material.qty} onChange={(event) => onUpdate((current) => ({ ...current, materials: current.materials.map((item) => item.id === material.id ? { ...item, qty: Math.max(1, Number.parseInt(event.target.value, 10) || 1) } : item) }))} />
<button type="button" className="task-planner-delete-button nds-icon-button" data-size="xs" data-variant="danger" onClick={() => onUpdate((current) => ({ ...current, materials: current.materials.filter((item) => item.id !== material.id) }))} title={textContent.deleteMaterialTitle || "Supprimer"}> <button type="button" className="task-planner-delete-button danger" onClick={() => onUpdate((current) => ({ ...current, materials: current.materials.filter((item) => item.id !== material.id) }))} title={textContent.deleteMaterialTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</> </>

View file

@ -109,8 +109,8 @@ export function EquipmentPlannerModule({ toolboxId, moduleId, context, editing,
options={EQUIPMENT_ICONS} options={EQUIPMENT_ICONS}
onChange={setEquipmentIcon} onChange={setEquipmentIcon}
/> />
<input className="nds-input" data-size="sm" type="text" value={equipmentName} onChange={(event) => setEquipmentName(event.target.value)} placeholder={textContent.equipmentPlaceholder || "Nouvel équipement"} /> <input value={equipmentName} onChange={(event) => setEquipmentName(event.target.value)} placeholder={textContent.equipmentPlaceholder || "Nouvel équipement"} />
<button className="nds-button" data-size="sm" data-variant="primary">{textContent.addEquipmentButton || "Ajouter"}</button> <button className="primary">{textContent.addEquipmentButton || "Ajouter"}</button>
</form> </form>
</div> </div>
)} )}

View file

@ -21,9 +21,7 @@ export function IconChoiceDropdown({ className = "", label, value, options, onCh
columns={Math.min(options.length, 5)} columns={Math.min(options.length, 5)}
renderTrigger={({ open, toggle }) => ( renderTrigger={({ open, toggle }) => (
<button <button
className={`equipment-planner-picker-trigger nds-select-trigger ${open ? "active" : ""}`} className={`equipment-planner-picker-trigger ${open ? "active" : ""}`}
data-size="sm"
data-variant="icon-picker"
type="button" type="button"
onClick={toggle} onClick={toggle}
aria-label={label} aria-label={label}
@ -31,6 +29,7 @@ export function IconChoiceDropdown({ className = "", label, value, options, onCh
title={selected.label} title={selected.label}
> >
<Icon name={selected.icon} /> <Icon name={selected.icon} />
<Icon name="dropdown" />
</button> </button>
)} )}
> >
@ -64,8 +63,9 @@ export function ShapeChoiceDropdown({ label, value, onChange }) {
label={label} label={label}
columns={SOCKET_SHAPES.length} columns={SOCKET_SHAPES.length}
renderTrigger={({ open, toggle }) => ( renderTrigger={({ open, toggle }) => (
<button className={`equipment-planner-picker-trigger nds-select-trigger ${open ? "active" : ""}`} data-size="sm" data-variant="icon-picker" type="button" onClick={toggle} aria-label={label} aria-expanded={open} title={label}> <button className={`equipment-planner-picker-trigger ${open ? "active" : ""}`} type="button" onClick={toggle} aria-label={label} aria-expanded={open} title={label}>
<SocketShapeIcon shape={value} /> <SocketShapeIcon shape={value} />
<Icon name="dropdown" />
</button> </button>
)} )}
> >
@ -98,8 +98,9 @@ export function ColorChoiceDropdown({ label, value, onChange }) {
label={label} label={label}
columns={6} columns={6}
renderTrigger={({ open, toggle }) => ( renderTrigger={({ open, toggle }) => (
<button className={`equipment-planner-picker-trigger nds-select-trigger ${open ? "active" : ""}`} data-size="sm" data-variant="icon-picker" type="button" onClick={toggle} aria-label={label} aria-expanded={open} title={value}> <button className={`equipment-planner-picker-trigger ${open ? "active" : ""}`} type="button" onClick={toggle} aria-label={label} aria-expanded={open} title={value}>
<span className={`equipment-planner-color-swatch color-${value}`} aria-hidden="true" /> <span className={`equipment-planner-color-swatch color-${value}`} aria-hidden="true" />
<Icon name="dropdown" />
</button> </button>
)} )}
> >

View file

@ -53,10 +53,10 @@ export function SocketItemsEditor({ equipment, textContent, editing, onUpdate })
<h4>{textContent.socketItemsTitle || "Objets sertis"}</h4> <h4>{textContent.socketItemsTitle || "Objets sertis"}</h4>
{editing && ( {editing && (
<form className="inline-form equipment-planner-socket-form" onSubmit={addSocketItem}> <form className="inline-form equipment-planner-socket-form" onSubmit={addSocketItem}>
<input className="nds-input" data-size="sm" type="text" value={draft.name} onChange={(event) => setDraft((current) => ({ ...current, name: event.target.value }))} placeholder={textContent.socketNamePlaceholder || "Joyau, materia..."} /> <input value={draft.name} onChange={(event) => setDraft((current) => ({ ...current, name: event.target.value }))} placeholder={textContent.socketNamePlaceholder || "Joyau, materia..."} />
<ShapeChoiceDropdown label={textContent.socketShapeLabel || "Forme"} value={draft.shape} onChange={(shape) => setDraft((current) => ({ ...current, shape }))} /> <ShapeChoiceDropdown label={textContent.socketShapeLabel || "Forme"} value={draft.shape} onChange={(shape) => setDraft((current) => ({ ...current, shape }))} />
<ColorChoiceDropdown label={textContent.socketColorLabel || "Couleur"} value={draft.color} onChange={(color) => setDraft((current) => ({ ...current, color }))} /> <ColorChoiceDropdown label={textContent.socketColorLabel || "Couleur"} value={draft.color} onChange={(color) => setDraft((current) => ({ ...current, color }))} />
<button className="nds-button" data-size="sm" data-variant="primary">{textContent.addSocketButton || "Ajouter"}</button> <button>{textContent.addSocketButton || "Ajouter"}</button>
</form> </form>
)} )}
<div className="equipment-planner-socket-grid"> <div className="equipment-planner-socket-grid">
@ -104,8 +104,8 @@ function SocketItemEditor({ equipment, socketItem, textContent, editing, onUpdat
<SocketShapeIcon shape={socketItem.shape} color={socketItem.color} /> <SocketShapeIcon shape={socketItem.shape} color={socketItem.color} />
{editing ? ( {editing ? (
<> <>
<input {...nameEdit.getInputProps({ className: "nds-inline-input", type: "text" })} /> <input {...nameEdit.getInputProps()} />
<button type="button" className="task-planner-delete-button nds-icon-button" data-size="xs" data-variant="danger" onClick={onDelete} title={textContent.deleteSocketTitle || "Supprimer"}> <button type="button" className="task-planner-delete-button danger" onClick={onDelete} title={textContent.deleteSocketTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</> </>
@ -119,16 +119,16 @@ function SocketItemEditor({ equipment, socketItem, textContent, editing, onUpdat
<ShapeChoiceDropdown label={textContent.socketShapeLabel || "Forme"} value={socketItem.shape} onChange={(shape) => onUpdate((current) => ({ ...current, shape }))} /> <ShapeChoiceDropdown label={textContent.socketShapeLabel || "Forme"} value={socketItem.shape} onChange={(shape) => onUpdate((current) => ({ ...current, shape }))} />
<ColorChoiceDropdown label={textContent.socketColorLabel || "Couleur"} value={socketItem.color} onChange={(color) => onUpdate((current) => ({ ...current, color }))} /> <ColorChoiceDropdown label={textContent.socketColorLabel || "Couleur"} value={socketItem.color} onChange={(color) => onUpdate((current) => ({ ...current, color }))} />
{equipment.socketItems.length > 1 && ( {equipment.socketItems.length > 1 && (
<select className="nds-select" data-size="xs" defaultValue="" onChange={(event) => { onAddLink(event.target.value); event.target.value = ""; }} aria-label={textContent.addSocketLinkLabel || "Lier à"}> <select defaultValue="" onChange={(event) => { onAddLink(event.target.value); event.target.value = ""; }} aria-label={textContent.addSocketLinkLabel || "Lier à"}>
<option value="">{textContent.addSocketLinkLabel || "Lier à"}</option> <option value="">{textContent.addSocketLinkLabel || "Lier à"}</option>
{equipment.socketItems.filter((item) => item.id !== socketItem.id).map((item) => <option key={item.id} value={item.id}>{item.name}</option>)} {equipment.socketItems.filter((item) => item.id !== socketItem.id).map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
</select> </select>
)} )}
</div> </div>
<form className="inline-form equipment-planner-bonus-form" onSubmit={addBonus}> <form className="inline-form equipment-planner-bonus-form" onSubmit={addBonus}>
<input className="nds-input" data-size="sm" type="text" value={bonusDraft.name} onChange={(event) => setBonusDraft((current) => ({ ...current, name: event.target.value }))} placeholder={textContent.bonusNamePlaceholder || "Bonus"} /> <input value={bonusDraft.name} onChange={(event) => setBonusDraft((current) => ({ ...current, name: event.target.value }))} placeholder={textContent.bonusNamePlaceholder || "Bonus"} />
<input className="nds-input" data-size="sm" type="text" value={bonusDraft.value} onChange={(event) => setBonusDraft((current) => ({ ...current, value: event.target.value }))} placeholder={textContent.traitValuePlaceholder || "Valeur"} /> <input value={bonusDraft.value} onChange={(event) => setBonusDraft((current) => ({ ...current, value: event.target.value }))} placeholder={textContent.traitValuePlaceholder || "Valeur"} />
<button className="nds-button" data-size="sm" data-variant="primary" aria-label={textContent.addBonusButton || "Ajouter"} title={textContent.addBonusButton || "Ajouter"}>+</button> <button aria-label={textContent.addBonusButton || "Ajouter"} title={textContent.addBonusButton || "Ajouter"}>+</button>
</form> </form>
</> </>
)} )}
@ -138,7 +138,7 @@ function SocketItemEditor({ equipment, socketItem, textContent, editing, onUpdat
<span>{bonus.category ? `${bonus.category} · ` : ""}{bonus.name}</span> <span>{bonus.category ? `${bonus.category} · ` : ""}{bonus.name}</span>
<b>{formatValue(bonus.value)}</b> <b>{formatValue(bonus.value)}</b>
{editing && ( {editing && (
<button type="button" className="task-planner-delete-button nds-icon-button" data-size="xs" data-variant="danger" onClick={() => onUpdate((current) => ({ ...current, bonuses: current.bonuses.filter((item) => item.id !== bonus.id) }))} title={textContent.deleteBonusTitle || "Supprimer"}> <button type="button" className="task-planner-delete-button danger" onClick={() => onUpdate((current) => ({ ...current, bonuses: current.bonuses.filter((item) => item.id !== bonus.id) }))} title={textContent.deleteBonusTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
)} )}

View file

@ -49,19 +49,17 @@ export function GoalTreeBranch({ nodes, parentId, selectedNodeId, editing, reord
<article className={`goal-tree-node-card ${editing ? "is-editing" : "is-viewing"} ${hasNodeActions ? "has-actions" : ""}`} data-diagram-node={node.id} onClick={selectFromCard}> <article className={`goal-tree-node-card ${editing ? "is-editing" : "is-viewing"} ${hasNodeActions ? "has-actions" : ""}`} data-diagram-node={node.id} onClick={selectFromCard}>
{!editing && ( {!editing && (
<div className="goal-tree-node-hover-actions" aria-label={textContent.cardActionsLabel || "Actions de l'objectif"}> <div className="goal-tree-node-hover-actions" aria-label={textContent.cardActionsLabel || "Actions de l'objectif"}>
<button className="nds-icon-button" data-size="tiny" type="button" onClick={() => onEditNode(node.id)} aria-label={`${textContent.editTitle || "Modifier"} ${node.title}`} title={textContent.editTitle || "Modifier"}> <button type="button" onClick={() => onEditNode(node.id)} aria-label={`${textContent.editTitle || "Modifier"} ${node.title}`} title={textContent.editTitle || "Modifier"}>
<Icon name="edit" /> <Icon name="edit" />
</button> </button>
<button className="nds-icon-button" data-size="tiny" data-variant="danger" type="button" onClick={() => onDeleteNode(node.id)} aria-label={`${textContent.deleteTitle || "Supprimer"} ${node.title}`} title={textContent.deleteTitle || "Supprimer"}> <button className="danger" type="button" onClick={() => onDeleteNode(node.id)} aria-label={`${textContent.deleteTitle || "Supprimer"} ${node.title}`} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</div> </div>
)} )}
{editing && ( {editing && (
<button <button
className="goal-tree-drag-handle nds-icon-button" className="goal-tree-drag-handle"
data-size="micro"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, node.id)} onPointerDown={(event) => reorder.itemReorder.startDrag(event, node.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${node.title}`} aria-label={`${textContent.reorderTitle || "Déplacer"} ${node.title}`}
@ -111,14 +109,13 @@ export function GoalTreeBranch({ nodes, parentId, selectedNodeId, editing, reord
{hasNodeActions && ( {hasNodeActions && (
<div className="goal-tree-node-actions"> <div className="goal-tree-node-actions">
{editing && ( {editing && (
<button className="nds-icon-button" data-size="micro" type="button" onClick={() => onAdd(node.id)} aria-label={`${textContent.addChildTitle || "Ajouter une sous-étape"} ${node.title}`} title={textContent.addChildTitle || "Ajouter une sous-étape"}> <button type="button" onClick={() => onAdd(node.id)} aria-label={`${textContent.addChildTitle || "Ajouter une sous-étape"} ${node.title}`} title={textContent.addChildTitle || "Ajouter une sous-étape"}>
<Icon name="add" /> <Icon name="add" />
</button> </button>
)} )}
{hasBranchToggle && ( {hasBranchToggle && (
<button <button
className="goal-tree-branch-toggle nds-icon-button" className="goal-tree-branch-toggle"
data-size="micro"
type="button" type="button"
onClick={() => onToggleBranch(node.id)} onClick={() => onToggleBranch(node.id)}
aria-expanded={!branchCollapsed} aria-expanded={!branchCollapsed}
@ -129,7 +126,7 @@ export function GoalTreeBranch({ nodes, parentId, selectedNodeId, editing, reord
</button> </button>
)} )}
{editing && ( {editing && (
<button className="nds-icon-button" data-size="sm" data-variant="danger" type="button" onClick={() => onDeleteNode(node.id)} aria-label={`${textContent.deleteTitle || "Supprimer"} ${node.title}`} title={textContent.deleteTitle || "Supprimer"}> <button className="danger" type="button" onClick={() => onDeleteNode(node.id)} aria-label={`${textContent.deleteTitle || "Supprimer"} ${node.title}`} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
)} )}

View file

@ -94,11 +94,11 @@ export function GoalTreeEditor({
)} )}
<div className="goal-tree-form-actions"> <div className="goal-tree-form-actions">
<button className="nds-button" data-size="sm" type="button" onClick={onClose}> <button type="button" onClick={onClose}>
<Icon name="close" /> <Icon name="close" />
<span>{textContent.closeButton || "Fermer"}</span> <span>{textContent.closeButton || "Fermer"}</span>
</button> </button>
<button className="nds-button" data-size="sm" data-variant="danger" type="button" onClick={() => onDeleteNode(selectedNode.id)}> <button className="danger" type="button" onClick={() => onDeleteNode(selectedNode.id)}>
<Icon name="trash" /> <Icon name="trash" />
<span>{textContent.deleteButton || "Supprimer la branche"}</span> <span>{textContent.deleteButton || "Supprimer la branche"}</span>
</button> </button>
@ -113,7 +113,7 @@ function GoalTreeBasePanel({ selectedNode, selectedNodeIsStep, textContent, onSe
<div className="goal-tree-form-panel"> <div className="goal-tree-form-panel">
<label> <label>
<span>{textContent.kindLabel || "Type de l'objectif"}</span> <span>{textContent.kindLabel || "Type de l'objectif"}</span>
<select className="nds-select" data-size="sm" value={selectedNode.kind} onChange={(event) => onSetKind(selectedNode.id, event.target.value)}> <select value={selectedNode.kind} onChange={(event) => onSetKind(selectedNode.id, event.target.value)}>
{GOAL_TREE_KINDS.map((kind) => <option key={kind} value={kind}>{textContent[`${kind}Kind`] || KIND_LABELS[kind]}</option>)} {GOAL_TREE_KINDS.map((kind) => <option key={kind} value={kind}>{textContent[`${kind}Kind`] || KIND_LABELS[kind]}</option>)}
</select> </select>
</label> </label>
@ -139,7 +139,7 @@ function GoalTreeBasePanel({ selectedNode, selectedNodeIsStep, textContent, onSe
{GOAL_TREE_COLORS.map((color) => ( {GOAL_TREE_COLORS.map((color) => (
<button <button
key={color} key={color}
className={`goal-tree-color-swatch nds-swatch ${selectedNode.color === color ? "active" : ""}`} className={`goal-tree-color-swatch ${selectedNode.color === color ? "active" : ""}`}
type="button" type="button"
onClick={() => onUpdateNode(selectedNode.id, (node) => ({ ...node, color }))} onClick={() => onUpdateNode(selectedNode.id, (node) => ({ ...node, color }))}
aria-pressed={selectedNode.color === color} aria-pressed={selectedNode.color === color}
@ -164,20 +164,20 @@ function GoalTreeAdvancedPanel({ selectedNode, selectedNodeIsStep, selectedProgr
<div className="goal-tree-form-panel"> <div className="goal-tree-form-panel">
<label> <label>
<span>{textContent.resetTypeLabel || "Type de reset"}</span> <span>{textContent.resetTypeLabel || "Type de reset"}</span>
<select className="nds-select" data-size="sm" value={selectedNode.resetType} onChange={(event) => onUpdateNode(selectedNode.id, (node) => ({ ...node, resetType: event.target.value }))}> <select value={selectedNode.resetType} onChange={(event) => onUpdateNode(selectedNode.id, (node) => ({ ...node, resetType: event.target.value }))}>
{GOAL_TREE_RESET_TYPES.map((resetType) => <option key={resetType} value={resetType}>{textContent[`${resetType}Reset`] || RESET_LABELS[resetType]}</option>)} {GOAL_TREE_RESET_TYPES.map((resetType) => <option key={resetType} value={resetType}>{textContent[`${resetType}Reset`] || RESET_LABELS[resetType]}</option>)}
</select> </select>
</label> </label>
{selectedNode.resetType !== "none" && ( {selectedNode.resetType !== "none" && (
<label> <label>
<span>{textContent.resetTimeLabel || "Heure de reset"}</span> <span>{textContent.resetTimeLabel || "Heure de reset"}</span>
<input className="nds-time" data-size="sm" type="time" value={selectedNode.resetTime || "00:00"} onChange={(event) => onUpdateNode(selectedNode.id, (node) => ({ ...node, resetTime: event.target.value || "00:00" }))} /> <input type="time" value={selectedNode.resetTime || "00:00"} onChange={(event) => onUpdateNode(selectedNode.id, (node) => ({ ...node, resetTime: event.target.value || "00:00" }))} />
</label> </label>
)} )}
{selectedNode.resetType === "weekly" && ( {selectedNode.resetType === "weekly" && (
<label> <label>
<span>{textContent.weeklyResetDayLabel || "Jour de reset"}</span> <span>{textContent.weeklyResetDayLabel || "Jour de reset"}</span>
<select className="nds-select" data-size="sm" value={selectedNode.weeklyResetDay ?? 1} onChange={(event) => onUpdateNode(selectedNode.id, (node) => ({ ...node, weeklyResetDay: Number(event.target.value) }))}> <select value={selectedNode.weeklyResetDay ?? 1} onChange={(event) => onUpdateNode(selectedNode.id, (node) => ({ ...node, weeklyResetDay: Number(event.target.value) }))}>
{GOAL_TREE_WEEK_DAYS.map((day) => <option key={day.value} value={day.value}>{day.label}</option>)} {GOAL_TREE_WEEK_DAYS.map((day) => <option key={day.value} value={day.value}>{day.label}</option>)}
</select> </select>
</label> </label>
@ -199,8 +199,7 @@ function GoalTreeAdvancedPanel({ selectedNode, selectedNodeIsStep, selectedProgr
</label> </label>
<div className="goal-tree-form-section-title">{textContent.progressSectionTitle || "Progression"}</div> <div className="goal-tree-form-section-title">{textContent.progressSectionTitle || "Progression"}</div>
<button <button
className={`tool-split-scroll-toggle goal-tree-progress-mode-toggle nds-switch ${selectedProgressMode === "auto" ? "active" : ""}`} className={`tool-split-scroll-toggle goal-tree-progress-mode-toggle ${selectedProgressMode === "auto" ? "active" : ""}`}
data-size="lg"
type="button" type="button"
onClick={() => onUpdateNode(selectedNode.id, (node) => ({ ...node, progressMode: selectedProgressMode === "auto" ? "manual" : "auto" }))} onClick={() => onUpdateNode(selectedNode.id, (node) => ({ ...node, progressMode: selectedProgressMode === "auto" ? "manual" : "auto" }))}
aria-pressed={selectedProgressMode === "auto"} aria-pressed={selectedProgressMode === "auto"}

View file

@ -22,13 +22,13 @@ export function GoalTreePrerequisitePicker({ nodes, node, textContent, onToggle
<div className="goal-tree-prerequisite-list themed-scrollbar"> <div className="goal-tree-prerequisite-list themed-scrollbar">
{primaryParent && ( {primaryParent && (
<label className="goal-tree-prerequisite-item is-primary"> <label className="goal-tree-prerequisite-item is-primary">
<input className="nds-checkbox" data-size="xs" type="checkbox" checked disabled readOnly /> <input type="checkbox" checked disabled readOnly />
<span>{primaryParent.title}</span> <span>{primaryParent.title}</span>
</label> </label>
)} )}
{options.map((candidate) => ( {options.map((candidate) => (
<label className="goal-tree-prerequisite-item" key={candidate.id}> <label className="goal-tree-prerequisite-item" key={candidate.id}>
<input className="nds-checkbox" data-size="xs" type="checkbox" checked={parentIds.has(candidate.id)} onChange={() => onToggle(candidate.id)} /> <input type="checkbox" checked={parentIds.has(candidate.id)} onChange={() => onToggle(candidate.id)} />
<span>{candidate.title}</span> <span>{candidate.title}</span>
</label> </label>
))} ))}
@ -41,7 +41,6 @@ export function GoalTreeTitleInput({ value, onChange }) {
const [draft, setDraft] = useState(value || ""); const [draft, setDraft] = useState(value || "");
return ( return (
<input <input
type="text"
value={draft} value={draft}
onChange={(event) => { onChange={(event) => {
const title = event.target.value; const title = event.target.value;
@ -55,7 +54,7 @@ export function GoalTreeTitleInput({ value, onChange }) {
export function GoalTreeFormulaInput({ value, onChange, hasParent, textContent, placeholder, title, className = "", "aria-label": ariaLabel }) { export function GoalTreeFormulaInput({ value, onChange, hasParent, textContent, placeholder, title, className = "", "aria-label": ariaLabel }) {
const normalizedValue = String(value ?? ""); const normalizedValue = String(value ?? "");
if (!hasParent) { if (!hasParent) {
return <input type="text" aria-label={ariaLabel} className={`${className} nds-inline-input`.trim()} inputMode="decimal" onChange={(event) => onChange(event.target.value.replaceAll(",", "."))} placeholder={placeholder} title={title} value={normalizedValue} />; return <input aria-label={ariaLabel} className={className} inputMode="decimal" onChange={(event) => onChange(event.target.value.replaceAll(",", "."))} placeholder={placeholder} title={title} value={normalizedValue} />;
} }
return ( return (
@ -88,7 +87,7 @@ export function GoalTreeContentEditor({ node, textContent, onAdd, onUpdate, onDe
<div className="goal-tree-form-section-title">{textContent.contentSectionTitle || "Contenu"}</div> <div className="goal-tree-form-section-title">{textContent.contentSectionTitle || "Contenu"}</div>
{(node.items || []).map((item) => ( {(node.items || []).map((item) => (
<div className={`goal-tree-content-editor-row is-${node.kind}`} key={item.id}> <div className={`goal-tree-content-editor-row is-${node.kind}`} key={item.id}>
<input className="goal-tree-content-label-input nds-input" data-size="sm" type="text" value={item.label} placeholder={textContent.itemLabelPlaceholder || "Élément"} onChange={(event) => onUpdate(item.id, (current) => ({ ...current, label: event.target.value }))} aria-label={textContent.itemLabelLabel || "Libellé"} /> <input className="goal-tree-content-label-input" value={item.label} placeholder={textContent.itemLabelPlaceholder || "Élément"} onChange={(event) => onUpdate(item.id, (current) => ({ ...current, label: event.target.value }))} aria-label={textContent.itemLabelLabel || "Libellé"} />
{node.kind === "checklist" && ( {node.kind === "checklist" && (
<GoalTreeFormulaInput <GoalTreeFormulaInput
aria-label={textContent.itemTotalLabel || "Total"} aria-label={textContent.itemTotalLabel || "Total"}
@ -101,12 +100,12 @@ export function GoalTreeContentEditor({ node, textContent, onAdd, onUpdate, onDe
title={textContent.itemTotalHelp || "Saisissez un nombre ou une formule avec base pour référencer la cible du parent."} title={textContent.itemTotalHelp || "Saisissez un nombre ou une formule avec base pour référencer la cible du parent."}
/> />
)} )}
<button className="nds-icon-button" data-size="xs" data-variant="danger" type="button" onClick={() => onDelete(item.id)} aria-label={textContent.deleteTitle || "Supprimer"} title={textContent.deleteTitle || "Supprimer"}> <button className="danger" type="button" onClick={() => onDelete(item.id)} aria-label={textContent.deleteTitle || "Supprimer"} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</div> </div>
))} ))}
<button className="goal-tree-content-add-button nds-button" data-size="xs" data-variant="dashed" type="button" onClick={onAdd}> <button className="goal-tree-content-add-button" type="button" onClick={onAdd}>
<Icon name="add" /> <Icon name="add" />
<span>{textContent.addContentItemButton || "Ajouter une ligne"}</span> <span>{textContent.addContentItemButton || "Ajouter une ligne"}</span>
</button> </button>
@ -123,9 +122,12 @@ export function GoalTreeIconPicker({ label, value, textContent, onChange }) {
menuClassName="goal-tree-icon-picker-menu" menuClassName="goal-tree-icon-picker-menu"
label={label} label={label}
renderTrigger={({ open, toggle }) => ( renderTrigger={({ open, toggle }) => (
<button className="goal-tree-icon-picker-button nds-select-trigger" data-size="sm" data-variant="icon-picker" type="button" onClick={toggle} aria-label={label} aria-expanded={open} title={label}> <button className="goal-tree-icon-picker-button" type="button" onClick={toggle} aria-label={label} aria-expanded={open} title={label}>
<span className="goal-tree-icon-preview" aria-hidden="true">
<Icon name={value} /> <Icon name={value} />
</span>
<span>{textContent[`icon${value}`] || value}</span> <span>{textContent[`icon${value}`] || value}</span>
<Icon name="dropdown" />
</button> </button>
)} )}
> >
@ -134,8 +136,7 @@ export function GoalTreeIconPicker({ label, value, textContent, onChange }) {
{GOAL_TREE_ICON_NAMES.map((icon) => ( {GOAL_TREE_ICON_NAMES.map((icon) => (
<button <button
key={icon} key={icon}
className={`nds-icon-button ${icon === value ? "active" : ""}`.trim()} className={icon === value ? "active" : ""}
data-size="sm"
type="button" type="button"
onClick={() => { onClick={() => {
onChange(icon); onChange(icon);

View file

@ -7,10 +7,10 @@ function GoalTreeChecklistInlineControl({ item, textContent, onUpdate }) {
const target = Math.max(1, item.qtyTarget || 1); const target = Math.max(1, item.qtyTarget || 1);
const current = Math.min(Math.max(0, item.qtyCurrent || 0), target); const current = Math.min(Math.max(0, item.qtyCurrent || 0), target);
if (target <= 1) { if (target <= 1) {
return <input className="goal-tree-node-check nds-checkbox" data-size="xs" type="checkbox" checked={current >= 1} onChange={(event) => onUpdate((entry) => ({ ...entry, qtyCurrent: event.target.checked ? 1 : 0 }))} aria-label={textContent.itemCheckedLabel || "État"} />; return <input className="goal-tree-node-check" type="checkbox" checked={current >= 1} onChange={(event) => onUpdate((entry) => ({ ...entry, qtyCurrent: event.target.checked ? 1 : 0 }))} aria-label={textContent.itemCheckedLabel || "État"} />;
} }
return ( return (
<span className="goal-tree-content-qty-stepper nds-quantity-stepper" data-size="sm"> <span className="goal-tree-content-qty-stepper">
<button type="button" onClick={() => onUpdate((entry) => ({ ...entry, qtyCurrent: Math.max(0, (entry.qtyCurrent || 0) - 1) }))} aria-label={textContent.decrementItemTitle || "Retirer"} title={textContent.decrementItemTitle || "Retirer"}> <button type="button" onClick={() => onUpdate((entry) => ({ ...entry, qtyCurrent: Math.max(0, (entry.qtyCurrent || 0) - 1) }))} aria-label={textContent.decrementItemTitle || "Retirer"} title={textContent.decrementItemTitle || "Retirer"}>
<span aria-hidden="true">-</span> <span aria-hidden="true">-</span>
</button> </button>
@ -26,10 +26,10 @@ export function GoalTreeStepLeafControl({ node, textContent, onChange }) {
const target = Math.max(1, node.computedQtyTarget || node.qtyTarget || 1); const target = Math.max(1, node.computedQtyTarget || node.qtyTarget || 1);
const current = Math.min(Math.max(0, node.qtyCurrent || 0), target); const current = Math.min(Math.max(0, node.qtyCurrent || 0), target);
if (target <= 1) { if (target <= 1) {
return <input className="goal-tree-step-check nds-checkbox" data-size="xs" type="checkbox" checked={current >= 1} onChange={(event) => onChange(event.target.checked ? 1 : 0)} aria-label={textContent.itemCheckedLabel || "État"} />; return <input className="goal-tree-step-check" type="checkbox" checked={current >= 1} onChange={(event) => onChange(event.target.checked ? 1 : 0)} aria-label={textContent.itemCheckedLabel || "État"} />;
} }
return ( return (
<span className="goal-tree-step-qty-stepper goal-tree-content-qty-stepper nds-quantity-stepper" data-size="xs"> <span className="goal-tree-step-qty-stepper goal-tree-content-qty-stepper">
<button type="button" onClick={() => onChange(Math.max(0, current - 1))} aria-label={textContent.decrementItemTitle || "Retirer"} title={textContent.decrementItemTitle || "Retirer"}> <button type="button" onClick={() => onChange(Math.max(0, current - 1))} aria-label={textContent.decrementItemTitle || "Retirer"} title={textContent.decrementItemTitle || "Retirer"}>
<span aria-hidden="true">-</span> <span aria-hidden="true">-</span>
</button> </button>

View file

@ -87,8 +87,7 @@ export function GoalTreeView({
return ( return (
<section className={`goal-tree-view tool-split-results ${verticalView ? "is-vertical" : "is-horizontal"} ${editing ? "is-editing" : ""}`} aria-label={textContent.treeTitle || "Arbre d'objectifs"}> <section className={`goal-tree-view tool-split-results ${verticalView ? "is-vertical" : "is-horizontal"} ${editing ? "is-editing" : ""}`} aria-label={textContent.treeTitle || "Arbre d'objectifs"}>
<button <button
className={`module-scroll-button goal-tree-orientation-switch nds-switch ${verticalView ? "active" : ""}`} className={`module-scroll-button goal-tree-orientation-switch ${verticalView ? "active" : ""}`}
data-size="lg"
type="button" type="button"
onClick={onToggleOrientation} onClick={onToggleOrientation}
aria-label={verticalView ? textContent.horizontalViewTitle || "Afficher horizontalement" : textContent.verticalViewTitle || "Afficher verticalement"} aria-label={verticalView ? textContent.horizontalViewTitle || "Afficher horizontalement" : textContent.verticalViewTitle || "Afficher verticalement"}
@ -101,7 +100,7 @@ export function GoalTreeView({
<div className="goal-tree-scroll-area themed-scrollbar"> <div className="goal-tree-scroll-area themed-scrollbar">
<div className="goal-tree-view-controls"> <div className="goal-tree-view-controls">
{editing && ( {editing && (
<button className="goal-tree-add-root-button nds-button" data-size="sm" data-variant="dashed" type="button" onClick={() => onAdd("")}> <button className="goal-tree-add-root-button" type="button" onClick={() => onAdd("")}>
<Icon name="add" /> <Icon name="add" />
<span>{textContent.addRootSchemaButton || "Ajouter un objectif a la racine"}</span> <span>{textContent.addRootSchemaButton || "Ajouter un objectif a la racine"}</span>
</button> </button>

View file

@ -123,8 +123,7 @@ export function ImageAnnotationModule({ toolboxId, moduleId, context, editing, t
<img src={data.image} alt={textContent.imageAlt || "Image annotée"} draggable="false" /> <img src={data.image} alt={textContent.imageAlt || "Image annotée"} draggable="false" />
<div className="annotation-preview-actions"> <div className="annotation-preview-actions">
<button <button
className="tool-split-action-button nds-icon-button" className="tool-split-action-button"
data-size="sm"
type="button" type="button"
onClick={() => context.setImage({ onClick={() => context.setImage({
dataUrl: data.image, dataUrl: data.image,
@ -165,7 +164,7 @@ export function ImageAnnotationModule({ toolboxId, moduleId, context, editing, t
<Icon name="zoom" /> <Icon name="zoom" />
</button> </button>
{editing && ( {editing && (
<button className="tool-split-action-button nds-icon-button" data-size="sm" data-variant="danger" type="button" onClick={removeImage} aria-label={textContent.deleteImageTitle || "Supprimer l'image"} title={textContent.deleteImageTitle || "Supprimer l'image"}> <button className="tool-split-action-button danger" type="button" onClick={removeImage} aria-label={textContent.deleteImageTitle || "Supprimer l'image"} title={textContent.deleteImageTitle || "Supprimer l'image"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
)} )}

View file

@ -104,9 +104,7 @@ export function ImagesModule({ toolboxId, moduleId, context, editing, textExchan
return ( return (
<figure key={image.id} className={className} {...getItemProps({ itemId: image.id, parentId: moduleId })}> <figure key={image.id} className={className} {...getItemProps({ itemId: image.id, parentId: moduleId })}>
<button <button
className="image-drag-handle nds-icon-button" className="image-drag-handle"
data-size="sm"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => itemReorder.startDrag(event, image.id)} onPointerDown={(event) => itemReorder.startDrag(event, image.id)}
aria-label={textContent.reorderAriaLabel || "Déplacer l'image"} aria-label={textContent.reorderAriaLabel || "Déplacer l'image"}
@ -119,8 +117,7 @@ export function ImagesModule({ toolboxId, moduleId, context, editing, textExchan
</button> </button>
{editing ? ( {editing ? (
<input <input
className="image-label-input nds-inline-input" className="image-label-input"
type="text"
value={image.label || ""} value={image.label || ""}
placeholder={textContent.labelPlaceholder || "Libellé de l'image"} placeholder={textContent.labelPlaceholder || "Libellé de l'image"}
onChange={(event) => updateImage(image.id, { label: event.target.value })} onChange={(event) => updateImage(image.id, { label: event.target.value })}
@ -131,8 +128,7 @@ export function ImagesModule({ toolboxId, moduleId, context, editing, textExchan
) : null} ) : null}
<div className="image-actions"> <div className="image-actions">
<button <button
className="image-annotate-button nds-icon-button" className="image-annotate-button"
data-size="sm"
type="button" type="button"
onClick={() => context.createImageAnnotationModule?.(image.dataUrl)} onClick={() => context.createImageAnnotationModule?.(image.dataUrl)}
aria-label={textContent.annotateAriaLabel || "Annoter l'image"} aria-label={textContent.annotateAriaLabel || "Annoter l'image"}
@ -140,7 +136,7 @@ export function ImagesModule({ toolboxId, moduleId, context, editing, textExchan
> >
<Icon name="map" /> <Icon name="map" />
</button> </button>
<button className="image-delete-button nds-icon-button" data-size="sm" data-variant="danger" type="button" onClick={() => { <button className="image-delete-button danger" type="button" onClick={() => {
context.setModuleData(toolboxId, moduleId, { images: data.images.filter((item) => item.id !== image.id) }); context.setModuleData(toolboxId, moduleId, { images: data.images.filter((item) => item.id !== image.id) });
}} aria-label={textContent.deleteAriaLabel || "Supprimer l'image"} title={textContent.deleteTitle || "Supprimer"}> }} aria-label={textContent.deleteAriaLabel || "Supprimer l'image"} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />

View file

@ -59,9 +59,9 @@ export function LinksModule({ toolboxId, moduleId, context, editing, textExchang
{editing && ( {editing && (
<div className="module-add-panel"> <div className="module-add-panel">
<form className="inline-form links-add-form" onSubmit={addLink}> <form className="inline-form links-add-form" onSubmit={addLink}>
<input {...linkDraft.getFieldProps("title", { className: "nds-input", "data-size": "sm", type: "text", placeholder: textContent.titlePlaceholder || "Nom du lien" })} /> <input {...linkDraft.getFieldProps("title", { placeholder: textContent.titlePlaceholder || "Nom du lien" })} />
<input {...linkDraft.getFieldProps("url", { className: "nds-input", "data-size": "sm", type: "url", placeholder: textContent.urlPlaceholder || "https://..." })} /> <input {...linkDraft.getFieldProps("url", { placeholder: textContent.urlPlaceholder || "https://..." })} />
<button className="nds-button" data-size="sm" data-variant="primary">{textContent.addButton || "Ajouter"}</button> <button className="primary">{textContent.addButton || "Ajouter"}</button>
</form> </form>
</div> </div>
)} )}
@ -82,9 +82,7 @@ export function LinksModule({ toolboxId, moduleId, context, editing, textExchang
return ( return (
<li className={className} key={link.id} {...getItemProps({ itemId: link.id, parentId: moduleId })}> <li className={className} key={link.id} {...getItemProps({ itemId: link.id, parentId: moduleId })}>
<button <button
className="link-drag-handle nds-icon-button" className="link-drag-handle"
data-size="sm"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => itemReorder.startDrag(event, link.id)} onPointerDown={(event) => itemReorder.startDrag(event, link.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${link.title || link.url}`} aria-label={`${textContent.reorderTitle || "Déplacer"} ${link.title || link.url}`}
@ -97,10 +95,10 @@ export function LinksModule({ toolboxId, moduleId, context, editing, textExchang
<span>{link.url}</span> <span>{link.url}</span>
</a> </a>
<div> <div>
<button type="button" className="nds-icon-button" data-size="sm" onClick={() => copyUrl(link)} aria-label={`${textContent.copyTitle || "Copier"} ${link.title || link.url}`} title={copiedId === link.id ? textContent.copiedTitle || "Copié" : textContent.copyTitle || "Copier"}> <button type="button" onClick={() => copyUrl(link)} aria-label={`${textContent.copyTitle || "Copier"} ${link.title || link.url}`} title={copiedId === link.id ? textContent.copiedTitle || "Copié" : textContent.copyTitle || "Copier"}>
<Icon name="copy" /> <Icon name="copy" />
</button> </button>
<button type="button" className="nds-icon-button" data-size="sm" data-variant="danger" onClick={() => save(data.links.filter((item) => item.id !== link.id))} aria-label={`${textContent.deleteTitle || "Supprimer"} ${link.title || link.url}`} title={textContent.deleteTitle || "Supprimer"}> <button type="button" className="danger" onClick={() => save(data.links.filter((item) => item.id !== link.id))} aria-label={`${textContent.deleteTitle || "Supprimer"} ${link.title || link.url}`} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</div> </div>

View file

@ -40,7 +40,7 @@ export function MemosModule({ toolboxId, moduleId, context, editing, textExchang
function resizeMemoTextarea(element) { function resizeMemoTextarea(element) {
if (!element) return; if (!element) return;
element.style.height = "auto"; element.style.height = "auto";
element.style.height = `${element.scrollHeight + 2}px`; element.style.height = `${element.scrollHeight}px`;
} }
function save(nextData) { function save(nextData) {
@ -93,7 +93,7 @@ export function MemosModule({ toolboxId, moduleId, context, editing, textExchang
{editing && ( {editing && (
<div className="module-add-panel"> <div className="module-add-panel">
<button className="memos-add-button nds-button" data-size="sm" data-variant="primary" type="button" onClick={addMemo}> <button className="primary memos-add-button" type="button" onClick={addMemo}>
<Icon name="add" /> <Icon name="add" />
<span>{textContent.addButton || "Ajouter"}</span> <span>{textContent.addButton || "Ajouter"}</span>
</button> </button>
@ -102,8 +102,7 @@ export function MemosModule({ toolboxId, moduleId, context, editing, textExchang
<div className="memos-toolbar"> <div className="memos-toolbar">
<button <button
className={`memos-ticker-toggle nds-icon-button ${data.tickerEnabled ? "active" : ""}`} className={`memos-ticker-toggle ${data.tickerEnabled ? "active" : ""}`}
data-size="sm"
type="button" type="button"
onClick={() => save({ ...data, tickerEnabled: !data.tickerEnabled })} onClick={() => save({ ...data, tickerEnabled: !data.tickerEnabled })}
aria-pressed={data.tickerEnabled} aria-pressed={data.tickerEnabled}
@ -136,9 +135,7 @@ export function MemosModule({ toolboxId, moduleId, context, editing, textExchang
return ( return (
<article className={className} key={memo.id} style={{ "--memo-color": memo.color }} {...getItemProps({ itemId: memo.id, parentId: moduleId })}> <article className={className} key={memo.id} style={{ "--memo-color": memo.color }} {...getItemProps({ itemId: memo.id, parentId: moduleId })}>
<button <button
className="memo-drag-handle nds-icon-button" className="memo-drag-handle"
data-size="sm"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => itemReorder.startDrag(event, memo.id)} onPointerDown={(event) => itemReorder.startDrag(event, memo.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${memo.text}`} aria-label={`${textContent.reorderTitle || "Déplacer"} ${memo.text}`}
@ -147,9 +144,6 @@ export function MemosModule({ toolboxId, moduleId, context, editing, textExchang
<Icon name="drag" /> <Icon name="drag" />
</button> </button>
<textarea <textarea
className="nds-textarea"
data-size="sm"
data-variant="ghost"
ref={(element) => { ref={(element) => {
if (element) { if (element) {
textareaRefs.current.set(memo.id, element); textareaRefs.current.set(memo.id, element);
@ -175,7 +169,7 @@ export function MemosModule({ toolboxId, moduleId, context, editing, textExchang
label={textContent.colorLabel || "Couleur du mémo"} label={textContent.colorLabel || "Couleur du mémo"}
renderTrigger={({ toggle }) => ( renderTrigger={({ toggle }) => (
<button <button
className="notepad-color-toggle nds-swatch" className="notepad-color-toggle"
type="button" type="button"
style={{ "--swatch-color": memo.color }} style={{ "--swatch-color": memo.color }}
onClick={toggle} onClick={toggle}
@ -189,7 +183,7 @@ export function MemosModule({ toolboxId, moduleId, context, editing, textExchang
{TEXT_COLORS.map((color) => ( {TEXT_COLORS.map((color) => (
<button <button
key={color} key={color}
className={`notepad-swatch nds-swatch ${memo.color === color ? "active" : ""}`} className={`notepad-swatch ${memo.color === color ? "active" : ""}`}
type="button" type="button"
style={{ "--swatch-color": color }} style={{ "--swatch-color": color }}
onClick={() => { onClick={() => {
@ -202,14 +196,14 @@ export function MemosModule({ toolboxId, moduleId, context, editing, textExchang
</> </>
)} )}
</CompactDropdown> </CompactDropdown>
<button className="memo-delete-button nds-icon-button" data-size="sm" data-variant="danger" type="button" onClick={() => deleteMemo(memo.id)} aria-label={textContent.deleteTitle || "Supprimer le mémo"} title={textContent.deleteTitle || "Supprimer"}> <button className="memo-delete-button danger" type="button" onClick={() => deleteMemo(memo.id)} aria-label={textContent.deleteTitle || "Supprimer le mémo"} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</footer> </footer>
</article> </article>
); );
}) : ( }) : (
<button className="memos-empty nds-button" data-size="sm" data-variant="dashed" type="button" onClick={addMemo}> <button className="memos-empty" type="button" onClick={addMemo}>
<Icon name="add" /> <Icon name="add" />
<span>{textContent.emptyLabel || "Ajouter un premier mémo"}</span> <span>{textContent.emptyLabel || "Ajouter un premier mémo"}</span>
</button> </button>

View file

@ -295,11 +295,7 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
label={textContent.pagesMenuLabel || "Pages du bloc note"} label={textContent.pagesMenuLabel || "Pages du bloc note"}
renderTrigger={({ open, toggle }) => ( renderTrigger={({ open, toggle }) => (
<button <button
className={`notepad-pages-toggle nds-icon-button ${open ? "active" : ""}`} className={`notepad-pages-toggle ${open ? "active" : ""}`}
data-size="sm"
data-variant="panel-toggle"
data-edge="right"
data-border="joined"
type="button" type="button"
onClick={toggle} onClick={toggle}
aria-label={textContent.pagesToggleLabel || "Afficher les pages du bloc note"} aria-label={textContent.pagesToggleLabel || "Afficher les pages du bloc note"}
@ -315,12 +311,10 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
<div className="notepad-pages-list"> <div className="notepad-pages-list">
{normalizedData.pages.map((page) => ( {normalizedData.pages.map((page) => (
<div className={`notepad-page-row ${page.id === normalizedData.activePageId ? "active" : ""}`} key={page.id}> <div className={`notepad-page-row ${page.id === normalizedData.activePageId ? "active" : ""}`} key={page.id}>
<div className="notepad-page-main nds-list-row"> <div className="notepad-page-main">
{editingPageId === page.id ? ( {editingPageId === page.id ? (
<input <input
className="notepad-page-title-input nds-input" className="notepad-page-title-input"
data-size="xs"
type="text"
value={pageTitleDrafts[page.id] ?? page.title} value={pageTitleDrafts[page.id] ?? page.title}
aria-label={`${textContent.renamePageLabel || "Renommer"} ${page.title}`} aria-label={`${textContent.renamePageLabel || "Renommer"} ${page.title}`}
autoFocus autoFocus
@ -345,8 +339,7 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
</div> </div>
<div className="notepad-page-actions"> <div className="notepad-page-actions">
<button <button
className="notepad-page-edit nds-icon-button" className="notepad-page-edit"
data-size="xxs"
type="button" type="button"
onClick={() => { onClick={() => {
setPageTitleDrafts((current) => ({ ...current, [page.id]: page.title })); setPageTitleDrafts((current) => ({ ...current, [page.id]: page.title }));
@ -358,9 +351,7 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
<Icon name="edit" /> <Icon name="edit" />
</button> </button>
<button <button
className="notepad-page-delete nds-icon-button" className="notepad-page-delete danger"
data-size="xxs"
data-variant="danger"
type="button" type="button"
onClick={() => deletePage(page.id, close)} onClick={() => deletePage(page.id, close)}
disabled={normalizedData.pages.length <= 1} disabled={normalizedData.pages.length <= 1}
@ -373,7 +364,7 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
</div> </div>
))} ))}
</div> </div>
<button className="notepad-page-add nds-button" data-size="xs" data-variant="dashed" type="button" onClick={() => addPage(close)}> <button className="notepad-page-add" type="button" onClick={() => addPage(close)}>
<Icon name="add" /> <Icon name="add" />
<span>{textContent.addPageLabel || "Ajouter une page"}</span> <span>{textContent.addPageLabel || "Ajouter une page"}</span>
</button> </button>

View file

@ -52,8 +52,7 @@ export function NotepadToolbar({
function ToolbarButton({ active, title, children, onClick }) { function ToolbarButton({ active, title, children, onClick }) {
return ( return (
<button <button
className={`notepad-toolbar-button nds-icon-button ${active ? "active" : ""}`} className={`notepad-toolbar-button ${active ? "active" : ""}`}
data-size="sm"
type="button" type="button"
aria-pressed={active} aria-pressed={active}
title={title} title={title}
@ -75,8 +74,7 @@ function ListTypeDropdown({ textContent, activeFormats, onCommand }) {
preventMouseDown preventMouseDown
renderTrigger={({ toggle }) => ( renderTrigger={({ toggle }) => (
<button <button
className={`notepad-toolbar-button nds-icon-button ${activeFormats.bulletList || activeFormats.numberList ? "active" : ""}`} className={`notepad-toolbar-button ${activeFormats.bulletList || activeFormats.numberList ? "active" : ""}`}
data-size="sm"
type="button" type="button"
aria-pressed={activeFormats.bulletList || activeFormats.numberList} aria-pressed={activeFormats.bulletList || activeFormats.numberList}
title={textContent.listGroupLabel || "Listes"} title={textContent.listGroupLabel || "Listes"}
@ -89,8 +87,7 @@ function ListTypeDropdown({ textContent, activeFormats, onCommand }) {
{({ close }) => ( {({ close }) => (
<> <>
<button <button
className={`notepad-list-option nds-icon-button ${activeFormats.bulletList ? "active" : ""}`} className={`notepad-list-option ${activeFormats.bulletList ? "active" : ""}`}
data-size="sm"
type="button" type="button"
aria-pressed={activeFormats.bulletList} aria-pressed={activeFormats.bulletList}
onClick={() => { onClick={() => {
@ -101,8 +98,7 @@ function ListTypeDropdown({ textContent, activeFormats, onCommand }) {
• •
</button> </button>
<button <button
className={`notepad-list-option nds-icon-button ${activeFormats.numberList ? "active" : ""}`} className={`notepad-list-option ${activeFormats.numberList ? "active" : ""}`}
data-size="sm"
type="button" type="button"
aria-pressed={activeFormats.numberList} aria-pressed={activeFormats.numberList}
onClick={() => { onClick={() => {

View file

@ -33,7 +33,6 @@ export function TableRow({ rowIndex, columns, cells, evaluatedCells, editingCell
return ( return (
<input <input
type="text"
key={address} key={address}
ref={(element) => registerInput(address, element)} ref={(element) => registerInput(address, element)}
className={className} className={className}
@ -83,7 +82,6 @@ export function HeaderLabelInput({ className, value, fallback, onSave, ...props
<input <input
{...inlineEdit.getInputProps({ {...inlineEdit.getInputProps({
...props, ...props,
type: "text",
className, className,
onFocus: (event) => { onFocus: (event) => {
if (value === fallback) window.requestAnimationFrame(() => event.target.select()); if (value === fallback) window.requestAnimationFrame(() => event.target.select());

View file

@ -8,8 +8,7 @@ export function TableToolbar({ rows, columns, textContent, onResize, onCopy }) {
<div className="notepad-format-controls table-controls"> <div className="notepad-format-controls table-controls">
<div className="notepad-toolbar-group" role="group" aria-label={textContent.rowActionsLabel || "Lignes"}> <div className="notepad-toolbar-group" role="group" aria-label={textContent.rowActionsLabel || "Lignes"}>
<button <button
className="notepad-toolbar-button nds-icon-button" className="notepad-toolbar-button"
data-size="sm"
type="button" type="button"
onClick={() => onResize(Math.min(MAX_ROWS, rows + 1), columns)} onClick={() => onResize(Math.min(MAX_ROWS, rows + 1), columns)}
disabled={rows >= MAX_ROWS} disabled={rows >= MAX_ROWS}
@ -19,9 +18,7 @@ export function TableToolbar({ rows, columns, textContent, onResize, onCopy }) {
<Icon name="add-row" /> <Icon name="add-row" />
</button> </button>
<button <button
className="notepad-toolbar-button nds-icon-button" className="notepad-toolbar-button danger"
data-size="sm"
data-variant="danger"
type="button" type="button"
onClick={() => onResize(Math.max(1, rows - 1), columns)} onClick={() => onResize(Math.max(1, rows - 1), columns)}
disabled={rows <= 1} disabled={rows <= 1}
@ -33,8 +30,7 @@ export function TableToolbar({ rows, columns, textContent, onResize, onCopy }) {
</div> </div>
<div className="notepad-toolbar-group" role="group" aria-label={textContent.columnActionsLabel || "Colonnes"}> <div className="notepad-toolbar-group" role="group" aria-label={textContent.columnActionsLabel || "Colonnes"}>
<button <button
className="notepad-toolbar-button nds-icon-button" className="notepad-toolbar-button"
data-size="sm"
type="button" type="button"
onClick={() => onResize(rows, Math.min(MAX_COLUMNS, columns + 1))} onClick={() => onResize(rows, Math.min(MAX_COLUMNS, columns + 1))}
disabled={columns >= MAX_COLUMNS} disabled={columns >= MAX_COLUMNS}
@ -44,9 +40,7 @@ export function TableToolbar({ rows, columns, textContent, onResize, onCopy }) {
<Icon name="add-column" /> <Icon name="add-column" />
</button> </button>
<button <button
className="notepad-toolbar-button nds-icon-button" className="notepad-toolbar-button danger"
data-size="sm"
data-variant="danger"
type="button" type="button"
onClick={() => onResize(rows, Math.max(1, columns - 1))} onClick={() => onResize(rows, Math.max(1, columns - 1))}
disabled={columns <= 1} disabled={columns <= 1}
@ -62,8 +56,7 @@ export function TableToolbar({ rows, columns, textContent, onResize, onCopy }) {
</div> </div>
<div className="notepad-toolbar-group table-copy-group"> <div className="notepad-toolbar-group table-copy-group">
<button <button
className="notepad-toolbar-button table-copy-button nds-button" className="notepad-toolbar-button table-copy-button"
data-size="sm"
type="button" type="button"
onClick={onCopy} onClick={onCopy}
aria-label={textContent.copyTitle || "Copier les valeurs"} aria-label={textContent.copyTitle || "Copier les valeurs"}

View file

@ -78,8 +78,6 @@ export function TaskPlannerBranch({
<li className="task-planner-hidden-parent" key={`${task.id}:visible-children`}> <li className="task-planner-hidden-parent" key={`${task.id}:visible-children`}>
<div className="task-planner-hidden-parent-line"> <div className="task-planner-hidden-parent-line">
<input <input
className="nds-checkbox"
data-size="sm"
type="checkbox" type="checkbox"
checked={task.checked} checked={task.checked}
onChange={(event) => onUpdateTask(task.id, (current) => ({ onChange={(event) => onUpdateTask(task.id, (current) => ({
@ -160,9 +158,7 @@ export function TaskPlannerBranch({
<div className="checklist-section-header task-planner-category-header"> <div className="checklist-section-header task-planner-category-header">
<div className="task-planner-category-title"> <div className="task-planner-category-title">
<button <button
className="task-planner-category-drag-handle nds-icon-button" className="task-planner-category-drag-handle"
data-size="xs"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.groupReorder.startDrag(event, { groupId: group.category, parentId })} onPointerDown={(event) => reorder.groupReorder.startDrag(event, { groupId: group.category, parentId })}
aria-label={`${textContent.categoryReorderTitle || "Déplacer la catégorie"} ${group.category}`} aria-label={`${textContent.categoryReorderTitle || "Déplacer la catégorie"} ${group.category}`}
@ -175,8 +171,7 @@ export function TaskPlannerBranch({
<div className="task-planner-category-actions"> <div className="task-planner-category-actions">
<span>{groupCompletedCount} / {groupCount}</span> <span>{groupCompletedCount} / {groupCount}</span>
<button <button
className="checklist-section-collapse-button nds-icon-button" className="checklist-section-collapse-button"
data-size="xs"
type="button" type="button"
onClick={() => onToggleCategoryCollapsed(group.category)} onClick={() => onToggleCategoryCollapsed(group.category)}
aria-expanded={!isCollapsed} aria-expanded={!isCollapsed}

View file

@ -65,9 +65,7 @@ export function TaskPlannerItem({
<li className={className} {...reorder.getItemProps({ itemId: task.id, groupId: getTaskCategoryDataset(task, tasks, parentMap), parentId })}> <li className={className} {...reorder.getItemProps({ itemId: task.id, groupId: getTaskCategoryDataset(task, tasks, parentMap), parentId })}>
<div className="task-planner-line"> <div className="task-planner-line">
<button <button
className="task-planner-drag-handle nds-icon-button" className="task-planner-drag-handle"
data-size="xs"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, task.id)} onPointerDown={(event) => reorder.itemReorder.startDrag(event, task.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${task.title}`} aria-label={`${textContent.reorderTitle || "Déplacer"} ${task.title}`}
@ -76,8 +74,6 @@ export function TaskPlannerItem({
<Icon name="drag" /> <Icon name="drag" />
</button> </button>
<input <input
className="nds-checkbox"
data-size="sm"
type="checkbox" type="checkbox"
checked={task.checked} checked={task.checked}
onChange={updateChecked} onChange={updateChecked}
@ -85,14 +81,12 @@ export function TaskPlannerItem({
/> />
<input <input
{...titleEdit.getInputProps({ {...titleEdit.getInputProps({
className: "tool-split-entry-label task-planner-title-input nds-inline-input", className: "tool-split-entry-label task-planner-title-input",
type: "text",
"aria-label": textContent.titleLabel || "Titre" "aria-label": textContent.titleLabel || "Titre"
})} })}
/> />
<select <select
className="task-planner-type-select nds-select" className="task-planner-type-select"
data-size="xs"
value={task.type} value={task.type}
onChange={(event) => onUpdateTask(task.id, (current) => { onChange={(event) => onUpdateTask(task.id, (current) => {
const nextType = event.target.value; const nextType = event.target.value;
@ -110,8 +104,7 @@ export function TaskPlannerItem({
)} )}
<div className="task-planner-actions"> <div className="task-planner-actions">
<button <button
className={`task-planner-dropdown-button nds-icon-button ${descriptionOpen ? "active" : ""}`} className={`task-planner-dropdown-button ${descriptionOpen ? "active" : ""}`}
data-size="xs"
type="button" type="button"
onClick={onToggleDescription} onClick={onToggleDescription}
aria-expanded={descriptionOpen} aria-expanded={descriptionOpen}
@ -121,8 +114,7 @@ export function TaskPlannerItem({
<Icon name={descriptionOpen ? "chevron-up" : "chevron-down"} /> <Icon name={descriptionOpen ? "chevron-up" : "chevron-down"} />
</button> </button>
<button <button
className={`task-planner-settings-button nds-icon-button ${settingsOpen ? "active" : ""}`} className={`task-planner-settings-button ${settingsOpen ? "active" : ""}`}
data-size="xs"
type="button" type="button"
onClick={onToggleSettings} onClick={onToggleSettings}
aria-expanded={settingsOpen} aria-expanded={settingsOpen}
@ -131,7 +123,7 @@ export function TaskPlannerItem({
> >
<Icon name="settings" /> <Icon name="settings" />
</button> </button>
<button className="task-planner-delete-button nds-icon-button" data-size="xs" data-variant="danger" type="button" onClick={() => onDeleteTask(task.id)} aria-label={`${textContent.deleteTitle || "Supprimer"} ${task.title}`} title={textContent.deleteTitle || "Supprimer"}> <button className="task-planner-delete-button danger" type="button" onClick={() => onDeleteTask(task.id)} aria-label={`${textContent.deleteTitle || "Supprimer"} ${task.title}`} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</div> </div>

View file

@ -110,9 +110,7 @@ function TaskDescriptionPanel({ task, tasks, parentMap, parentId, textContent, o
<span>{textContent.categoryLabel || "Catégorie"}</span> <span>{textContent.categoryLabel || "Catégorie"}</span>
<input <input
{...categoryEdit.getInputProps({ {...categoryEdit.getInputProps({
className: "nds-inline-input",
disabled: !categoryEditable, disabled: !categoryEditable,
type: "text",
placeholder: textContent.categoryPlaceholder || "Nom de catégorie", placeholder: textContent.categoryPlaceholder || "Nom de catégorie",
"aria-label": `${textContent.categoryLabel || "Catégorie"} ${task.title}` "aria-label": `${textContent.categoryLabel || "Catégorie"} ${task.title}`
})} })}
@ -122,8 +120,7 @@ function TaskDescriptionPanel({ task, tasks, parentMap, parentId, textContent, o
<span>{textContent.descriptionLabel || "Description"}</span> <span>{textContent.descriptionLabel || "Description"}</span>
<textarea <textarea
{...descriptionEdit.getInputProps({ {...descriptionEdit.getInputProps({
className: "task-planner-description nds-textarea", className: "task-planner-description",
"data-size": "sm",
placeholder: textContent.descriptionPlaceholder || "Description", placeholder: textContent.descriptionPlaceholder || "Description",
"aria-label": `${textContent.descriptionLabel || "Description"} ${task.title}` "aria-label": `${textContent.descriptionLabel || "Description"} ${task.title}`
})} })}
@ -141,8 +138,6 @@ function TaskResetSettings({ task, data, textContent, onUpdateTask }) {
<span>{textContent.taskDailyResetTimeLabel || "Heure quotidienne"}</span> <span>{textContent.taskDailyResetTimeLabel || "Heure quotidienne"}</span>
<div className="task-planner-reset-control"> <div className="task-planner-reset-control">
<input <input
className="nds-time"
data-size="xs"
type="time" type="time"
value={task.dailyResetTime || data.resetTime} value={task.dailyResetTime || data.resetTime}
onChange={(event) => onUpdateTask(task.id, (current) => { onChange={(event) => onUpdateTask(task.id, (current) => {
@ -154,8 +149,6 @@ function TaskResetSettings({ task, data, textContent, onUpdateTask }) {
aria-label={`${textContent.taskDailyResetTimeLabel || "Heure quotidienne"} ${task.title}`} aria-label={`${textContent.taskDailyResetTimeLabel || "Heure quotidienne"} ${task.title}`}
/> />
<button <button
className="task-planner-reset-button nds-icon-button"
data-size="xs"
type="button" type="button"
onClick={() => onUpdateTask(task.id, (current) => { onClick={() => onUpdateTask(task.id, (current) => {
const nextTask = { ...current }; const nextTask = { ...current };
@ -178,8 +171,6 @@ function TaskResetSettings({ task, data, textContent, onUpdateTask }) {
<label className="task-planner-reset-override"> <label className="task-planner-reset-override">
<span>{textContent.taskWeeklyResetDayLabel || "Jour hebdo"}</span> <span>{textContent.taskWeeklyResetDayLabel || "Jour hebdo"}</span>
<select <select
className="nds-select"
data-size="xs"
value={task.weeklyResetDay ?? ""} value={task.weeklyResetDay ?? ""}
onChange={(event) => onUpdateTask(task.id, (current) => { onChange={(event) => onUpdateTask(task.id, (current) => {
const nextTask = { ...current }; const nextTask = { ...current };
@ -221,8 +212,6 @@ function LinksEditor({ label, relations, task, tasks, parentMap, relationTargets
<div className="task-planner-relations"> <div className="task-planner-relations">
<span>{label}</span> <span>{label}</span>
<select <select
className="nds-select"
data-size="sm"
value="" value=""
onChange={(event) => { onChange={(event) => {
onAddLinkTarget(task.id, event.target.value); onAddLinkTarget(task.id, event.target.value);
@ -239,8 +228,6 @@ function LinksEditor({ label, relations, task, tasks, parentMap, relationTargets
<span>{getTaskTitle(tasks, relation.toTaskId)}</span> <span>{getTaskTitle(tasks, relation.toTaskId)}</span>
<label title={prerequisiteLabel}> <label title={prerequisiteLabel}>
<input <input
className="nds-checkbox"
data-size="sm"
type="checkbox" type="checkbox"
checked={relation.prerequisite} checked={relation.prerequisite}
onChange={(event) => onSetPrerequisite(task.id, relation.toTaskId, event.target.checked)} onChange={(event) => onSetPrerequisite(task.id, relation.toTaskId, event.target.checked)}
@ -248,7 +235,7 @@ function LinksEditor({ label, relations, task, tasks, parentMap, relationTargets
/> />
<span>{prerequisiteLabel}</span> <span>{prerequisiteLabel}</span>
</label> </label>
<button className="nds-icon-button" data-size="xs" data-variant="danger" type="button" onClick={() => onRemoveLinkTarget(task.id, relation.toTaskId)} title={removeLabel} aria-label={`${removeLabel} ${getTaskTitle(tasks, relation.toTaskId)}`}> <button type="button" onClick={() => onRemoveLinkTarget(task.id, relation.toTaskId)} title={removeLabel} aria-label={`${removeLabel} ${getTaskTitle(tasks, relation.toTaskId)}`}>
<Icon name="close" /> <Icon name="close" />
</button> </button>
</div> </div>

View file

@ -7,19 +7,17 @@ export function TaskPlannerSettings({ data, textContent, onWeeklyResetDayChange,
<section className="task-planner-settings" aria-label={textContent.settingsTitle || "Réglages du planificateur"}> <section className="task-planner-settings" aria-label={textContent.settingsTitle || "Réglages du planificateur"}>
<label> <label>
<span>{textContent.globalWeeklyResetDayLabel || "Reset hebdo"}</span> <span>{textContent.globalWeeklyResetDayLabel || "Reset hebdo"}</span>
<select className="nds-select" data-size="sm" value={data.weeklyResetDay} onChange={(event) => onWeeklyResetDayChange(Number(event.target.value))}> <select value={data.weeklyResetDay} onChange={(event) => onWeeklyResetDayChange(Number(event.target.value))}>
{WEEK_DAYS.map((day) => <option key={day.value} value={day.value}>{day.label}</option>)} {WEEK_DAYS.map((day) => <option key={day.value} value={day.value}>{day.label}</option>)}
</select> </select>
</label> </label>
<label> <label>
<span>{textContent.resetTimeLabel || "Heure de reset"}</span> <span>{textContent.resetTimeLabel || "Heure de reset"}</span>
<input className="nds-time" data-size="sm" type="time" value={data.resetTime} onChange={(event) => onResetTimeChange(event.target.value || "00:00")} /> <input type="time" value={data.resetTime} onChange={(event) => onResetTimeChange(event.target.value || "00:00")} />
</label> </label>
<div className="task-planner-settings-actions"> <div className="task-planner-settings-actions">
<button <button
className="task-planner-clear-completed-button nds-button" className="task-planner-clear-completed-button danger"
data-size="sm"
data-variant="danger"
type="button" type="button"
onClick={onClearCompletedUniqueTasks} onClick={onClearCompletedUniqueTasks}
disabled={!data.tasks.some((task) => task.type === "unique" && task.checked)} disabled={!data.tasks.some((task) => task.type === "unique" && task.checked)}

View file

@ -23,8 +23,7 @@ export function TaskPlannerTopbar({
<div className="task-planner-topbar"> <div className="task-planner-topbar">
<div className="task-planner-global-controls"> <div className="task-planner-global-controls">
<button <button
className={`tool-split-scroll-toggle task-planner-hide-completed-toggle nds-switch ${data.hideCompleted ? "active" : ""}`} className={`tool-split-scroll-toggle task-planner-hide-completed-toggle ${data.hideCompleted ? "active" : ""}`}
data-size="lg"
type="button" type="button"
onClick={onHideCompletedToggle} onClick={onHideCompletedToggle}
aria-pressed={data.hideCompleted} aria-pressed={data.hideCompleted}
@ -35,8 +34,7 @@ export function TaskPlannerTopbar({
<i aria-hidden="true" /> <i aria-hidden="true" />
</button> </button>
<button <button
className={`task-planner-settings-toggle nds-button ${settingsOpen ? "active" : ""}`} className={`task-planner-settings-toggle ${settingsOpen ? "active" : ""}`}
data-size="sm"
type="button" type="button"
onClick={onSettingsToggle} onClick={onSettingsToggle}
aria-expanded={settingsOpen} aria-expanded={settingsOpen}
@ -50,11 +48,11 @@ export function TaskPlannerTopbar({
{editing && ( {editing && (
<div className="task-planner-add-stack"> <div className="task-planner-add-stack">
<form className="inline-form task-planner-add-form" onSubmit={onAddTask}> <form className="inline-form task-planner-add-form" onSubmit={onAddTask}>
<input {...taskDraft.getFieldProps("title", { className: "nds-input", "data-size": "sm", type: "text", placeholder: textContent.titlePlaceholder || "Nouvelle tâche" })} /> <input {...taskDraft.getFieldProps("title", { placeholder: textContent.titlePlaceholder || "Nouvelle tâche" })} />
<select {...taskDraft.getFieldProps("type", { className: "nds-select", "data-size": "sm", "aria-label": textContent.typeLabel || "Type" })}> <select {...taskDraft.getFieldProps("type", { "aria-label": textContent.typeLabel || "Type" })}>
{TASK_TYPES.map((taskType) => <option key={taskType} value={taskType}>{getTaskTypeLabel(taskType, textContent)}</option>)} {TASK_TYPES.map((taskType) => <option key={taskType} value={taskType}>{getTaskTypeLabel(taskType, textContent)}</option>)}
</select> </select>
<button className="nds-button" data-size="sm" data-variant="primary">{textContent.addButton || "Ajouter"}</button> <button className="primary">{textContent.addButton || "Ajouter"}</button>
</form> </form>
</div> </div>
)} )}

View file

@ -9,11 +9,11 @@ export function TextExchangeActions({ type, data, context, onImport, textContent
return ( return (
<> <>
<div className="text-import-actions"> <div className="text-import-actions">
<button className="nds-button" data-size="sm" type="button" onClick={exchange.openImport}> <button type="button" onClick={exchange.openImport}>
<Icon name="import" /> <Icon name="import" />
<span>{textContent.importOpenButton || "Importer du texte"}</span> <span>{textContent.importOpenButton || "Importer du texte"}</span>
</button> </button>
<button className="nds-button" data-size="sm" type="button" onClick={exchange.openExport} disabled={!exchange.canExport}> <button type="button" onClick={exchange.openExport} disabled={!exchange.canExport}>
<Icon name="export" /> <Icon name="export" />
<span>{textContent.exportOpenButton || exportLabel || "Exporter en format texte"}</span> <span>{textContent.exportOpenButton || exportLabel || "Exporter en format texte"}</span>
</button> </button>

View file

@ -24,15 +24,14 @@ export function TextImportModal({ title, value, placeholder, submitLabel, readOn
<section className="confirm-modal text-import-modal-dialog" role="dialog" aria-modal="true" aria-labelledby="text-import-title"> <section className="confirm-modal text-import-modal-dialog" role="dialog" aria-modal="true" aria-labelledby="text-import-title">
<header> <header>
<h2 id="text-import-title">{title}</h2> <h2 id="text-import-title">{title}</h2>
<button className="drawer-close-button nds-icon-button" data-size="sm" type="button" onClick={onClose} aria-label="Fermer" title="Fermer"> <button className="drawer-close-button" type="button" onClick={onClose} aria-label="Fermer" title="Fermer">
<Icon name="close" /> <Icon name="close" />
</button> </button>
</header> </header>
<form className="text-import-modal-form" onSubmit={onSubmit}> <form className="text-import-modal-form" onSubmit={onSubmit}>
<textarea <textarea
autoFocus autoFocus
className={`nds-textarea ${error ? "has-error" : ""}`.trim()} className={error ? "has-error" : ""}
data-size="sm"
readOnly={readOnly} readOnly={readOnly}
value={value} value={value}
onChange={(event) => onChange?.(event.target.value)} onChange={(event) => onChange?.(event.target.value)}
@ -54,11 +53,11 @@ export function TextImportModal({ title, value, placeholder, submitLabel, readOn
</p> </p>
)} )}
<footer> <footer>
<button className="nds-button" data-size="sm" type="button" onClick={onClose}>Annuler</button> <button type="button" onClick={onClose}>Annuler</button>
{readOnly ? ( {readOnly ? (
<button className="nds-button" data-size="sm" data-variant="primary" type="button" onClick={onCopy}>{submitLabel}</button> <button className="primary" type="button" onClick={onCopy}>{submitLabel}</button>
) : ( ) : (
<button className="nds-button" data-size="sm" data-variant="primary" type="submit">{submitLabel}</button> <button className="primary" type="submit">{submitLabel}</button>
)} )}
</footer> </footer>
</form> </form>

View file

@ -18,11 +18,11 @@ export function CountdownControls({ textContent, form, error, onChange, onSubmit
<form className="timer-control-panel timer-countdown-form" role="tabpanel" onSubmit={onSubmit} aria-describedby={error ? "timer-countdown-error" : undefined}> <form className="timer-control-panel timer-countdown-form" role="tabpanel" onSubmit={onSubmit} aria-describedby={error ? "timer-countdown-error" : undefined}>
<label> <label>
<span>{textContent.countdownLabel || "Libellé"}</span> <span>{textContent.countdownLabel || "Libellé"}</span>
<input className="nds-input" data-size="sm" type="text" value={form.label} onChange={(event) => onChange({ label: event.target.value })} placeholder={textContent.countdownLabelPlaceholder || "Boss, event, craft..."} /> <input value={form.label} onChange={(event) => onChange({ label: event.target.value })} placeholder={textContent.countdownLabelPlaceholder || "Boss, event, craft..."} />
</label> </label>
<label> <label>
<span>{textContent.countdownTypeLabel || "Type"}</span> <span>{textContent.countdownTypeLabel || "Type"}</span>
<select className="nds-select" data-size="sm" value={form.type} onChange={(event) => onChange({ type: event.target.value })}> <select value={form.type} onChange={(event) => onChange({ type: event.target.value })}>
<option value="duration">{textContent.durationType || "Durée"}</option> <option value="duration">{textContent.durationType || "Durée"}</option>
<option value="daily_time">{textContent.dailyTimeType || "Heure précise"}</option> <option value="daily_time">{textContent.dailyTimeType || "Heure précise"}</option>
<option value="time_pattern">{textContent.timePatternType || "Pattern horaire"}</option> <option value="time_pattern">{textContent.timePatternType || "Pattern horaire"}</option>
@ -68,7 +68,7 @@ export function CountdownControls({ textContent, form, error, onChange, onSubmit
<span>{error}</span> <span>{error}</span>
</p> </p>
)} )}
<button className="nds-button" data-size="sm" data-variant="primary" type="submit">{textContent.addCountdownButton || "Ajouter"}</button> <button className="primary" type="submit">{textContent.addCountdownButton || "Ajouter"}</button>
</form> </form>
); );
} }
@ -102,8 +102,7 @@ export function CountdownList({ countdowns, nowMs, textContent, reorder, reorder
<div className={`tool-split-entry timer-entry has-single-action ${reorderEnabled ? "has-drag-handle" : ""} ${editingId !== countdown.id ? "has-inline-controls" : ""} ${(canReset || canToggleRefresh) && editingId !== countdown.id ? "has-inline-reset" : ""}`}> <div className={`tool-split-entry timer-entry has-single-action ${reorderEnabled ? "has-drag-handle" : ""} ${editingId !== countdown.id ? "has-inline-controls" : ""} ${(canReset || canToggleRefresh) && editingId !== countdown.id ? "has-inline-reset" : ""}`}>
{reorderEnabled && ( {reorderEnabled && (
<button <button
className="timer-drag-handle nds-icon-button" className="timer-drag-handle"
data-size="xs"
type="button" type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, countdown.id)} onPointerDown={(event) => reorder.itemReorder.startDrag(event, countdown.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${countdown.label}`} aria-label={`${textContent.reorderTitle || "Déplacer"} ${countdown.label}`}
@ -136,8 +135,7 @@ export function CountdownList({ countdowns, nowMs, textContent, reorder, reorder
<span className="timer-inline-controls"> <span className="timer-inline-controls">
{canReset && ( {canReset && (
<button <button
className="timer-inline-reset nds-icon-button" className="timer-inline-reset"
data-size="xxs"
type="button" type="button"
onClick={(event) => { onClick={(event) => {
event.stopPropagation(); event.stopPropagation();
@ -151,8 +149,7 @@ export function CountdownList({ countdowns, nowMs, textContent, reorder, reorder
)} )}
{canToggleRefresh && ( {canToggleRefresh && (
<button <button
className={`timer-inline-refresh-toggle nds-switch ${countdown.autoRefresh ? "active" : ""} ${refreshBlocked ? "is-blocked" : ""}`} className={`timer-inline-refresh-toggle ${countdown.autoRefresh ? "active" : ""} ${refreshBlocked ? "is-blocked" : ""}`}
data-size="xs"
type="button" type="button"
onClick={(event) => { onClick={(event) => {
event.stopPropagation(); event.stopPropagation();
@ -177,7 +174,7 @@ export function CountdownList({ countdowns, nowMs, textContent, reorder, reorder
</span> </span>
</span> </span>
)} )}
<button className="nds-icon-button" data-size="xs" data-variant="danger" type="button" onClick={() => onDelete(countdown.id)} aria-label={textContent.deleteTitle || "Supprimer"} title={textContent.deleteTitle || "Supprimer"}> <button className="danger" type="button" onClick={() => onDelete(countdown.id)} aria-label={textContent.deleteTitle || "Supprimer"} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</div> </div>

View file

@ -286,13 +286,12 @@ export function TimerModule({ toolboxId, moduleId, context, textExchangeOpen })
</section> </section>
<section className={`tool-split-results timer-results-card ${data.scrollResults ? "is-scrollable" : ""}`}> <section className={`tool-split-results timer-results-card ${data.scrollResults ? "is-scrollable" : ""}`}>
<div className="tool-split-actions"> <div className="tool-split-actions">
<button className="tool-split-action-button nds-icon-button" data-size="sm" data-variant="danger" type="button" onClick={resetActiveResults} aria-label={textContent.resetTitle || "Réinitialiser"} title={textContent.resetTitle || "Réinitialiser"}> <button className="tool-split-action-button danger" type="button" onClick={resetActiveResults} aria-label={textContent.resetTitle || "Réinitialiser"} title={textContent.resetTitle || "Réinitialiser"}>
<Icon name="rubber" /> <Icon name="rubber" />
</button> </button>
{activeTab === "countdown" && ( {activeTab === "countdown" && (
<button <button
className={`tool-split-scroll-toggle timer-sort-toggle nds-switch ${data.sortResults ? "active" : ""}`} className={`tool-split-scroll-toggle timer-sort-toggle ${data.sortResults ? "active" : ""}`}
data-size="md"
type="button" type="button"
onClick={() => setSortResults(!data.sortResults)} onClick={() => setSortResults(!data.sortResults)}
aria-pressed={data.sortResults} aria-pressed={data.sortResults}
@ -304,8 +303,7 @@ export function TimerModule({ toolboxId, moduleId, context, textExchangeOpen })
</button> </button>
)} )}
<button <button
className={`tool-split-scroll-toggle timer-scroll-toggle nds-switch ${data.scrollResults ? "active" : ""}`} className={`tool-split-scroll-toggle timer-scroll-toggle ${data.scrollResults ? "active" : ""}`}
data-size="md"
type="button" type="button"
onClick={() => setScrollResults(!data.scrollResults)} onClick={() => setScrollResults(!data.scrollResults)}
aria-pressed={data.scrollResults} aria-pressed={data.scrollResults}

View file

@ -26,8 +26,7 @@ export function TimerAlertModeSwitch({ mode, blocked, textContent, onChange }) {
return ( return (
<div <div
className={`timer-alert-mode-switch nds-radio-button-group ${blocked ? "is-blocked" : ""}`} className={`timer-alert-mode-switch ${blocked ? "is-blocked" : ""}`}
data-size="xs"
role="group" role="group"
aria-label={textContent.alertModeTitle || "Mode d'alerte"} aria-label={textContent.alertModeTitle || "Mode d'alerte"}
title={blocked ? blockedTitle : undefined} title={blocked ? blockedTitle : undefined}
@ -64,11 +63,11 @@ export function TimePartsInput({ value, onChange, allowWildcard = false }) {
return ( return (
<div className="time-parts-input" role="group" aria-label="Format heure minute seconde"> <div className="time-parts-input" role="group" aria-label="Format heure minute seconde">
<input type="text" value={value.hours} onChange={(event) => updatePart("hours", event.target.value)} placeholder="hh" inputMode={allowWildcard ? "text" : "numeric"} /> <input value={value.hours} onChange={(event) => updatePart("hours", event.target.value)} placeholder="hh" inputMode={allowWildcard ? "text" : "numeric"} />
<span>:</span> <span>:</span>
<input type="text" value={value.minutes} onChange={(event) => updatePart("minutes", event.target.value)} placeholder="mm" inputMode={allowWildcard ? "text" : "numeric"} /> <input value={value.minutes} onChange={(event) => updatePart("minutes", event.target.value)} placeholder="mm" inputMode={allowWildcard ? "text" : "numeric"} />
<span>:</span> <span>:</span>
<input type="text" value={value.seconds} onChange={(event) => updatePart("seconds", event.target.value)} placeholder="ss" inputMode={allowWildcard ? "text" : "numeric"} /> <input value={value.seconds} onChange={(event) => updatePart("seconds", event.target.value)} placeholder="ss" inputMode={allowWildcard ? "text" : "numeric"} />
</div> </div>
); );
} }
@ -88,7 +87,6 @@ export function EditableTimerLabel({ value, placeholder, ariaLabel, children, on
<input <input
{...inlineEdit.getInputProps({ {...inlineEdit.getInputProps({
className: "tool-split-entry-label timer-lap-label", className: "tool-split-entry-label timer-lap-label",
type: "text",
placeholder, placeholder,
"aria-label": ariaLabel "aria-label": ariaLabel
})} })}

View file

@ -10,13 +10,13 @@ export function StopwatchControls({ textContent, elapsedMs, running, onStart, on
<span>{textContent.stopwatchTitle || "Chronomètre à étapes"}</span> <span>{textContent.stopwatchTitle || "Chronomètre à étapes"}</span>
<strong><TimerValue ms={elapsedMs} compact /></strong> <strong><TimerValue ms={elapsedMs} compact /></strong>
<div className="timer-actions"> <div className="timer-actions">
<button className="timer-action-button nds-icon-button" data-size="sm" data-variant="primary" type="button" onClick={running ? onPause : onStart} aria-label={running ? textContent.pauseButton || "Pause" : textContent.startButton || "Lancer"} title={running ? textContent.pauseButton || "Pause" : textContent.startButton || "Lancer"}> <button className="primary timer-action-button" type="button" onClick={running ? onPause : onStart} aria-label={running ? textContent.pauseButton || "Pause" : textContent.startButton || "Lancer"} title={running ? textContent.pauseButton || "Pause" : textContent.startButton || "Lancer"}>
<Icon name={running ? "pause-circle" : "play-circle"} /> <Icon name={running ? "pause-circle" : "play-circle"} />
</button> </button>
<button className="timer-action-button nds-icon-button" data-size="sm" type="button" onClick={onLap} disabled={!elapsedMs} aria-label={textContent.lapButton || "Étape"} title={textContent.lapButton || "Étape"}> <button className="timer-action-button" type="button" onClick={onLap} disabled={!elapsedMs} aria-label={textContent.lapButton || "Étape"} title={textContent.lapButton || "Étape"}>
<Icon name="record-circle" /> <Icon name="record-circle" />
</button> </button>
<button className="timer-action-button nds-icon-button" data-size="sm" data-variant="danger" type="button" onClick={onReset} aria-label={textContent.resetTitle || "Réinitialiser"} title={textContent.resetTitle || "Réinitialiser"}> <button className="danger timer-action-button" type="button" onClick={onReset} aria-label={textContent.resetTitle || "Réinitialiser"} title={textContent.resetTitle || "Réinitialiser"}>
<Icon name="stop-circle" /> <Icon name="stop-circle" />
</button> </button>
</div> </div>
@ -56,7 +56,7 @@ export function StopwatchLapList({ laps, textContent, onRename, onDelete }) {
</span> </span>
</span> </span>
)} )}
<button className="nds-icon-button" data-size="xs" data-variant="danger" type="button" onClick={() => onDelete(lap.id)} aria-label={textContent.deleteTitle || "Supprimer"} title={textContent.deleteTitle || "Supprimer"}> <button className="danger" type="button" onClick={() => onDelete(lap.id)} aria-label={textContent.deleteTitle || "Supprimer"} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</div> </div>

View file

@ -188,9 +188,7 @@ function ModuleShell({ toolbox, module, context, reorder, itemGroup = "", onRena
<header> <header>
<div> <div>
<button <button
className="module-drag-handle nds-icon-button" className="module-drag-handle"
data-size="md"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, module.id)} onPointerDown={(event) => reorder.itemReorder.startDrag(event, module.id)}
aria-label={`Déplacer ${module.title || label}`} aria-label={`Déplacer ${module.title || label}`}
@ -206,8 +204,7 @@ function ModuleShell({ toolbox, module, context, reorder, itemGroup = "", onRena
<div> <div>
{definition.scrollable && ( {definition.scrollable && (
<button <button
className={`module-scroll-button nds-switch ${scrollable ? "active" : ""}`} className={`module-scroll-button ${scrollable ? "active" : ""}`}
data-size="lg"
onClick={() => onUpdateModule(module.id, (currentModule) => { onClick={() => onUpdateModule(module.id, (currentModule) => {
const nextModule = { ...currentModule }; const nextModule = { ...currentModule };
if (scrollable) { if (scrollable) {
@ -227,8 +224,7 @@ function ModuleShell({ toolbox, module, context, reorder, itemGroup = "", onRena
)} )}
{hasAddPanel && ( {hasAddPanel && (
<button <button
className={`module-edit-button nds-icon-button ${editing ? "active" : ""}`} className={`module-edit-button ${editing ? "active" : ""}`}
data-size="md"
onClick={() => setEditing((value) => { onClick={() => setEditing((value) => {
const nextEditing = !value; const nextEditing = !value;
if (nextEditing) setTextExchangeOpen(false); if (nextEditing) setTextExchangeOpen(false);
@ -243,8 +239,7 @@ function ModuleShell({ toolbox, module, context, reorder, itemGroup = "", onRena
)} )}
{hasTextExchange && ( {hasTextExchange && (
<button <button
className={`module-exchange-button nds-icon-button ${textExchangeOpen ? "active" : ""}`} className={`module-exchange-button ${textExchangeOpen ? "active" : ""}`}
data-size="md"
onClick={() => setTextExchangeOpen((value) => { onClick={() => setTextExchangeOpen((value) => {
const nextOpen = !value; const nextOpen = !value;
if (nextOpen) setEditing(false); if (nextOpen) setEditing(false);
@ -257,7 +252,7 @@ function ModuleShell({ toolbox, module, context, reorder, itemGroup = "", onRena
<Icon name="import" /> <Icon name="import" />
</button> </button>
)} )}
<button className="module-delete-button nds-icon-button" data-size="md" data-variant="danger" onClick={() => onDelete(module.id)} aria-label={`Retirer ${module.title || label}`} data-toolbox-tutorial-target={module.type === "links" ? "deleteTool" : undefined} title="Retirer"> <button className="module-delete-button danger" onClick={() => onDelete(module.id)} aria-label={`Retirer ${module.title || label}`} data-toolbox-tutorial-target={module.type === "links" ? "deleteTool" : undefined} title="Retirer">
<span className="ui-icon ui-icon-trash" aria-hidden="true" /> <span className="ui-icon ui-icon-trash" aria-hidden="true" />
</button> </button>
</div> </div>

View file

@ -6,7 +6,7 @@ export const HOUR_HALF_SLOT_COUNT = 2;
export const WEEK_SLOT_COUNT = DAY_HOUR_COUNT * HOUR_HALF_SLOT_COUNT; export const WEEK_SLOT_COUNT = DAY_HOUR_COUNT * HOUR_HALF_SLOT_COUNT;
export const WEEK_HOUR_ROW_HEIGHT = 27; export const WEEK_HOUR_ROW_HEIGHT = 27;
export const WEEK_HIDDEN_ROW_HEIGHT = 20; export const WEEK_HIDDEN_ROW_HEIGHT = 20;
export const WEEK_COMPACT_EVENT_HEIGHT = 22; export const WEEK_COMPACT_EVENT_HEIGHT = WEEK_HOUR_ROW_HEIGHT / HOUR_HALF_SLOT_COUNT;
export const WEEKDAY_LABELS = ["lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche"]; export const WEEKDAY_LABELS = ["lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche"];
function occurrencesOverlap(first, second) { function occurrencesOverlap(first, second) {

View file

@ -6,15 +6,6 @@ export const CALENDAR_MONTH_REPEAT_MODES = ["none", "weekly", "monthly"];
export const CALENDAR_ALERT_MODES = ["off", "visible", "site"]; export const CALENDAR_ALERT_MODES = ["off", "visible", "site"];
export const CALENDAR_ALERT_OFFSETS = [10, 5, 0]; export const CALENDAR_ALERT_OFFSETS = [10, 5, 0];
export const CALENDAR_COLORS = ["violet", "gold", "cyan", "pink", "green", "gray"]; export const CALENDAR_COLORS = ["violet", "gold", "cyan", "pink", "green", "gray"];
export const CALENDAR_COLOR_VALUES = {
violet: "#8b5cf6",
gold: "#f6c453",
cyan: "#22d3ee",
pink: "#d946ef",
green: "#22c55e",
gray: "#7d879f"
};
export const CALENDAR_DARK_TEXT_COLORS = new Set(["gold", "cyan", "green"]);
export const WEEK_SLOT_MINUTES = 30; export const WEEK_SLOT_MINUTES = 30;
export const WEEK_TIME_INPUT_STEP_MINUTES = 5; export const WEEK_TIME_INPUT_STEP_MINUTES = 5;
export const WEEK_SLOT_COUNT = 48; export const WEEK_SLOT_COUNT = 48;
@ -22,14 +13,6 @@ export const DAY_MS = 24 * 60 * 60 * 1000;
export const MINUTE_MS = 60 * 1000; export const MINUTE_MS = 60 * 1000;
export const WEEK_DAY_COUNT = 7; export const WEEK_DAY_COUNT = 7;
export function getCalendarColorValue(color) {
return CALENDAR_COLOR_VALUES[color] || CALENDAR_COLOR_VALUES.violet;
}
export function getCalendarEventSurfaceVariant(color) {
return CALENDAR_DARK_TEXT_COLORS.has(color) ? "dark" : "default";
}
export function pad2(value) { export function pad2(value) {
return String(value).padStart(2, "0"); return String(value).padStart(2, "0");
} }

View file

@ -112,7 +112,7 @@ export function AboutPage({ siteContent }) {
</article> </article>
))} ))}
</div> </div>
<a className="about-tools-library-button nds-button" data-size="md" data-variant="primary" href="#/library"><Icon name="book" />{content.toolsLibraryLink}</a> <a className="button primary about-tools-library-button" href="#/library"><Icon name="book" />{content.toolsLibraryLink}</a>
</section> </section>
{content.contribute && ( {content.contribute && (
<section className="about-contribute-section nebula-panel" id="contribuer" aria-labelledby="about-contribute-title"> <section className="about-contribute-section nebula-panel" id="contribuer" aria-labelledby="about-contribute-title">

View file

@ -1,641 +0,0 @@
// Rôle : expose en développement un catalogue vivant et interactif des contrôles NDS.
import { useState } from "react";
import { Icon } from "../components/Icon.jsx";
import { MixTagsInput } from "../components/MixTagsInput.jsx";
import { ToastPositionSwitch } from "../components/ToastPositionSwitch.jsx";
const SIZE_VALUES = {
field: ["xs", "sm", "md"],
button: ["xs", "sm", "md"],
iconButton: ["micro", "xxs", "xs", "sm", "md"],
group: ["xs", "sm", "md"],
switch: ["xs", "sm", "md", "lg"],
none: ["default"]
};
const VARIANT_VALUES = {
button: ["secondary", "primary", "ghost", "dashed", "danger"],
iconButton: ["secondary", "primary", "ghost", "panel-toggle", "danger"],
selectTrigger: ["default", "icon-picker"],
textarea: ["default", "ghost"],
swatch: ["color", "rainbow"],
event: ["default", "dark"],
none: ["default"]
};
const EDGE_VALUES = ["none", "left", "middle", "right", "top", "center", "bottom", "top-left", "top-right", "bottom-right", "bottom-left"];
const BORDER_VALUES = ["default", "joined", "none"];
const SCROLL_AXIS_VALUES = ["horizontal", "vertical"];
const EVENT_COLOR_VALUES = ["#8b5cf6", "#f6c453", "#22d3ee", "#d946ef", "#22c55e", "#7d879f"];
const STATE_VALUES = ["normal", "active", "disabled"];
const FIELD_STATE_VALUES = ["normal", "disabled"];
const DEFAULT_STATE_VALUES = ["normal"];
const CONTENT_VALUES = ["text", "icon", "mixed"];
const MIX_TAG_TOKENS = [
{ value: "base", label: "Base", title: "Valeur base" },
{ value: "bonus", label: "Bonus", title: "Valeur bonus" }
];
const COLOR_TOKENS = [
["Page", "var(--color-bg-page)"],
["Surface", "var(--color-bg-surface)"],
["Primary", "var(--color-primary)"],
["Gold", "var(--color-accent-gold)"],
["Cyan", "var(--color-accent-cyan)"],
["Danger", "var(--color-danger)"]
];
const RADIO_OPTIONS = {
text: [
{ value: "a", label: "A" },
{ value: "b", label: "B" },
{ value: "c", label: "C" }
],
icon: [
{ value: "rows", label: "Rows", icon: "rows" },
{ value: "columns", label: "Columns", icon: "columns" }
],
mixed: [
{ value: "one", label: "One", icon: "eye-open" },
{ value: "two", label: "Two", icon: "home" },
{ value: "three", label: "Three", icon: "close" }
]
};
const COMPONENTS = [
{
id: "nds-input",
name: "Input",
className: "nds-input",
sizes: SIZE_VALUES.field,
variants: VARIANT_VALUES.none,
attributes: { "data-size": SIZE_VALUES.field, "data-state": FIELD_STATE_VALUES },
render: ({ attributes, state, label, size }) => <input className="nds-input" {...attributes} type="text" defaultValue={state === "disabled" ? "Disabled" : size.toUpperCase()} disabled={state === "disabled"} aria-label={label} />
},
{
id: "nds-select",
name: "Select",
className: "nds-select",
sizes: SIZE_VALUES.field,
variants: VARIANT_VALUES.none,
attributes: { "data-size": SIZE_VALUES.field, "data-state": FIELD_STATE_VALUES },
render: ({ attributes, state, label }) => (
<select className="nds-select" {...attributes} defaultValue="b" disabled={state === "disabled"} aria-label={label}>
<option value="a">Option A</option>
<option value="b">{state === "disabled" ? "Disabled" : "Option B"}</option>
<option value="c">Option C</option>
</select>
)
},
{
id: "nds-select-trigger",
name: "Select Trigger",
className: "nds-select-trigger",
sizes: SIZE_VALUES.field,
variants: VARIANT_VALUES.selectTrigger,
attributes: { "data-size": SIZE_VALUES.field, "data-variant": VARIANT_VALUES.selectTrigger, "data-state": STATE_VALUES },
render: ({ attributes, state, variant }) => (
<button className="nds-select-trigger" {...attributes} type="button" disabled={state === "disabled"} aria-pressed={state === "active"}>
<Icon name={variant === "icon-picker" ? "shield" : "calendar"} />
{variant !== "icon-picker" && <span>{state === "disabled" ? "Disabled" : "27 août"}</span>}
</button>
)
},
{
id: "nds-time",
name: "Time Input",
className: "nds-time",
sizes: SIZE_VALUES.field,
variants: VARIANT_VALUES.none,
attributes: { "data-size": SIZE_VALUES.field, "data-state": FIELD_STATE_VALUES },
render: ({ attributes, state, label }) => <input className="nds-time" {...attributes} type="time" defaultValue="21:30" disabled={state === "disabled"} aria-label={label} />
},
{
id: "nds-textarea",
name: "Textarea",
className: "nds-textarea",
sizes: SIZE_VALUES.field,
variants: VARIANT_VALUES.textarea,
attributes: { "data-size": SIZE_VALUES.field, "data-variant": VARIANT_VALUES.textarea, "data-state": FIELD_STATE_VALUES },
render: ({ attributes, state, variant, label }) => <textarea className="nds-textarea" {...attributes} defaultValue={state === "disabled" ? "Disabled" : `${variant || "default"}\nscrollbar commune\nresize`} disabled={state === "disabled"} aria-label={label} />
},
{
id: "nds-inline-input",
name: "Inline Input",
className: "nds-inline-input",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.none,
attributes: { "data-state": FIELD_STATE_VALUES },
render: ({ state, label }) => <input className="nds-inline-input" type="text" defaultValue={state === "disabled" ? "Disabled" : "Inline edit"} disabled={state === "disabled"} aria-label={label} />
},
{
id: "nds-button",
name: "Button",
className: "nds-button",
sizes: SIZE_VALUES.button,
variants: VARIANT_VALUES.button,
configurable: { "data-edge": EDGE_VALUES, "data-border": BORDER_VALUES },
attributes: { "data-size": SIZE_VALUES.button, "data-variant": VARIANT_VALUES.button, "data-edge": EDGE_VALUES, "data-border": BORDER_VALUES, "data-state": STATE_VALUES },
render: ({ attributes, state, variant, label }) => <button className="nds-button" {...attributes} type="button" disabled={state === "disabled"} aria-pressed={state === "active"}>{variant === "secondary" ? "secondary" : variant || label}</button>
},
{
id: "nds-icon-button",
name: "Icon Button",
className: "nds-icon-button",
sizes: SIZE_VALUES.iconButton,
variants: VARIANT_VALUES.iconButton,
configurable: { "data-edge": EDGE_VALUES, "data-border": BORDER_VALUES },
attributes: { "data-size": SIZE_VALUES.iconButton, "data-variant": VARIANT_VALUES.iconButton, "data-edge": EDGE_VALUES, "data-border": BORDER_VALUES, "data-state": STATE_VALUES },
render: ({ attributes, state, variant, label }) => {
const icon = variant === "danger" ? "trash" : variant === "primary" ? "export" : "settings";
return <button className="nds-icon-button" {...attributes} type="button" disabled={state === "disabled"} aria-pressed={state === "active"} aria-label={label}><Icon name={icon} /></button>;
}
},
{
id: "nds-radio-button-group",
name: "Radio Button Group",
className: "nds-radio-button-group",
sizes: SIZE_VALUES.group,
variants: VARIANT_VALUES.none,
configurable: { content: CONTENT_VALUES },
attributes: { "data-size": SIZE_VALUES.group, content: CONTENT_VALUES, "data-state": ["normal", "disabled"] },
render: ({ attributes, config, state }) => state === "disabled"
? <div className="nds-radio-button-group" {...attributes} aria-disabled="true"><span>A</span><span>B</span></div>
: <InteractiveRadioButtonGroup attributes={attributes} options={RADIO_OPTIONS[config.content || "text"]} iconOnly={config.content === "icon"} />
},
{
id: "nds-checkbox",
name: "Checkbox",
className: "nds-checkbox",
sizes: ["md", "sm", "xs"],
variants: VARIANT_VALUES.none,
attributes: { "data-size": ["md", "sm", "xs"], "data-state": STATE_VALUES },
render: ({ attributes, state, label }) => <input className="nds-checkbox" {...attributes} type="checkbox" defaultChecked={state === "active"} disabled={state === "disabled"} aria-label={label} />
},
{
id: "nds-quantity-stepper",
name: "Quantity Stepper",
className: "nds-quantity-stepper",
sizes: SIZE_VALUES.group,
variants: VARIANT_VALUES.none,
configurable: { content: ["value", "input"] },
attributes: { "data-size": SIZE_VALUES.group, content: ["value", "input"] },
render: ({ attributes, config }) => <InteractiveStepper attributes={attributes} input={config.content === "input"} />
},
{
id: "nds-switch",
name: "Switch",
className: "nds-switch",
sizes: SIZE_VALUES.switch,
variants: VARIANT_VALUES.none,
attributes: { "data-size": SIZE_VALUES.switch, "data-state": STATE_VALUES },
render: ({ attributes, state }) => state === "disabled"
? <button className="nds-switch" {...attributes} type="button" disabled aria-pressed="true"><Icon name="scrollable" /><i aria-hidden="true" /></button>
: <InteractiveSwitch attributes={attributes} initialActive={state === "active"} />
},
{
id: "nds-native-switch",
name: "Native Switch",
className: "nds-native-switch",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.none,
attributes: { "data-state": STATE_VALUES },
render: ({ state }) => <NativeSwitchPreview disabled={state === "disabled"} initialChecked={state === "active"} />
},
{
id: "nds-swatch",
name: "Swatch",
className: "nds-swatch",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.swatch,
attributes: { "data-variant": VARIANT_VALUES.swatch, "data-state": STATE_VALUES },
render: ({ attributes, state, variant, label }) => <button className="nds-swatch" {...attributes} type="button" style={variant === "rainbow" ? undefined : { "--swatch-color": "#22d3ee" }} disabled={state === "disabled"} aria-pressed={state === "active"} aria-label={label} />
},
{
id: "nds-compound-toggle-field",
name: "Compound Toggle Field",
className: "nds-compound-toggle-field",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.none,
attributes: { "data-state": STATE_VALUES },
render: ({ state }) => <div className="nds-compound-toggle-field" aria-disabled={state === "disabled"}><button type="button" disabled={state === "disabled"} aria-pressed={state === "active"}>Hold</button><input className="nds-compound-toggle-field-input" type="number" defaultValue="2" disabled={state === "disabled"} aria-label="Hold seconds" /></div>
},
{
id: "nds-list-row",
name: "List Row",
className: "nds-list-row",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.none,
attributes: { "data-state": ["normal", "active", "disabled"] },
render: ({ state }) => <div className={`nds-list-row ${state === "active" ? "is-active" : ""} ${state === "disabled" ? "is-disabled" : ""}`.trim()}><button type="button" disabled={state === "disabled"} aria-pressed={state === "active"}>{state === "active" ? "Page active" : "Page"}</button></div>
},
{
id: "nds-large-icon-picker-trigger",
name: "Large Icon Picker Trigger",
className: "nds-large-icon-picker-trigger",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.none,
attributes: { "data-state": STATE_VALUES },
render: ({ state, label }) => <button className="nds-large-icon-picker-trigger" type="button" disabled={state === "disabled"} aria-pressed={state === "active"} aria-label={label}><Icon name="toolbox" /></button>
},
{
id: "nds-event-surface",
name: "Event Surface",
className: "nds-event-surface",
sizes: ["xs", "sm", "md"],
variants: VARIANT_VALUES.event,
configurable: { "data-color": EVENT_COLOR_VALUES },
attributes: { "data-size": ["xs", "sm", "md"], "data-variant": VARIANT_VALUES.event, "data-color": EVENT_COLOR_VALUES },
render: ({ attributes, config, variant, size }) => (
<div className="nds-event-surface" {...attributes} style={{ "--nds-event-color": config["data-color"], height: size === "xs" ? "10px" : size === "sm" ? "22px" : "44px" }}>
<button type="button"><strong>{variant === "dark" ? "Market" : "Raid"}</strong><span>21:30 - 22:45</span></button>
</div>
)
},
{
id: "nds-scrollbox",
name: "Scrollbox",
className: "nds-scrollbox",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.none,
configurable: { "data-axis": SCROLL_AXIS_VALUES },
attributes: { "data-axis": SCROLL_AXIS_VALUES },
render: ({ attributes, config }) => config["data-axis"] === "horizontal"
? <div className="nds-scrollbox" {...attributes}><div className="nds-scrollbox-content-wide"><span>Alpha sector</span><span>Beta sector</span><span>Gamma sector</span><span>Delta sector</span><span>Epsilon sector</span><span>Zeta sector</span><span>Eta sector</span></div></div>
: <div className="nds-scrollbox" {...attributes}><p>Zone scrollable</p><p>Track transparent</p><p>Thumb commun</p><p>Contenu supplémentaire</p><p>Encore une ligne</p></div>
}
];
const NAV_ITEMS = [
{ id: "foundations", label: "Foundations" },
...COMPONENTS.map(({ id, name }) => ({ id, label: name })),
{ id: "date-picker-buttons", label: "Date Picker Buttons" },
{ id: "specialized-components", label: "Specialized Components" },
{ id: "audit", label: "Audit" }
];
function dataProps(attributes = {}) {
return Object.fromEntries(Object.entries(attributes).filter(([, value]) => value !== undefined && value !== null && value !== ""));
}
function attributeLabel(attributes = {}) {
const entries = Object.entries(attributes).filter(([, value]) => value && value !== "default" && value !== "secondary" && value !== "md" && value !== "vertical" && value !== "none" && value !== "normal");
return entries.length ? entries.map(([key, value]) => `${key}="${value}"`).join(" ") : "default";
}
function DesignSystemNav() {
const scrollToSection = (id) => {
document.getElementById(id)?.scrollIntoView({ behavior: "smooth", block: "start" });
};
return (
<aside className="design-system-nav" aria-label="Navigation design system">
<div>
<p className="eyebrow">Sokko G</p>
<h1>Nebula Design System</h1>
<p>Catalogue vivant des classes publiques, attributs, tailles, variants et états des contrôles interactifs Nebula.</p>
</div>
<div className="design-system-token-stack">
<SpecPill label="Theme" value="Nebula Command" />
<SpecPill label="Focus" value="gold outline" />
<SpecPill label="API" value="class + data-*" />
</div>
<nav>
{NAV_ITEMS.map((item) => <button key={item.id} type="button" onClick={() => scrollToSection(item.id)}>{item.label}</button>)}
</nav>
</aside>
);
}
function SpecPill({ label, value }) {
return <span className="design-system-spec-pill"><b>{label}</b><span>{value}</span></span>;
}
function ComponentSection({ id, title, meta, children }) {
return (
<section id={id} className="design-system-section">
<header>
<div>
<p className="eyebrow">{meta}</p>
<h2>{title}</h2>
</div>
</header>
{children}
</section>
);
}
function AttributeSummary({ component }) {
return (
<article className="design-system-component-intro">
<div className="design-system-doc-summary">
<span>Element</span>
<strong>{component.name}</strong>
<span>Classe</span>
<code>{component.className}</code>
</div>
<div className="design-system-attributes-doc">
<span>Attributes</span>
<dl>
{Object.entries(component.attributes).map(([name, values]) => (
<div key={name}>
<dt><code>{name}</code></dt>
<dd>{values.map((value) => <code key={value}>{value}</code>)}</dd>
</div>
))}
</dl>
</div>
</article>
);
}
function VariantMatrix({ columns, rows, controls, children }) {
return (
<article className="design-system-matrix">
{controls && <header>{controls}</header>}
<div className="design-system-matrix-grid" style={{ "--nds-matrix-columns": columns.length }}>
<div className="design-system-matrix-corner" aria-hidden="true" />
{columns.map((column) => <div key={column} className="design-system-matrix-heading">{column}</div>)}
{children || rows.map((row) => <StatePreview key={row.label} label={row.label} cells={row.cells} />)}
</div>
</article>
);
}
function StatePreview({ label, cells }) {
const [attribute, value] = label.includes("=") ? label.split("=") : [null, label];
return (
<>
<div className="design-system-matrix-row-label">
{attribute ? (
<>
<span>{attribute}</span>
<strong>{value}</strong>
</>
) : <strong>{value}</strong>}
</div>
{cells.map((cell, index) => <div key={`${label}-${index}`} className="design-system-matrix-cell">{cell}</div>)}
</>
);
}
function ConfigSelect({ name, values, value, onChange }) {
return (
<label className="design-system-config-field">
<span>{name}</span>
<select className="nds-select" data-size="xs" value={value} onChange={(event) => onChange(name, event.target.value)}>
{values.map((option) => <option key={option} value={option}>{option}</option>)}
</select>
</label>
);
}
function ControlSample({ children }) {
return (
<div className="design-system-control-sample">
<div className="design-system-sample-preview">{children}</div>
</div>
);
}
function ComponentCatalogSection({ component }) {
const [config, setConfig] = useState(() => Object.fromEntries(Object.entries(component.configurable || {}).map(([name, values]) => [name, values[0]])));
const sizes = component.sizes;
const variants = component.variants;
const stateValues = component.attributes["data-state"] || DEFAULT_STATE_VALUES;
const stateRows = stateValues.filter((state) => state !== "normal");
const hasConfigurableAttributes = Boolean(component.configurable && Object.keys(component.configurable).length);
const defaultStateVariant = variants.includes("secondary") ? "secondary" : variants[0];
const setConfigValue = (name, value) => setConfig((current) => ({ ...current, [name]: value }));
const renderCell = ({ size, variant, state = "normal" }) => {
const attrs = {
...Object.fromEntries(Object.entries(config).filter(([name]) => name.startsWith("data-"))),
"data-size": size,
"data-variant": variant,
"data-edge": config["data-edge"],
"data-border": config["data-border"],
"data-axis": config["data-axis"],
"data-state": state
};
const attributes = dataProps(attrs);
const label = `${component.className} ${attributeLabel(attributes)}`;
return <ControlSample key={`${variant}-${size}-${state}`} label={label}>{component.render({ attributes, config, state, variant, label, size })}</ControlSample>;
};
return (
<ComponentSection id={component.id} title={component.name} meta="NDS element">
<AttributeSummary component={component} />
<VariantMatrix
title="data-size x data-variant"
note="Les colonnes pilotent data-size. Les lignes affichent d'abord data-variant, puis data-state. Le formulaire applique les autres attributs à toute la matrice."
columns={sizes.map((size) => size === "default" ? "default" : `data-size=${size}`)}
controls={hasConfigurableAttributes && (
<div className="design-system-config-panel">
{Object.entries(component.configurable).map(([name, values]) => <ConfigSelect key={name} name={name} values={values} value={config[name]} onChange={setConfigValue} />)}
</div>
)}
>
{variants.map((variant) => (
<StatePreview
key={variant}
label={variant === "default" ? "default" : `data-variant=${variant}`}
cells={sizes.map((size) => renderCell({ size, variant }))}
/>
))}
{stateRows.length > 0 && <div className="design-system-matrix-separator">data-state</div>}
{stateRows.map((state) => (
<StatePreview
key={state}
label={`data-state=${state}`}
cells={sizes.map((size) => renderCell({ size, variant: defaultStateVariant, state }))}
/>
))}
</VariantMatrix>
</ComponentSection>
);
}
function ToggleButton({ children, className = "nds-button", attributes, ariaLabel }) {
const [pressed, setPressed] = useState(false);
return (
<button className={className} {...dataProps(attributes)} type="button" aria-pressed={pressed} aria-label={ariaLabel} onClick={() => setPressed((value) => !value)}>
{children}
</button>
);
}
function InteractiveRadioButtonGroup({ attributes, options, iconOnly = false }) {
const [value, setValue] = useState(options[0]?.value);
return (
<div className="nds-radio-button-group" {...dataProps(attributes)} role="radiogroup" aria-label="Groupe interactif">
{options.map((option) => {
const active = option.value === value;
return (
<button key={option.value} className={active ? "is-active active" : ""} type="button" role="radio" aria-checked={active} aria-label={iconOnly ? option.label : undefined} onClick={() => setValue(option.value)}>
{option.icon && <Icon name={option.icon} />}
{!iconOnly && <span>{option.label}</span>}
</button>
);
})}
</div>
);
}
function InteractiveStepper({ attributes, input = false }) {
const [value, setValue] = useState(2);
const increment = (step) => setValue((current) => Math.min(20, Math.max(0, current + step)));
return (
<span className="nds-quantity-stepper" {...dataProps(attributes)} aria-label="Stepper interactif">
<button type="button" aria-label="Retirer" onClick={() => increment(-1)}><span aria-hidden="true">-</span></button>
<span className="nds-quantity-stepper-value">
{input ? <input className="nds-quantity-stepper-input" type="number" min="0" max="20" value={value} onChange={(event) => setValue(Math.min(20, Math.max(0, Number.parseInt(event.target.value, 10) || 0)))} aria-label="Quantité actuelle" /> : value}
<span>/ 20</span>
</span>
<button type="button" aria-label="Ajouter" onClick={() => increment(1)}><Icon name="add" /></button>
</span>
);
}
function InteractiveSwitch({ attributes, initialActive = false }) {
const [active, setActive] = useState(initialActive);
return (
<button className="nds-switch" {...dataProps(attributes)} type="button" aria-pressed={active} onClick={() => setActive((value) => !value)}>
<Icon name="scrollable" />
<i aria-hidden="true" />
</button>
);
}
function NativeSwitchPreview({ disabled = false, initialChecked = false }) {
const [checked, setChecked] = useState(initialChecked);
return (
<label className="nds-native-switch">
<input type="checkbox" checked={checked} disabled={disabled} onChange={(event) => setChecked(event.target.checked)} aria-label="Inclure" />
<span aria-hidden="true" />
<b>{disabled ? "Disabled" : "Inclus"}</b>
</label>
);
}
function DatePickerPreview() {
const [activeDay, setActiveDay] = useState(2);
const days = Array.from({ length: 35 }, (_, index) => {
const day = index - 2;
return { id: index, label: day > 0 && day <= 31 ? day : "", outside: day <= 0 || day > 31 };
});
return (
<div className="nds-date-picker-preview" role="group" aria-label="Mini calendrier interactif">
{["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"].map((day) => <span key={day} className="nds-date-picker-preview-day">{day}</span>)}
{days.map((day) => (
<button key={day.id} className={`${day.label === activeDay ? "active" : ""} ${day.outside ? "is-outside" : ""}`.trim()} type="button" aria-pressed={day.label === activeDay} disabled={day.outside} onClick={() => setActiveDay(day.label)}>
{day.label}
</button>
))}
</div>
);
}
function FoundationsSection() {
return (
<ComponentSection id="foundations" title="Foundations" meta="tokens">
<div className="design-system-foundations">
<article className="design-system-type-card"><p>Typography</p><strong>Aa</strong><span>Inter, system-ui</span></article>
<article className="design-system-color-card">
<p>Colors</p>
<div className="design-system-swatches">{COLOR_TOKENS.map(([label, color]) => <span key={label} style={{ "--nds-swatch-color": color }} title={label} />)}</div>
</article>
<article className="design-system-radius-card">
<p>Radius</p>
<div><span style={{ borderRadius: "var(--radius-sm)" }}>sm</span><span style={{ borderRadius: "var(--radius-md)" }}>md</span><span style={{ borderRadius: "var(--radius-lg)" }}>lg</span></div>
</article>
<article className="design-system-focus-card"><p>Focus</p><button className="nds-button" data-variant="primary" type="button">Focus visible</button></article>
</div>
</ComponentSection>
);
}
function DatePickerButtonsSection() {
return (
<ComponentSection id="date-picker-buttons" title="Date Picker Buttons" meta="internal NDS pattern">
<VariantMatrix title="month grid" note="Boutons internes du mini calendrier, affichés avec un mois complet." columns={["interactive"]} rows={[{ label: "buttons", cells: [<ControlSample label="date-picker-button mixin"><DatePickerPreview /></ControlSample>] }]} />
</ComponentSection>
);
}
function SpecializedComponentsSection() {
const [mixValue, setMixValue] = useState("base + bonus");
const [smallMixValue, setSmallMixValue] = useState("base * 2");
const [toastPosition, setToastPosition] = useState("right");
return (
<ComponentSection id="specialized-components" title="Specialized Components" meta="not NDS public classes">
<VariantMatrix
title="Composants spécialisés conservés"
note="Ces éléments restent spécialisés : ils ne créent pas de classe de famille NDS publique."
columns={["MixTagsInput", "MixTagsInput small", "Toast switch"]}
rows={[{
label: "interactive",
cells: [
<ControlSample label="MixTagsInput"><MixTagsInput value={mixValue} onChange={setMixValue} tokens={MIX_TAG_TOKENS} insertToken={{ value: "base", icon: "add", title: "Insérer base" }} aria-label="MixTagsInput standard" /></ControlSample>,
<ControlSample label="MixTagsInput size=small"><MixTagsInput size="small" value={smallMixValue} onChange={setSmallMixValue} tokens={MIX_TAG_TOKENS} insertToken={{ value: "bonus", icon: "add", title: "Insérer bonus" }} aria-label="MixTagsInput compact" /></ControlSample>,
<ControlSample label="ToastPositionSwitch"><ToastPositionSwitch position={toastPosition} labels={{ left: "Notifications à gauche", right: "Notifications à droite", title: "Position des notifications" }} onChange={setToastPosition} /></ControlSample>
]
}]}
/>
</ComponentSection>
);
}
function AuditSection() {
return (
<ComponentSection id="audit" title="Audit" meta="api conventions">
<VariantMatrix
title="NDS Attribute API"
note="Une classe publique correspond à une famille. Les attributs data-* configurent les variations. Les états restent natifs, ARIA ou métier."
columns={["family class", "configuration", "state"]}
rows={[
{
label: "rule",
cells: [
<code>className=&quot;nds-button&quot;</code>,
<code>data-size data-variant data-edge data-border data-axis</code>,
<code>disabled aria-pressed .active .is-active</code>
]
},
{
label: "global",
cells: [
<code>Home / About / Games CTAs</code>,
<code>Shell / drawer / modales / filters</code>,
<code>Image viewer / library actions</code>
]
},
{
label: "sample",
cells: [
<button className="nds-icon-button" data-size="sm" data-variant="primary" type="button" aria-label="Exemple"><Icon name="export" /></button>,
<button className="nds-button" data-size="xs" data-variant="dashed" data-edge="left" data-border="joined" type="button"><Icon name="add" />XS</button>,
<ToggleButton attributes={{ "data-variant": "primary" }}>Actif</ToggleButton>
]
}
]}
/>
</ComponentSection>
);
}
export function DesignSystemPage() {
return (
<div className="design-system-page">
<DesignSystemNav />
<main className="design-system-main">
<FoundationsSection />
{COMPONENTS.map((component) => <ComponentCatalogSection key={component.id} component={component} />)}
<DatePickerButtonsSection />
<SpecializedComponentsSection />
<AuditSection />
</main>
</div>
);
}

View file

@ -47,7 +47,7 @@ export function HomeOnboardingSection({ content, className = "", onCreateToolbox
</ol> </ol>
{onCreateToolbox && content.primaryAction && ( {onCreateToolbox && content.primaryAction && (
<div className="home-onboarding-steps-action"> <div className="home-onboarding-steps-action">
<button className="home-onboarding-create-button nds-button" data-size="md" data-variant="primary" type="button" onClick={onCreateToolbox}> <button className="primary home-onboarding-create-button" type="button" onClick={onCreateToolbox}>
{content.primaryAction} {content.primaryAction}
</button> </button>
</div> </div>

View file

@ -15,8 +15,8 @@ export function HomePage({ siteContent, toolboxes, actions }) {
<h1>{content.hero.title}</h1> <h1>{content.hero.title}</h1>
<p>{content.hero.description}</p> <p>{content.hero.description}</p>
<div className="actions"> <div className="actions">
<a className="nds-button" data-size="md" data-variant="primary" href="#/toolboxes">{content.hero.primaryAction}</a> <a className="button primary" href="#/toolboxes">{content.hero.primaryAction}</a>
<a className="nds-button" data-size="md" href="#/games">{content.hero.secondaryAction}</a> <a className="button" href="#/games">{content.hero.secondaryAction}</a>
</div> </div>
</div> </div>
<div className="home-stats hero-stats" aria-label="Statistiques locales du site"> <div className="home-stats hero-stats" aria-label="Statistiques locales du site">

View file

@ -356,8 +356,7 @@ function LibraryToolExample({ module, sourceData, description, categoryTitle, do
<div className="library-example-actions"> <div className="library-example-actions">
{definition.scrollable && ( {definition.scrollable && (
<button <button
className={`module-scroll-button nds-switch ${scrollable ? "active" : ""}`} className={`module-scroll-button ${scrollable ? "active" : ""}`}
data-size="md"
type="button" type="button"
onClick={() => setScrollable((value) => !value)} onClick={() => setScrollable((value) => !value)}
aria-label={`${scrollable ? "Désactiver" : "Activer"} le scroll de ${title}`} aria-label={`${scrollable ? "Désactiver" : "Activer"} le scroll de ${title}`}
@ -370,8 +369,7 @@ function LibraryToolExample({ module, sourceData, description, categoryTitle, do
)} )}
{definition.editable && definition.addable !== false && ( {definition.editable && definition.addable !== false && (
<button <button
className={`module-edit-button nds-icon-button ${editing ? "active" : ""}`} className={`module-edit-button ${editing ? "active" : ""}`}
data-size="sm"
type="button" type="button"
onClick={() => setEditing((value) => !value)} onClick={() => setEditing((value) => !value)}
aria-label={`${editing ? "Masquer" : "Afficher"} l'ajout de ${title}`} aria-label={`${editing ? "Masquer" : "Afficher"} l'ajout de ${title}`}
@ -381,7 +379,7 @@ function LibraryToolExample({ module, sourceData, description, categoryTitle, do
<Icon name="add" /> <Icon name="add" />
</button> </button>
)} )}
<button className="library-reset-button nds-icon-button" data-size="sm" type="button" onClick={() => context.setModuleData(LIBRARY_TOOLBOX_ID, module.id, cloneData(sourceData))} aria-label={`Réinitialiser ${title}`} title="Réinitialiser l'exemple"> <button className="library-reset-button" type="button" onClick={() => context.setModuleData(LIBRARY_TOOLBOX_ID, module.id, cloneData(sourceData))} aria-label={`Réinitialiser ${title}`} title="Réinitialiser l'exemple">
<Icon name="refresh" /> <Icon name="refresh" />
</button> </button>
</div> </div>
@ -508,12 +506,7 @@ function ControlPreview({ item }) {
return ( return (
<span className="library-control-preview" aria-hidden="true"> <span className="library-control-preview" aria-hidden="true">
{buttons.map((button, index) => ( {buttons.map((button, index) => (
<span <span className={`notepad-toolbar-button ${button.label && String(button.label).length > 2 ? "is-wide" : ""} ${button.active ? "active" : ""} ${button.danger ? "danger" : ""}`} key={`${button.label || button.icon || index}-${index}`}>
className={`notepad-toolbar-button nds-icon-button ${button.label && String(button.label).length > 2 ? "is-wide" : ""} ${button.active ? "active" : ""}`.trim()}
data-size="sm"
data-variant={button.danger ? "danger" : undefined}
key={`${button.label || button.icon || index}-${index}`}
>
{button.icon ? <Icon name={button.icon} /> : button.label} {button.icon ? <Icon name={button.icon} /> : button.label}
</span> </span>
))} ))}
@ -523,7 +516,7 @@ function ControlPreview({ item }) {
return ( return (
<span className="library-control-preview" aria-hidden="true"> <span className="library-control-preview" aria-hidden="true">
<span className={`notepad-toolbar-button nds-icon-button ${preview.active ? "active" : ""}`.trim()} data-size="sm" data-variant={preview.danger ? "danger" : undefined}> <span className={`notepad-toolbar-button ${preview.active ? "active" : ""} ${preview.danger ? "danger" : ""}`}>
<Icon name={preview.icon || item.icon || "settings"} /> <Icon name={preview.icon || item.icon || "settings"} />
</span> </span>
</span> </span>
@ -589,7 +582,7 @@ function ToolAdvancedFeatureList({ docs, heading, copyLabel, copySuccess, contex
<p>{docs.importFormat.text}</p> <p>{docs.importFormat.text}</p>
<div className="library-tool-import-example"> <div className="library-tool-import-example">
<pre><code>{docs.importFormat.example}</code></pre> <pre><code>{docs.importFormat.example}</code></pre>
<button className="nds-button" data-size="sm" type="button" onClick={copyExample} title={copyLabel || "Copier l'exemple"}> <button type="button" onClick={copyExample} title={copyLabel || "Copier l'exemple"}>
<Icon name="copy" /> <Icon name="copy" />
<span>{copied ? copySuccess || "Exemple copié." : copyLabel || "Copier l'exemple"}</span> <span>{copied ? copySuccess || "Exemple copié." : copyLabel || "Copier l'exemple"}</span>
</button> </button>

View file

@ -9,9 +9,6 @@ const GamesPage = lazy(() => import("../features/games/GamesPage.jsx").then((mod
const LibraryPage = lazy(() => import("../pages/LibraryPage.jsx").then((module) => ({ default: module.LibraryPage }))); const LibraryPage = lazy(() => import("../pages/LibraryPage.jsx").then((module) => ({ default: module.LibraryPage })));
const ToolboxPage = lazy(() => import("../features/toolboxes/ToolboxPages.jsx").then((module) => ({ default: module.ToolboxPage }))); const ToolboxPage = lazy(() => import("../features/toolboxes/ToolboxPages.jsx").then((module) => ({ default: module.ToolboxPage })));
const ToolboxesPage = lazy(() => import("../features/toolboxes/ToolboxPages.jsx").then((module) => ({ default: module.ToolboxesPage }))); const ToolboxesPage = lazy(() => import("../features/toolboxes/ToolboxPages.jsx").then((module) => ({ default: module.ToolboxesPage })));
const DesignSystemPage = import.meta.env.DEV
? lazy(() => import("../pages/DesignSystemPage.jsx").then((module) => ({ default: module.DesignSystemPage })))
: null;
function RouteLoader() { function RouteLoader() {
return <section className="empty"><p>Chargement...</p></section>; return <section className="empty"><p>Chargement...</p></section>;
@ -22,7 +19,6 @@ export function RouteContent(props) {
if (route === "/") return <HomePage {...props} />; if (route === "/") return <HomePage {...props} />;
if (route === "/about") return <AboutPage siteContent={props.siteContent} />; if (route === "/about") return <AboutPage siteContent={props.siteContent} />;
if (route === "/library") return <Suspense fallback={<RouteLoader />}><LibraryPage {...props} /></Suspense>; if (route === "/library") return <Suspense fallback={<RouteLoader />}><LibraryPage {...props} /></Suspense>;
if (route === "/design-system" && DesignSystemPage) return <Suspense fallback={<RouteLoader />}><DesignSystemPage /></Suspense>;
if (route === "/toolboxes") return <Suspense fallback={<RouteLoader />}><ToolboxesPage {...props} /></Suspense>; if (route === "/toolboxes") return <Suspense fallback={<RouteLoader />}><ToolboxesPage {...props} /></Suspense>;
if (route.startsWith("/toolbox/")) return <Suspense fallback={<RouteLoader />}><ToolboxPage {...props} toolboxId={route.split("/")[2]} /></Suspense>; if (route.startsWith("/toolbox/")) return <Suspense fallback={<RouteLoader />}><ToolboxPage {...props} toolboxId={route.split("/")[2]} /></Suspense>;
if (route === "/games") return <Suspense fallback={<RouteLoader />}><GamesPage {...props} /></Suspense>; if (route === "/games") return <Suspense fallback={<RouteLoader />}><GamesPage {...props} /></Suspense>;

View file

@ -1,6 +1,4 @@
// Rôle : définit les bases globales HTML, body, typographie et contrôles natifs. // Rôle : définit les bases globales HTML, body, typographie et contrôles natifs.
@use "design-system/controls";
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
@ -38,73 +36,52 @@ a {
text-decoration: none; text-decoration: none;
} }
button { button,
@include controls.text-button; .button,
select,
input,
textarea,
.import-button {
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: rgba(21, 26, 48, 0.92);
color: var(--color-text-primary);
font: inherit;
transition:
transform var(--duration-fast) var(--ease-standard),
border-color var(--duration-fast) var(--ease-standard),
background-color var(--duration-fast) var(--ease-standard),
color var(--duration-fast) var(--ease-standard),
box-shadow var(--duration-fast) var(--ease-standard);
} }
select { button,
@include controls.select-control; .button,
.import-button {
display: inline-flex;
min-height: 42px;
align-items: center;
justify-content: center;
gap: 8px;
padding: 0 18px;
cursor: pointer;
} }
select:hover, button:hover,
select:focus { .button:hover,
@include controls.select-control-hover; .import-button:hover {
border-color: rgba(196, 181, 253, 0.34);
background:
linear-gradient(135deg, rgba(31, 37, 68, 0.96), rgba(22, 26, 51, 0.96)) padding-box,
linear-gradient(135deg, rgba(139, 92, 246, 0.32), rgba(196, 181, 253, 0.2)) border-box;
box-shadow:
0 0 14px rgba(139, 92, 246, 0.12),
inset 0 1px 0 rgba(255, 255, 255, 0.05);
} }
select:disabled { button:active,
@include controls.control-disabled; .button:active,
} .import-button:active {
select option {
@include controls.select-option;
}
:where(input[type="text"], input[type="search"], input[type="email"], input[type="url"], input[type="password"], textarea) {
@include controls.field-control;
}
input[type="time"] {
@include controls.time-control;
}
:where(textarea) {
@include controls.field-textarea;
@include controls.scrollbar-control;
@include controls.scrollbar-resizer;
}
:where(input, textarea)::placeholder {
@include controls.field-placeholder;
}
:where(input[type="text"], input[type="search"], input[type="email"], input[type="url"], input[type="password"], textarea):hover,
input[type="time"]:hover {
@include controls.field-hover;
}
:where(input[type="text"], input[type="search"], input[type="email"], input[type="url"], input[type="password"], textarea):focus,
input[type="time"]:focus {
@include controls.field-focus;
outline: none;
}
:where(input[type="text"], input[type="search"], input[type="email"], input[type="url"], input[type="password"], textarea):focus-visible,
input[type="time"]:focus-visible {
outline: 2px solid var(--color-accent-gold);
outline-offset: 3px;
}
button:disabled,
:where(input[type="text"], input[type="search"], input[type="email"], input[type="url"], input[type="password"], textarea):disabled,
input[type="time"]:disabled {
@include controls.control-disabled;
}
button:hover:not(:disabled, .active, .is-active, [aria-pressed="true"], .nds-button, .nds-icon-button) {
@include controls.text-button-hover;
}
button:active {
transform: translateY(0); transform: translateY(0);
} }
@ -114,31 +91,66 @@ button:active {
} }
input[type="checkbox"] { input[type="checkbox"] {
@include controls.checkbox-control; appearance: none;
-webkit-appearance: none;
display: inline-grid;
width: 20px;
min-width: 20px;
max-width: 20px;
inline-size: 20px;
min-inline-size: 20px;
max-inline-size: 20px;
aspect-ratio: 1;
height: 20px;
min-height: 20px;
max-height: 20px;
block-size: 20px;
min-block-size: 20px;
max-block-size: 20px;
place-content: center;
margin: 0;
padding: 0;
border: 1px solid transparent;
border-radius: 3px;
line-height: 0;
background:
linear-gradient(135deg, rgba(27, 34, 66, 0.92), rgba(16, 20, 44, 0.94)) padding-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.5), rgba(139, 92, 246, 0.44)) border-box;
box-shadow:
inset 0 0 0 1px rgba(255, 255, 255, 0.06),
0 0 0 1px rgba(5, 7, 17, 0.42);
cursor: pointer;
} }
input[type="checkbox"]::before { input[type="checkbox"]::before {
@include controls.checkbox-check; content: "";
width: 11px;
height: 11px;
background: #fff;
transform: scale(0);
transition: transform var(--duration-fast) var(--ease-standard);
clip-path: polygon(14% 52%, 0 66%, 38% 100%, 100% 18%, 84% 5%, 34% 68%);
} }
input[type="checkbox"]:hover { input[type="checkbox"]:hover {
@include controls.checkbox-hover; border-color: rgba(246, 196, 83, 0.6);
box-shadow:
inset 0 0 0 1px rgba(255, 255, 255, 0.04),
0 0 14px rgba(246, 196, 83, 0.14);
} }
input[type="checkbox"]:checked { input[type="checkbox"]:checked {
@include controls.checkbox-checked; border-color: rgba(246, 196, 83, 0.88);
background:
linear-gradient(135deg, rgba(246, 196, 83, 0.96), rgba(139, 92, 246, 0.86)) padding-box,
linear-gradient(135deg, var(--color-accent-gold), rgba(196, 181, 253, 0.88)) border-box;
box-shadow:
inset 0 0 0 1px rgba(255, 255, 255, 0.12),
0 0 16px rgba(246, 196, 83, 0.2);
} }
input[type="checkbox"]:checked::before { input[type="checkbox"]:checked::before {
@include controls.checkbox-checked-check; transform: scale(1);
}
input[type="checkbox"]:disabled {
@include controls.checkbox-disabled;
}
input[type="checkbox"]:disabled::before {
opacity: 0.54;
} }
input[type="checkbox"]:focus-visible { input[type="checkbox"]:focus-visible {
@ -148,24 +160,38 @@ input[type="checkbox"]:focus-visible {
.themed-scrollbar, .themed-scrollbar,
.legacy-scrollbar { .legacy-scrollbar {
@include controls.scrollbar-control; scrollbar-color: rgb(79, 26, 114) transparent;
scrollbar-width: thin;
} }
:is( .filter-options::-webkit-scrollbar,
.annotation-marker-list, .drawer-panel::-webkit-scrollbar,
.damage-table-wrap, .damage-table-wrap::-webkit-scrollbar,
.drawer-panel, .themed-scrollbar::-webkit-scrollbar,
.filter-options, .legacy-scrollbar::-webkit-scrollbar {
.goal-tree-editor-panel, background-color: transparent;
.goal-tree-prerequisite-list, width: 6px;
.goal-tree-view-wrap, height: 6px;
.module-content, border-radius: 0;
.notepad-pages-list, }
.notepad-surface,
.table-grid-wrap, .filter-options::-webkit-scrollbar-track,
.tool-split-tree .drawer-panel::-webkit-scrollbar-track,
) { .damage-table-wrap::-webkit-scrollbar-track,
@include controls.scrollbar-control; .themed-scrollbar::-webkit-scrollbar-track,
.legacy-scrollbar::-webkit-scrollbar-track {
background-color: transparent;
border-radius: 0;
box-shadow: none;
}
.filter-options::-webkit-scrollbar-thumb,
.drawer-panel::-webkit-scrollbar-thumb,
.damage-table-wrap::-webkit-scrollbar-thumb,
.themed-scrollbar::-webkit-scrollbar-thumb,
.legacy-scrollbar::-webkit-scrollbar-thumb {
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0.5, rgb(79, 26, 114)), color-stop(1, rgb(44, 1, 135)));
border-radius: 10px;
} }
body.is-modal-open { body.is-modal-open {
@ -177,3 +203,238 @@ body.is-modal-open {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.primary {
border-color: rgba(246, 196, 83, 0.34);
background:
linear-gradient(135deg, rgba(72, 43, 160, 0.96), rgba(45, 52, 146, 0.94)) padding-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.66), rgba(139, 92, 246, 0.36)) border-box;
box-shadow:
0 8px 22px rgba(5, 7, 17, 0.22),
inset 0 1px 0 rgba(255, 255, 255, 0.08);
color: #fff;
font-weight: 700;
}
.primary:hover {
border-color: rgba(246, 196, 83, 0.72);
background:
linear-gradient(135deg, rgba(109, 40, 217, 0.98), rgba(67, 56, 202, 0.96)) padding-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.96), rgba(196, 181, 253, 0.68)) border-box;
box-shadow:
0 0 0 1px rgba(246, 196, 83, 0.16),
0 0 18px rgba(139, 92, 246, 0.3),
0 0 30px rgba(246, 196, 83, 0.16),
inset 0 1px 0 rgba(255, 255, 255, 0.18);
}
.ghost {
border-color: transparent;
background: transparent;
color: var(--color-text-secondary);
}
.ghost:hover {
border-color: rgba(196, 181, 253, 0.18);
background: rgba(139, 92, 246, 0.08);
color: var(--color-text-primary);
box-shadow: none;
}
.danger {
color: var(--color-danger);
}
.danger:hover {
border-color: rgba(251, 113, 133, 0.48);
background:
linear-gradient(135deg, rgba(80, 25, 42, 0.72), rgba(48, 18, 34, 0.82)) padding-box,
linear-gradient(135deg, rgba(251, 113, 133, 0.56), rgba(196, 181, 253, 0.16)) border-box;
color: #fff;
box-shadow:
0 0 14px rgba(251, 113, 133, 0.14),
inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
:is(
.sidebar-action-button,
.import-icon-button,
.toolbox-icon-button,
.card-icon-button,
.drawer-action-button,
.drawer-close-button,
.filter-reset-button,
.image-viewer-button,
.module-edit-button,
.module-exchange-button,
.module-delete-button,
.image-annotate-button,
.image-delete-button,
.checklist-delete-button,
.tool-quick-add-button
) {
display: inline-flex;
width: 40px;
min-width: 40px;
min-height: 40px;
align-items: center;
justify-content: center;
padding: 0;
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: rgba(21, 26, 48, 0.88);
color: var(--color-text-primary);
font: inherit;
cursor: pointer;
transition:
transform var(--duration-fast) var(--ease-standard),
border-color var(--duration-fast) var(--ease-standard),
background-color var(--duration-fast) var(--ease-standard),
color var(--duration-fast) var(--ease-standard),
box-shadow var(--duration-fast) var(--ease-standard);
}
:is(
.sidebar-action-button,
.import-icon-button,
.toolbox-icon-button,
.card-icon-button,
.drawer-action-button,
.drawer-close-button,
.filter-reset-button,
.image-viewer-button,
.module-edit-button,
.module-exchange-button,
.module-delete-button,
.image-annotate-button,
.image-delete-button,
.checklist-delete-button,
.tool-quick-add-button
) .ui-icon {
transition:
background-color var(--duration-fast) var(--ease-standard),
filter var(--duration-fast) var(--ease-standard);
}
:is(
.sidebar-action-button,
.import-icon-button,
.toolbox-icon-button,
.card-icon-button,
.drawer-action-button,
.drawer-close-button,
.filter-reset-button,
.image-viewer-button,
.module-edit-button,
.module-exchange-button,
.module-delete-button,
.image-annotate-button,
.image-delete-button,
.checklist-delete-button,
.tool-quick-add-button
):hover {
border-color: rgba(196, 181, 253, 0.32);
background:
linear-gradient(135deg, rgba(31, 37, 68, 0.94), rgba(18, 22, 44, 0.96)) padding-box,
linear-gradient(135deg, rgba(139, 92, 246, 0.3), rgba(196, 181, 253, 0.2)) border-box;
color: #ddd6fe;
box-shadow:
0 0 12px rgba(139, 92, 246, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
:is(
.sidebar-action-button,
.import-icon-button,
.toolbox-icon-button,
.card-icon-button,
.drawer-action-button,
.drawer-close-button,
.filter-reset-button,
.image-viewer-button,
.module-edit-button,
.module-exchange-button,
.module-delete-button,
.image-annotate-button,
.image-delete-button,
.tool-quick-add-button
).primary {
border-color: rgba(246, 196, 83, 0.34);
background:
linear-gradient(135deg, rgba(72, 43, 160, 0.96), rgba(45, 52, 146, 0.94)) padding-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.66), rgba(139, 92, 246, 0.36)) border-box;
color: #fff;
box-shadow:
0 8px 22px rgba(5, 7, 17, 0.22),
inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
:is(
.sidebar-action-button,
.import-icon-button,
.toolbox-icon-button,
.card-icon-button,
.drawer-action-button,
.drawer-close-button,
.filter-reset-button,
.image-viewer-button,
.module-edit-button,
.module-exchange-button,
.module-delete-button,
.image-annotate-button,
.image-delete-button,
.tool-quick-add-button
).primary:hover {
border-color: rgba(246, 196, 83, 0.72);
background:
linear-gradient(135deg, rgba(109, 40, 217, 0.98), rgba(67, 56, 202, 0.96)) padding-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.96), rgba(196, 181, 253, 0.68)) border-box;
color: #fff;
box-shadow:
0 0 0 1px rgba(246, 196, 83, 0.16),
0 0 18px rgba(139, 92, 246, 0.3),
0 0 30px rgba(246, 196, 83, 0.16),
inset 0 1px 0 rgba(255, 255, 255, 0.18);
}
:is(
.sidebar-action-button,
.import-icon-button,
.toolbox-icon-button,
.card-icon-button,
.drawer-action-button,
.drawer-close-button,
.filter-reset-button,
.image-viewer-button,
.module-edit-button,
.module-exchange-button,
.module-delete-button,
.image-annotate-button,
.image-delete-button,
.checklist-delete-button,
.tool-quick-add-button
):hover .ui-icon {
background-color: #c4b5fd;
filter: drop-shadow(0 0 6px rgba(196, 181, 253, 0.28));
}
:is(.card-icon-button, .module-delete-button, .image-delete-button, .checklist-delete-button).danger {
border-color: rgba(251, 113, 133, 0.2);
color: var(--color-danger);
}
:is(.card-icon-button, .module-delete-button, .image-delete-button, .checklist-delete-button).danger:hover {
border-color: rgba(251, 113, 133, 0.56);
background:
linear-gradient(135deg, rgba(80, 25, 42, 0.72), rgba(48, 18, 34, 0.82)) padding-box,
linear-gradient(135deg, rgba(251, 113, 133, 0.56), rgba(196, 181, 253, 0.16)) border-box;
color: #fff;
box-shadow:
0 0 14px rgba(251, 113, 133, 0.14),
inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
:is(.card-icon-button, .module-delete-button, .image-delete-button, .checklist-delete-button).danger:hover .ui-icon {
background-color: #fff;
filter: drop-shadow(0 0 6px rgba(251, 113, 133, 0.26));
}

View file

@ -452,5 +452,5 @@
} }
.card-icon-button { .card-icon-button {
flex: 0 0 auto; flex: 0 0 40px;
} }

View file

@ -1,514 +0,0 @@
// Rôle : met en page le catalogue de contrôles visible uniquement en développement.
@use "design-system/controls";
@use "design-system/interactive-controls";
.design-system-page {
display: grid;
grid-template-columns: minmax(220px, 280px) minmax(0, 1fr);
gap: var(--space-6);
width: min(1480px, calc(100% - 32px));
margin: 0 auto;
padding: var(--space-6) 0;
}
.design-system-nav {
position: sticky;
top: var(--space-4);
display: grid;
max-height: calc(100vh - var(--space-6));
align-content: start;
gap: var(--space-4);
padding: var(--space-5);
overflow: auto;
border: 1px solid rgba(150, 165, 205, 0.14);
border-radius: var(--radius-lg);
background:
radial-gradient(circle at 100% 0%, rgba(246, 196, 83, 0.08), transparent 34%),
rgba(7, 10, 24, 0.66);
box-shadow: var(--shadow-md);
}
.design-system-nav h1,
.design-system-section h2,
.design-system-matrix h3,
.design-system-nav p {
margin: 0;
}
.design-system-nav h1 {
font-size: clamp(2rem, 4vw, 3.2rem);
line-height: 0.95;
}
.design-system-nav p:not(.eyebrow) {
color: var(--color-text-secondary);
font-size: var(--font-size-sm);
}
.design-system-nav nav,
.design-system-token-stack {
display: grid;
gap: 8px;
}
.design-system-nav nav {
padding-top: var(--space-2);
border-top: 1px solid rgba(150, 165, 205, 0.12);
}
.design-system-nav button {
display: flex;
min-height: 34px;
align-items: center;
padding: 0 10px;
border: 1px solid transparent;
border-radius: var(--radius-md);
background: transparent;
color: var(--color-text-secondary);
font-size: var(--font-size-sm);
font-weight: 800;
text-align: left;
cursor: pointer;
}
.design-system-nav button:hover,
.design-system-nav button:focus-visible {
border-color: rgba(246, 196, 83, 0.28);
background: rgba(246, 196, 83, 0.08);
color: var(--color-text-primary);
}
.design-system-main {
display: grid;
min-width: 0;
gap: var(--space-6);
}
.design-system-section {
display: grid;
min-width: 0;
scroll-margin-top: var(--space-5);
gap: var(--space-4);
}
.design-system-section > header {
display: flex;
min-width: 0;
align-items: end;
justify-content: space-between;
gap: var(--space-4);
padding: 0;
border: 0;
}
.design-system-section h2 {
font-size: clamp(1.6rem, 3vw, 2.35rem);
line-height: 1;
}
.design-system-component-intro {
display: grid;
grid-template-columns: minmax(180px, 0.8fr) minmax(0, 2.4fr);
gap: var(--space-3);
padding: var(--space-4);
border: 1px solid rgba(150, 165, 205, 0.14);
border-radius: var(--radius-lg);
background: rgba(7, 10, 24, 0.34);
}
.design-system-doc-summary,
.design-system-attributes-doc {
display: grid;
align-content: start;
gap: 8px;
min-width: 0;
}
.design-system-component-intro span:first-child {
color: var(--color-accent-gold);
font-size: var(--font-size-xs);
font-weight: 900;
text-transform: uppercase;
}
.design-system-component-intro strong {
color: var(--color-text-primary);
font-size: var(--font-size-lg);
}
.design-system-component-intro code,
.design-system-component-intro li code {
color: var(--color-text-primary);
font-size: var(--font-size-sm);
}
.design-system-attributes-doc dl {
display: grid;
overflow: hidden;
margin: 0;
padding: 0;
border: 1px solid rgba(165, 180, 252, 0.12);
border-radius: var(--radius-md);
background: rgba(5, 7, 17, 0.22);
}
.design-system-attributes-doc dl > div {
display: grid;
grid-template-columns: minmax(118px, 0.42fr) minmax(0, 1fr);
min-width: 0;
border-top: 1px solid rgba(165, 180, 252, 0.1);
}
.design-system-attributes-doc dl > div:first-child {
border-top: 0;
}
.design-system-attributes-doc dt,
.design-system-attributes-doc dd {
min-width: 0;
margin: 0;
padding: 8px 10px;
}
.design-system-attributes-doc dt {
border-right: 1px solid rgba(165, 180, 252, 0.1);
background: rgba(139, 92, 246, 0.06);
}
.design-system-attributes-doc dd {
display: flex;
flex-wrap: wrap;
gap: 6px;
color: var(--color-text-secondary);
font-size: var(--font-size-xs);
font-weight: 800;
}
.design-system-attributes-doc dd code {
padding: 2px 6px;
border: 1px solid rgba(246, 196, 83, 0.16);
border-radius: var(--radius-sm);
background: rgba(246, 196, 83, 0.06);
}
.design-system-matrix {
display: grid;
min-width: 0;
overflow: hidden;
border: 1px solid rgba(150, 165, 205, 0.14);
border-radius: var(--radius-lg);
background: rgba(7, 10, 24, 0.42);
box-shadow: var(--shadow-sm);
}
.design-system-matrix > header {
display: flex;
min-width: 0;
align-items: start;
justify-content: flex-end;
gap: var(--space-3);
padding: var(--space-3);
border-bottom: 1px solid rgba(150, 165, 205, 0.12);
}
.design-system-matrix h3 {
font-size: var(--font-size-lg);
}
.design-system-matrix p {
margin: 4px 0 0;
color: var(--color-text-secondary);
font-size: var(--font-size-sm);
}
.design-system-config-panel {
display: flex;
width: 100%;
min-width: 0;
flex-wrap: wrap;
justify-content: flex-end;
gap: 8px;
}
.design-system-config-field {
display: grid;
min-width: 118px;
gap: 4px;
}
.design-system-config-field span {
color: var(--color-text-muted);
font-size: 0.68rem;
font-weight: 900;
text-transform: uppercase;
}
.design-system-matrix-grid {
@include controls.scrollbar-control;
display: grid;
grid-template-columns: 136px repeat(var(--nds-matrix-columns), minmax(150px, 1fr));
min-width: 0;
overflow-x: auto;
overscroll-behavior-x: contain;
}
.design-system-matrix-grid > * {
min-width: 0;
border-top: 1px dashed rgba(139, 92, 246, 0.42);
border-left: 1px dashed rgba(139, 92, 246, 0.42);
}
.design-system-matrix-corner,
.design-system-matrix-heading,
.design-system-matrix-row-label {
display: flex;
min-height: 48px;
align-items: center;
padding: 10px 12px;
color: var(--color-text-secondary);
font-size: var(--font-size-xs);
font-weight: 900;
text-transform: uppercase;
}
.design-system-matrix-heading {
color: var(--color-accent-gold);
}
.design-system-matrix-row-label {
display: grid;
align-content: center;
gap: 3px;
background: rgba(5, 7, 17, 0.22);
line-height: 1.25;
overflow-wrap: anywhere;
}
.design-system-matrix-row-label span {
color: var(--color-text-muted);
font-size: 0.66rem;
}
.design-system-matrix-row-label strong {
color: var(--color-text-secondary);
font-size: 0.78rem;
}
.design-system-matrix-cell {
display: flex;
min-height: 104px;
align-items: center;
gap: 10px;
padding: 12px;
}
.design-system-matrix-separator {
grid-column: 1 / -1;
display: flex;
min-height: 38px;
align-items: center;
padding: 8px 12px;
border-top: 2px solid rgba(246, 196, 83, 0.34);
border-left: 0;
background: rgba(246, 196, 83, 0.055);
color: var(--color-accent-gold);
font-size: var(--font-size-xs);
font-weight: 900;
text-transform: uppercase;
}
.design-system-control-sample {
display: grid;
width: 100%;
min-width: 0;
min-height: 48px;
align-items: center;
justify-items: center;
}
.design-system-sample-preview {
display: flex;
width: 100%;
min-width: 0;
align-items: center;
justify-content: center;
gap: 10px;
}
.design-system-matrix-cell > input,
.design-system-matrix-cell > select,
.design-system-matrix-cell > textarea,
.design-system-matrix-cell > .nds-field,
.design-system-matrix-cell > .mix-tags-input,
.design-system-control-sample input,
.design-system-control-sample select,
.design-system-control-sample textarea,
.design-system-control-sample .mix-tags-input {
width: 100%;
}
.design-system-foundations {
display: grid;
grid-template-columns: minmax(210px, 1.2fr) repeat(3, minmax(180px, 1fr));
gap: var(--space-3);
}
.design-system-foundations article {
display: grid;
min-height: 180px;
align-content: space-between;
gap: var(--space-3);
padding: var(--space-4);
border: 1px solid rgba(150, 165, 205, 0.14);
border-radius: var(--radius-lg);
background: rgba(7, 10, 24, 0.42);
box-shadow: var(--shadow-sm);
}
.design-system-foundations p {
margin: 0;
color: var(--color-text-primary);
font-size: var(--font-size-lg);
font-weight: 900;
}
.design-system-type-card strong {
display: block;
color: var(--color-text-primary);
font-size: clamp(4rem, 10vw, 7rem);
line-height: 0.9;
}
.design-system-type-card span {
color: var(--color-text-secondary);
}
.design-system-swatches {
display: grid;
grid-template-columns: repeat(6, minmax(24px, 1fr));
overflow: hidden;
border-radius: var(--radius-md);
}
.design-system-swatches span {
min-height: 96px;
background: var(--nds-swatch-color);
}
.design-system-radius-card > div,
.design-system-focus-card {
align-items: end;
}
.design-system-radius-card > div {
display: flex;
gap: 8px;
}
.design-system-radius-card span {
display: grid;
width: 52px;
height: 52px;
place-items: center;
border: 1px solid rgba(246, 196, 83, 0.32);
background: rgba(21, 26, 48, 0.72);
color: var(--color-text-secondary);
font-size: var(--font-size-xs);
font-weight: 900;
}
.design-system-spec-pill,
.design-system-size-spec {
display: inline-flex;
min-width: 0;
align-items: center;
}
.design-system-size-spec {
flex-wrap: wrap;
gap: 6px;
}
.design-system-spec-pill {
gap: 5px;
width: fit-content;
max-width: 100%;
padding: 3px 7px;
border: 1px solid rgba(165, 180, 252, 0.12);
border-radius: var(--radius-pill);
background: rgba(5, 7, 17, 0.28);
color: var(--color-text-muted);
font-size: 0.68rem;
font-weight: 800;
}
.design-system-spec-pill b {
color: var(--color-accent-gold);
}
.design-system-inline-controls {
display: flex;
align-items: center;
gap: 6px;
}
.design-system-page .calendar-month-event-repeat-switch {
width: min(230px, 100%);
}
@media (max-width: 980px) {
.design-system-page {
grid-template-columns: 1fr;
}
.design-system-nav {
position: static;
max-height: none;
}
.design-system-nav nav {
grid-template-columns: repeat(auto-fit, minmax(138px, 1fr));
}
.design-system-foundations {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.design-system-component-intro {
grid-template-columns: 1fr;
}
.design-system-matrix > header {
display: grid;
}
.design-system-config-panel {
max-width: none;
justify-content: start;
}
}
@media (max-width: 640px) {
.design-system-page {
width: min(100% - 20px, 1480px);
padding-block: var(--space-4) calc(var(--space-4) + 88px);
}
.design-system-nav,
.design-system-foundations article,
.design-system-matrix > header {
padding: var(--space-3);
}
.design-system-foundations {
grid-template-columns: 1fr;
}
.design-system-matrix-grid {
grid-template-columns: 116px repeat(var(--nds-matrix-columns), minmax(140px, 1fr));
}
.design-system-matrix-cell {
padding: 12px;
}
}

View file

@ -62,19 +62,4 @@
box-shadow: box-shadow:
inset 0 -2px 0 rgba(246, 196, 83, 0.34), inset 0 -2px 0 rgba(246, 196, 83, 0.34),
inset 0 1px 0 rgba(255, 255, 255, 0.05); inset 0 1px 0 rgba(255, 255, 255, 0.05);
&:hover:not(:disabled) {
border-color: rgba(246, 196, 83, 0.86);
background:
radial-gradient(circle at 100% 100%, rgba(246, 196, 83, 0.18), transparent 48%) padding-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.2), rgba(139, 92, 246, 0.12) 52%, rgba(21, 26, 48, 0.08)) padding-box,
linear-gradient(135deg, rgba(16, 20, 38, 0.94), rgba(21, 26, 48, 0.86)) padding-box,
linear-gradient(120deg, rgba(246, 196, 83, 1), rgba(255, 244, 196, 0.92), rgba(246, 196, 83, 0.84)) border-box;
color: var(--color-text-primary);
box-shadow:
0 0 0 1px rgba(246, 196, 83, 0.18),
0 0 16px rgba(246, 196, 83, 0.18),
inset 0 -2px 0 rgba(246, 196, 83, 0.48),
inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
} }

View file

@ -290,6 +290,7 @@
.sidebar-action-button { .sidebar-action-button {
display: inline-flex; display: inline-flex;
background: rgba(7, 10, 24, 0.36);
} }
.content-shell { .content-shell {

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,116 +0,0 @@
// Rôle : complète le catalogue dev avec les helpers de présentation non publics.
@use "controls";
.design-system-page {
.nds-control-row,
.nds-field {
display: grid;
gap: 6px;
min-width: 0;
}
.nds-field {
width: 100%;
}
.nds-label,
.nds-checkbox-label {
color: var(--color-text-secondary);
font-size: var(--font-size-xs);
font-weight: 900;
text-transform: uppercase;
}
.design-system-spec-list {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin: 0;
}
.design-system-spec-list div {
display: inline-flex;
min-width: 0;
align-items: center;
gap: 4px;
padding: 3px 6px;
border: 1px solid rgba(165, 180, 252, 0.1);
border-radius: var(--radius-sm);
background: rgba(5, 7, 17, 0.28);
color: var(--color-text-muted);
font-size: 0.68rem;
font-weight: 800;
}
.design-system-spec-list dt,
.design-system-spec-list dd {
margin: 0;
}
.design-system-spec-list dt {
color: var(--color-accent-gold);
}
.nds-checkbox-label {
display: inline-flex;
align-items: center;
gap: 8px;
text-transform: none;
}
.nds-date-picker-preview {
display: grid;
grid-template-columns: repeat(7, minmax(0, 1fr));
gap: 3px;
width: min(300px, 100%);
padding: 4px;
border: 1px solid rgba(150, 165, 205, 0.16);
border-radius: var(--radius-sm);
background: rgba(8, 11, 24, 0.98);
}
.nds-date-picker-preview button {
@include controls.date-picker-button;
}
.nds-date-picker-preview button:hover,
.nds-date-picker-preview button.active {
@include controls.date-picker-button-hover;
}
.nds-date-picker-preview button.is-outside {
opacity: 0.32;
}
.nds-date-picker-preview-day {
display: grid;
min-height: 22px;
place-items: center;
color: var(--color-text-muted);
font-size: 0.66rem;
font-weight: 900;
text-transform: uppercase;
}
.nds-scrollbox-content-wide {
display: inline-flex;
width: max-content;
min-width: 1280px;
gap: 10px;
}
.nds-scrollbox-content-wide span {
display: inline-flex;
min-width: 170px;
min-height: 42px;
align-items: center;
justify-content: center;
padding: 0 12px;
border: 1px solid rgba(165, 180, 252, 0.12);
border-radius: var(--radius-md);
background: rgba(21, 26, 48, 0.58);
color: var(--color-text-secondary);
font-size: var(--font-size-sm);
font-weight: 800;
}
}

View file

@ -76,6 +76,15 @@
font-size: var(--font-size-lg); font-size: var(--font-size-lg);
} }
.filter-reset-button {
color: var(--color-text-secondary);
box-shadow: var(--shadow-sm);
}
.filter-reset-button:hover {
background: rgba(21, 26, 48, 0.96);
}
.field.compact { .field.compact {
padding: var(--space-4) 0; padding: var(--space-4) 0;
} }
@ -89,16 +98,29 @@
background: rgba(5, 7, 17, 0.36); background: rgba(5, 7, 17, 0.36);
} }
.filter-logic-group { .filter-logic button {
flex: 0 0 auto;
}
.filter-logic-group button {
min-height: 30px; min-height: 30px;
padding: 0 12px; padding: 0 12px;
border-color: rgba(246, 196, 83, 0.24);
background:
linear-gradient(135deg, rgba(72, 43, 160, 0.72), rgba(45, 52, 146, 0.68)) padding-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.46), rgba(139, 92, 246, 0.28)) border-box;
color: var(--color-text-primary);
font-size: var(--font-size-sm);
font-weight: 800;
text-transform: uppercase; text-transform: uppercase;
} }
.filter-logic button:hover {
border-color: rgba(246, 196, 83, 0.52);
background:
linear-gradient(135deg, rgba(109, 40, 217, 0.86), rgba(67, 56, 202, 0.82)) padding-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.72), rgba(196, 181, 253, 0.42)) border-box;
box-shadow:
0 0 14px rgba(139, 92, 246, 0.14),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
}
.filter-options { .filter-options {
display: grid; display: grid;
gap: 8px; gap: 8px;
@ -129,6 +151,22 @@
color: var(--color-text-primary); color: var(--color-text-primary);
} }
.filter-chip input {
flex: 0 0 18px;
width: 18px;
min-width: 18px;
max-width: 18px;
inline-size: 18px;
min-inline-size: 18px;
max-inline-size: 18px;
height: 18px;
min-height: 18px;
max-height: 18px;
block-size: 18px;
min-block-size: 18px;
max-block-size: 18px;
}
.filter-chip img { .filter-chip img {
width: 22px; width: 22px;
height: 22px; height: 22px;

View file

@ -20,8 +20,26 @@
} }
.game-list-help-link { .game-list-help-link {
display: inline-flex;
width: fit-content; width: fit-content;
min-height: 36px;
align-items: center;
justify-content: center;
margin-top: var(--space-3); margin-top: var(--space-3);
border-color: rgba(165, 180, 252, 0.18);
background: rgba(5, 7, 17, 0.24);
color: var(--color-text-secondary);
font-size: var(--font-size-sm);
font-weight: 800;
padding: 7px 12px;
}
.game-list-help-link:hover {
border-color: rgba(246, 196, 83, 0.44);
background:
linear-gradient(135deg, rgba(246, 196, 83, 0.1), rgba(139, 92, 246, 0.08)) padding-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.42), rgba(196, 181, 253, 0.22)) border-box;
color: var(--color-text-primary);
} }
.game-list-selector { .game-list-selector {
@ -226,6 +244,13 @@
width: 40px; width: 40px;
min-width: 40px; min-width: 40px;
min-height: 40px; min-height: 40px;
padding: 0;
border-radius: var(--radius-md);
}
.game-list-create-button:disabled {
opacity: 0.45;
cursor: not-allowed;
} }
.game-list-create-button .module-icon { .game-list-create-button .module-icon {

View file

@ -138,6 +138,17 @@ span {
gap: 10px; gap: 10px;
} }
.toolbar-stack {
align-items: flex-start;
flex-direction: column;
}
.toolbar-stack > div {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.hero-library-button { .hero-library-button {
display: inline-flex; display: inline-flex;
width: fit-content; width: fit-content;
@ -187,3 +198,4 @@ span {
.home-section-heading p:not(.eyebrow) { .home-section-heading p:not(.eyebrow) {
max-width: 980px; max-width: 980px;
} }

Some files were not shown because too many files have changed in this diff Show more