too lazy to think of a message, so enjoy this pointless text. Good luck future me...
All checks were successful
Test action / Print hello world (push) Successful in 6m12s
All checks were successful
Test action / Print hello world (push) Successful in 6m12s
This commit is contained in:
parent
a502a50176
commit
ff31c28d38
46 changed files with 1403 additions and 1397 deletions
2
src/features/shell/index.tsx
Normal file → Executable file
2
src/features/shell/index.tsx
Normal file → Executable file
|
|
@ -1 +1 @@
|
|||
export { Shell } from './shell';
|
||||
export { Shell } from './shell';
|
||||
|
|
|
|||
336
src/features/shell/nav.module.css
Normal file → Executable file
336
src/features/shell/nav.module.css
Normal file → Executable file
|
|
@ -1,168 +1,168 @@
|
|||
.nav {
|
||||
grid-area: 2 / 1 / 3 / 2;
|
||||
display: block grid;
|
||||
grid-auto-flow: row;
|
||||
justify-content: space-between;
|
||||
inline-size: 5em;
|
||||
block-size: 100%;
|
||||
padding: 1em;
|
||||
background: inherit;
|
||||
z-index: 0;
|
||||
transition: z-index 0.3s step-end;
|
||||
|
||||
& > ul {
|
||||
position: relative;
|
||||
display: block grid;
|
||||
grid-template-columns: 2.5rem auto;
|
||||
align-content: center;
|
||||
inline-size: 4rem;
|
||||
gap: 1rem;
|
||||
transform-origin: left center;
|
||||
padding: 0;
|
||||
padding-inline-start: 0.5rem;
|
||||
margin: 0;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset-inline-start: 100%;
|
||||
inset-block: -1em;
|
||||
inline-size: 40vw;
|
||||
background-image: linear-gradient(to right, rgb(from var(--surface-1) r g b / .9) 50%, transparent);
|
||||
mask: radial-gradient(
|
||||
ellipse 40vw 100% at left center,
|
||||
black 25%,
|
||||
transparent
|
||||
);
|
||||
backdrop-filter: blur(5px);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s var(--ease-3);
|
||||
}
|
||||
|
||||
& > a {
|
||||
position: relative;
|
||||
grid-column: span 2;
|
||||
display: block grid;
|
||||
grid-template-columns: subgrid;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
transform-origin: center left;
|
||||
transition:
|
||||
transform 2s var(--ease-spring-5),
|
||||
opacity 0.3s var(--ease-3);
|
||||
color: var(--text-2);
|
||||
font-size: 2rem;
|
||||
line-height: 1.5;
|
||||
|
||||
& > span {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s var(--ease-3);
|
||||
text-shadow: 0 0 .5em var(--surface-1);
|
||||
}
|
||||
|
||||
& > svg {
|
||||
fill: var(--text-2);
|
||||
inline-size: 2.5rem;
|
||||
block-size: 2.5rem;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--yellow-5);
|
||||
list-style: disc;
|
||||
|
||||
&::before {
|
||||
content: "•";
|
||||
position: absolute;
|
||||
inset-inline-start: -1rem;
|
||||
}
|
||||
|
||||
& > svg {
|
||||
fill: var(--yellow-5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus)) {
|
||||
&::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
& > a {
|
||||
transform: scale(max(1, calc(1.5 - (0.2 * abs(var(--target) - var(--sibling-index))))));
|
||||
|
||||
& > span {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus)) > a:not(:is(:hover, :focus)) {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(1)) {
|
||||
--target: 1;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(2)) {
|
||||
--target: 2;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(3)) {
|
||||
--target: 3;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(4)) {
|
||||
--target: 4;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(5)) {
|
||||
--target: 5;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(6)) {
|
||||
--target: 6;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(7)) {
|
||||
--target: 7;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(8)) {
|
||||
--target: 8;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(9)) {
|
||||
--target: 9;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(10)) {
|
||||
--target: 10;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(11)) {
|
||||
--target: 11;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(12)) {
|
||||
--target: 12;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(13)) {
|
||||
--target: 13;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(14)) {
|
||||
--target: 14;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(15)) {
|
||||
--target: 15;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(a:hover, :focus-within) {
|
||||
z-index: 1;
|
||||
transition: z-index 0.3s step-start;
|
||||
}
|
||||
}
|
||||
.nav {
|
||||
grid-area: 2 / 1 / 3 / 2;
|
||||
display: block grid;
|
||||
grid-auto-flow: row;
|
||||
justify-content: space-between;
|
||||
inline-size: 5em;
|
||||
block-size: 100%;
|
||||
padding: 1em;
|
||||
background: inherit;
|
||||
z-index: 0;
|
||||
transition: z-index 0.3s step-end;
|
||||
|
||||
& > ul {
|
||||
position: relative;
|
||||
display: block grid;
|
||||
grid-template-columns: 2.5rem auto;
|
||||
align-content: center;
|
||||
inline-size: 4rem;
|
||||
gap: 1rem;
|
||||
transform-origin: left center;
|
||||
padding: 0;
|
||||
padding-inline-start: 0.5rem;
|
||||
margin: 0;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset-inline-start: 100%;
|
||||
inset-block: -1em;
|
||||
inline-size: 40vw;
|
||||
background-image: linear-gradient(to right, rgb(from var(--surface-1) r g b / .9) 50%, transparent);
|
||||
mask: radial-gradient(
|
||||
ellipse 40vw 100% at left center,
|
||||
black 25%,
|
||||
transparent
|
||||
);
|
||||
backdrop-filter: blur(5px);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s var(--ease-3);
|
||||
}
|
||||
|
||||
& > a {
|
||||
position: relative;
|
||||
grid-column: span 2;
|
||||
display: block grid;
|
||||
grid-template-columns: subgrid;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
transform-origin: center left;
|
||||
transition:
|
||||
transform 2s var(--ease-spring-5),
|
||||
opacity 0.3s var(--ease-3);
|
||||
color: var(--text-2);
|
||||
font-size: 2rem;
|
||||
line-height: 1.5;
|
||||
|
||||
& > span {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s var(--ease-3);
|
||||
text-shadow: 0 0 .5em var(--surface-1);
|
||||
}
|
||||
|
||||
& > svg {
|
||||
fill: var(--text-2);
|
||||
inline-size: 2.5rem;
|
||||
block-size: 2.5rem;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--yellow-5);
|
||||
list-style: disc;
|
||||
|
||||
&::before {
|
||||
content: "•";
|
||||
position: absolute;
|
||||
inset-inline-start: -1rem;
|
||||
}
|
||||
|
||||
& > svg {
|
||||
fill: var(--yellow-5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus)) {
|
||||
&::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
& > a {
|
||||
transform: scale(max(1, calc(1.5 - (0.2 * abs(var(--target) - var(--sibling-index))))));
|
||||
|
||||
& > span {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus)) > a:not(:is(:hover, :focus)) {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(1)) {
|
||||
--target: 1;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(2)) {
|
||||
--target: 2;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(3)) {
|
||||
--target: 3;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(4)) {
|
||||
--target: 4;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(5)) {
|
||||
--target: 5;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(6)) {
|
||||
--target: 6;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(7)) {
|
||||
--target: 7;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(8)) {
|
||||
--target: 8;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(9)) {
|
||||
--target: 9;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(10)) {
|
||||
--target: 10;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(11)) {
|
||||
--target: 11;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(12)) {
|
||||
--target: 12;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(13)) {
|
||||
--target: 13;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(14)) {
|
||||
--target: 14;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(15)) {
|
||||
--target: 15;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(a:hover, :focus-within) {
|
||||
z-index: 1;
|
||||
transition: z-index 0.3s step-start;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
50
src/features/shell/nav.tsx
Normal file → Executable file
50
src/features/shell/nav.tsx
Normal file → Executable file
|
|
@ -1,25 +1,25 @@
|
|||
import { A } from "@solidjs/router";
|
||||
import { AiOutlineHome, AiOutlineStar, AiOutlineSearch } from "solid-icons/ai";
|
||||
import { Component } from "solid-js";
|
||||
import css from "./nav.module.css";
|
||||
|
||||
export const Nav: Component = (props) => {
|
||||
return (
|
||||
<nav class={css.nav}>
|
||||
<ul>
|
||||
<A href="/" activeClass={css.active} end={true}>
|
||||
<AiOutlineHome />
|
||||
<span>Home</span>
|
||||
</A>
|
||||
<A href="/library" activeClass={css.active}>
|
||||
<AiOutlineStar />
|
||||
<span>Library</span>
|
||||
</A>
|
||||
<A href="/search" activeClass={css.active}>
|
||||
<AiOutlineSearch />
|
||||
<span>Search</span>
|
||||
</A>
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
import { A } from "@solidjs/router";
|
||||
import { AiOutlineHome, AiOutlineStar, AiOutlineSearch } from "solid-icons/ai";
|
||||
import { Component } from "solid-js";
|
||||
import css from "./nav.module.css";
|
||||
|
||||
export const Nav: Component = (props) => {
|
||||
return (
|
||||
<nav class={css.nav}>
|
||||
<ul>
|
||||
<A href="/" activeClass={css.active} end={true}>
|
||||
<AiOutlineHome />
|
||||
<span>Home</span>
|
||||
</A>
|
||||
<A href="/library" activeClass={css.active}>
|
||||
<AiOutlineStar />
|
||||
<span>Library</span>
|
||||
</A>
|
||||
<A href="/search" activeClass={css.active}>
|
||||
<AiOutlineSearch />
|
||||
<span>Search</span>
|
||||
</A>
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
102
src/features/shell/shell.module.css
Normal file → Executable file
102
src/features/shell/shell.module.css
Normal file → Executable file
|
|
@ -1,52 +1,52 @@
|
|||
.container {
|
||||
position: relative;
|
||||
display: block grid;
|
||||
grid: auto 1fr / 5em 1fr;
|
||||
grid-template-areas:
|
||||
"top top"
|
||||
"nav content";
|
||||
inline-size: 100%;
|
||||
block-size: 100%;
|
||||
z-index: 0;
|
||||
overflow: clip;
|
||||
container-type: inline-size;
|
||||
background-color: var(--surface-1);
|
||||
contain: layout style paint;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
grid-area: content;
|
||||
display: block;
|
||||
position: absolute;
|
||||
inset-inline-start: 0;
|
||||
inset-block-start: 0;
|
||||
inline-size: var(--radius-4);
|
||||
block-size: var(--radius-4);
|
||||
background: radial-gradient(circle at bottom right, transparent var(--radius-4), var(--surface-1) var(--radius-4));
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
grid-area: 2 / 1 / 3 / 3;
|
||||
inline-size: 100%;
|
||||
block-size: 100%;
|
||||
background: linear-gradient(180deg,
|
||||
transparent,
|
||||
transparent 90vh,
|
||||
var(--surface-500) 90vh,
|
||||
var(--surface-500));
|
||||
overflow: clip auto;
|
||||
padding-inline-start: 5em;
|
||||
transition: filter var(--duration-moderate-1) var(--ease-3);
|
||||
container-type: size;
|
||||
|
||||
& > div {
|
||||
background-color: var(--surface-2);
|
||||
container-type: inline-size;
|
||||
contain: layout style paint;
|
||||
inline-size: 100%;
|
||||
block-size: fit-content;
|
||||
min-block-size: 100%;
|
||||
}
|
||||
.container {
|
||||
position: relative;
|
||||
display: block grid;
|
||||
grid: auto 1fr / 5em 1fr;
|
||||
grid-template-areas:
|
||||
"top top"
|
||||
"nav content";
|
||||
inline-size: 100%;
|
||||
block-size: 100%;
|
||||
z-index: 0;
|
||||
overflow: clip;
|
||||
container-type: inline-size;
|
||||
background-color: var(--surface-1);
|
||||
contain: layout style paint;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
grid-area: content;
|
||||
display: block;
|
||||
position: absolute;
|
||||
inset-inline-start: 0;
|
||||
inset-block-start: 0;
|
||||
inline-size: var(--radius-4);
|
||||
block-size: var(--radius-4);
|
||||
background: radial-gradient(circle at bottom right, transparent var(--radius-4), var(--surface-1) var(--radius-4));
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
grid-area: 2 / 1 / 3 / 3;
|
||||
inline-size: 100%;
|
||||
block-size: 100%;
|
||||
background: linear-gradient(180deg,
|
||||
transparent,
|
||||
transparent 90vh,
|
||||
var(--surface-500) 90vh,
|
||||
var(--surface-500));
|
||||
overflow: clip auto;
|
||||
padding-inline-start: 5em;
|
||||
transition: filter var(--duration-moderate-1) var(--ease-3);
|
||||
container-type: size;
|
||||
|
||||
& > div {
|
||||
background-color: var(--surface-2);
|
||||
container-type: inline-size;
|
||||
contain: layout style paint;
|
||||
inline-size: 100%;
|
||||
block-size: fit-content;
|
||||
min-block-size: 100%;
|
||||
}
|
||||
}
|
||||
44
src/features/shell/shell.tsx
Normal file → Executable file
44
src/features/shell/shell.tsx
Normal file → Executable file
|
|
@ -1,22 +1,22 @@
|
|||
import { ParentComponent } from "solid-js";
|
||||
import { Top } from "./top";
|
||||
import { Nav } from "./nav";
|
||||
import css from "./shell.module.css";
|
||||
import { User } from "../user";
|
||||
|
||||
interface ShellProps {
|
||||
user: User | undefined;
|
||||
}
|
||||
|
||||
export const Shell: ParentComponent<ShellProps> = (props) => {
|
||||
return (
|
||||
<main class={css.container}>
|
||||
<Top user={props.user} />
|
||||
<Nav />
|
||||
|
||||
<div class={css.body}>
|
||||
<div>{props.children}</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
import { ParentComponent } from "solid-js";
|
||||
import { Top } from "./top";
|
||||
import { Nav } from "./nav";
|
||||
import css from "./shell.module.css";
|
||||
import { User } from "../user";
|
||||
|
||||
interface ShellProps {
|
||||
user: User | undefined;
|
||||
}
|
||||
|
||||
export const Shell: ParentComponent<ShellProps> = (props) => {
|
||||
return (
|
||||
<main class={css.container}>
|
||||
<Top user={props.user} />
|
||||
<Nav />
|
||||
|
||||
<div class={css.body}>
|
||||
<div>{props.children}</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
74
src/features/shell/top.module.css
Normal file → Executable file
74
src/features/shell/top.module.css
Normal file → Executable file
|
|
@ -1,37 +1,37 @@
|
|||
.top {
|
||||
grid-area: 1 / 1 / 2 / 3;
|
||||
display: block grid;
|
||||
grid-auto-flow: column;
|
||||
justify-content: end;
|
||||
z-index: 1;
|
||||
background-color: inherit;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.accountTrigger {
|
||||
anchor-name: --account-trigger;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-radius: var(--radius-round);
|
||||
}
|
||||
|
||||
.accountMenu {
|
||||
position-anchor: --account-trigger;
|
||||
position: absolute;
|
||||
inset: auto;
|
||||
inset-inline-end: anchor(end);
|
||||
inset-block-start: anchor(start);
|
||||
|
||||
display: block grid;
|
||||
grid-auto-flow: row;
|
||||
gap: var(--size-3);
|
||||
padding: var(--size-3);
|
||||
background-color: light-dark(var(--gray-1), var(--gray-9));
|
||||
border-radius: var(--radius-2);
|
||||
box-shadow: var(--shadow-2);
|
||||
|
||||
&:not(:popover-open) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.top {
|
||||
grid-area: 1 / 1 / 2 / 3;
|
||||
display: block grid;
|
||||
grid-auto-flow: column;
|
||||
justify-content: end;
|
||||
z-index: 1;
|
||||
background-color: inherit;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.accountTrigger {
|
||||
anchor-name: --account-trigger;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-radius: var(--radius-round);
|
||||
}
|
||||
|
||||
.accountMenu {
|
||||
position-anchor: --account-trigger;
|
||||
position: absolute;
|
||||
inset: auto;
|
||||
inset-inline-end: anchor(end);
|
||||
inset-block-start: anchor(start);
|
||||
|
||||
display: block grid;
|
||||
grid-auto-flow: row;
|
||||
gap: var(--size-3);
|
||||
padding: var(--size-3);
|
||||
background-color: light-dark(var(--gray-1), var(--gray-9));
|
||||
border-radius: var(--radius-2);
|
||||
box-shadow: var(--shadow-2);
|
||||
|
||||
&:not(:popover-open) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
118
src/features/shell/top.tsx
Normal file → Executable file
118
src/features/shell/top.tsx
Normal file → Executable file
|
|
@ -1,59 +1,59 @@
|
|||
import { Component, Show } from "solid-js";
|
||||
import { signIn, signOut, client } from "~/auth.client";
|
||||
import { Avatar, Profile, User } from "../user";
|
||||
import { ColorSchemePicker } from "../theme";
|
||||
import css from "./top.module.css";
|
||||
|
||||
interface TopProps {
|
||||
user: User | undefined;
|
||||
}
|
||||
|
||||
export const Top: Component<TopProps> = (props) => {
|
||||
const login = async (e: SubmitEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
await signIn.oauth2({
|
||||
providerId: "authelia",
|
||||
callbackURL: "/",
|
||||
});
|
||||
};
|
||||
|
||||
const logout = async (e: SubmitEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
await signOut();
|
||||
};
|
||||
|
||||
return (
|
||||
<aside class={css.top}>
|
||||
<Show
|
||||
when={props.user}
|
||||
fallback={
|
||||
<form method="post" onSubmit={login}>
|
||||
<button type="submit">Sign in</button>
|
||||
</form>
|
||||
}
|
||||
>
|
||||
{(user) => (
|
||||
<>
|
||||
<button
|
||||
class={css.accountTrigger}
|
||||
id="account-menu-trigger"
|
||||
popovertarget="account-menu-popover"
|
||||
>
|
||||
<Avatar user={user()} />
|
||||
</button>
|
||||
<div class={css.accountMenu} id="account-menu-popover" popover>
|
||||
<Profile user={user()} />
|
||||
<a href="/settings">Settings</a>
|
||||
<form method="post" onSubmit={logout}>
|
||||
<button type="submit">Log out</button>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Show>
|
||||
<ColorSchemePicker />
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
import { Component, Show } from "solid-js";
|
||||
import { signIn, signOut, client } from "~/auth.client";
|
||||
import { Avatar, Profile, User } from "../user";
|
||||
import { ColorSchemePicker } from "../theme";
|
||||
import css from "./top.module.css";
|
||||
|
||||
interface TopProps {
|
||||
user: User | undefined;
|
||||
}
|
||||
|
||||
export const Top: Component<TopProps> = (props) => {
|
||||
const login = async (e: SubmitEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
await signIn.oauth2({
|
||||
providerId: "authelia",
|
||||
callbackURL: "/",
|
||||
});
|
||||
};
|
||||
|
||||
const logout = async (e: SubmitEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
await signOut();
|
||||
};
|
||||
|
||||
return (
|
||||
<aside class={css.top}>
|
||||
<Show
|
||||
when={props.user}
|
||||
fallback={
|
||||
<form method="post" onSubmit={login}>
|
||||
<button type="submit">Sign in</button>
|
||||
</form>
|
||||
}
|
||||
>
|
||||
{(user) => (
|
||||
<>
|
||||
<button
|
||||
class={css.accountTrigger}
|
||||
id="account-menu-trigger"
|
||||
popovertarget="account-menu-popover"
|
||||
>
|
||||
<Avatar user={user()} />
|
||||
</button>
|
||||
<div class={css.accountMenu} id="account-menu-popover" popover>
|
||||
<Profile user={user()} />
|
||||
<a href="/settings">Settings</a>
|
||||
<form method="post" onSubmit={logout}>
|
||||
<button type="submit">Log out</button>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Show>
|
||||
<ColorSchemePicker />
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
0
src/features/theme/context.ts
Normal file → Executable file
0
src/features/theme/context.ts
Normal file → Executable file
0
src/features/theme/index.ts
Normal file → Executable file
0
src/features/theme/index.ts
Normal file → Executable file
0
src/features/theme/picker.module.css
Normal file → Executable file
0
src/features/theme/picker.module.css
Normal file → Executable file
0
src/features/theme/picker.tsx
Normal file → Executable file
0
src/features/theme/picker.tsx
Normal file → Executable file
14
src/features/user/avatar.module.css
Normal file → Executable file
14
src/features/user/avatar.module.css
Normal file → Executable file
|
|
@ -1,7 +1,7 @@
|
|||
.avatar {
|
||||
inline-size: var(--size-8);
|
||||
border-radius: var(--radius-round);
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
.avatar {
|
||||
inline-size: var(--size-8);
|
||||
border-radius: var(--radius-round);
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
|
|
|
|||
54
src/features/user/avatar.tsx
Normal file → Executable file
54
src/features/user/avatar.tsx
Normal file → Executable file
|
|
@ -1,27 +1,27 @@
|
|||
import { Component, createMemo, Show } from "solid-js";
|
||||
import { User } from "./user";
|
||||
import { hash } from "~/utilities";
|
||||
import css from "./avatar.module.css";
|
||||
|
||||
interface AvatarProps {
|
||||
user: User | undefined;
|
||||
}
|
||||
|
||||
export const Avatar: Component<AvatarProps> = (props) => {
|
||||
const hashedEmail = hash("SHA-256", () => props.user?.email);
|
||||
const src = createMemo(() => {
|
||||
const user = props.user;
|
||||
|
||||
if (user === undefined) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (user.image === null) {
|
||||
return `https://www.gravatar.com/avatar/${hashedEmail()}`;
|
||||
}
|
||||
|
||||
return user.image;
|
||||
});
|
||||
|
||||
return <img src={src()} class={css.avatar} />;
|
||||
};
|
||||
import { Component, createMemo, Show } from "solid-js";
|
||||
import { User } from "./user";
|
||||
import { hash } from "~/utilities";
|
||||
import css from "./avatar.module.css";
|
||||
|
||||
interface AvatarProps {
|
||||
user: User | undefined;
|
||||
}
|
||||
|
||||
export const Avatar: Component<AvatarProps> = (props) => {
|
||||
const hashedEmail = hash("SHA-256", () => props.user?.email);
|
||||
const src = createMemo(() => {
|
||||
const user = props.user;
|
||||
|
||||
if (user === undefined) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (user.image === null) {
|
||||
return `https://www.gravatar.com/avatar/${hashedEmail()}`;
|
||||
}
|
||||
|
||||
return user.image;
|
||||
});
|
||||
|
||||
return <img src={src()} class={css.avatar} />;
|
||||
};
|
||||
|
|
|
|||
8
src/features/user/index.ts
Normal file → Executable file
8
src/features/user/index.ts
Normal file → Executable file
|
|
@ -1,4 +1,4 @@
|
|||
export type { User } from "./user";
|
||||
|
||||
export { Avatar } from "./avatar";
|
||||
export { Profile } from "./profile";
|
||||
export type { User } from "./user";
|
||||
|
||||
export { Avatar } from "./avatar";
|
||||
export { Profile } from "./profile";
|
||||
|
|
|
|||
44
src/features/user/profile.module.css
Normal file → Executable file
44
src/features/user/profile.module.css
Normal file → Executable file
|
|
@ -1,22 +1,22 @@
|
|||
.profile {
|
||||
display: block grid;
|
||||
grid: auto 1fr / auto 1fr;
|
||||
gap: var(--size-2);
|
||||
place-content: start;
|
||||
background-color: light-dark(var(--gray-1), var(--gray-9));
|
||||
|
||||
& > img {
|
||||
grid-area: span 2 / 1;
|
||||
}
|
||||
|
||||
& > strong {
|
||||
font-size: var(--size-4);
|
||||
line-height: 1;
|
||||
color: light-dark(var(--gray-7), var(--gray-3));
|
||||
}
|
||||
|
||||
& > span {
|
||||
line-height: 1;
|
||||
color: light-dark(var(--gray-4), var(--gray-6));
|
||||
}
|
||||
}
|
||||
.profile {
|
||||
display: block grid;
|
||||
grid: auto 1fr / auto 1fr;
|
||||
gap: var(--size-2);
|
||||
place-content: start;
|
||||
background-color: light-dark(var(--gray-1), var(--gray-9));
|
||||
|
||||
& > img {
|
||||
grid-area: span 2 / 1;
|
||||
}
|
||||
|
||||
& > strong {
|
||||
font-size: var(--size-4);
|
||||
line-height: 1;
|
||||
color: light-dark(var(--gray-7), var(--gray-3));
|
||||
}
|
||||
|
||||
& > span {
|
||||
line-height: 1;
|
||||
color: light-dark(var(--gray-4), var(--gray-6));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
36
src/features/user/profile.tsx
Normal file → Executable file
36
src/features/user/profile.tsx
Normal file → Executable file
|
|
@ -1,18 +1,18 @@
|
|||
import { Component } from "solid-js";
|
||||
import { User } from "./user";
|
||||
import { Avatar } from "./avatar";
|
||||
import css from "./profile.module.css";
|
||||
|
||||
interface ProfileProps {
|
||||
user: User | undefined;
|
||||
}
|
||||
|
||||
export const Profile: Component<ProfileProps> = (props) => {
|
||||
return (
|
||||
<div class={css.profile}>
|
||||
<Avatar user={props.user} />
|
||||
<strong>{props.user?.name ?? ""}</strong>
|
||||
<span>{props.user?.email ?? ""}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
import { Component } from "solid-js";
|
||||
import { User } from "./user";
|
||||
import { Avatar } from "./avatar";
|
||||
import css from "./profile.module.css";
|
||||
|
||||
interface ProfileProps {
|
||||
user: User | undefined;
|
||||
}
|
||||
|
||||
export const Profile: Component<ProfileProps> = (props) => {
|
||||
return (
|
||||
<div class={css.profile}>
|
||||
<Avatar user={props.user} />
|
||||
<strong>{props.user?.name ?? ""}</strong>
|
||||
<span>{props.user?.email ?? ""}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
12
src/features/user/user.ts
Normal file → Executable file
12
src/features/user/user.ts
Normal file → Executable file
|
|
@ -1,6 +1,6 @@
|
|||
export interface User {
|
||||
username: string;
|
||||
name: string;
|
||||
email: string;
|
||||
image: string | null;
|
||||
}
|
||||
export interface User {
|
||||
username: string;
|
||||
name: string;
|
||||
email: string;
|
||||
image: string | null;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue