diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-07-28 15:07:57 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-07-28 15:07:57 +0200 |
| commit | 04fabf115ccea31ac504b06cc51d78423b697f60 (patch) | |
| tree | 26c21f8822ccd24fce3a80b7361382c8a87d0c96 /src/app.css | |
| parent | 45cc0afb734a422ee7765dc5c60dccbcc6721367 (diff) | |
refactor: services layer, valibot validation, shared CSS utilities, aria labels, and drizzle migrate
- Extract groceries/categories/references services with db injection
- Add valibot for form validation and safe numeric id parsing
- Fix category delete FK behavior with onDelete: set null + migration
- Replace custom migrate.js with drizzle-orm/libsql/migrator
- Add shared CSS utility classes in app.css (forms, lists, buttons)
- Add aria-pressed and aria-label on interactive elements
- Remove redundant app.css imports and unused /stores import
- Add integration tests for all services using drizzle migrate helper
- Remove smoke test; add db:migrate and db:studio scripts
Diffstat (limited to 'src/app.css')
| -rw-r--r-- | src/app.css | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/src/app.css b/src/app.css index 2aeefb0..b9ce7f3 100644 --- a/src/app.css +++ b/src/app.css @@ -54,3 +54,249 @@ button { cursor: pointer; font-family: inherit; } + +/* ===== Utility Classes ===== */ + +/* Lists */ +.item-list { + list-style: none; + display: flex; + flex-direction: column; + gap: 0.375rem; +} + +.item-list--wide { + gap: 0.5rem; +} + +/* Row containers */ +.item-row { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.item-row--card { + padding: 0.5rem 0.75rem; + background: var(--surface); + border-radius: 0.75rem; + box-shadow: 0 1px 2px var(--shadow); + transition: all 0.2s ease; +} + +.item-row--card:hover { + box-shadow: 0 2px 6px var(--shadow); + transform: translateY(-1px); +} + +.item-row--fixed { + height: 2.75rem; +} + +.item-row--ref { + padding: 0.875rem 1rem; + gap: 0.75rem; + background: var(--surface); + border-radius: 0.75rem; + box-shadow: 0 1px 2px var(--shadow); + transition: all 0.2s ease; +} + +.item-row--ref:hover { + box-shadow: 0 2px 6px var(--shadow); + transform: translateY(-1px); +} + +/* Buttons inside rows */ +.item-btn { + width: 100%; + text-align: left; + padding: 0 0.875rem; + height: 100%; + border: none; + border-radius: 0.75rem; + background: var(--surface); + font-size: 1rem; + font-weight: 500; + display: flex; + align-items: center; + transition: all 0.2s ease; + box-shadow: 0 1px 2px var(--shadow); +} + +.item-btn:hover { + background: #fff8f5; + box-shadow: 0 2px 6px rgba(217, 119, 87, 0.1); + transform: translateY(-1px); +} + +.item-btn--grow { + flex-grow: 1; + width: auto; +} + +/* Text */ +.item-text { + display: flex; + flex-direction: column; + text-align: left; + line-height: 1.2; + gap: 0.0625rem; + overflow: hidden; +} + +.best-ref { + font-size: 0.75rem; + color: var(--text-secondary); + font-weight: 400; +} + +/* Links */ +.ref-link { + width: 2.75rem; + flex-shrink: 0; + height: 2.75rem; + aspect-ratio: 1; + display: flex; + align-items: center; + justify-content: center; + border-radius: 0.75rem; + background: var(--surface); + color: var(--text-secondary); + text-decoration: none; + font-size: 1rem; + transition: all 0.2s ease; + box-shadow: 0 1px 2px var(--shadow); +} + +.ref-link:hover { + background: #fff8f5; + color: var(--accent); + box-shadow: 0 2px 6px rgba(217, 119, 87, 0.1); + transform: translateY(-1px); +} + +/* Category heading */ +.category-name { + font-size: 0.75rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--accent); + margin: 1.25rem 0 0.5rem; + padding-left: 0.5rem; + border-left: 3px solid var(--accent); +} + +/* Empty state */ +.empty { + color: var(--text-secondary); + font-size: 0.9375rem; + text-align: center; + background: var(--surface); + border-radius: 0.75rem; + box-shadow: 0 1px 2px var(--shadow); +} + +/* Error */ +.error { + color: var(--danger); + font-size: 0.875rem; + padding: 0.5rem 0.75rem; + background: rgba(217, 83, 79, 0.08); + border-radius: 0.5rem; +} + +/* Forms */ +.form-card { + display: flex; + flex-direction: column; + gap: 0.75rem; + margin-bottom: 1.5rem; + padding: 1rem; + background: var(--surface); + border-radius: 0.75rem; + box-shadow: 0 1px 2px var(--shadow); +} + +.form-card--compact { + gap: 0.5rem; +} + +.form-card label { + display: flex; + flex-direction: column; + gap: 0.375rem; +} + +.form-label { + font-size: 0.75rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-secondary); +} + +.form-input, +.form-select { + padding: 0.75rem 1rem; + border: 1.5px solid var(--border); + border-radius: 0.75rem; + font-size: 1rem; + font-family: inherit; + background: var(--bg); + min-width: 0; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.form-input:focus, +.form-select:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(217, 119, 87, 0.15); +} + +/* Buttons */ +.btn-primary { + padding: 0.75rem 1.5rem; + border: none; + border-radius: 0.75rem; + background: var(--accent); + color: #fff; + font-size: 0.9375rem; + font-weight: 500; + align-self: flex-start; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0 1px 2px var(--shadow); +} + +.btn-primary:hover { + background: var(--accent-hover); + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(217, 119, 87, 0.2); +} + +.btn-ghost { + flex-shrink: 0; + width: 2rem; + aspect-ratio: 1; + padding: 0; + border: none; + border-radius: 0.625rem; + background: transparent; + color: var(--text-secondary); + font-size: 1.25rem; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s ease; +} + +.btn-ghost:hover { + background: rgba(217, 83, 79, 0.1); + color: var(--danger); +} |
