mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Adding components.
This commit is contained in:
parent
9c1e2e9be5
commit
85459d531e
6 changed files with 584 additions and 0 deletions
80
src/components/Checkbox.svelte
Normal file
80
src/components/Checkbox.svelte
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
<script lang="ts">
|
||||
export let labelText: string;
|
||||
export let showLabel: boolean;
|
||||
</script>
|
||||
|
||||
<label htmlFor={`${guest.id}`} className="checkbox">
|
||||
<span class="checkbox__input">
|
||||
<input
|
||||
id={`${guest.id}`}
|
||||
name={`${guest.id}`}
|
||||
checked={inputs[`${guest.id}`].plusOne}
|
||||
onChange={onChangePlusOne}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span class="checkbox__control">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="3"
|
||||
d="M1.73 12.91l6.37 6.37L22.79 4.59"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<span class="checkbox__label">Plus one? </span>
|
||||
</label>
|
||||
|
||||
<style lang="scss">
|
||||
.checkbox {
|
||||
display: grid;
|
||||
grid-template-columns: min-content auto;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 2rem;
|
||||
|
||||
.checkbox__input {
|
||||
display: grid;
|
||||
grid-template-areas: 'checkbox';
|
||||
|
||||
> * {
|
||||
grid-area: checkbox;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox__input input:checked + .checkbox__control svg {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.checkbox__input input:focus + .checkbox__control {
|
||||
box-shadow: var(--level-2-primary);
|
||||
}
|
||||
|
||||
.checkbox__control {
|
||||
display: inline-grid;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
border-radius: 0.1em;
|
||||
border: 0.1em solid var(--lightViolet);
|
||||
|
||||
svg {
|
||||
transition: transform 0.1s ease-in 25ms;
|
||||
transform: scale(0);
|
||||
transform-origin: bottom left;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
opacity: 0;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
112
src/components/CustomCheckbox.svelte
Normal file
112
src/components/CustomCheckbox.svelte
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
<script>
|
||||
/**
|
||||
* @event {boolean} check
|
||||
*/
|
||||
|
||||
/**
|
||||
* Specify the value of the checkbox
|
||||
* @type {any}
|
||||
*/
|
||||
export let value = '';
|
||||
|
||||
/** Specify whether the checkbox is checked */
|
||||
export let checked = false;
|
||||
|
||||
/**
|
||||
* Specify the bound group
|
||||
* @type {any[]}
|
||||
*/
|
||||
export let group = undefined;
|
||||
|
||||
/** Specify whether the checkbox is indeterminate */
|
||||
export let indeterminate = false;
|
||||
|
||||
/** Set to `true` to display the skeleton state */
|
||||
export let skeleton = false;
|
||||
|
||||
/** Set to `true` to mark the field as required */
|
||||
export let required = false;
|
||||
|
||||
/** Set to `true` for the checkbox to be read-only */
|
||||
export let readonly = false;
|
||||
|
||||
/** Set to `true` to disable the checkbox */
|
||||
export let disabled = false;
|
||||
|
||||
/** Specify the label text */
|
||||
export let labelText = '';
|
||||
|
||||
/** Set to `true` to visually hide the label text */
|
||||
export let hideLabel = false;
|
||||
|
||||
/** Set a name for the input element */
|
||||
export let name = '';
|
||||
|
||||
/**
|
||||
* Specify the title attribute for the label element
|
||||
* @type {string}
|
||||
*/
|
||||
export let title = undefined;
|
||||
|
||||
/** Set an id for the input label */
|
||||
export let id = 'ccs-' + Math.random().toString(36);
|
||||
|
||||
/** Obtain a reference to the input HTML element */
|
||||
export let ref = null;
|
||||
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import CheckboxSkeleton from './CheckboxSkeleton.svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
$: useGroup = Array.isArray(group);
|
||||
$: checked = useGroup ? group.includes(value) : checked;
|
||||
$: dispatch('check', checked);
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
{#if skeleton}
|
||||
<CheckboxSkeleton {...$$restProps} on:click on:mouseover on:mouseenter on:mouseleave />
|
||||
{:else}
|
||||
<div
|
||||
class:bx--form-item={true}
|
||||
class:bx--checkbox-wrapper={true}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:mouseover
|
||||
on:mouseenter
|
||||
on:mouseleave
|
||||
>
|
||||
<input
|
||||
bind:this={ref}
|
||||
type="checkbox"
|
||||
{value}
|
||||
{checked}
|
||||
{disabled}
|
||||
{id}
|
||||
{indeterminate}
|
||||
{name}
|
||||
{required}
|
||||
{readonly}
|
||||
class:bx--checkbox={true}
|
||||
on:change={() => {
|
||||
if (useGroup) {
|
||||
group = group.includes(value)
|
||||
? group.filter((_value) => _value !== value)
|
||||
: [...group, value];
|
||||
} else {
|
||||
checked = !checked;
|
||||
}
|
||||
}}
|
||||
on:change
|
||||
on:blur
|
||||
/>
|
||||
<label for={id} {title} class:bx--checkbox-label={true}>
|
||||
<span class:bx--checkbox-label-text={true} class:bx--visually-hidden={hideLabel}>
|
||||
<slot name="labelText">
|
||||
{labelText}
|
||||
</slot>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
48
src/components/game.svelte
Normal file
48
src/components/game.svelte
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<script lang="ts">
|
||||
import { fade, fly } from 'svelte/transition';
|
||||
import type { GameType } from '$lib/types';
|
||||
|
||||
export let game: GameType;
|
||||
</script>
|
||||
|
||||
<article class="game-container" transition:fade>
|
||||
<a class="thumbnail" href={game.url}>
|
||||
<img width="140" height="140" src={game.thumb_url} alt={`Image of ${game.name}`} />
|
||||
</a>
|
||||
|
||||
<div class="game-details">
|
||||
<div class="game">
|
||||
<div class="content">
|
||||
<h2>{game.name}</h2>
|
||||
<p>{game.year_published}</p>
|
||||
<p>{game.players} {game.max_players === 1 ? 'player' : 'players'}</p>
|
||||
<p>{game.playtime} minutes</p>
|
||||
<p>Minimum Age: {game.min_age}</p>
|
||||
<div class="description">{@html game.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<style>
|
||||
.thumbnail {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
img {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.game-container:hover {
|
||||
background-color: var(--primary);
|
||||
}
|
||||
|
||||
.game-container {
|
||||
display: grid;
|
||||
grid-template-columns: min-content 1fr;
|
||||
gap: var(--spacing-16);
|
||||
padding: var(--spacing-16) var(--spacing-16);
|
||||
transition: all 0.3s;
|
||||
border-radius: 8px;
|
||||
}
|
||||
</style>
|
||||
131
src/components/listbox.svelte
Normal file
131
src/components/listbox.svelte
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
<script lang="ts">
|
||||
import { fade } from 'svelte/transition';
|
||||
import {
|
||||
Listbox,
|
||||
ListboxButton,
|
||||
ListboxOptions,
|
||||
ListboxOption
|
||||
} from '@rgossiaux/svelte-headlessui';
|
||||
|
||||
const shows = [
|
||||
{ id: 1, name: 'Cowboy Bebop', completed: false },
|
||||
{ id: 2, name: 'Naruto', completed: false },
|
||||
{ id: 3, name: 'One Piece', completed: false },
|
||||
{ id: 4, name: 'Fullmetal Alchemist', completed: true },
|
||||
{ id: 5, name: 'One Punch Man', completed: true },
|
||||
{ id: 6, name: 'Death Note', completed: true }
|
||||
];
|
||||
|
||||
let selected = shows[0];
|
||||
</script>
|
||||
|
||||
<h4>Listbox</h4>
|
||||
|
||||
<div class="listbox">
|
||||
<Listbox value={selected} on:change={(event) => (selected = event.detail)} let:open>
|
||||
<ListboxButton class="button">
|
||||
<span>{selected.name}</span>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
class="arrows"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</ListboxButton>
|
||||
{#if open}
|
||||
<div transition:fade={{ duration: 200 }}>
|
||||
<ListboxOptions class="options">
|
||||
{#each shows as anime (anime.id)}
|
||||
<ListboxOption
|
||||
class="option"
|
||||
value={anime}
|
||||
disabled={anime.completed}
|
||||
let:active
|
||||
let:selected
|
||||
>
|
||||
<span class:active class:selected>{anime.name}</span>
|
||||
</ListboxOption>
|
||||
{/each}
|
||||
</ListboxOptions>
|
||||
</div>
|
||||
{/if}
|
||||
</Listbox>
|
||||
</div>
|
||||
|
||||
<!-- ... -->
|
||||
<style>
|
||||
.listbox {
|
||||
max-width: 280px;
|
||||
position: relative;
|
||||
font-weight: 500;
|
||||
color: hsl(220, 20%, 98%);
|
||||
}
|
||||
|
||||
.listbox :global(.button) {
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
|
||||
background-color: hsl(220, 20%, 2%);
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.listbox :global(.arrows) {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.listbox :global(.options) {
|
||||
position: absolute;
|
||||
top: 44px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
padding: 1rem;
|
||||
background-color: hsl(220, 20%, 4%);
|
||||
border-radius: 10px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.listbox :global(.option) {
|
||||
padding: 0.8rem 0.4rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.listbox :global(.option[aria-disabled='true']) {
|
||||
color: hsl(220, 20%, 30%);
|
||||
}
|
||||
|
||||
.listbox :global(.active) {
|
||||
color: hsl(220, 80%, 70%);
|
||||
}
|
||||
|
||||
.listbox :global(.active)::before {
|
||||
content: '👉️ ';
|
||||
}
|
||||
|
||||
.listbox :global(.selected) {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.listbox :global(.selected)::before {
|
||||
content: '⭐️ ';
|
||||
}
|
||||
</style>
|
||||
147
src/components/preferences/themes.svelte
Normal file
147
src/components/preferences/themes.svelte
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
<script lang="ts">
|
||||
import { fade } from 'svelte/transition';
|
||||
import { browser } from '$app/env';
|
||||
import {
|
||||
Listbox,
|
||||
ListboxButton,
|
||||
ListboxOption,
|
||||
ListboxOptions
|
||||
} from '@rgossiaux/svelte-headlessui';
|
||||
|
||||
const themes = {
|
||||
'🌛 Night': { name: '🌛 Night' },
|
||||
'☀️ Daylight': { name: '☀️ Daylight' },
|
||||
'😎 Synthwave': { name: '😎 Synthwave' }
|
||||
};
|
||||
|
||||
let selectedTheme = getTheme() ?? themes['🌛 Night'];
|
||||
|
||||
function getTheme() {
|
||||
if (!browser) return;
|
||||
|
||||
const htmlElement = document.documentElement;
|
||||
const userTheme = localStorage.theme;
|
||||
const prefersDarkMode = window.matchMedia('prefers-color-scheme: dark').matches;
|
||||
const prefersLightMode = window.matchMedia('prefers-color-scheme: light').matches;
|
||||
|
||||
// check if the user set a theme
|
||||
if (userTheme) {
|
||||
htmlElement.dataset.theme = userTheme;
|
||||
return themes[userTheme];
|
||||
}
|
||||
|
||||
// otherwise check for user preference
|
||||
if (!userTheme && prefersDarkMode) {
|
||||
htmlElement.dataset.theme = '🌛 Night';
|
||||
localStorage.theme = '🌛 Night';
|
||||
}
|
||||
if (!userTheme && prefersLightMode) {
|
||||
htmlElement.dataset.theme = '☀️ Daylight';
|
||||
localStorage.theme = '☀️ Daylight';
|
||||
}
|
||||
|
||||
// if nothing is set default to dark mode
|
||||
if (!userTheme && !prefersDarkMode && !prefersLightMode) {
|
||||
htmlElement.dataset.theme = '🌛 Night';
|
||||
localStorage.theme = '🌛 Night';
|
||||
}
|
||||
|
||||
return themes[userTheme];
|
||||
}
|
||||
|
||||
function handleChange(event: CustomEvent) {
|
||||
selectedTheme = themes[event.detail.name];
|
||||
|
||||
const htmlElement = document.documentElement;
|
||||
htmlElement.dataset.theme = selectedTheme.name;
|
||||
localStorage.theme = selectedTheme.name;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="theme">
|
||||
<span>Theme</span>
|
||||
|
||||
<div class="listbox">
|
||||
<Listbox value={selectedTheme} on:change={handleChange} let:open>
|
||||
<ListboxButton class="button">
|
||||
<span>{selectedTheme.name}</span>
|
||||
|
||||
<span>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
class="arrows"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</ListboxButton>
|
||||
|
||||
{#if open}
|
||||
<div transition:fade={{ duration: 100 }}>
|
||||
<ListboxOptions class="options" static>
|
||||
{#each Object.entries(themes) as [key, theme] (key)}
|
||||
<ListboxOption value={theme} let:active let:selected>
|
||||
<span class="option" class:active class:selected>
|
||||
{theme.name}
|
||||
</span>
|
||||
</ListboxOption>
|
||||
{/each}
|
||||
</ListboxOptions>
|
||||
</div>
|
||||
{/if}
|
||||
</Listbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.listbox {
|
||||
--width: 184px;
|
||||
}
|
||||
.listbox :global(.button) {
|
||||
width: var(--width);
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--spacing-16) var(--spacing-24);
|
||||
font-weight: 700;
|
||||
background-color: var(--clr-primary);
|
||||
color: var(--clr-theme-txt);
|
||||
border-radius: var(--rounded-20);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
.listbox :global(.arrows) {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: block;
|
||||
}
|
||||
.listbox :global(.options) {
|
||||
width: var(--width);
|
||||
position: absolute;
|
||||
margin-top: 0.4rem;
|
||||
font-weight: 700;
|
||||
color: var(--clr-theme-txt);
|
||||
background-color: var(--clr-primary);
|
||||
border-radius: var(--rounded-20);
|
||||
box-shadow: var(--shadow-sm);
|
||||
list-style: none;
|
||||
}
|
||||
.listbox :global(.option) {
|
||||
display: block;
|
||||
padding: var(--spacing-16) var(--spacing-24);
|
||||
border-radius: var(--rounded-20);
|
||||
cursor: pointer;
|
||||
}
|
||||
.listbox :global(.active) {
|
||||
background-color: var(--clr-theme-active);
|
||||
}
|
||||
</style>
|
||||
66
src/components/toggle.svelte
Normal file
66
src/components/toggle.svelte
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<script>
|
||||
import { Switch } from '@rgossiaux/svelte-headlessui';
|
||||
let enabled = false;
|
||||
</script>
|
||||
|
||||
<Switch
|
||||
checked={enabled}
|
||||
on:change={(e) => (enabled = e.detail)}
|
||||
class={enabled ? 'switch switch-enabled' : 'switch switch-disabled'}
|
||||
>
|
||||
<span class="sr-only">Dark Mode</span>
|
||||
<span class="toggle" class:toggle-on={enabled} class:toggle-off={!enabled} />
|
||||
</Switch>
|
||||
|
||||
<style>
|
||||
:global(.switch) {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: 1rem;
|
||||
border: 0;
|
||||
height: 1.25rem;
|
||||
width: 2.5rem;
|
||||
}
|
||||
|
||||
:global(.switch-enabled) {
|
||||
/* Blue */
|
||||
background-color: hsla(0, 0%, 0%, 1);
|
||||
}
|
||||
|
||||
:global(.switch-disabled) {
|
||||
/* Gray */
|
||||
background-color: hsla(0, 0%, 100%, 0.5);
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.toggle {
|
||||
display: inline-block;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
background-color: hsla(0, 0%, 100%, 1);
|
||||
border-radius: 1rem;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
transition-property: transform;
|
||||
}
|
||||
|
||||
.toggle-on {
|
||||
transform: translateX(1.4rem);
|
||||
}
|
||||
|
||||
.toggle-off {
|
||||
transform: translateX(0.1rem);
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in a new issue