diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 19da613..e76390d 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -15,6 +15,7 @@ "@angular/platform-browser": "^20.0.0", "@angular/router": "^20.0.0", "@ngrx/signals": "^19.2.1", + "@picocss/pico": "^2.1.1", "rxjs": "~7.8.0", "tslib": "^2.3.0", "uuid": "^11.1.0" @@ -2816,6 +2817,12 @@ "license": "MIT", "optional": true }, + "node_modules/@picocss/pico": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@picocss/pico/-/pico-2.1.1.tgz", + "integrity": "sha512-kIDugA7Ps4U+2BHxiNHmvgPIQDWPDU4IeU6TNRdvXQM1uZX+FibqDQT2xUOnnO2yq/LUHcwnGlu1hvf4KfXnMg==", + "license": "MIT" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", diff --git a/webapp/package.json b/webapp/package.json index d1cd31d..4519742 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -17,6 +17,7 @@ "@angular/platform-browser": "^20.0.0", "@angular/router": "^20.0.0", "@ngrx/signals": "^19.2.1", + "@picocss/pico": "^2.1.1", "rxjs": "~7.8.0", "tslib": "^2.3.0", "uuid": "^11.1.0" diff --git a/webapp/src/app/components/card/card.html b/webapp/src/app/components/card/card.html deleted file mode 100644 index 4d82bc6..0000000 --- a/webapp/src/app/components/card/card.html +++ /dev/null @@ -1,11 +0,0 @@ -
- -
- -
- -
- - diff --git a/webapp/src/app/components/card/card.scss b/webapp/src/app/components/card/card.scss deleted file mode 100644 index 7649468..0000000 --- a/webapp/src/app/components/card/card.scss +++ /dev/null @@ -1,22 +0,0 @@ -:host { - border: 1px solid var(--color-outline); - border-radius: 1.2rem; - color: var(--color-on-surface); - display: inline-grid; - font-size: 1.8rem; - grid-template-areas: - 'head' - 'body' - 'foot'; - grid-template-rows: min-content auto min-content; - padding: 1.6rem; -} -header { - margin-top: 0.4rem; -} -footer { - justify-content: flex-end; - display: flex; - flex-direction: row; - margin-top: 1.2rem; -} diff --git a/webapp/src/app/components/card/card.spec.ts b/webapp/src/app/components/card/card.spec.ts deleted file mode 100644 index 9ec58c2..0000000 --- a/webapp/src/app/components/card/card.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { Card } from './card'; - -describe('Card', () => { - let component: Card; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [Card], - }).compileComponents(); - - fixture = TestBed.createComponent(Card); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/webapp/src/app/components/card/card.ts b/webapp/src/app/components/card/card.ts deleted file mode 100644 index 10f8b3f..0000000 --- a/webapp/src/app/components/card/card.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-card', - templateUrl: './card.html', - styleUrl: './card.scss', -}) -export class Card {} diff --git a/webapp/src/app/routes/login/login.html b/webapp/src/app/routes/login/login.html index 6e0f73b..3df9be5 100644 --- a/webapp/src/app/routes/login/login.html +++ b/webapp/src/app/routes/login/login.html @@ -1,8 +1,16 @@ - -

Login

-
-
- -
-
-
+

Aisle17

+
+
+ + + +
+
+
+ diff --git a/webapp/src/app/routes/login/login.scss b/webapp/src/app/routes/login/login.scss index b01a4ba..5f330e0 100644 --- a/webapp/src/app/routes/login/login.scss +++ b/webapp/src/app/routes/login/login.scss @@ -1,3 +1,10 @@ :host { display: grid; } + +form { + fieldset, + input[type='submit'] { + margin-bottom: 0; + } +} diff --git a/webapp/src/app/routes/login/login.ts b/webapp/src/app/routes/login/login.ts index 9722c16..b5ac528 100644 --- a/webapp/src/app/routes/login/login.ts +++ b/webapp/src/app/routes/login/login.ts @@ -1,16 +1,47 @@ -import { Component, inject } from '@angular/core'; +import { Component, inject, Signal } from '@angular/core'; +import { toSignal } from '@angular/core/rxjs-interop'; import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; -import { Card } from 'components/card/card'; +import { debounceTime, map } from 'rxjs'; @Component({ selector: 'app-login', templateUrl: './login.html', styleUrl: './login.scss', - imports: [Card, FormsModule, ReactiveFormsModule], + imports: [FormsModule, ReactiveFormsModule], }) export class Login { readonly form = inject(FormBuilder).group({ - username: ['', Validators.required], - password: ['', Validators.required], + email: ['', Validators.compose([Validators.required, Validators.email])], + password: ['', Validators.compose([Validators.required, Validators.minLength(8)])], }); + + private get email() { + return this.form.get('email')!; + } + + private get password() { + return this.form.get('password')!; + } + + readonly isEmailInvalid: Signal = toSignal( + this.email.statusChanges.pipe( + debounceTime(500), + map(status => this.email.dirty && status !== 'VALID') + ), + { initialValue: false } + ); + + readonly isPasswordInvalid: Signal = toSignal( + this.password.statusChanges.pipe( + debounceTime(500), + map(status => this.email.dirty && status !== 'VALID') + ), + { initialValue: false } + ); + + readonly isFormValid: Signal = toSignal(this.form.statusChanges.pipe(map(status => status === 'VALID')), { + initialValue: this.form.dirty && this.form.status === 'VALID', + }); + + onSubmit() {} } diff --git a/webapp/src/app/routes/plan/components/meal/meal.html b/webapp/src/app/routes/plan/components/meal/meal.html index d1f2200..ae9480b 100644 --- a/webapp/src/app/routes/plan/components/meal/meal.html +++ b/webapp/src/app/routes/plan/components/meal/meal.html @@ -1,13 +1,5 @@ - -

{{ name }}

