Compare commits
No commits in common. "817e21567bf05f0959e5c365bab6545ee8299321" and "91d99d6ca47858721359c2dc7d052ca9316c5a9c" have entirely different histories.
817e21567b
...
91d99d6ca4
143 changed files with 2361 additions and 6091 deletions
|
|
@ -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.
|
|
||||||
|
|
@ -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
77
.beads/.gitignore
vendored
|
|
@ -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.
|
|
||||||
|
|
@ -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* ⚡
|
|
||||||
|
|
@ -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"
|
|
||||||
|
|
@ -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 ---
|
|
||||||
|
|
@ -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 ---
|
|
||||||
|
|
@ -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 ---
|
|
||||||
|
|
@ -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 ---
|
|
||||||
|
|
@ -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 ---
|
|
||||||
|
|
@ -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."}}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"database": "dolt",
|
|
||||||
"backend": "dolt",
|
|
||||||
"dolt_mode": "embedded",
|
|
||||||
"dolt_database": "sokko_g",
|
|
||||||
"project_id": "383fe8d1-bd71-460f-9ff5-e71f14df4827"
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"hooks": {
|
|
||||||
"SessionStart": [
|
|
||||||
{
|
|
||||||
"hooks": [
|
|
||||||
{
|
|
||||||
"command": "bd prime --hook-json",
|
|
||||||
"type": "command"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"matcher": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
[features]
|
|
||||||
hooks = true
|
|
||||||
|
|
@ -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
7
.gitignore
vendored
|
|
@ -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/
|
|
||||||
|
|
|
||||||
80
AGENTS.md
80
AGENTS.md
|
|
@ -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 -->
|
|
||||||
|
|
|
||||||
77
CLAUDE.md
77
CLAUDE.md
|
|
@ -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_
|
|
||||||
242
DESIGN_SYSTEM.md
242
DESIGN_SYSTEM.md
|
|
@ -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 ;
|
||||||
|
|
|
||||||
|
|
@ -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/);
|
||||||
|
|
|
||||||
|
|
@ -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/);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
|
||||||
|
|
@ -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"
|
</button>
|
||||||
role="radio"
|
|
||||||
aria-checked={normalizedPosition === "left"}
|
|
||||||
aria-label={labels.left}
|
|
||||||
onClick={() => onChange("left")}
|
|
||||||
>
|
|
||||||
<Icon name="toast-left" />
|
|
||||||
</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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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)}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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}>
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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>}
|
||||||
|
|
|
||||||
|
|
@ -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" />
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,27 +126,25 @@ 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" },
|
{ mode: "hidden", label: textContent.completedSectionsHiddenLabel || "Caché", icon: "eye-closed" }
|
||||||
{ mode: "hidden", label: textContent.completedSectionsHiddenLabel || "Caché", icon: "eye-closed" }
|
].map((option) => (
|
||||||
].map((option) => (
|
<button
|
||||||
<button
|
className={`checklist-complete-mode-button ${completedSectionsMode === option.mode ? "active" : ""}`}
|
||||||
className={`checklist-complete-mode-button ${completedSectionsMode === option.mode ? "active" : ""}`}
|
type="button"
|
||||||
type="button"
|
role="radio"
|
||||||
role="radio"
|
aria-checked={completedSectionsMode === option.mode}
|
||||||
aria-checked={completedSectionsMode === option.mode}
|
aria-label={option.label}
|
||||||
aria-label={option.label}
|
key={option.mode}
|
||||||
key={option.mode}
|
onClick={() => setCompletedSectionsMode(option.mode)}
|
||||||
onClick={() => setCompletedSectionsMode(option.mode)}
|
title={option.label}
|
||||||
title={option.label}
|
>
|
||||||
>
|
<Icon name={option.icon} />
|
||||||
<Icon name={option.icon} />
|
</button>
|
||||||
</button>
|
))}
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<ul className="checklist">
|
<ul className="checklist">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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}`}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -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"}
|
||||||
|
|
|
||||||
|
|
@ -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}>
|
||||||
<Icon name={value} />
|
<span className="goal-tree-icon-preview" aria-hidden="true">
|
||||||
|
<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);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -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" />
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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={() => {
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
|
|
||||||
|
|
@ -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"}
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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)}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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="nds-button"</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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>;
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -452,5 +452,5 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-icon-button {
|
.card-icon-button {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 40px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue