silkui 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- silkui/__init__.py +1 -0
- silkui/apps.py +7 -0
- silkui/cli.py +18 -0
- silkui/commands/__init__.py +0 -0
- silkui/commands/add.py +91 -0
- silkui/commands/init.py +167 -0
- silkui/commands/list.py +40 -0
- silkui/registry/__init__.py +30 -0
- silkui/registry/base.css +59 -0
- silkui/registry/components/alert/alert-description.html +2 -0
- silkui/registry/components/alert/alert-title.html +2 -0
- silkui/registry/components/alert/alert.html +8 -0
- silkui/registry/components/alert/meta.json +11 -0
- silkui/registry/components/badge/badge.html +10 -0
- silkui/registry/components/badge/meta.json +11 -0
- silkui/registry/components/button/button.html +23 -0
- silkui/registry/components/button/meta.json +11 -0
- silkui/registry/components/card/card-content.html +2 -0
- silkui/registry/components/card/card-description.html +2 -0
- silkui/registry/components/card/card-footer.html +2 -0
- silkui/registry/components/card/card-header.html +2 -0
- silkui/registry/components/card/card-title.html +2 -0
- silkui/registry/components/card/card.html +2 -0
- silkui/registry/components/card/meta.json +11 -0
- silkui/registry/components/checkbox/checkbox.html +24 -0
- silkui/registry/components/checkbox/meta.json +11 -0
- silkui/registry/components/dropdown/dropdown.html +37 -0
- silkui/registry/components/dropdown/meta.json +11 -0
- silkui/registry/components/input/input.html +43 -0
- silkui/registry/components/input/meta.json +11 -0
- silkui/registry/components/modal/meta.json +11 -0
- silkui/registry/components/modal/modal.html +71 -0
- silkui/registry/components/radio/meta.json +11 -0
- silkui/registry/components/radio/radio.html +20 -0
- silkui/registry/components/select/meta.json +11 -0
- silkui/registry/components/select/select.html +31 -0
- silkui/registry/components/separator/meta.json +11 -0
- silkui/registry/components/separator/separator.html +2 -0
- silkui/registry/components/tabs/meta.json +11 -0
- silkui/registry/components/tabs/tabs.html +39 -0
- silkui/registry/components/textarea/meta.json +11 -0
- silkui/registry/components/textarea/textarea.html +29 -0
- silkui/registry/components/toast/meta.json +11 -0
- silkui/registry/components/toast/toast.html +50 -0
- silkui/registry/components/toast/toast.js +21 -0
- silkui/templatetags/__init__.py +0 -0
- silkui/templatetags/silkui_tags.py +440 -0
- silkui-1.0.0.dist-info/METADATA +797 -0
- silkui-1.0.0.dist-info/RECORD +53 -0
- silkui-1.0.0.dist-info/WHEEL +5 -0
- silkui-1.0.0.dist-info/entry_points.txt +2 -0
- silkui-1.0.0.dist-info/licenses/LICENSE +21 -0
- silkui-1.0.0.dist-info/top_level.txt +1 -0
silkui/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.0"
|
silkui/apps.py
ADDED
silkui/cli.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from silkui.commands.init import init
|
|
3
|
+
from silkui.commands.add import add
|
|
4
|
+
from silkui.commands.list import list_components
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.group()
|
|
8
|
+
@click.version_option(package_name="silkui")
|
|
9
|
+
def main():
|
|
10
|
+
"""SilkUI — UI components for Django, inspired by shadcn/ui.
|
|
11
|
+
|
|
12
|
+
Components are copied into your project. You own the code.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
main.add_command(init)
|
|
17
|
+
main.add_command(add)
|
|
18
|
+
main.add_command(list_components, name="list")
|
|
File without changes
|
silkui/commands/add.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
|
|
7
|
+
console = Console()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _load_config() -> dict:
|
|
11
|
+
config_path = Path("silkui.json")
|
|
12
|
+
if not config_path.exists():
|
|
13
|
+
console.print("[red]silkui.json not found. Run [bold]silkui init[/bold] first.[/red]")
|
|
14
|
+
raise SystemExit(1)
|
|
15
|
+
return json.loads(config_path.read_text())
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.command()
|
|
19
|
+
@click.argument("components", nargs=-1)
|
|
20
|
+
@click.option("--all", "all_components", is_flag=True, help="Install all available components")
|
|
21
|
+
@click.option("--overwrite", is_flag=True, help="Overwrite existing files")
|
|
22
|
+
def add(components, all_components, overwrite):
|
|
23
|
+
"""Add one or more components to your project.
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
|
|
27
|
+
silkui add button
|
|
28
|
+
|
|
29
|
+
silkui add button card input
|
|
30
|
+
|
|
31
|
+
silkui add --all
|
|
32
|
+
"""
|
|
33
|
+
from silkui.registry import get_registry, get_component_files
|
|
34
|
+
|
|
35
|
+
registry = get_registry()
|
|
36
|
+
|
|
37
|
+
if all_components:
|
|
38
|
+
components = tuple(registry.keys())
|
|
39
|
+
|
|
40
|
+
if not components:
|
|
41
|
+
console.print("[yellow]No component specified. Use --all to install everything.[/yellow]")
|
|
42
|
+
console.print("Run [bold]silkui list[/bold] to see available components.")
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
config = _load_config()
|
|
46
|
+
components_dir = Path(config["components_dir"])
|
|
47
|
+
js_dir = Path(config["js_dir"])
|
|
48
|
+
|
|
49
|
+
for name in components:
|
|
50
|
+
name = name.lower()
|
|
51
|
+
if name not in registry:
|
|
52
|
+
console.print(f"[red]✗[/red] Component [bold]{name}[/bold] not found.")
|
|
53
|
+
console.print(" Run [bold]silkui list[/bold] to see available components.")
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
meta = registry[name]
|
|
57
|
+
files = get_component_files(name)
|
|
58
|
+
installed_count = 0
|
|
59
|
+
|
|
60
|
+
for filename, content in files.items():
|
|
61
|
+
dest = js_dir / filename if filename.endswith(".js") else components_dir / filename
|
|
62
|
+
|
|
63
|
+
if dest.exists() and not overwrite:
|
|
64
|
+
console.print(f" [yellow]~[/yellow] {dest} already exists (use --overwrite to replace)")
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
dest.write_text(content)
|
|
68
|
+
console.print(f" [green]✓[/green] {dest}")
|
|
69
|
+
installed_count += 1
|
|
70
|
+
|
|
71
|
+
if installed_count > 0:
|
|
72
|
+
console.print(f"[green]Added[/green] [bold]{name}[/bold]")
|
|
73
|
+
|
|
74
|
+
if meta.get("alpine"):
|
|
75
|
+
console.print(
|
|
76
|
+
f" [dim]Requires Alpine.js — "
|
|
77
|
+
"add [cyan]<script defer src=\"https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js\"></script>[/cyan] "
|
|
78
|
+
"to your base template[/dim]"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if meta.get("dependencies"):
|
|
82
|
+
console.print(f" [dim]Also needed: {', '.join(meta['dependencies'])}[/dim]")
|
|
83
|
+
|
|
84
|
+
_print_usage(name, meta)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _print_usage(name: str, meta: dict) -> None:
|
|
88
|
+
usage = meta.get("usage")
|
|
89
|
+
if usage:
|
|
90
|
+
console.print(f"\n [dim]Usage:[/dim]")
|
|
91
|
+
console.print(f" [cyan]{usage}[/cyan]\n")
|
silkui/commands/init.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
|
|
10
|
+
DEFAULT_CONFIG = {
|
|
11
|
+
"version": "1.0.0",
|
|
12
|
+
"templates_dir": "templates",
|
|
13
|
+
"components_dir": "templates/components",
|
|
14
|
+
"static_dir": "static",
|
|
15
|
+
"js_dir": "static/js/silkui",
|
|
16
|
+
"css_dir": "static/css",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
# Lines we ensure exist in .prettierignore
|
|
20
|
+
PRETTIER_IGNORE_ENTRIES = [
|
|
21
|
+
"# Django templates — formatters like Prettier break template tags on multiline",
|
|
22
|
+
"{templates_dir}/",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _setup_prettier_ignore(templates_dir: str) -> None:
|
|
27
|
+
"""
|
|
28
|
+
Add the templates directory to .prettierignore so HTML formatters
|
|
29
|
+
don't break Django template tags when indenting across multiple lines.
|
|
30
|
+
"""
|
|
31
|
+
ignore_path = Path(".prettierignore")
|
|
32
|
+
entry = f"{templates_dir}/"
|
|
33
|
+
|
|
34
|
+
existing = ignore_path.read_text() if ignore_path.exists() else ""
|
|
35
|
+
|
|
36
|
+
if entry in existing:
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
lines_to_add = []
|
|
40
|
+
comment = "# Django templates — formatters break {% %} tags across multiple lines"
|
|
41
|
+
if comment not in existing:
|
|
42
|
+
lines_to_add.append(comment)
|
|
43
|
+
lines_to_add.append(entry)
|
|
44
|
+
|
|
45
|
+
separator = "\n" if existing and not existing.endswith("\n") else ""
|
|
46
|
+
new_content = existing + separator + "\n".join(lines_to_add) + "\n"
|
|
47
|
+
|
|
48
|
+
ignore_path.write_text(new_content)
|
|
49
|
+
console.print(f"[green]✓[/green] Added [bold]{entry}[/bold] to .prettierignore")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _setup_djlint() -> None:
|
|
53
|
+
djlint_path = Path(".djlintrc")
|
|
54
|
+
if djlint_path.exists():
|
|
55
|
+
return
|
|
56
|
+
config = {"profile": "django", "indent": 2, "max_line_length": 120}
|
|
57
|
+
djlint_path.write_text(json.dumps(config, indent=2))
|
|
58
|
+
console.print("[green]✓[/green] Created [bold].djlintrc[/bold]")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _setup_vscode() -> None:
|
|
62
|
+
vscode_dir = Path(".vscode")
|
|
63
|
+
vscode_dir.mkdir(exist_ok=True)
|
|
64
|
+
|
|
65
|
+
settings_path = vscode_dir / "settings.json"
|
|
66
|
+
if not settings_path.exists():
|
|
67
|
+
settings = {
|
|
68
|
+
"[html]": {
|
|
69
|
+
"editor.defaultFormatter": "monosans.djlint",
|
|
70
|
+
"editor.formatOnSave": True,
|
|
71
|
+
},
|
|
72
|
+
"files.associations": {"*.html": "django-html"},
|
|
73
|
+
"prettier.ignorePath": ".prettierignore",
|
|
74
|
+
}
|
|
75
|
+
settings_path.write_text(json.dumps(settings, indent=2))
|
|
76
|
+
console.print("[green]✓[/green] Created [bold].vscode/settings.json[/bold]")
|
|
77
|
+
|
|
78
|
+
extensions_path = vscode_dir / "extensions.json"
|
|
79
|
+
if not extensions_path.exists():
|
|
80
|
+
extensions = {"recommendations": ["monosans.djlint"]}
|
|
81
|
+
extensions_path.write_text(json.dumps(extensions, indent=2))
|
|
82
|
+
console.print("[green]✓[/green] Created [bold].vscode/extensions.json[/bold] (VS Code will prompt to install djlint)")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@click.command()
|
|
86
|
+
@click.option("--templates-dir", default="templates", show_default=True, help="Templates directory")
|
|
87
|
+
@click.option("--static-dir", default="static", show_default=True, help="Static files directory")
|
|
88
|
+
def init(templates_dir, static_dir):
|
|
89
|
+
"""Initialize SilkUI in your Django project."""
|
|
90
|
+
config = {
|
|
91
|
+
**DEFAULT_CONFIG,
|
|
92
|
+
"templates_dir": templates_dir,
|
|
93
|
+
"components_dir": f"{templates_dir}/components",
|
|
94
|
+
"static_dir": static_dir,
|
|
95
|
+
"js_dir": f"{static_dir}/js/silkui",
|
|
96
|
+
"css_dir": f"{static_dir}/css",
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# silkui.json
|
|
100
|
+
config_path = Path("silkui.json")
|
|
101
|
+
if config_path.exists():
|
|
102
|
+
console.print("[yellow]silkui.json already exists, skipping.[/yellow]")
|
|
103
|
+
else:
|
|
104
|
+
config_path.write_text(json.dumps(config, indent=2))
|
|
105
|
+
console.print("[green]✓[/green] Created [bold]silkui.json[/bold]")
|
|
106
|
+
|
|
107
|
+
# Directories
|
|
108
|
+
for key in ("components_dir", "js_dir", "css_dir"):
|
|
109
|
+
Path(config[key]).mkdir(parents=True, exist_ok=True)
|
|
110
|
+
|
|
111
|
+
# silkui.css
|
|
112
|
+
from silkui.registry import get_base_css
|
|
113
|
+
css_file = Path(config["css_dir"]) / "silkui.css"
|
|
114
|
+
if not css_file.exists():
|
|
115
|
+
css_file.write_text(get_base_css())
|
|
116
|
+
console.print(f"[green]✓[/green] Created [bold]{css_file}[/bold]")
|
|
117
|
+
|
|
118
|
+
# .prettierignore — Prettier must never reformat Django templates
|
|
119
|
+
_setup_prettier_ignore(templates_dir)
|
|
120
|
+
|
|
121
|
+
# .djlintrc — djlint understands Django syntax and formats HTML without
|
|
122
|
+
# breaking {% %} tags. Use it instead of Prettier for HTML files.
|
|
123
|
+
_setup_djlint()
|
|
124
|
+
|
|
125
|
+
# .vscode/settings.json — configure djlint as the HTML formatter
|
|
126
|
+
_setup_vscode()
|
|
127
|
+
|
|
128
|
+
console.print(
|
|
129
|
+
Panel(
|
|
130
|
+
"\n".join([
|
|
131
|
+
"[bold]SilkUI initialized![/bold]",
|
|
132
|
+
"",
|
|
133
|
+
"Next steps:",
|
|
134
|
+
"",
|
|
135
|
+
" 1. Add [cyan]silkui[/cyan] to INSTALLED_APPS in settings.py",
|
|
136
|
+
"",
|
|
137
|
+
" 2. Add SilkUI colors to your [bold]tailwind.config.js[/bold]:",
|
|
138
|
+
" [cyan]theme: { extend: { colors: {",
|
|
139
|
+
" border: \"hsl(var(--border))\", input: \"hsl(var(--input))\",",
|
|
140
|
+
" background: \"hsl(var(--background))\", foreground: \"hsl(var(--foreground))\",",
|
|
141
|
+
" primary: { DEFAULT: \"hsl(var(--primary))\", foreground: \"hsl(var(--primary-foreground))\" },",
|
|
142
|
+
" secondary: { DEFAULT: \"hsl(var(--secondary))\", foreground: \"hsl(var(--secondary-foreground))\" },",
|
|
143
|
+
" destructive: { DEFAULT: \"hsl(var(--destructive))\", foreground: \"hsl(var(--destructive-foreground))\" },",
|
|
144
|
+
" muted: { DEFAULT: \"hsl(var(--muted))\", foreground: \"hsl(var(--muted-foreground))\" },",
|
|
145
|
+
" accent: { DEFAULT: \"hsl(var(--accent))\", foreground: \"hsl(var(--accent-foreground))\" },",
|
|
146
|
+
" card: { DEFAULT: \"hsl(var(--card))\", foreground: \"hsl(var(--card-foreground))\" },",
|
|
147
|
+
" popover: { DEFAULT: \"hsl(var(--popover))\", foreground: \"hsl(var(--popover-foreground))\" },",
|
|
148
|
+
" }, borderRadius: { lg: \"var(--radius)\", md: \"calc(var(--radius) - 2px)\", sm: \"calc(var(--radius) - 4px)\" } } }[/cyan]",
|
|
149
|
+
"",
|
|
150
|
+
" [dim]Using Tailwind CDN? See README for the inline config block.[/dim]",
|
|
151
|
+
"",
|
|
152
|
+
" 3. In your base.html:",
|
|
153
|
+
" [cyan]<link rel=\"stylesheet\" href=\"{% static 'css/silkui.css' %}\">",
|
|
154
|
+
" <body x-data>",
|
|
155
|
+
" <script defer src=\"https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js\"></script>",
|
|
156
|
+
" </body>[/cyan]",
|
|
157
|
+
"",
|
|
158
|
+
" 4. Add your first component:",
|
|
159
|
+
" [cyan]silkui add button[/cyan]",
|
|
160
|
+
"",
|
|
161
|
+
" 5. Use it in any template:",
|
|
162
|
+
" [cyan]{% load silkui_tags %}",
|
|
163
|
+
" {% button %}Save{% endbutton %}[/cyan]",
|
|
164
|
+
]),
|
|
165
|
+
title="[bold green]Done[/bold green]",
|
|
166
|
+
)
|
|
167
|
+
)
|
silkui/commands/list.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.table import Table
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command()
|
|
12
|
+
def list_components():
|
|
13
|
+
"""List all available SilkUI components."""
|
|
14
|
+
from silkui.registry import get_registry
|
|
15
|
+
|
|
16
|
+
registry = get_registry()
|
|
17
|
+
|
|
18
|
+
installed = set()
|
|
19
|
+
config_path = Path("silkui.json")
|
|
20
|
+
if config_path.exists():
|
|
21
|
+
config = json.loads(config_path.read_text())
|
|
22
|
+
components_dir = Path(config.get("components_dir", "templates/components"))
|
|
23
|
+
if components_dir.exists():
|
|
24
|
+
installed = {f.stem for f in components_dir.glob("*.html")}
|
|
25
|
+
|
|
26
|
+
table = Table(title="SilkUI Components", show_header=True, header_style="bold")
|
|
27
|
+
table.add_column("", width=2)
|
|
28
|
+
table.add_column("Component", style="bold")
|
|
29
|
+
table.add_column("Description")
|
|
30
|
+
table.add_column("Alpine.js", justify="center")
|
|
31
|
+
|
|
32
|
+
for name, meta in registry.items():
|
|
33
|
+
status = "[green]✓[/green]" if name in installed else " "
|
|
34
|
+
needs_alpine = "[cyan]yes[/cyan]" if meta.get("alpine") else "[dim]no[/dim]"
|
|
35
|
+
table.add_row(status, name, meta.get("description", ""), needs_alpine)
|
|
36
|
+
|
|
37
|
+
console.print()
|
|
38
|
+
console.print(table)
|
|
39
|
+
console.print(f"\n[dim]{len(registry)} components available, {len(installed)} installed[/dim]")
|
|
40
|
+
console.print("[dim]Run [bold]silkui add <name>[/bold] to install a component.[/dim]\n")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
COMPONENTS_DIR = Path(__file__).parent / "components"
|
|
5
|
+
BASE_CSS_FILE = Path(__file__).parent / "base.css"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_registry() -> dict:
|
|
9
|
+
registry = {}
|
|
10
|
+
for component_dir in sorted(COMPONENTS_DIR.iterdir()):
|
|
11
|
+
if not component_dir.is_dir():
|
|
12
|
+
continue
|
|
13
|
+
meta_file = component_dir / "meta.json"
|
|
14
|
+
if meta_file.exists():
|
|
15
|
+
meta = json.loads(meta_file.read_text())
|
|
16
|
+
registry[meta["name"]] = meta
|
|
17
|
+
return registry
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_component_files(name: str) -> dict[str, str]:
|
|
21
|
+
component_dir = COMPONENTS_DIR / name
|
|
22
|
+
return {
|
|
23
|
+
f.name: f.read_text()
|
|
24
|
+
for f in component_dir.iterdir()
|
|
25
|
+
if f.suffix in (".html", ".js")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_base_css() -> str:
|
|
30
|
+
return BASE_CSS_FILE.read_text()
|
silkui/registry/base.css
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
@layer base {
|
|
6
|
+
:root {
|
|
7
|
+
--background: 0 0% 100%;
|
|
8
|
+
--foreground: 222.2 84% 4.9%;
|
|
9
|
+
--card: 0 0% 100%;
|
|
10
|
+
--card-foreground: 222.2 84% 4.9%;
|
|
11
|
+
--popover: 0 0% 100%;
|
|
12
|
+
--popover-foreground: 222.2 84% 4.9%;
|
|
13
|
+
--primary: 222.2 47.4% 11.2%;
|
|
14
|
+
--primary-foreground: 210 40% 98%;
|
|
15
|
+
--secondary: 210 40% 96.1%;
|
|
16
|
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
17
|
+
--muted: 210 40% 96.1%;
|
|
18
|
+
--muted-foreground: 215.4 16.3% 46.9%;
|
|
19
|
+
--accent: 210 40% 96.1%;
|
|
20
|
+
--accent-foreground: 222.2 47.4% 11.2%;
|
|
21
|
+
--destructive: 0 84.2% 60.2%;
|
|
22
|
+
--destructive-foreground: 210 40% 98%;
|
|
23
|
+
--border: 214.3 31.8% 91.4%;
|
|
24
|
+
--input: 214.3 31.8% 91.4%;
|
|
25
|
+
--ring: 222.2 84% 4.9%;
|
|
26
|
+
--radius: 0.5rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.dark {
|
|
30
|
+
--background: 222.2 84% 4.9%;
|
|
31
|
+
--foreground: 210 40% 98%;
|
|
32
|
+
--card: 222.2 84% 4.9%;
|
|
33
|
+
--card-foreground: 210 40% 98%;
|
|
34
|
+
--popover: 222.2 84% 4.9%;
|
|
35
|
+
--popover-foreground: 210 40% 98%;
|
|
36
|
+
--primary: 210 40% 98%;
|
|
37
|
+
--primary-foreground: 222.2 47.4% 11.2%;
|
|
38
|
+
--secondary: 217.2 32.6% 17.5%;
|
|
39
|
+
--secondary-foreground: 210 40% 98%;
|
|
40
|
+
--muted: 217.2 32.6% 17.5%;
|
|
41
|
+
--muted-foreground: 215 20.2% 65.1%;
|
|
42
|
+
--accent: 217.2 32.6% 17.5%;
|
|
43
|
+
--accent-foreground: 210 40% 98%;
|
|
44
|
+
--destructive: 0 62.8% 30.6%;
|
|
45
|
+
--destructive-foreground: 210 40% 98%;
|
|
46
|
+
--border: 217.2 32.6% 17.5%;
|
|
47
|
+
--input: 217.2 32.6% 17.5%;
|
|
48
|
+
--ring: 212.7 26.8% 83.9%;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@layer base {
|
|
53
|
+
* {
|
|
54
|
+
@apply border-border;
|
|
55
|
+
}
|
|
56
|
+
body {
|
|
57
|
+
@apply bg-background text-foreground;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{% comment %}NexUI Alert v1.0.0{% endcomment %}
|
|
2
|
+
{% with nv=variant|default:"default" %}
|
|
3
|
+
<div role="alert" class="relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4
|
|
4
|
+
{% if nv == 'destructive' %}border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive
|
|
5
|
+
{% else %}bg-background text-foreground
|
|
6
|
+
{% endif %}
|
|
7
|
+
{{ class }}">{{ slot }}</div>
|
|
8
|
+
{% endwith %}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "alert",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Bloc d'alerte composable. Variantes: default, destructive.",
|
|
5
|
+
"files": ["alert.html", "alert-title.html", "alert-description.html"],
|
|
6
|
+
"js_files": [],
|
|
7
|
+
"alpine": false,
|
|
8
|
+
"htmx_ready": false,
|
|
9
|
+
"dependencies": [],
|
|
10
|
+
"usage": "{% alert %}{% alert_title %}Heads up!{% endalert_title %}{% alert_description %}Something happened.{% endalert_description %}{% endalert %}"
|
|
11
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{% comment %}NexUI Badge v1.0.0{% endcomment %}
|
|
2
|
+
{% with nv=variant|default:"default" %}
|
|
3
|
+
<span class="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2
|
|
4
|
+
{% if nv == 'default' %}border-transparent bg-primary text-primary-foreground hover:bg-primary/80
|
|
5
|
+
{% elif nv == 'secondary' %}border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80
|
|
6
|
+
{% elif nv == 'destructive' %}border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80
|
|
7
|
+
{% elif nv == 'outline' %}text-foreground
|
|
8
|
+
{% endif %}
|
|
9
|
+
{{ class }}">{{ slot }}</span>
|
|
10
|
+
{% endwith %}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "badge",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Badge compact avec variantes (default, secondary, destructive, outline)",
|
|
5
|
+
"files": ["badge.html"],
|
|
6
|
+
"js_files": [],
|
|
7
|
+
"alpine": false,
|
|
8
|
+
"htmx_ready": false,
|
|
9
|
+
"dependencies": [],
|
|
10
|
+
"usage": "{% include \"components/badge.html\" with label=\"New\" variant=\"default\" %}"
|
|
11
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{% comment %}NexUI Button v1.0.0{% endcomment %}
|
|
2
|
+
{% with nv=variant|default:"default" ns=size|default:"md" %}
|
|
3
|
+
<button
|
|
4
|
+
type="{{ type|default:'button' }}"
|
|
5
|
+
{% if id %}id="{{ id }}"{% endif %}
|
|
6
|
+
class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50
|
|
7
|
+
{% if nv == 'default' %}bg-primary text-primary-foreground hover:bg-primary/90
|
|
8
|
+
{% elif nv == 'destructive' %}bg-destructive text-destructive-foreground hover:bg-destructive/90
|
|
9
|
+
{% elif nv == 'outline' %}border border-input bg-background hover:bg-accent hover:text-accent-foreground
|
|
10
|
+
{% elif nv == 'secondary' %}bg-secondary text-secondary-foreground hover:bg-secondary/80
|
|
11
|
+
{% elif nv == 'ghost' %}hover:bg-accent hover:text-accent-foreground
|
|
12
|
+
{% elif nv == 'link' %}text-primary underline-offset-4 hover:underline
|
|
13
|
+
{% endif %}
|
|
14
|
+
{% if ns == 'sm' %}h-9 px-3 text-xs
|
|
15
|
+
{% elif ns == 'lg' %}h-11 px-8 text-base
|
|
16
|
+
{% elif ns == 'icon' %}h-10 w-10 p-0
|
|
17
|
+
{% else %}h-10 px-4 py-2
|
|
18
|
+
{% endif %}
|
|
19
|
+
{{ class }}"
|
|
20
|
+
{% if disabled %}disabled{% endif %}
|
|
21
|
+
{{ attrs }}
|
|
22
|
+
>{{ slot }}</button>
|
|
23
|
+
{% endwith %}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "button",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Bouton avec variantes (default, destructive, outline, secondary, ghost, link) et tailles",
|
|
5
|
+
"files": ["button.html"],
|
|
6
|
+
"js_files": [],
|
|
7
|
+
"alpine": false,
|
|
8
|
+
"htmx_ready": true,
|
|
9
|
+
"dependencies": [],
|
|
10
|
+
"usage": "{% include \"components/button.html\" with label=\"Click me\" variant=\"default\" size=\"md\" %}"
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "card",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Conteneur composable avec header, title, description, content et footer.",
|
|
5
|
+
"files": ["card.html", "card-header.html", "card-title.html", "card-description.html", "card-content.html", "card-footer.html"],
|
|
6
|
+
"js_files": [],
|
|
7
|
+
"alpine": false,
|
|
8
|
+
"htmx_ready": true,
|
|
9
|
+
"dependencies": [],
|
|
10
|
+
"usage": "{% card %}{% card_header %}{% card_title %}Title{% endcard_title %}{% endcard_header %}{% card_content %}Body{% endcard_content %}{% endcard %}"
|
|
11
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{% comment %}NexUI Checkbox v1.0.0{% endcomment %}
|
|
2
|
+
<div class="{{ container_class }}">
|
|
3
|
+
<div class="flex items-center gap-2">
|
|
4
|
+
<input
|
|
5
|
+
type="checkbox"
|
|
6
|
+
name="{{ name }}"
|
|
7
|
+
id="{{ id }}"
|
|
8
|
+
{% if value %}value="{{ value }}"{% endif %}
|
|
9
|
+
{% if checked %}checked{% endif %}
|
|
10
|
+
{% if required %}required{% endif %}
|
|
11
|
+
{% if disabled %}disabled{% endif %}
|
|
12
|
+
class="h-4 w-4 rounded border border-input accent-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 {{ input_class }}"
|
|
13
|
+
{{ attrs }}
|
|
14
|
+
/>
|
|
15
|
+
{% if label %}
|
|
16
|
+
<label
|
|
17
|
+
for="{{ id }}"
|
|
18
|
+
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 {{ label_class }}"
|
|
19
|
+
>{{ label }}</label>
|
|
20
|
+
{% endif %}
|
|
21
|
+
</div>
|
|
22
|
+
{% if hint %}<p class="text-xs text-muted-foreground mt-1 ml-6">{{ hint }}</p>{% endif %}
|
|
23
|
+
{% if error %}<p class="text-xs text-destructive mt-1 ml-6">{{ error }}</p>{% endif %}
|
|
24
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "checkbox",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Case à cocher avec label inline, hint et error.",
|
|
5
|
+
"files": ["checkbox.html"],
|
|
6
|
+
"js_files": [],
|
|
7
|
+
"alpine": false,
|
|
8
|
+
"htmx_ready": true,
|
|
9
|
+
"dependencies": [],
|
|
10
|
+
"usage": "{% checkbox name=\"terms\" label=\"J'accepte les CGU\" %}"
|
|
11
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{% comment %}NexUI Dropdown v1.0.0
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
{% dropdown trigger_label="My Account" %}
|
|
5
|
+
{% dropdown_item href="/profile/" %}Profile{% enddropdown_item %}
|
|
6
|
+
{% dropdown_item href="/settings/" %}Settings{% enddropdown_item %}
|
|
7
|
+
{% dropdown_separator %}
|
|
8
|
+
{% dropdown_item href="/logout/" %}Logout{% enddropdown_item %}
|
|
9
|
+
{% enddropdown %}
|
|
10
|
+
{% endcomment %}
|
|
11
|
+
<div x-data="{ open: false }" class="relative inline-block {{ class }}">
|
|
12
|
+
<button
|
|
13
|
+
type="button"
|
|
14
|
+
x-on:click="open = !open"
|
|
15
|
+
x-on:keydown.escape="open = false"
|
|
16
|
+
class="inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2 {{ trigger_class }}"
|
|
17
|
+
aria-haspopup="true"
|
|
18
|
+
x-bind:aria-expanded="open"
|
|
19
|
+
>
|
|
20
|
+
{{ trigger_label }}
|
|
21
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" x-bind:class="open ? 'rotate-180' : ''" class="transition-transform duration-200"><path d="m6 9 6 6 6-6"/></svg>
|
|
22
|
+
</button>
|
|
23
|
+
|
|
24
|
+
<div
|
|
25
|
+
x-show="open"
|
|
26
|
+
x-on:click.outside="open = false"
|
|
27
|
+
x-transition:enter="transition ease-out duration-100"
|
|
28
|
+
x-transition:enter-start="opacity-0 scale-95"
|
|
29
|
+
x-transition:enter-end="opacity-100 scale-100"
|
|
30
|
+
x-transition:leave="transition ease-in duration-75"
|
|
31
|
+
x-transition:leave-start="opacity-100 scale-100"
|
|
32
|
+
x-transition:leave-end="opacity-0 scale-95"
|
|
33
|
+
class="absolute right-0 z-50 mt-1 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md {{ menu_class }}"
|
|
34
|
+
style="display:none"
|
|
35
|
+
role="menu"
|
|
36
|
+
>{{ slot }}</div>
|
|
37
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dropdown",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Menu déroulant avec items et séparateurs, contrôlé par Alpine.js",
|
|
5
|
+
"files": ["dropdown.html"],
|
|
6
|
+
"js_files": [],
|
|
7
|
+
"alpine": true,
|
|
8
|
+
"htmx_ready": true,
|
|
9
|
+
"dependencies": [],
|
|
10
|
+
"usage": "{% include \"components/dropdown.html\" with trigger_label=\"Options\" items=items_list %}"
|
|
11
|
+
}
|