+@if (foods!!.length > 0) { - @if (foods!!.length > 0) { - - } @else { - No foods planned. Click the button below to add some! - } - - -
+} @else { + No foods planned. Click the button below to add some! +} diff --git a/webapp/src/app/routes/plan/components/meal/meal.ts b/webapp/src/app/routes/plan/components/meal/meal.ts index 19d36ed..b799bbd 100644 --- a/webapp/src/app/routes/plan/components/meal/meal.ts +++ b/webapp/src/app/routes/plan/components/meal/meal.ts @@ -1,10 +1,8 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { IFood, MealType } from 'models/meal.model'; -import { Card } from 'components/card/card'; @Component({ selector: 'app-meal', - imports: [Card], templateUrl: './meal.html', styleUrl: './meal.scss', changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/webapp/src/index.html b/webapp/src/index.html index 0d5097b..b00fee1 100644 --- a/webapp/src/index.html +++ b/webapp/src/index.html @@ -1,5 +1,5 @@ - + Webapp diff --git a/webapp/src/styles.scss b/webapp/src/styles.scss index 0991b86..519d5fc 100644 --- a/webapp/src/styles.scss +++ b/webapp/src/styles.scss @@ -1,45 +1,25 @@ -@import url('./styles/reset.scss'); -@import url('./styles/typography.scss'); -@import url('./styles/components/button.scss'); +@use '@picocss/pico/scss' with ( + $enable-semantic-container: true, + $enable-classes: false, + $modules: ( + // Theme + 'themes/default': false, -:root { - --color-primary: oklch(0.327 0.057852 184.2083); - --color-on-primary: white; - --color-primary-container: oklch(0.5247 0.0849 184.3); - --color-primary-container-alt: oklch(from var(--color-primary-container) 0.565 c h); - --color-on-primary-container: white; + // Layout + 'layout/grid': false, - --color-secondary: oklch(0.3394 0.0927 273.22); - --color-on-secondary: white; - --color-secondary-container: oklch(0.5379 0.0899 276.11); - --color-on-secondary-container: white; + // Content + 'content/code': false, - --color-tertiary: oklch(0.3384 0.0788 60.74); - --color-on-tertiary: white; - --color-tertiary-container: oklch(0.5416 0.1017 61.52); - --color-on-tertiary-container: white; + // Forms + 'forms/input-color': false, + 'forms/input-file': false, + ) +); - --color-error: oklch(0.3413 0.087 34.27); - --color-on-error: white; - --color-error-container: oklch(0.5441 0.0989 34.26); - --color-on-error-container: white; - - --color-surface: oklch(0.9816 0.0055 211.04); - --color-on-surface: oklch(0.1764 0.0095 208.78); - --color-surface-container: oklch(0.9297 0.0066 208.78); - - --color-outline: oklch(0.4386 0.0119 212.57); - --color-outline-variant: oklch(0.5336 0.0123 204.04); - - --color-shadow: oklch(0.3396 0.0743 281.7 / 25%); - --color-shadow-variant: oklch(0 0 0 / 30%); - - --box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.24) 0px 1px 2px; - - font-family: system-ui, sans-serif; - font-size: 62.5%; - font-weight: 400; -} +// Custom Theme +@use 'styles/theme/styles'; +@use 'styles/theme/schemes'; body { margin: 0; diff --git a/webapp/src/styles/components/button.scss b/webapp/src/styles/components/button.scss deleted file mode 100644 index 9e2e8e4..0000000 --- a/webapp/src/styles/components/button.scss +++ /dev/null @@ -1,23 +0,0 @@ -button { - background-color: var(--color-surface-container); - border: 1px solid var(--color-outline); - border-radius: 0.4rem; - color: var(--color-on-surface-container); - cursor: pointer; - font-size: 0.8rem; - padding: 0.2rem 0.6rem; - - &:hover { - box-shadow: var(--color-shadow) 0px 3px 8px; - box-shadow: var(--color-shadow) 0px 2px 5px -1px, var(--color-shadow-variant) 0px 1px 3px -1px; - - &:active { - background-color: var(--color-primary-container-alt); - } - } - - &.primary { - background-color: var(--color-primary-container); - color: var(--color-on-primary-container); - } -} diff --git a/webapp/src/styles/reset.scss b/webapp/src/styles/reset.scss deleted file mode 100644 index 0761796..0000000 --- a/webapp/src/styles/reset.scss +++ /dev/null @@ -1,54 +0,0 @@ -/* 1. Use a more-intuitive box-sizing model */ -*, *::before, *::after { - box-sizing: border-box; -} - -/* 2. Remove default margin */ -* { - margin: 0; -} - -/* 3. Enable keyword animations */ -@media (prefers-reduced-motion: no-preference) { - html { - interpolate-size: allow-keywords; - } -} - -body { - /* 4. Add accessible line-height */ - line-height: 1.5; - /* 5. Improve text rendering */ - -webkit-font-smoothing: antialiased; -} - -/* 6. Improve media defaults */ -img, picture, video, canvas, svg { - display: block; - max-width: 100%; -} - -/* 7. Inherit fonts for form controls */ -input, button, textarea, select { - font: inherit; -} - -/* 8. Avoid text overflows */ -p, h1, h2, h3, h4, h5, h6 { - overflow-wrap: break-word; -} - -/* 9. Improve line wrapping */ -p { - text-wrap: pretty; -} -h1, h2, h3, h4, h5, h6 { - text-wrap: balance; -} - -/* - 10. Create a root stacking context -*/ -#root, #__next { - isolation: isolate; -} diff --git a/webapp/src/styles/theme/_dark.scss b/webapp/src/styles/theme/_dark.scss new file mode 100644 index 0000000..e72089d --- /dev/null +++ b/webapp/src/styles/theme/_dark.scss @@ -0,0 +1,144 @@ +@use 'sass:color'; +@use 'sass:map'; +@use '@picocss/pico/scss/settings' as *; +@use '@picocss/pico/scss/colors' as *; +@use '@picocss/pico/scss/helpers/functions'; + +$theme-color: #0074d9; + +// Default: Dark theme +@mixin theme { + #{$css-var-prefix}background-color: #{color.mix($slate-950, $slate-900)}; + + // Text color + #{$css-var-prefix}color: #{$zinc-100}; + + // Text selection color + #{$css-var-prefix}text-selection-color: #{rgba($theme-color, 0.1875)}; + + // Muted colors + #{$css-var-prefix}muted-color: #{$zinc-450}; + #{$css-var-prefix}muted-border-color: #{$slate-850}; + + // Primary colors + #{$css-var-prefix}primary: #{$theme-color}; + #{$css-var-prefix}primary-background: var(#{$css-var-prefix}primary); + #{$css-var-prefix}primary-border: var(#{$css-var-prefix}primary-background); + #{$css-var-prefix}primary-underline: #{rgba($theme-color, 0.5)}; + #{$css-var-prefix}primary-hover: #{color.scale($theme-color, $lightness: 21%)}; + #{$css-var-prefix}primary-hover-background: var(#{$css-var-prefix}primary-hover); + #{$css-var-prefix}primary-hover-border: var(#{$css-var-prefix}primary-hover-background); + #{$css-var-prefix}primary-hover-underline: var(#{$css-var-prefix}primary-hover); + #{$css-var-prefix}primary-focus: #{rgba($theme-color, 0.375)}; + #{$css-var-prefix}primary-inverse: #{$black}; + + // Secondary colors + #{$css-var-prefix}secondary: #{$zinc-350}; + #{$css-var-prefix}secondary-background: #{$slate-600}; + #{$css-var-prefix}secondary-border: var(#{$css-var-prefix}secondary-background); + #{$css-var-prefix}secondary-underline: #{rgba($zinc-350, 0.5)}; + #{$css-var-prefix}secondary-hover: #{$zinc-250}; + #{$css-var-prefix}secondary-hover-background: #{$slate-550}; + #{$css-var-prefix}secondary-hover-border: var(#{$css-var-prefix}secondary-hover-background); + #{$css-var-prefix}secondary-hover-underline: var(#{$css-var-prefix}secondary-hover); + #{$css-var-prefix}secondary-focus: #{rgba($slate-350, 0.25)}; + #{$css-var-prefix}secondary-inverse: #{$white}; + + // Contrast colors + #{$css-var-prefix}contrast: #{$slate-100}; + #{$css-var-prefix}contrast-background: #{$slate-50}; + #{$css-var-prefix}contrast-border: var(#{$css-var-prefix}contrast-background); + #{$css-var-prefix}contrast-underline: #{rgba($slate-100, 0.5)}; + #{$css-var-prefix}contrast-hover: #{$white}; + #{$css-var-prefix}contrast-hover-background: #{$white}; + #{$css-var-prefix}contrast-hover-border: var(#{$css-var-prefix}contrast-hover-background); + #{$css-var-prefix}contrast-hover-underline: var(#{$css-var-prefix}contrast-hover); + #{$css-var-prefix}contrast-focus: #{rgba($slate-150, 0.25)}; + #{$css-var-prefix}contrast-inverse: #{$black}; + + // Box shadow + #{$css-var-prefix}box-shadow: functions.shadow($black); + + // Typography + @if map.get($modules, 'content/typography') { + // Headings colors + #{$css-var-prefix}h1-color: #{$zinc-50}; + #{$css-var-prefix}h2-color: #{$zinc-100}; + #{$css-var-prefix}h3-color: #{$zinc-200}; + #{$css-var-prefix}h4-color: #{$zinc-250}; + #{$css-var-prefix}h5-color: #{$zinc-300}; + #{$css-var-prefix}h6-color: #{$zinc-400}; + + // Highlighted text () + #{$css-var-prefix}mark-background-color: #{$azure-750}; + #{$css-var-prefix}mark-color: #{$white}; + + // Inserted () & Deleted () + #{$css-var-prefix}ins-color: #{color.mix($jade-450, $zinc-200)}; + #{$css-var-prefix}del-color: #{color.mix($red-500, $zinc-200)}; + + // Blockquote + #{$css-var-prefix}blockquote-border-color: var(#{$css-var-prefix}muted-border-color); + #{$css-var-prefix}blockquote-footer-color: var(#{$css-var-prefix}muted-color); + } + + // Button + @if map.get($modules, 'content/button') { + // To disable box-shadow, remove the var or set to '0 0 0 rgba(0, 0, 0, 0)' + // Don't use, 'none, 'false, 'null', '0', etc. + #{$css-var-prefix}button-box-shadow: 0 0 0 rgba(0, 0, 0, 0); + #{$css-var-prefix}button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0); + } + + // Table + @if map.get($modules, 'content/table') { + #{$css-var-prefix}table-border-color: var(#{$css-var-prefix}muted-border-color); + #{$css-var-prefix}table-row-stripped-background-color: #{rgba($zinc-500, 0.0375)}; + } + + // Form elements + @if map.get($modules, 'forms/basics') { + #{$css-var-prefix}form-element-background-color: #{color.mix($slate-900, $slate-850)}; + #{$css-var-prefix}form-element-selected-background-color: #{$slate-800}; + #{$css-var-prefix}form-element-border-color: #{$slate-800}; + #{$css-var-prefix}form-element-color: #{$zinc-100}; + #{$css-var-prefix}form-element-placeholder-color: #{$zinc-400}; + #{$css-var-prefix}form-element-active-background-color: #{color.mix($slate-900, $slate-850, 75%)}; + #{$css-var-prefix}form-element-active-border-color: var(#{$css-var-prefix}primary-border); + #{$css-var-prefix}form-element-focus-color: var(#{$css-var-prefix}primary-focus); + #{$css-var-prefix}form-element-disabled-background-color: var(#{$css-var-prefix}form-element-background-color); + #{$css-var-prefix}form-element-disabled-border-color: var(#{$css-var-prefix}form-element-border-color); + #{$css-var-prefix}form-element-disabled-opacity: 0.5; + #{$css-var-prefix}form-element-invalid-border-color: #{color.mix($red-500, $slate-600)}; + #{$css-var-prefix}form-element-invalid-active-border-color: #{color.mix($red-500, $slate-600, 75%)}; + #{$css-var-prefix}form-element-invalid-focus-color: var( + #{$css-var-prefix}form-element-invalid-active-border-color + ); + #{$css-var-prefix}form-element-valid-border-color: #{color.mix($jade-450, $slate-600)}; + #{$css-var-prefix}form-element-valid-active-border-color: #{color.mix($jade-450, $slate-600, 75%)}; + #{$css-var-prefix}form-element-valid-focus-color: var(#{$css-var-prefix}form-element-valid-active-border-color); + + // Focus for buttons, radio and select + input:is([type='submit'], [type='button'], [type='reset'], [type='checkbox'], [type='radio'], [type='file']) { + #{$css-var-prefix}form-element-focus-color: var(#{$css-var-prefix}primary-focus); + } + } + + // Range (input[type="range"]) + @if map.get($modules, 'forms/input-range') { + #{$css-var-prefix}range-border-color: #{$slate-850}; + #{$css-var-prefix}range-active-border-color: #{$slate-800}; + #{$css-var-prefix}range-thumb-border-color: var(#{$css-var-prefix}background-color); + #{$css-var-prefix}range-thumb-color: var(#{$css-var-prefix}primary-background); + #{$css-var-prefix}range-thumb-active-color: var(#{$css-var-prefix}primary-hover-background); + } + + // Form validation icons + @if map.get($modules, 'forms/basics') { + #{$css-var-prefix}icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{functions.display-rgb(color.mix($jade-450, $slate-600))}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); + #{$css-var-prefix}icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{functions.display-rgb(color.mix($red-500, $slate-600))}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E"); + } + + // Document + color-scheme: dark; +} diff --git a/webapp/src/styles/theme/_light.scss b/webapp/src/styles/theme/_light.scss new file mode 100644 index 0000000..6135305 --- /dev/null +++ b/webapp/src/styles/theme/_light.scss @@ -0,0 +1,144 @@ +@use 'sass:color'; +@use 'sass:map'; +@use '@picocss/pico/scss/settings' as *; +@use '@picocss/pico/scss/colors' as *; +@use '@picocss/pico/scss/helpers/functions'; + +$theme-color: #001f3f; + +// Default: Light theme +@mixin theme { + #{$css-var-prefix}background-color: #{$white}; + + // Text color + #{$css-var-prefix}color: #{$zinc-900}; + + // Text selection color + #{$css-var-prefix}text-selection-color: #{rgba($theme-color, 0.25)}; + + // Muted colors + #{$css-var-prefix}muted-color: #{$zinc-550}; + #{$css-var-prefix}muted-border-color: #{color.mix($slate-100, $slate-50)}; + + // Primary colors + #{$css-var-prefix}primary: #{$theme-color}; + #{$css-var-prefix}primary-background: var(#{$css-var-prefix}primary); + #{$css-var-prefix}primary-border: var(#{$css-var-prefix}primary-background); + #{$css-var-prefix}primary-underline: #{rgba($theme-color, 0.5)}; + #{$css-var-prefix}primary-hover: #{color.scale($theme-color, $lightness: -21%)}; + #{$css-var-prefix}primary-hover-background: #{color.scale($theme-color, $lightness: 21%)}; + #{$css-var-prefix}primary-hover-border: var(#{$css-var-prefix}primary-hover-background); + #{$css-var-prefix}primary-hover-underline: var(#{$css-var-prefix}primary-hover); + #{$css-var-prefix}primary-focus: #{rgba($theme-color, 0.375)}; + #{$css-var-prefix}primary-inverse: #{$black}; + + // Secondary colors + #{$css-var-prefix}secondary: #{$slate-550}; + #{$css-var-prefix}secondary-background: #{$slate-600}; + #{$css-var-prefix}secondary-border: var(#{$css-var-prefix}secondary-background); + #{$css-var-prefix}secondary-underline: #{rgba($slate-550, 0.5)}; + #{$css-var-prefix}secondary-hover: #{$slate-650}; + #{$css-var-prefix}secondary-hover-background: #{$slate-650}; + #{$css-var-prefix}secondary-hover-border: var(#{$css-var-prefix}secondary-hover-background); + #{$css-var-prefix}secondary-hover-underline: var(#{$css-var-prefix}secondary-hover); + #{$css-var-prefix}secondary-focus: #{rgba($slate-550, 0.25)}; + #{$css-var-prefix}secondary-inverse: #{$white}; + + // Contrast colors + #{$css-var-prefix}contrast: #{$slate-900}; + #{$css-var-prefix}contrast-background: #{$slate-900}; + #{$css-var-prefix}contrast-border: var(#{$css-var-prefix}contrast-background); + #{$css-var-prefix}contrast-underline: #{rgba($slate-900, 0.5)}; + #{$css-var-prefix}contrast-hover: #{$black}; + #{$css-var-prefix}contrast-hover-background: #{$black}; + #{$css-var-prefix}contrast-hover-border: var(#{$css-var-prefix}contrast-hover-background); + #{$css-var-prefix}contrast-hover-underline: var(#{$css-var-prefix}secondary-hover); + #{$css-var-prefix}contrast-focus: #{rgba($slate-550, 0.25)}; + #{$css-var-prefix}contrast-inverse: #{$white}; + + // Box shadow + #{$css-var-prefix}box-shadow: functions.shadow($slate-400); + + // Typography + @if map.get($modules, 'content/typography') { + // Headings colors + #{$css-var-prefix}h1-color: #{$zinc-800}; + #{$css-var-prefix}h2-color: #{$zinc-750}; + #{$css-var-prefix}h3-color: #{$zinc-700}; + #{$css-var-prefix}h4-color: #{$zinc-650}; + #{$css-var-prefix}h5-color: #{$zinc-600}; + #{$css-var-prefix}h6-color: #{$zinc-550}; + + // Highlighted text () + #{$css-var-prefix}mark-background-color: #{color.mix($amber-100, $amber-50)}; + #{$css-var-prefix}mark-color: #{$zinc-950}; + + // Inserted () & Deleted () + #{$css-var-prefix}ins-color: #{color.mix($jade-450, $zinc-750)}; + #{$css-var-prefix}del-color: #{color.mix($red-500, $zinc-750)}; + + // Blockquote + #{$css-var-prefix}blockquote-border-color: var(#{$css-var-prefix}muted-border-color); + #{$css-var-prefix}blockquote-footer-color: var(#{$css-var-prefix}muted-color); + } + + // Button + @if map.get($modules, 'content/button') { + // To disable box-shadow, remove the var or set to '0 0 0 rgba(0, 0, 0, 0)' + // Don't use, 'none, 'false, 'null', '0', etc. + #{$css-var-prefix}button-box-shadow: 0 0 0 rgba(0, 0, 0, 0); + #{$css-var-prefix}button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0); + } + + // Table + @if map.get($modules, 'content/table') { + #{$css-var-prefix}table-border-color: var(#{$css-var-prefix}muted-border-color); + #{$css-var-prefix}table-row-stripped-background-color: #{rgba($zinc-500, 0.0375)}; + } + + // Form elements + @if map.get($modules, 'forms/basics') { + #{$css-var-prefix}form-element-background-color: #{color.mix($slate-50, $white, 25%)}; + #{$css-var-prefix}form-element-selected-background-color: #{$slate-100}; + #{$css-var-prefix}form-element-border-color: #{$slate-150}; + #{$css-var-prefix}form-element-color: #{$zinc-850}; + #{$css-var-prefix}form-element-placeholder-color: var(#{$css-var-prefix}muted-color); + #{$css-var-prefix}form-element-active-background-color: #{$white}; + #{$css-var-prefix}form-element-active-border-color: var(#{$css-var-prefix}primary-border); + #{$css-var-prefix}form-element-focus-color: var(#{$css-var-prefix}primary-focus); + #{$css-var-prefix}form-element-disabled-background-color: var(#{$css-var-prefix}form-element-background-color); + #{$css-var-prefix}form-element-disabled-border-color: var(#{$css-var-prefix}form-element-border-color); + #{$css-var-prefix}form-element-disabled-opacity: 0.5; + #{$css-var-prefix}form-element-invalid-border-color: #{color.mix($red-500, $zinc-350)}; + #{$css-var-prefix}form-element-invalid-active-border-color: #{color.mix($red-500, $zinc-350, 75%)}; + #{$css-var-prefix}form-element-invalid-focus-color: var( + #{$css-var-prefix}form-element-invalid-active-border-color + ); + #{$css-var-prefix}form-element-valid-border-color: #{color.mix($jade-450, $zinc-350)}; + #{$css-var-prefix}form-element-valid-active-border-color: #{color.mix($jade-450, $zinc-350, 75%)}; + #{$css-var-prefix}form-element-valid-focus-color: var(#{$css-var-prefix}form-element-valid-active-border-color); + + // Focus for buttons, radio and select + input:is([type='submit'], [type='button'], [type='reset'], [type='checkbox'], [type='radio'], [type='file']) { + #{$css-var-prefix}form-element-focus-color: var(#{$css-var-prefix}primary-focus); + } + } + + // Range (input[type="range"]) + @if map.get($modules, 'forms/input-range') { + #{$css-var-prefix}range-border-color: #{$slate-100}; + #{$css-var-prefix}range-active-border-color: #{$slate-200}; + #{$css-var-prefix}range-thumb-border-color: var(#{$css-var-prefix}background-color); + #{$css-var-prefix}range-thumb-color: var(#{$css-var-prefix}primary-background); + #{$css-var-prefix}range-thumb-active-color: var(#{$css-var-prefix}primary-hover-background); + } + + // Form validation icons + @if map.get($modules, 'forms/basics') { + #{$css-var-prefix}icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{functions.display-rgb(color.mix($jade-450, $zinc-350))}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); + #{$css-var-prefix}icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{functions.display-rgb(color.mix($red-500, $zinc-350, 75%))}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E"); + } + + // Document + color-scheme: light; +} diff --git a/webapp/src/styles/theme/_schemes.scss b/webapp/src/styles/theme/_schemes.scss new file mode 100644 index 0000000..1ce5f5d --- /dev/null +++ b/webapp/src/styles/theme/_schemes.scss @@ -0,0 +1,37 @@ +@use 'sass:map'; +@use '@picocss/pico/scss/settings' as *; + +@use './light'; +@use './dark'; + +/** + * Color schemes + */ + +// Light color scheme (Default) +// Can be forced with data-theme="light" +[data-theme='light'], +:root:not([data-theme='dark']) { + @include light.theme; +} + +// Dark color scheme (Auto) +// Automatically enabled if user has Dark mode enabled +@media only screen and (prefers-color-scheme: dark) { + :root:not([data-theme]) { + @include dark.theme; + } +} + +// Dark color scheme (Forced) +// Enabled if forced with data-theme="dark" +[data-theme='dark'] { + @include dark.theme; +} + +progress, +[type='checkbox'], +[type='radio'], +[type='range'] { + accent-color: var(#{$css-var-prefix}primary); +} diff --git a/webapp/src/styles/theme/_styles.scss b/webapp/src/styles/theme/_styles.scss new file mode 100644 index 0000000..aaf9c99 --- /dev/null +++ b/webapp/src/styles/theme/_styles.scss @@ -0,0 +1,187 @@ +@use 'sass:map'; +@use '@picocss/pico/scss/settings' as *; +@use '@picocss/pico/scss/colors' as *; +@use '@picocss/pico/scss/helpers/functions'; + +/** + * Styles + */ + +:root { + // Typography + #{$css-var-prefix}font-family-emoji: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + #{$css-var-prefix}font-family-sans-serif: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, + var(#{$css-var-prefix}font-family-emoji); + #{$css-var-prefix}font-family-monospace: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', + monospace, var(#{$css-var-prefix}font-family-emoji); + #{$css-var-prefix}font-family: var(#{$css-var-prefix}font-family-sans-serif); + #{$css-var-prefix}line-height: 1.5; + #{$css-var-prefix}font-weight: 400; + #{$css-var-prefix}font-size: 100%; + #{$css-var-prefix}text-underline-offset: 0.1rem; + + // Borders + #{$css-var-prefix}border-radius: 0.375rem; + #{$css-var-prefix}border-width: 0.0625rem; + #{$css-var-prefix}outline-width: 0.125rem; + + // Transitions + #{$css-var-prefix}transition: 0.2s ease-in-out; + + // Spacings + #{$css-var-prefix}spacing: 1rem; + + // Spacings for typography elements + @if map.get($modules, 'content/typography') { + & { + #{$css-var-prefix}typography-spacing-vertical: 1rem; + } + } + + // Spacings for body > header, body > main, body > footer, section, article + @if map.get($modules, 'layout/landmarks') or + map.get($modules, 'layout/section') or + map.get($modules, 'components/card') or + map.get($modules, 'components/modal') + { + & { + #{$css-var-prefix}block-spacing-vertical: calc(var(#{$css-var-prefix}spacing) * 1.5); + #{$css-var-prefix}block-spacing-horizontal: calc(var(#{$css-var-prefix}spacing) * 1.5); + } + } + + // Spacings for form elements and button + @if map.get($modules, 'content/button') or map.get($modules, 'forms/basic') { + & { + #{$css-var-prefix}form-element-spacing-vertical: 0.75rem; + #{$css-var-prefix}form-element-spacing-horizontal: 1rem; + } + } + + // Font weight for form labels & fieldsets legend + @if map.get($modules, 'forms/basic') { + & { + #{$css-var-prefix}form-label-font-weight: var(#{$css-var-prefix}font-weight); + } + } + + // Checkboxes icons + @if map.get($modules, 'forms/checkbox-radio-switch') { + & { + #{$css-var-prefix}icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{functions.display-rgb($white)}' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); + #{$css-var-prefix}icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{functions.display-rgb($white)}' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E"); + } + } + + // Chevron icons + @if map.get($modules, 'forms/basics') or + map.get($modules, 'components/accordion') or + map.get($modules, 'components/dropdown') + { + & { + #{$css-var-prefix}icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{functions.display-rgb($zinc-400)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + } + } + + // Responsive root font size + @if $enable-responsive-typography { + @each $key, $values in $breakpoints { + @if $values { + @media (min-width: map.get($values, 'breakpoint')) { + #{$css-var-prefix}font-size: map.get($values, 'root-font-size'); + } + } + } + } +} + +// Link +@if map.get($modules, 'content/link') { + a { + #{$css-var-prefix}text-decoration: underline; + + // Secondary & Contrast + @if enable-classes { + &.secondary, + &.contrast { + #{$css-var-prefix}text-decoration: underline; + } + } + } +} + +// Typography +@if map.get($modules, 'content/typography') { + // Small + small { + #{$css-var-prefix}font-size: 0.875em; + } + + // Headings + h1, + h2, + h3, + h4, + h5, + h6 { + #{$css-var-prefix}font-weight: 700; + } + + h1 { + #{$css-var-prefix}font-size: 2rem; + #{$css-var-prefix}line-height: 1.125; + #{$css-var-prefix}typography-spacing-top: 3rem; + } + + h2 { + #{$css-var-prefix}font-size: 1.75rem; + #{$css-var-prefix}line-height: 1.15; + #{$css-var-prefix}typography-spacing-top: 2.625rem; + } + + h3 { + #{$css-var-prefix}font-size: 1.5rem; + #{$css-var-prefix}line-height: 1.175; + #{$css-var-prefix}typography-spacing-top: 2.25rem; + } + + h4 { + #{$css-var-prefix}font-size: 1.25rem; + #{$css-var-prefix}line-height: 1.2; + #{$css-var-prefix}typography-spacing-top: 1.874rem; + } + + h5 { + #{$css-var-prefix}font-size: 1.125rem; + #{$css-var-prefix}line-height: 1.225; + #{$css-var-prefix}typography-spacing-top: 1.6875rem; + } + + h6 { + #{$css-var-prefix}font-size: 1rem; + #{$css-var-prefix}line-height: 1.25; + #{$css-var-prefix}typography-spacing-top: 1.5rem; + } +} + +// Buttons +button, +[type='submit'], +[type='reset'], +[type='button'], +[type='file']::file-selector-button, +[role='button'] { + #{$css-var-prefix}font-weight: 700; +} + +// Table +@if map.get($modules, 'content/table') { + thead, + tfoot { + th, + td { + #{$css-var-prefix}font-weight: 600; + #{$css-var-prefix}border-width: 0.1875rem; + } + } +} diff --git a/webapp/src/styles/typography.scss b/webapp/src/styles/typography.scss deleted file mode 100644 index b415606..0000000 --- a/webapp/src/styles/typography.scss +++ /dev/null @@ -1,18 +0,0 @@ -h1, -h2, -h3 { - font-family: Avenir, Montserrat, Corbel, 'URW Gothic', source-sans-pro, sans-serif; - font-weight: 500; -} - -h1 { - font-size: 5.7rem; -} - -h2 { - font-size: 4.5rem; -} - -h3 { - font-size: 3.6rem; -}