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",
|
||||
"version": "1.38.0",
|
||||
"version": "1.38.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,84 +1,114 @@
|
||||
<script lang='ts'>
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
let { href, ariaLabel = undefined, children } = $props<{ href: string, ariaLabel?: string, children?: Snippet }>();
|
||||
import "$lib/css/action_button.css"
|
||||
let {
|
||||
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>
|
||||
|
||||
{#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>
|
||||
.container{
|
||||
position: fixed;
|
||||
bottom:0;
|
||||
right:0;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
padding: 2rem;
|
||||
border-radius: var(--radius-pill);
|
||||
margin: 2rem;
|
||||
transition: var(--transition-normal);
|
||||
background-color: var(--red);
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
z-index: 100;
|
||||
.container {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
padding: 2rem;
|
||||
border-radius: var(--radius-pill);
|
||||
margin: 2rem;
|
||||
transition: var(--transition-normal);
|
||||
background-color: var(--red);
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
z-index: 100;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
@media screen and (max-width: 500px) {
|
||||
.container{
|
||||
.container {
|
||||
margin: 1rem;
|
||||
}
|
||||
}
|
||||
:global(.icon_svg){
|
||||
width: 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){
|
||||
:global(.icon_svg) {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
fill: white;
|
||||
}
|
||||
|
||||
@keyframes shake{
|
||||
0%{
|
||||
transform: rotate(0)
|
||||
scale(1,1);
|
||||
}
|
||||
25%{
|
||||
box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6);
|
||||
transform: rotate(var(--angle))
|
||||
scale(1.2,1.2)
|
||||
;
|
||||
}
|
||||
50%{
|
||||
: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);
|
||||
animation: shake 0.5s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
.container:disabled {
|
||||
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);
|
||||
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);
|
||||
@keyframes shake {
|
||||
0% {
|
||||
transform: rotate(0) scale(1, 1);
|
||||
}
|
||||
100%{
|
||||
transform: rotate(0)
|
||||
scale(1.2,1.2);
|
||||
}
|
||||
}
|
||||
25% {
|
||||
box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6);
|
||||
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>
|
||||
<a class="container action_button" {href} aria-label={ariaLabel}>
|
||||
{@render children?.()}
|
||||
</a>
|
||||
|
||||
@@ -1,51 +1,10 @@
|
||||
<script>
|
||||
import Check from '$lib/assets/icons/Check.svelte';
|
||||
import ActionButton from './ActionButton.svelte';
|
||||
|
||||
let { disabled = false, onclick, label = 'Save', type = 'submit' } = $props();
|
||||
</script>
|
||||
|
||||
<button
|
||||
{type}
|
||||
class="fab-save"
|
||||
{onclick}
|
||||
{disabled}
|
||||
aria-label={label}
|
||||
>
|
||||
<ActionButton {type} {onclick} {disabled} ariaLabel={label}>
|
||||
<Check fill="white" width="2rem" height="2rem" />
|
||||
</button>
|
||||
|
||||
<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>
|
||||
</ActionButton>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
||||
import { toast } from '$lib/js/toast.svelte';
|
||||
import DatePicker from '$lib/components/DatePicker.svelte';
|
||||
import SaveFab from '$lib/components/SaveFab.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
@@ -460,9 +461,6 @@
|
||||
<button type="button" class="ghost" onclick={() => { idx = 0; direction = -1; }}>
|
||||
<ArrowLeft size={14} /> {t('edit_again', lang)}
|
||||
</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>
|
||||
</section>
|
||||
{/if}
|
||||
@@ -579,6 +577,10 @@
|
||||
<span class="bottom-spacer"></span>
|
||||
{/if}
|
||||
</footer>
|
||||
|
||||
{#if done}
|
||||
<SaveFab type="button" onclick={save} disabled={saving} label={saving ? t('saving', lang) : t('save_measurement', lang)} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
Reference in New Issue
Block a user