sapium-ui 0.1.0__tar.gz
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.
- sapium_ui-0.1.0/.github/workflows/ci.yml +20 -0
- sapium_ui-0.1.0/.github/workflows/publish.yml +24 -0
- sapium_ui-0.1.0/.gitignore +16 -0
- sapium_ui-0.1.0/PKG-INFO +8 -0
- sapium_ui-0.1.0/README.md +127 -0
- sapium_ui-0.1.0/build/custom.css +107 -0
- sapium_ui-0.1.0/build/package-lock.json +1180 -0
- sapium_ui-0.1.0/build/package.json +12 -0
- sapium_ui-0.1.0/pyproject.toml +21 -0
- sapium_ui-0.1.0/src/sapium_ui/__init__.py +10 -0
- sapium_ui-0.1.0/src/sapium_ui/static/css/custom.css +107 -0
- sapium_ui-0.1.0/src/sapium_ui/static/css/dist.css +2 -0
- sapium_ui-0.1.0/src/sapium_ui/static/js/confirm.js +45 -0
- sapium_ui-0.1.0/src/sapium_ui/static/js/theme.js +34 -0
- sapium_ui-0.1.0/src/sapium_ui/templates/sapium_ui/base.html +120 -0
- sapium_ui-0.1.0/tests/test_helpers.py +19 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
commons:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: actions/setup-python@v5
|
|
13
|
+
with:
|
|
14
|
+
python-version: "3.13"
|
|
15
|
+
- name: Install dependencies
|
|
16
|
+
run: pip install -e ".[dev]"
|
|
17
|
+
- name: Lint
|
|
18
|
+
run: ruff check src/ && ruff format --check src/
|
|
19
|
+
- name: Test
|
|
20
|
+
run: pytest tests/
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
publish:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
id-token: write
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.13"
|
|
20
|
+
- name: Build package
|
|
21
|
+
run: |
|
|
22
|
+
pip install build
|
|
23
|
+
python -m build
|
|
24
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
sapium_ui-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sapium-ui
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Sapium shared frontend layer — CSS, Jinja2 base template, and JS for all Sapium consoles
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Provides-Extra: dev
|
|
7
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
8
|
+
Requires-Dist: ruff>=0.15.0; extra == 'dev'
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# sapium-ui
|
|
2
|
+
|
|
3
|
+
Shared frontend layer for all Sapium consoles: compiled CSS, Jinja2 base template, and shared JS.
|
|
4
|
+
|
|
5
|
+
Consoles install this package and receive a pre-compiled stylesheet (Tailwind v4 + DaisyUI v5), a `base.html` shell to extend, and two JS files for theme toggling and confirm modal behaviour. No Node.js is required in any console's Dockerfile.
|
|
6
|
+
|
|
7
|
+
## Package contents
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/sapium_ui/
|
|
11
|
+
├── __init__.py # get_static_dir(), get_templates_dir()
|
|
12
|
+
├── static/
|
|
13
|
+
│ ├── css/
|
|
14
|
+
│ │ ├── custom.css # Tailwind source — reference copy
|
|
15
|
+
│ │ └── dist.css # pre-compiled output — checked in, shipped in wheel
|
|
16
|
+
│ └── js/
|
|
17
|
+
│ ├── theme.js # dark/light theme toggle
|
|
18
|
+
│ └── confirm.js # htmx:confirm → DaisyUI modal bridge
|
|
19
|
+
└── templates/
|
|
20
|
+
└── sapium_ui/
|
|
21
|
+
└── base.html # shared Jinja2 shell
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Using in a console
|
|
25
|
+
|
|
26
|
+
### 1. Add the dependency
|
|
27
|
+
|
|
28
|
+
```toml
|
|
29
|
+
# pyproject.toml
|
|
30
|
+
[project]
|
|
31
|
+
dependencies = ["sapium-ui"]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 2. Wire up Jinja2 and static files
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from jinja2 import ChoiceLoader, Environment, PackageLoader
|
|
38
|
+
import sapium_ui
|
|
39
|
+
from fastapi.staticfiles import StaticFiles
|
|
40
|
+
|
|
41
|
+
env = Environment(
|
|
42
|
+
loader=ChoiceLoader([
|
|
43
|
+
PackageLoader("sapium_my_console"), # console-local templates win
|
|
44
|
+
PackageLoader("sapium_ui"), # shared templates as fallback
|
|
45
|
+
]),
|
|
46
|
+
autoescape=True,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
app.mount("/static/ui", StaticFiles(directory=str(sapium_ui.get_static_dir())), name="ui-static")
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 3. Extend the base template
|
|
53
|
+
|
|
54
|
+
```html
|
|
55
|
+
{% extends "sapium_ui/base.html" %}
|
|
56
|
+
|
|
57
|
+
{% block title %}My Console{% endblock %}
|
|
58
|
+
{% block product_name %}My Console{% endblock %}
|
|
59
|
+
{% block viewport_class %}xl{% endblock %}
|
|
60
|
+
|
|
61
|
+
{% block nav %}
|
|
62
|
+
<nav aria-label="Main navigation" class="flex-1 py-4 overflow-y-auto">
|
|
63
|
+
<ul class="space-y-1 px-2">
|
|
64
|
+
<li>
|
|
65
|
+
<a href="/" {{ nav_aria_current('/') }}
|
|
66
|
+
class="{{ nav_link_class('/') }} flex items-center gap-3 px-3 py-2 rounded text-sm transition-colors">
|
|
67
|
+
Dashboard
|
|
68
|
+
</a>
|
|
69
|
+
</li>
|
|
70
|
+
</ul>
|
|
71
|
+
</nav>
|
|
72
|
+
{% endblock %}
|
|
73
|
+
|
|
74
|
+
{% block content %}
|
|
75
|
+
{# page content #}
|
|
76
|
+
{% endblock %}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Template context variables
|
|
80
|
+
|
|
81
|
+
| Variable | Required | Description |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| `theme_key` | No | `localStorage` key for theme persistence (default: `sapium-theme`) |
|
|
84
|
+
| `logo_url` | No | URL for the favicon and sidebar logo |
|
|
85
|
+
| `ui_static_url` | No | Base URL where sapium-ui static assets are mounted (default: `/static/ui`) |
|
|
86
|
+
| `viewport_class` | No | Minimum breakpoint: `xl` (1280px, fixed sidebar) or `lg` (1024px, icon sidebar) |
|
|
87
|
+
|
|
88
|
+
### Template blocks
|
|
89
|
+
|
|
90
|
+
| Block | Purpose |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `title` | `<title>` tag content |
|
|
93
|
+
| `product_name` | Name shown in sidebar header |
|
|
94
|
+
| `viewport_class` | Override minimum breakpoint (alternative to context variable) |
|
|
95
|
+
| `viewport_message` | Small-screen message text |
|
|
96
|
+
| `nav` | Sidebar navigation — receives `nav_link_class(href)` and `nav_aria_current(href)` macros |
|
|
97
|
+
| `breadcrumb` | Top header breadcrumb area |
|
|
98
|
+
| `page_action` | Top header right-side action button |
|
|
99
|
+
| `content` | Main page content |
|
|
100
|
+
|
|
101
|
+
### Nav helper macros
|
|
102
|
+
|
|
103
|
+
The base template defines two macros accessible in `{% block nav %}` overrides:
|
|
104
|
+
|
|
105
|
+
- `nav_link_class(href)` — returns active/hover Tailwind classes based on the current URL path
|
|
106
|
+
- `nav_aria_current(href)` — returns `aria-current="page"` when the link is active
|
|
107
|
+
|
|
108
|
+
## Rebuilding the CSS
|
|
109
|
+
|
|
110
|
+
The CSS build is a **maintainer-only step** run before cutting a release. Consumers receive the pre-compiled `dist.css` in the wheel.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
cd build
|
|
114
|
+
npm install
|
|
115
|
+
npm run build
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Dependencies: `@tailwindcss/cli@^4`, `daisyui@^5`
|
|
119
|
+
|
|
120
|
+
The compiled output is committed to the repo and included in the wheel.
|
|
121
|
+
|
|
122
|
+
## Design system
|
|
123
|
+
|
|
124
|
+
- **Dark theme** (`business`): DaisyUI v5 built-in base surfaces provide depth. Only brand tokens are overridden (primary, neutral, status colours, shape).
|
|
125
|
+
- **Light theme**: same brand tokens plus `--color-base-200: white` for cards-on-white. Sidebar stays dark.
|
|
126
|
+
- **Sapium purple**: `oklch(38.5% 0.183 293.5)` — `#6a0dad`
|
|
127
|
+
- **WCAG AA**: error components force `color: var(--color-error-content)` to guarantee contrast.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@plugin "daisyui";
|
|
3
|
+
|
|
4
|
+
/* ─── Business (dark) theme — brand tokens only; DaisyUI v5 handles base surfaces ─── */
|
|
5
|
+
:root,
|
|
6
|
+
[data-theme="business"] {
|
|
7
|
+
color-scheme: dark;
|
|
8
|
+
|
|
9
|
+
/* brand */
|
|
10
|
+
--color-primary: oklch(38.5% 0.183 293.5); /* #6a0dad — sapium purple */
|
|
11
|
+
--color-primary-content: oklch(100% 0 0); /* white on primary bg */
|
|
12
|
+
|
|
13
|
+
/* sidebar */
|
|
14
|
+
--color-neutral: oklch(19.59% 0.027 286.57); /* #2A2535 — sidebar bg */
|
|
15
|
+
--color-neutral-content: oklch(91.19% 0.022 291.29); /* #E5E0F0 — sidebar text */
|
|
16
|
+
|
|
17
|
+
/* status */
|
|
18
|
+
--color-success: oklch(72.32% 0.189 142.5); /* #22C55E */
|
|
19
|
+
--color-success-content: oklch(100% 0 0);
|
|
20
|
+
--color-error: oklch(50% 0.213 22.18); /* ~red-700; white text ≥4.5:1 */
|
|
21
|
+
--color-error-content: oklch(100% 0 0);
|
|
22
|
+
--color-warning: oklch(77.48% 0.154 74.45); /* #F59E0B */
|
|
23
|
+
--color-warning-content: oklch(15% 0 0);
|
|
24
|
+
--color-info: oklch(63.37% 0.19 255.5); /* #3B82F6 */
|
|
25
|
+
--color-info-content: oklch(100% 0 0);
|
|
26
|
+
|
|
27
|
+
/* shape (DaisyUI v5 names) */
|
|
28
|
+
--radius-box: 0.75rem;
|
|
29
|
+
--radius-selector: 0.5rem;
|
|
30
|
+
--radius-field: 0.375rem;
|
|
31
|
+
--size-selector: 0.25rem;
|
|
32
|
+
--size-field: 0.25rem;
|
|
33
|
+
--border: 1px;
|
|
34
|
+
--depth: 1;
|
|
35
|
+
--noise: 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* ─── Light theme ─── */
|
|
39
|
+
[data-theme="light"] {
|
|
40
|
+
color-scheme: light;
|
|
41
|
+
|
|
42
|
+
/* pure white base-200 — makes cards-on-white work */
|
|
43
|
+
--color-base-200: oklch(100% 0 0); /* pure white */
|
|
44
|
+
|
|
45
|
+
/* brand */
|
|
46
|
+
--color-primary: oklch(38.5% 0.183 293.5); /* #6a0dad — sapium purple */
|
|
47
|
+
--color-primary-content: oklch(100% 0 0);
|
|
48
|
+
|
|
49
|
+
/* sidebar — stays dark in light mode */
|
|
50
|
+
--color-neutral: oklch(19.59% 0.027 286.57); /* #2A2535 — sidebar bg */
|
|
51
|
+
--color-neutral-content: oklch(91.19% 0.022 291.29); /* #E5E0F0 — sidebar text */
|
|
52
|
+
|
|
53
|
+
/* status */
|
|
54
|
+
--color-success: oklch(72.32% 0.189 142.5); /* #22C55E */
|
|
55
|
+
--color-success-content: oklch(100% 0 0);
|
|
56
|
+
--color-error: oklch(50% 0.213 22.18); /* red-700; white text ≥4.5:1 */
|
|
57
|
+
--color-error-content: oklch(100% 0 0);
|
|
58
|
+
--color-warning: oklch(77.48% 0.154 74.45); /* #F59E0B */
|
|
59
|
+
--color-warning-content: oklch(15% 0 0);
|
|
60
|
+
--color-info: oklch(63.37% 0.19 255.5); /* #3B82F6 */
|
|
61
|
+
--color-info-content: oklch(15% 0 0); /* dark text — white fails WCAG AA on ~63% L blue */
|
|
62
|
+
|
|
63
|
+
/* shape */
|
|
64
|
+
--radius-box: 0.75rem;
|
|
65
|
+
--radius-selector: 0.5rem;
|
|
66
|
+
--radius-field: 0.375rem;
|
|
67
|
+
--size-selector: 0.25rem;
|
|
68
|
+
--size-field: 0.25rem;
|
|
69
|
+
--border: 1px;
|
|
70
|
+
--depth: 0;
|
|
71
|
+
--noise: 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* WCAG AA fix: DaisyUI v5 error components inherit color from --color-error
|
|
75
|
+
which can fail contrast on dark backgrounds. Force error-content (white). */
|
|
76
|
+
[data-theme="business"] .alert-error,
|
|
77
|
+
[data-theme="business"] .badge-error,
|
|
78
|
+
[data-theme="business"] .btn-error {
|
|
79
|
+
color: var(--color-error-content);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
[data-theme="light"] .alert-error,
|
|
83
|
+
[data-theme="light"] .badge-error,
|
|
84
|
+
[data-theme="light"] .btn-error:not(.btn-outline) {
|
|
85
|
+
color: var(--color-error-content);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
[data-theme="light"] .btn-outline.btn-error {
|
|
89
|
+
border-color: var(--color-error);
|
|
90
|
+
color: var(--color-error);
|
|
91
|
+
background-color: transparent;
|
|
92
|
+
}
|
|
93
|
+
[data-theme="light"] .btn-outline.btn-error:hover {
|
|
94
|
+
background-color: var(--color-error);
|
|
95
|
+
border-color: var(--color-error);
|
|
96
|
+
color: var(--color-error-content);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* expand/collapse chevron — CSS-only rotate, no JS */
|
|
100
|
+
.card-chevron {
|
|
101
|
+
transition: transform 0.15s ease;
|
|
102
|
+
flex-shrink: 0;
|
|
103
|
+
transform: rotate(0deg);
|
|
104
|
+
}
|
|
105
|
+
details[open] summary .card-chevron {
|
|
106
|
+
transform: rotate(90deg);
|
|
107
|
+
}
|