refactor(ui): SaveFab shares ActionButton shell
ActionButton now renders as <a> (href) or <button> (onclick), so SaveFab wraps it to inherit the shake/hover/focus behavior already used by AddButton/EditButton. Body-parts review replaces its inline save button with SaveFab for consistency.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "homepage",
|
"name": "homepage",
|
||||||
"version": "1.38.0",
|
"version": "1.38.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,84 +1,114 @@
|
|||||||
<script lang='ts'>
|
<script lang='ts'>
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
let { href, ariaLabel = undefined, children } = $props<{ href: string, ariaLabel?: string, children?: Snippet }>();
|
let {
|
||||||
import "$lib/css/action_button.css"
|
href,
|
||||||
|
ariaLabel = undefined,
|
||||||
|
type = 'button',
|
||||||
|
onclick = undefined,
|
||||||
|
disabled = false,
|
||||||
|
children
|
||||||
|
} = $props<{
|
||||||
|
href?: string;
|
||||||
|
ariaLabel?: string;
|
||||||
|
type?: 'button' | 'submit' | 'reset';
|
||||||
|
onclick?: (e: MouseEvent) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
children?: Snippet;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
import "$lib/css/action_button.css";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#if href}
|
||||||
|
<a class="container action_button" {href} aria-label={ariaLabel}>
|
||||||
|
{@render children?.()}
|
||||||
|
</a>
|
||||||
|
{:else}
|
||||||
|
<button
|
||||||
|
class="container action_button"
|
||||||
|
{type}
|
||||||
|
{onclick}
|
||||||
|
{disabled}
|
||||||
|
aria-label={ariaLabel}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.container{
|
.container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom:0;
|
bottom: 0;
|
||||||
right:0;
|
right: 0;
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
border-radius: var(--radius-pill);
|
border-radius: var(--radius-pill);
|
||||||
margin: 2rem;
|
margin: 2rem;
|
||||||
transition: var(--transition-normal);
|
transition: var(--transition-normal);
|
||||||
background-color: var(--red);
|
background-color: var(--red);
|
||||||
display: grid;
|
display: grid;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@media screen and (max-width: 500px) {
|
@media screen and (max-width: 500px) {
|
||||||
.container{
|
.container {
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:global(.icon_svg){
|
:global(.icon_svg) {
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
fill: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root{
|
|
||||||
--angle: 15deg;
|
|
||||||
}
|
|
||||||
.container:hover,
|
|
||||||
.container:focus-within
|
|
||||||
{
|
|
||||||
background-color: var(--nord0);
|
|
||||||
box-shadow: 0em 0em 0.5em 0.5em rgba(0,0,0,0.2);
|
|
||||||
/*transform: scale(1.2,1.2);*/
|
|
||||||
animation: shake 0.5s;
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
}
|
|
||||||
:global(.container:hover .icon_svg),
|
|
||||||
:global(.container:focus-within .icon_svg){
|
|
||||||
fill: white;
|
fill: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes shake{
|
:root {
|
||||||
0%{
|
--angle: 15deg;
|
||||||
transform: rotate(0)
|
}
|
||||||
scale(1,1);
|
.container:hover,
|
||||||
}
|
.container:focus-within {
|
||||||
25%{
|
background-color: var(--nord0);
|
||||||
box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6);
|
box-shadow: 0em 0em 0.5em 0.5em rgba(0, 0, 0, 0.2);
|
||||||
transform: rotate(var(--angle))
|
animation: shake 0.5s;
|
||||||
scale(1.2,1.2)
|
animation-fill-mode: forwards;
|
||||||
;
|
}
|
||||||
}
|
.container:disabled {
|
||||||
50%{
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
.container:disabled:hover {
|
||||||
|
background-color: var(--red);
|
||||||
|
box-shadow: none;
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
:global(.container:hover .icon_svg),
|
||||||
|
:global(.container:focus-within .icon_svg) {
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6);
|
@keyframes shake {
|
||||||
transform: rotate(calc(-1* var(--angle)))
|
0% {
|
||||||
scale(1.2,1.2);
|
transform: rotate(0) scale(1, 1);
|
||||||
}
|
|
||||||
74%{
|
|
||||||
|
|
||||||
box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6);
|
|
||||||
transform: rotate(var(--angle))
|
|
||||||
scale(1.2, 1.2);
|
|
||||||
}
|
}
|
||||||
100%{
|
25% {
|
||||||
transform: rotate(0)
|
box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6);
|
||||||
scale(1.2,1.2);
|
transform: rotate(var(--angle)) scale(1.2, 1.2);
|
||||||
}
|
}
|
||||||
}
|
50% {
|
||||||
|
box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6);
|
||||||
|
transform: rotate(calc(-1 * var(--angle))) scale(1.2, 1.2);
|
||||||
|
}
|
||||||
|
74% {
|
||||||
|
box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6);
|
||||||
|
transform: rotate(var(--angle)) scale(1.2, 1.2);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(0) scale(1.2, 1.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<a class="container action_button" {href} aria-label={ariaLabel}>
|
|
||||||
{@render children?.()}
|
|
||||||
</a>
|
|
||||||
|
|||||||
@@ -1,51 +1,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import Check from '$lib/assets/icons/Check.svelte';
|
import Check from '$lib/assets/icons/Check.svelte';
|
||||||
|
import ActionButton from './ActionButton.svelte';
|
||||||
|
|
||||||
let { disabled = false, onclick, label = 'Save', type = 'submit' } = $props();
|
let { disabled = false, onclick, label = 'Save', type = 'submit' } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<ActionButton {type} {onclick} {disabled} ariaLabel={label}>
|
||||||
{type}
|
|
||||||
class="fab-save"
|
|
||||||
{onclick}
|
|
||||||
{disabled}
|
|
||||||
aria-label={label}
|
|
||||||
>
|
|
||||||
<Check fill="white" width="2rem" height="2rem" />
|
<Check fill="white" width="2rem" height="2rem" />
|
||||||
</button>
|
</ActionButton>
|
||||||
|
|
||||||
<style>
|
|
||||||
.fab-save {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
width: 1rem;
|
|
||||||
height: 1rem;
|
|
||||||
padding: 2rem;
|
|
||||||
margin: 2rem;
|
|
||||||
border: none;
|
|
||||||
border-radius: var(--radius-pill);
|
|
||||||
background-color: var(--red);
|
|
||||||
display: grid;
|
|
||||||
justify-content: center;
|
|
||||||
align-content: center;
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 100;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fab-save:hover, .fab-save:focus {
|
|
||||||
background-color: var(--nord11);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fab-save:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 500px) {
|
|
||||||
.fab-save {
|
|
||||||
margin: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
||||||
import { toast } from '$lib/js/toast.svelte';
|
import { toast } from '$lib/js/toast.svelte';
|
||||||
import DatePicker from '$lib/components/DatePicker.svelte';
|
import DatePicker from '$lib/components/DatePicker.svelte';
|
||||||
|
import SaveFab from '$lib/components/SaveFab.svelte';
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
@@ -460,9 +461,6 @@
|
|||||||
<button type="button" class="ghost" onclick={() => { idx = 0; direction = -1; }}>
|
<button type="button" class="ghost" onclick={() => { idx = 0; direction = -1; }}>
|
||||||
<ArrowLeft size={14} /> {t('edit_again', lang)}
|
<ArrowLeft size={14} /> {t('edit_again', lang)}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="nav-btn primary" onclick={save} disabled={saving}>
|
|
||||||
<Check size={16} /> {saving ? t('saving', lang) : t('save_measurement', lang)}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -579,6 +577,10 @@
|
|||||||
<span class="bottom-spacer"></span>
|
<span class="bottom-spacer"></span>
|
||||||
{/if}
|
{/if}
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
{#if done}
|
||||||
|
<SaveFab type="button" onclick={save} disabled={saving} label={saving ? t('saving', lang) : t('save_measurement', lang)} />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
Reference in New Issue
Block a user