mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-28 17:01:18 +00:00
ENH - modernize Clock controls with Sky UI
This commit is contained in:
+261
-50
@@ -4632,17 +4632,49 @@ button {
|
||||
color: #a0a0a7;
|
||||
}
|
||||
.clock-content {
|
||||
--sky-block-gutter-left: 0px;
|
||||
--sky-block-gutter-right: 0px;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 0 0
|
||||
calc(var(--sky-tabbar-height) + var(--sky-safe-area-bottom) + 16px);
|
||||
}
|
||||
.clock-navbar.sky-navbar--no-navigation {
|
||||
min-height: calc(
|
||||
var(--sky-navbar-safe-area-top) + var(--sky-navbar-large-title-height)
|
||||
);
|
||||
padding-top: var(--sky-navbar-safe-area-top);
|
||||
.clock-navbar.sky-navbar {
|
||||
--sky-navbar-safe-area-top: var(--sky-space-2);
|
||||
--sky-page-gutter: 12px;
|
||||
}
|
||||
.clock-navbar--alarm .sky-navbar__inner {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.clock-navbar--alarm .sky-navbar__left.sky-glass-surface,
|
||||
.clock-navbar--alarm .sky-navbar__right.sky-glass-surface {
|
||||
overflow: visible;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
backdrop-filter: none;
|
||||
}
|
||||
.clock-navbar-action.sky-link {
|
||||
min-height: var(--sky-touch-target);
|
||||
padding: 0 14px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--sky-radius-pill);
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
}
|
||||
.clock-navbar-action--icon.sky-link {
|
||||
width: var(--sky-touch-target);
|
||||
padding: 0;
|
||||
}
|
||||
.clock-navbar-action--icon svg {
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
}
|
||||
.phone-call-content {
|
||||
min-height: 0;
|
||||
@@ -4663,16 +4695,17 @@ button {
|
||||
}
|
||||
.clock-world-now {
|
||||
margin: 0;
|
||||
padding: 76px 16px 0;
|
||||
padding: 84px 16px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
.clock-world-location {
|
||||
color: #8e8e93;
|
||||
color: var(--sky-muted);
|
||||
font-size: 19px;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
}
|
||||
.clock-world-time {
|
||||
margin-top: 8px;
|
||||
@@ -4680,63 +4713,169 @@ button {
|
||||
font-size: 74px;
|
||||
font-weight: 200;
|
||||
letter-spacing: -4px;
|
||||
line-height: 1;
|
||||
}
|
||||
.clock-world-zone {
|
||||
margin-top: 2px;
|
||||
color: #8e8e93;
|
||||
margin-top: 4px;
|
||||
color: var(--sky-muted);
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.clock-alarm-list {
|
||||
margin-top: 8px;
|
||||
margin: 4px 12px 0;
|
||||
}
|
||||
.clock-alarm-item {
|
||||
min-height: 86px;
|
||||
min-height: 104px;
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
border-bottom: 1px solid var(--sky-hairline);
|
||||
}
|
||||
.clock-alarm-remove {
|
||||
.clock-alarm-copy {
|
||||
min-width: 0;
|
||||
min-height: 104px;
|
||||
grid-column: 1 / 3;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding: 10px 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--sky-text);
|
||||
text-align: left;
|
||||
}
|
||||
.clock-alarm-remove + .clock-alarm-copy {
|
||||
grid-column: 2;
|
||||
}
|
||||
.clock-alarm-copy:focus-visible {
|
||||
border-radius: 10px;
|
||||
outline: 2px solid var(--sky-app-accent);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
.clock-alarm-time {
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 50px;
|
||||
font-weight: 200;
|
||||
letter-spacing: -2px;
|
||||
line-height: 54px;
|
||||
}
|
||||
.clock-alarm-subtitle {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
line-height: 19px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.clock-alarm-item--disabled .clock-alarm-copy {
|
||||
color: var(--sky-muted);
|
||||
}
|
||||
.clock-alarm-chevron {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
grid-column: 3;
|
||||
justify-self: end;
|
||||
color: var(--sky-subtle);
|
||||
}
|
||||
.clock-alarm-item > .sky-toggle {
|
||||
grid-column: 3;
|
||||
justify-self: end;
|
||||
}
|
||||
.clock-alarm-remove.sky-button--icon-only {
|
||||
width: 28px;
|
||||
min-width: 28px;
|
||||
height: 28px;
|
||||
min-height: 28px;
|
||||
flex: none;
|
||||
padding: 0;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.clock-alarm-remove svg {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
stroke-width: 3;
|
||||
}
|
||||
.clock-alarm-editor {
|
||||
.clock-alarm-sheet {
|
||||
--sky-overlay-layer: 90;
|
||||
--sky-sheet-background: #1c1c1d;
|
||||
}
|
||||
.clock-alarm-sheet .sky-sheet__panel {
|
||||
height: calc(100% - var(--sky-safe-area-top) - var(--sky-space-2));
|
||||
max-height: calc(100% - var(--sky-safe-area-top) - var(--sky-space-2));
|
||||
overflow: hidden;
|
||||
border-radius: var(--sky-radius-sheet) var(--sky-radius-sheet) 0 0;
|
||||
background: var(--sky-sheet-background);
|
||||
}
|
||||
.clock-alarm-sheet .sky-sheet__grabber {
|
||||
display: none;
|
||||
}
|
||||
.clock-alarm-sheet-surface {
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: var(--sky-radius-sheet) var(--sky-radius-sheet) 0 0;
|
||||
background: var(--sky-sheet-background);
|
||||
color: var(--sky-text);
|
||||
}
|
||||
.clock-alarm-sheet-navbar.sky-navbar,
|
||||
.clock-alarm-sheet-surface > .sky-navbar {
|
||||
--sky-navbar-safe-area-top: var(--sky-space-3);
|
||||
--sky-page-gutter: 12px;
|
||||
--sky-navbar-glass: transparent;
|
||||
flex: none;
|
||||
background: transparent;
|
||||
}
|
||||
.clock-alarm-sheet-navbar .sky-navbar__blur,
|
||||
.clock-alarm-sheet-navbar .sky-navbar__background {
|
||||
background: transparent;
|
||||
}
|
||||
.clock-alarm-editor.sky-scroll-area--padded {
|
||||
--sky-page-gutter: 12px;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
padding: 8px 0 32px;
|
||||
padding-top: 8px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.clock-alarm-editor .time-wheel-picker {
|
||||
width: 100%;
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
.clock-alarm-editor .sky-list--strong,
|
||||
.clock-alarm-editor .sky-list--inset {
|
||||
background: #2c2c2e;
|
||||
}
|
||||
.time-wheel-picker {
|
||||
position: relative;
|
||||
width: calc(100% - 32px);
|
||||
height: 200px;
|
||||
margin: 0 16px 16px;
|
||||
height: 240px;
|
||||
margin: 8px 16px 12px;
|
||||
display: grid;
|
||||
grid-template-columns: 64px 64px;
|
||||
grid-template-columns: repeat(2, minmax(0, 88px));
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
gap: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.time-wheel-picker--duration {
|
||||
grid-template-columns: repeat(3, 64px);
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 0;
|
||||
}
|
||||
.time-wheel-picker__field {
|
||||
position: relative;
|
||||
width: 64px;
|
||||
height: 200px;
|
||||
width: 100%;
|
||||
height: 240px;
|
||||
}
|
||||
.time-wheel-picker--duration .time-wheel-picker__field {
|
||||
width: 64px;
|
||||
width: 100%;
|
||||
}
|
||||
.time-wheel-picker__selection {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
top: 80px;
|
||||
top: 100px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 40px;
|
||||
@@ -4747,8 +4886,8 @@ button {
|
||||
.time-wheel-picker__column {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
height: 200px;
|
||||
padding: 80px 0;
|
||||
height: 240px;
|
||||
padding: 100px 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
@@ -4766,16 +4905,17 @@ button {
|
||||
);
|
||||
}
|
||||
.time-wheel-picker--unit-labels .time-wheel-picker__column {
|
||||
width: 36px;
|
||||
width: 54%;
|
||||
}
|
||||
.time-wheel-picker__unit {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 80px;
|
||||
left: 34px;
|
||||
top: 100px;
|
||||
left: 53%;
|
||||
height: 40px;
|
||||
color: #8e8e93;
|
||||
font-size: 9px;
|
||||
color: var(--sky-text);
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
line-height: 40px;
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -4795,7 +4935,7 @@ button {
|
||||
scroll-snap-align: center;
|
||||
background: transparent;
|
||||
color: #8e8e93;
|
||||
font-size: 27px;
|
||||
font-size: 28px;
|
||||
font-weight: 300;
|
||||
line-height: 40px;
|
||||
opacity: 0.62;
|
||||
@@ -4810,25 +4950,40 @@ button {
|
||||
}
|
||||
.time-wheel-picker__value--selected {
|
||||
color: #fff;
|
||||
font-size: 32px;
|
||||
opacity: 1;
|
||||
}
|
||||
.clock-alarm-delete {
|
||||
width: calc(100% - 32px);
|
||||
margin: 18px 16px 0;
|
||||
width: 100%;
|
||||
margin: 18px 0 0;
|
||||
}
|
||||
.clock-alarm-save.sky-link {
|
||||
color: #ff9f0a;
|
||||
background: #ff9f0a;
|
||||
color: #fff;
|
||||
}
|
||||
.clock-tool {
|
||||
margin: 0;
|
||||
padding: 0 16px;
|
||||
text-align: center;
|
||||
}
|
||||
.clock-stopwatch {
|
||||
--sky-block-gutter-left: calc(
|
||||
var(--sky-page-gutter) + var(--sky-safe-area-left)
|
||||
);
|
||||
--sky-block-gutter-right: calc(
|
||||
var(--sky-page-gutter) + var(--sky-safe-area-right)
|
||||
);
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.clock-digits {
|
||||
margin: 70px 0 54px;
|
||||
margin: 118px 0 96px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 52px;
|
||||
font-size: 68px;
|
||||
font-weight: 200;
|
||||
letter-spacing: -4px;
|
||||
line-height: 1;
|
||||
}
|
||||
.clock-sound-menu {
|
||||
min-height: 0;
|
||||
@@ -4850,18 +5005,52 @@ button {
|
||||
height: 17px;
|
||||
}
|
||||
.clock-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 12px 0;
|
||||
display: grid;
|
||||
grid-template-columns: 82px minmax(0, 1fr) 82px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
.clock-action-button {
|
||||
width: 66px;
|
||||
height: 66px;
|
||||
min-width: 66px;
|
||||
.clock-action-button.sky-button {
|
||||
width: 82px;
|
||||
height: 82px;
|
||||
min-width: 82px;
|
||||
padding: 0;
|
||||
border-radius: 50%;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.clock-action-button.clock-action-button--secondary.sky-button--secondary {
|
||||
background: #1c1c1e;
|
||||
color: #8e8e93;
|
||||
}
|
||||
.clock-action-button.clock-action-button--start.sky-button--secondary {
|
||||
grid-column: 3;
|
||||
background: #032d13;
|
||||
color: #30d158;
|
||||
}
|
||||
.clock-action-button.clock-action-button--stop.sky-button--secondary {
|
||||
grid-column: 3;
|
||||
background: #330c0a;
|
||||
color: #ff453a;
|
||||
}
|
||||
.clock-action-button.sky-button:disabled {
|
||||
opacity: 0.42;
|
||||
}
|
||||
.clock-action-button--icon svg {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
stroke-width: 2;
|
||||
}
|
||||
.clock-action-button--icon.clock-action-button--start svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
.clock-tool-divider {
|
||||
height: 1px;
|
||||
background: var(--sky-hairline);
|
||||
}
|
||||
.clock-lap-list {
|
||||
margin-top: 24px;
|
||||
margin-top: 0;
|
||||
}
|
||||
.timer-ring {
|
||||
position: relative;
|
||||
@@ -4899,17 +5088,24 @@ button {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.clock-timer-settings {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
margin-right: 0;
|
||||
margin-top: 24px;
|
||||
margin-left: 0;
|
||||
}
|
||||
.clock-timer-note {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.clock-timer-sound {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.clock-timer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-bottom: 28px;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
.clock-timer-actions {
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.store-app {
|
||||
background: #000;
|
||||
@@ -5034,6 +5230,21 @@ button {
|
||||
background: #f2f2f7;
|
||||
color: #000;
|
||||
}
|
||||
.phone-app--light
|
||||
.clock-action-button.clock-action-button--secondary.sky-button--secondary {
|
||||
background: #e5e5ea;
|
||||
color: #8e8e93;
|
||||
}
|
||||
.phone-app--light
|
||||
.clock-action-button.clock-action-button--start.sky-button--secondary {
|
||||
background: #d9f5df;
|
||||
color: #248a3d;
|
||||
}
|
||||
.phone-app--light
|
||||
.clock-action-button.clock-action-button--stop.sky-button--secondary {
|
||||
background: #ffe5e5;
|
||||
color: #d70015;
|
||||
}
|
||||
.phone-app--light .time-wheel-picker__selection {
|
||||
background: #e5e5ea;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
SkyBlock as kBlock,
|
||||
SkyBlockTitle as kBlockTitle,
|
||||
SkyButton as kButton,
|
||||
SkyField as kListInput,
|
||||
@@ -8,6 +7,8 @@ import {
|
||||
SkyList as kList,
|
||||
SkyListItem as kListItem,
|
||||
SkyNavbar as kNavbar,
|
||||
SkyScrollArea as kScrollArea,
|
||||
SkySheet as kSheet,
|
||||
} from '@/ui'
|
||||
import { ChevronRight, Check, X } from 'lucide-vue-next'
|
||||
import { reactive, ref } from 'vue'
|
||||
@@ -61,106 +62,123 @@ function toggleWeekday(weekday: number): void {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlarmSoundMenu
|
||||
v-if="soundMenuOpen"
|
||||
:back-label="phone.t('Apps.clock.tabs.alarm')"
|
||||
:selected-sound="draft.sound"
|
||||
@close="soundMenuOpen = false"
|
||||
@select="draft.sound = $event"
|
||||
/>
|
||||
|
||||
<k-navbar v-show="!soundMenuOpen" :title="phone.t('Apps.clock.tabs.alarm')">
|
||||
<template #left>
|
||||
<k-link
|
||||
component="button"
|
||||
icon-only
|
||||
:link-props="{ type: 'button' }"
|
||||
:aria-label="phone.t('Common.cancel')"
|
||||
@click="emit('cancel')"
|
||||
>
|
||||
<X aria-hidden="true" />
|
||||
</k-link>
|
||||
</template>
|
||||
<template #right>
|
||||
<k-link
|
||||
component="button"
|
||||
icon-only
|
||||
class="clock-alarm-save"
|
||||
:link-props="{ type: 'button' }"
|
||||
:aria-label="phone.t('Common.save')"
|
||||
@click="save"
|
||||
>
|
||||
<Check aria-hidden="true" />
|
||||
</k-link>
|
||||
</template>
|
||||
</k-navbar>
|
||||
|
||||
<k-block
|
||||
v-show="!soundMenuOpen"
|
||||
component="section"
|
||||
class="clock-alarm-editor"
|
||||
<k-sheet
|
||||
opened
|
||||
class="clock-alarm-sheet"
|
||||
:aria-label="phone.t('Apps.clock.tabs.alarm')"
|
||||
:show-grabber="false"
|
||||
swipe-to-close
|
||||
@backdropclick="emit('cancel')"
|
||||
@escape="emit('cancel')"
|
||||
@swipeclose="emit('cancel')"
|
||||
>
|
||||
<TimeWheelPicker
|
||||
v-model="draft.time"
|
||||
:hours-label="phone.t('Apps.clock.alarm.hours')"
|
||||
:label="phone.t('Apps.clock.alarm.time')"
|
||||
:minutes-label="phone.t('Apps.clock.minutes')"
|
||||
/>
|
||||
|
||||
<k-block-title>{{ phone.t('Apps.clock.alarm.repeat') }}</k-block-title>
|
||||
<k-list strong inset>
|
||||
<k-list-item
|
||||
v-for="weekday in WEEKDAY_IDS"
|
||||
:key="weekday"
|
||||
link
|
||||
:chevron="false"
|
||||
:title="phone.t(`Apps.clock.alarm.days.${weekdayKeys[weekday]}`)"
|
||||
@click="toggleWeekday(weekday)"
|
||||
>
|
||||
<template #after>
|
||||
<Check
|
||||
v-if="draft.weekdays.includes(weekday)"
|
||||
class="w-5 h-5 text-primary"
|
||||
/>
|
||||
</template>
|
||||
</k-list-item>
|
||||
</k-list>
|
||||
|
||||
<k-list strong inset>
|
||||
<k-list-input
|
||||
:label="phone.t('Apps.clock.alarm.note')"
|
||||
:placeholder="phone.t('Apps.clock.alarm.notePlaceholder')"
|
||||
:value="draft.note"
|
||||
maxlength="80"
|
||||
@input="setNote"
|
||||
<section class="clock-alarm-sheet-surface">
|
||||
<AlarmSoundMenu
|
||||
v-if="soundMenuOpen"
|
||||
:back-label="phone.t('Apps.clock.tabs.alarm')"
|
||||
:selected-sound="draft.sound"
|
||||
@close="soundMenuOpen = false"
|
||||
@select="draft.sound = $event"
|
||||
/>
|
||||
</k-list>
|
||||
|
||||
<k-list strong inset>
|
||||
<k-list-item
|
||||
link
|
||||
:chevron="false"
|
||||
:title="phone.t('Apps.clock.alarm.sound')"
|
||||
@click="soundMenuOpen = true"
|
||||
>
|
||||
<template #after>
|
||||
<span class="clock-sound-selection">
|
||||
{{ phone.t(`Apps.clock.alarm.sounds.${draft.sound}`) }}
|
||||
<ChevronRight aria-hidden="true" />
|
||||
</span>
|
||||
</template>
|
||||
</k-list-item>
|
||||
</k-list>
|
||||
<template v-else>
|
||||
<k-navbar
|
||||
class="clock-alarm-sheet-navbar"
|
||||
data-sky-sheet-drag-handle
|
||||
:title="phone.t('Apps.clock.tabs.alarm')"
|
||||
>
|
||||
<template #left>
|
||||
<k-link
|
||||
component="button"
|
||||
icon-only
|
||||
:link-props="{ type: 'button' }"
|
||||
:aria-label="phone.t('Common.cancel')"
|
||||
@click="emit('cancel')"
|
||||
>
|
||||
<X aria-hidden="true" />
|
||||
</k-link>
|
||||
</template>
|
||||
<template #right>
|
||||
<k-link
|
||||
component="button"
|
||||
icon-only
|
||||
class="clock-alarm-save"
|
||||
:link-props="{ type: 'button' }"
|
||||
:aria-label="phone.t('Common.save')"
|
||||
@click="save"
|
||||
>
|
||||
<Check aria-hidden="true" />
|
||||
</k-link>
|
||||
</template>
|
||||
</k-navbar>
|
||||
|
||||
<k-button
|
||||
v-if="alarm"
|
||||
large
|
||||
rounded
|
||||
variant="danger"
|
||||
class="clock-alarm-delete"
|
||||
@click="emit('delete')"
|
||||
>
|
||||
{{ phone.t('Apps.clock.alarm.delete') }}
|
||||
</k-button>
|
||||
</k-block>
|
||||
<k-scroll-area padded class="clock-alarm-editor">
|
||||
<TimeWheelPicker
|
||||
v-model="draft.time"
|
||||
:hours-label="phone.t('Apps.clock.alarm.hours')"
|
||||
:label="phone.t('Apps.clock.alarm.time')"
|
||||
:minutes-label="phone.t('Apps.clock.minutes')"
|
||||
/>
|
||||
|
||||
<k-block-title>{{
|
||||
phone.t('Apps.clock.alarm.repeat')
|
||||
}}</k-block-title>
|
||||
<k-list strong inset>
|
||||
<k-list-item
|
||||
v-for="weekday in WEEKDAY_IDS"
|
||||
:key="weekday"
|
||||
link
|
||||
:chevron="false"
|
||||
:title="phone.t(`Apps.clock.alarm.days.${weekdayKeys[weekday]}`)"
|
||||
@click="toggleWeekday(weekday)"
|
||||
>
|
||||
<template #after>
|
||||
<Check
|
||||
v-if="draft.weekdays.includes(weekday)"
|
||||
class="w-5 h-5 text-primary"
|
||||
/>
|
||||
</template>
|
||||
</k-list-item>
|
||||
</k-list>
|
||||
|
||||
<k-list strong inset>
|
||||
<k-list-input
|
||||
:label="phone.t('Apps.clock.alarm.note')"
|
||||
:placeholder="phone.t('Apps.clock.alarm.notePlaceholder')"
|
||||
:value="draft.note"
|
||||
maxlength="80"
|
||||
@input="setNote"
|
||||
/>
|
||||
</k-list>
|
||||
|
||||
<k-list strong inset>
|
||||
<k-list-item
|
||||
link
|
||||
:chevron="false"
|
||||
:title="phone.t('Apps.clock.alarm.sound')"
|
||||
@click="soundMenuOpen = true"
|
||||
>
|
||||
<template #after>
|
||||
<span class="clock-sound-selection">
|
||||
{{ phone.t(`Apps.clock.alarm.sounds.${draft.sound}`) }}
|
||||
<ChevronRight aria-hidden="true" />
|
||||
</span>
|
||||
</template>
|
||||
</k-list-item>
|
||||
</k-list>
|
||||
|
||||
<k-button
|
||||
v-if="alarm"
|
||||
large
|
||||
rounded
|
||||
variant="danger"
|
||||
class="clock-alarm-delete"
|
||||
@click="emit('delete')"
|
||||
>
|
||||
{{ phone.t('Apps.clock.alarm.delete') }}
|
||||
</k-button>
|
||||
</k-scroll-area>
|
||||
</template>
|
||||
</section>
|
||||
</k-sheet>
|
||||
</template>
|
||||
|
||||
@@ -3666,6 +3666,7 @@ const defaultLocales: LocaleTree = {
|
||||
minutesShort: 'min',
|
||||
seconds: 'Seconds',
|
||||
secondsShort: 'sec',
|
||||
description: 'Description',
|
||||
note: 'Note',
|
||||
notePlaceholder: 'Timer',
|
||||
sound: 'Sound',
|
||||
|
||||
@@ -244,6 +244,10 @@
|
||||
background: rgba(120, 120, 128, 0.38);
|
||||
}
|
||||
|
||||
.sky-sheet [data-sky-sheet-drag-handle] {
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.sky-action-sheet__panel {
|
||||
max-width: 448px;
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -24,10 +24,16 @@ describe('SkySheet', () => {
|
||||
swipeToClose: true,
|
||||
})
|
||||
const ordinary = await renderSheet({ opened: true })
|
||||
const hiddenGrabber = await renderSheet({
|
||||
opened: true,
|
||||
showGrabber: false,
|
||||
swipeToClose: true,
|
||||
})
|
||||
|
||||
expect(swipeable).toContain('class="sky-sheet__grabber"')
|
||||
expect(swipeable).toContain('Sheet content')
|
||||
expect(ordinary).not.toContain('sky-sheet__grabber')
|
||||
expect(hiddenGrabber).not.toContain('sky-sheet__grabber')
|
||||
})
|
||||
|
||||
it('can expose the drag handle as an accessible close button', async () => {
|
||||
@@ -56,11 +62,16 @@ describe('SkySheet', () => {
|
||||
expect(source).toContain('swipeclose: [event: PointerEvent]')
|
||||
expect(source).toContain('grabberclick: [event: MouseEvent]')
|
||||
expect(source).toContain('setPointerCapture(event.pointerId)')
|
||||
expect(source).toContain("target.closest('[data-sky-sheet-drag-handle]')")
|
||||
expect(source).toContain('@pointerdown="startDrag"')
|
||||
expect(source).toContain('dragOffset.value >= closeThreshold')
|
||||
expect(source).toContain("emit('swipeclose', event)")
|
||||
expect(overlays).toMatch(
|
||||
/\.sky-sheet__grabber\s*\{[^}]*touch-action:\s*none;/s,
|
||||
)
|
||||
expect(overlays).toMatch(
|
||||
/\.sky-sheet \[data-sky-sheet-drag-handle\]\s*\{[^}]*touch-action:\s*none;/s,
|
||||
)
|
||||
expect(overlays).toMatch(
|
||||
/\.sky-sheet__panel--settling\s*\{[^}]*transition:\s*transform 220ms/s,
|
||||
)
|
||||
|
||||
@@ -16,6 +16,7 @@ const props = withDefaults(
|
||||
grabberLabel?: string
|
||||
opened: boolean
|
||||
role?: 'alertdialog' | 'dialog' | 'none' | 'presentation'
|
||||
showGrabber?: boolean
|
||||
swipeToClose?: boolean
|
||||
tabindex?: number | string
|
||||
}>(),
|
||||
@@ -25,6 +26,7 @@ const props = withDefaults(
|
||||
component: 'div',
|
||||
grabberClickable: false,
|
||||
grabberLabel: '',
|
||||
showGrabber: true,
|
||||
tabindex: -1,
|
||||
},
|
||||
)
|
||||
@@ -76,9 +78,26 @@ function settleDrag(): void {
|
||||
}
|
||||
|
||||
function startDrag(event: PointerEvent): void {
|
||||
const target = event.target
|
||||
const grabber =
|
||||
target instanceof Element
|
||||
? target.closest('.sky-sheet__grabber')
|
||||
: undefined
|
||||
const extendedHandle =
|
||||
target instanceof Element
|
||||
? target.closest('[data-sky-sheet-drag-handle]')
|
||||
: undefined
|
||||
const interactiveTarget =
|
||||
target instanceof Element
|
||||
? target.closest(
|
||||
'button, a, input, select, textarea, [role="button"], [contenteditable="true"]',
|
||||
)
|
||||
: undefined
|
||||
|
||||
if (
|
||||
!props.swipeToClose ||
|
||||
!event.isPrimary ||
|
||||
(!grabber && (!extendedHandle || interactiveTarget)) ||
|
||||
(event.pointerType === 'mouse' && event.button !== 0)
|
||||
) {
|
||||
return
|
||||
@@ -204,20 +223,20 @@ useOverlayFocusTrap({
|
||||
"
|
||||
:aria-describedby="effectiveRole ? ariaDescribedby : undefined"
|
||||
:tabindex="tabindex"
|
||||
@lostpointercapture="finishDrag($event, true)"
|
||||
@pointercancel="finishDrag($event, true)"
|
||||
@pointerdown="startDrag"
|
||||
@pointermove="moveDrag"
|
||||
@pointerup="finishDrag($event)"
|
||||
>
|
||||
<component
|
||||
:is="grabberClickable ? 'button' : 'div'"
|
||||
v-if="swipeToClose"
|
||||
v-if="swipeToClose && showGrabber"
|
||||
class="sky-sheet__grabber"
|
||||
:type="grabberClickable ? 'button' : undefined"
|
||||
:aria-hidden="grabberClickable ? undefined : true"
|
||||
:aria-label="grabberClickable ? grabberLabel : undefined"
|
||||
@click="grabberClickable && emit('grabberclick', $event)"
|
||||
@lostpointercapture="finishDrag($event, true)"
|
||||
@pointercancel="finishDrag($event, true)"
|
||||
@pointerdown="startDrag"
|
||||
@pointermove="moveDrag"
|
||||
@pointerup="finishDrag($event)"
|
||||
></component>
|
||||
<slot />
|
||||
</component>
|
||||
|
||||
@@ -6,28 +6,42 @@ const clockView = readFileSync(
|
||||
new URL('./ClockApp.vue', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
const alarmEditorView = readFileSync(
|
||||
new URL('../../components/AlarmEditor.vue', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
const mainCss = readFileSync(
|
||||
new URL('../../assets/main.css', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
|
||||
describe('Clock app controls', () => {
|
||||
it('removes the empty navigation row from world clock and timer titles', () => {
|
||||
expect(clockView).toContain('class="clock-navbar"')
|
||||
it('only renders a title navbar for the alarm tab', () => {
|
||||
expect(clockView.match(/<sky-navbar/g)).toHaveLength(1)
|
||||
expect(clockView).toContain('v-if="tab === \'alarm\'"')
|
||||
expect(clockView).toContain('class="clock-navbar clock-navbar--alarm"')
|
||||
expect(clockView).toContain('variant="large"')
|
||||
expect(clockView).not.toContain('clock-navbar__empty-inner')
|
||||
expect(clockView).not.toContain('v-else-if="tab !== \'stopwatch\'"')
|
||||
expect(mainCss).toMatch(
|
||||
/\.clock-navbar\.sky-navbar--no-navigation\s*\{[^}]*--sky-navbar-large-title-height[^}]*padding-top:\s*var\(--sky-navbar-safe-area-top\)/s,
|
||||
/\.clock-navbar\.sky-navbar\s*\{[^}]*--sky-navbar-safe-area-top:\s*var\(--sky-space-2\);/s,
|
||||
)
|
||||
expect(mainCss).not.toContain('.clock-navbar.sky-navbar--no-navigation')
|
||||
})
|
||||
|
||||
it('keeps the single world clock centered with readable line boxes', () => {
|
||||
expect(clockView).toContain('class="clock-world-now"')
|
||||
expect(clockView).not.toContain('clock-world-list')
|
||||
expect(mainCss).toMatch(
|
||||
/\.clock-world-time\s*\{[^}]*font-size:\s*74px;[^}]*line-height:\s*1;/s,
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the danger color while stopwatch and timer controls are running', () => {
|
||||
expect(clockView).toMatch(
|
||||
/clock\.stopwatchStartedAt === null\s*\? 'primary'\s*:\s*'danger'/,
|
||||
)
|
||||
expect(clockView).toMatch(
|
||||
/:variant="clock\.timerStartedAt === null \? 'primary' : 'danger'"/,
|
||||
)
|
||||
it('uses the orange clock accent and state-specific circular actions', () => {
|
||||
expect(clockView).toContain('accent="#ff9f0a"')
|
||||
expect(clockView).toContain('clock-action-button--start')
|
||||
expect(clockView).toContain('clock-action-button--stop')
|
||||
expect(clockView).toContain('<Play')
|
||||
expect(clockView).toContain('<Pause')
|
||||
})
|
||||
|
||||
it('uses the shared full-width Sky tab bar', () => {
|
||||
@@ -35,4 +49,91 @@ describe('Clock app controls', () => {
|
||||
expect(clockView).toContain('<sky-tab-button')
|
||||
expect(clockView).not.toContain('<sky-segmented')
|
||||
})
|
||||
|
||||
it('does not nest a second SkyBlock around the timer layout', () => {
|
||||
expect(clockView).toContain(
|
||||
'<section v-else class="clock-tool clock-timer">',
|
||||
)
|
||||
expect(clockView).not.toContain(
|
||||
'<sky-block v-else nested class="clock-tool clock-timer">',
|
||||
)
|
||||
expect(mainCss).toMatch(
|
||||
/\.clock-timer\s*\{[^}]*display:\s*flex;[^}]*flex-direction:\s*column;[^}]*align-items:\s*center;/s,
|
||||
)
|
||||
expect(mainCss).toMatch(/\.clock-timer-actions\s*\{[^}]*width:\s*100%;/s)
|
||||
expect(mainCss).toMatch(/\.clock-timer-settings\s*\{[^}]*width:\s*100%;/s)
|
||||
})
|
||||
|
||||
it('keeps the stopwatch single-page and separates alarm actions', () => {
|
||||
expect(clockView).not.toContain('clock-stopwatch-pages')
|
||||
expect(clockView).not.toContain('clock-stopwatch-page')
|
||||
expect(clockView).toMatch(
|
||||
/<template #left>[\s\S]*?clock-navbar-action--icon[\s\S]*?<\/template>\s*<template #right>[\s\S]*?clock-navbar-action--edit/,
|
||||
)
|
||||
expect(clockView).not.toContain('clock-navbar-actions')
|
||||
expect(clockView).toMatch(
|
||||
/<sky-button\s+v-if="alarmsEditing"[\s\S]*?icon-only[\s\S]*?class="clock-alarm-remove"/,
|
||||
)
|
||||
expect(mainCss).toMatch(
|
||||
/\.clock-navbar--alarm \.sky-navbar__inner\s*\{[^}]*margin-bottom:\s*0;/s,
|
||||
)
|
||||
expect(mainCss).toMatch(
|
||||
/\.clock-navbar--alarm \.sky-navbar__left\.sky-glass-surface,\s*\.clock-navbar--alarm \.sky-navbar__right\.sky-glass-surface\s*\{/s,
|
||||
)
|
||||
expect(mainCss).not.toMatch(/\.clock-navbar--alarm\s*\{[^}]*margin-top:/s)
|
||||
expect(mainCss).toMatch(
|
||||
/\.clock-content\s*\{[^}]*--sky-block-gutter-left:\s*0px;[^}]*--sky-block-gutter-right:\s*0px;/s,
|
||||
)
|
||||
expect(mainCss).toMatch(
|
||||
/\.clock-stopwatch\s*\{[^}]*--sky-block-gutter-left:\s*calc\(\s*var\(--sky-page-gutter\) \+ var\(--sky-safe-area-left\)\s*\);[^}]*--sky-block-gutter-right:\s*calc\(\s*var\(--sky-page-gutter\) \+ var\(--sky-safe-area-right\)\s*\);/s,
|
||||
)
|
||||
expect(mainCss).toMatch(
|
||||
/\.clock-alarm-item > \.sky-toggle\s*\{[^}]*grid-column:\s*3;[^}]*justify-self:\s*end;/s,
|
||||
)
|
||||
expect(mainCss).toMatch(
|
||||
/\.clock-alarm-remove\.sky-button--icon-only\s*\{[^}]*width:\s*28px;[^}]*height:\s*28px;[^}]*min-height:\s*28px;[^}]*padding:\s*0;[^}]*border-radius:\s*50%;/s,
|
||||
)
|
||||
})
|
||||
|
||||
it('opens the alarm editor as a compact Sky sheet over the alarm page', () => {
|
||||
expect(clockView).toMatch(/<AlarmEditor\s+v-if="alarmEditor"/)
|
||||
expect(clockView).not.toContain('v-else-if="alarmEditor"')
|
||||
expect(alarmEditorView).toContain('<k-sheet')
|
||||
expect(alarmEditorView).toContain(':show-grabber="false"')
|
||||
expect(alarmEditorView).toContain('swipe-to-close')
|
||||
expect(alarmEditorView).toContain('data-sky-sheet-drag-handle')
|
||||
expect(alarmEditorView).toContain(
|
||||
'<k-scroll-area padded class="clock-alarm-editor">',
|
||||
)
|
||||
expect(mainCss).toMatch(
|
||||
/\.clock-alarm-sheet \.sky-sheet__panel\s*\{[^}]*height:\s*calc\(\s*100% - var\(--sky-safe-area-top\) - var\(--sky-space-2\)\s*\);[^}]*overflow:\s*hidden;[^}]*border-radius:\s*var\(--sky-radius-sheet\) var\(--sky-radius-sheet\) 0 0;[^}]*background:\s*var\(--sky-sheet-background\);/s,
|
||||
)
|
||||
expect(mainCss).toMatch(
|
||||
/\.clock-alarm-sheet\s*\{[^}]*--sky-sheet-background:\s*#1c1c1d;/s,
|
||||
)
|
||||
expect(mainCss).toMatch(
|
||||
/\.clock-alarm-sheet \.sky-sheet__grabber\s*\{[^}]*display:\s*none;/s,
|
||||
)
|
||||
expect(mainCss).toMatch(
|
||||
/\.clock-alarm-sheet-surface\s*\{[^}]*height:\s*100%;[^}]*border-radius:\s*var\(--sky-radius-sheet\) var\(--sky-radius-sheet\) 0 0;[^}]*background:\s*var\(--sky-sheet-background\);/s,
|
||||
)
|
||||
expect(mainCss).toMatch(
|
||||
/\.clock-alarm-sheet-navbar\.sky-navbar,[\s\S]*?\.clock-alarm-sheet-surface > \.sky-navbar\s*\{[^}]*--sky-navbar-safe-area-top:\s*var\(--sky-space-3\);/s,
|
||||
)
|
||||
expect(mainCss).toMatch(
|
||||
/\.clock-alarm-sheet-navbar \.sky-navbar__blur,[\s\S]*?\.clock-alarm-sheet-navbar \.sky-navbar__background\s*\{[^}]*background:\s*transparent;/s,
|
||||
)
|
||||
expect(mainCss).toMatch(
|
||||
/\.clock-alarm-save\.sky-link\s*\{[^}]*background:\s*#ff9f0a;[^}]*color:\s*#fff;/s,
|
||||
)
|
||||
expect(mainCss).toMatch(
|
||||
/\.clock-alarm-editor\.sky-scroll-area--padded\s*\{[^}]*--sky-page-gutter:\s*12px;/s,
|
||||
)
|
||||
expect(mainCss).toMatch(
|
||||
/\.clock-alarm-editor \.time-wheel-picker\s*\{[^}]*width:\s*100%;[^}]*margin-right:\s*0;[^}]*margin-left:\s*0;/s,
|
||||
)
|
||||
expect(mainCss).toMatch(
|
||||
/\.clock-alarm-editor \.sky-list--strong,[\s\S]*?\.clock-alarm-editor \.sky-list--inset\s*\{[^}]*background:\s*#2c2c2e;/s,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -15,11 +15,14 @@ import {
|
||||
import {
|
||||
AlarmClock,
|
||||
ChevronRight,
|
||||
Clock3,
|
||||
Globe2,
|
||||
Minus,
|
||||
Pause,
|
||||
Play,
|
||||
Plus,
|
||||
Timer,
|
||||
TimerReset,
|
||||
X,
|
||||
} from 'lucide-vue-next'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
@@ -95,6 +98,18 @@ const currentTimeZone = computed(
|
||||
.formatToParts(now.value)
|
||||
.find((part) => part.type === 'timeZoneName')?.value ?? browserTimeZone,
|
||||
)
|
||||
const stopwatchDecimalSeparator = computed(
|
||||
() =>
|
||||
new Intl.NumberFormat(phone.lang)
|
||||
.formatToParts(1.1)
|
||||
.find((part) => part.type === 'decimal')?.value ?? '.',
|
||||
)
|
||||
const stopwatchDisplay = computed(() =>
|
||||
formatStopwatch(stopwatchValue.value).replace(
|
||||
'.',
|
||||
stopwatchDecimalSeparator.value,
|
||||
),
|
||||
)
|
||||
const selectedAlarm = computed(() => {
|
||||
const editor = alarmEditor.value
|
||||
return editor?.mode === 'edit'
|
||||
@@ -102,7 +117,7 @@ const selectedAlarm = computed(() => {
|
||||
: undefined
|
||||
})
|
||||
const tabs = [
|
||||
{ id: 'world', icon: Clock3 },
|
||||
{ id: 'world', icon: Globe2 },
|
||||
{ id: 'alarm', icon: AlarmClock },
|
||||
{ id: 'stopwatch', icon: Timer },
|
||||
{ id: 'timer', icon: TimerReset },
|
||||
@@ -207,7 +222,12 @@ onBeforeUnmount(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<sky-app-page component="main" class="native-app clock-app">
|
||||
<sky-app-page
|
||||
component="main"
|
||||
accent="#ff9f0a"
|
||||
accent-soft="rgba(255, 159, 10, 0.16)"
|
||||
class="native-app clock-app"
|
||||
>
|
||||
<AlarmSoundMenu
|
||||
v-if="timerSoundMenuOpen"
|
||||
:back-label="phone.t('Apps.clock.tabs.timer')"
|
||||
@@ -216,42 +236,35 @@ onBeforeUnmount(() => {
|
||||
@select="clock.setTimerSound($event)"
|
||||
/>
|
||||
|
||||
<AlarmEditor
|
||||
v-else-if="alarmEditor"
|
||||
:alarm="selectedAlarm"
|
||||
@cancel="alarmEditor = null"
|
||||
@delete="deleteSelectedAlarm"
|
||||
@save="saveAlarm"
|
||||
/>
|
||||
|
||||
<template v-else>
|
||||
<sky-navbar
|
||||
v-if="tab !== 'stopwatch'"
|
||||
v-if="tab === 'alarm'"
|
||||
:title="phone.t(`Apps.clock.tabs.${tab}`)"
|
||||
variant="large"
|
||||
transparent
|
||||
class="clock-navbar"
|
||||
class="clock-navbar clock-navbar--alarm"
|
||||
>
|
||||
<template #left>
|
||||
<sky-link
|
||||
v-if="tab === 'alarm'"
|
||||
component="button"
|
||||
:link-props="{ type: 'button' }"
|
||||
@click="toggleAlarmEditing"
|
||||
>
|
||||
{{ phone.t(alarmsEditing ? 'Common.done' : 'Common.edit') }}
|
||||
</sky-link>
|
||||
</template>
|
||||
<template #right>
|
||||
<sky-link
|
||||
v-if="tab === 'alarm'"
|
||||
component="button"
|
||||
icon-only
|
||||
class="clock-navbar-action clock-navbar-action--icon sky-glass-surface"
|
||||
:link-props="{ type: 'button' }"
|
||||
:aria-label="phone.t('Apps.clock.add')"
|
||||
@click="openAlarmEditor()"
|
||||
>
|
||||
<Plus />
|
||||
<Plus aria-hidden="true" />
|
||||
</sky-link>
|
||||
</template>
|
||||
<template #right>
|
||||
<sky-link
|
||||
v-if="clock.alarms.length"
|
||||
component="button"
|
||||
class="clock-navbar-action clock-navbar-action--edit sky-glass-surface"
|
||||
:link-props="{ type: 'button' }"
|
||||
@click="toggleAlarmEditing"
|
||||
>
|
||||
{{ phone.t(alarmsEditing ? 'Common.done' : 'Common.edit') }}
|
||||
</sky-link>
|
||||
</template>
|
||||
</sky-navbar>
|
||||
@@ -265,55 +278,70 @@ onBeforeUnmount(() => {
|
||||
<span class="clock-world-zone">{{ currentTimeZone }}</span>
|
||||
</sky-block>
|
||||
|
||||
<sky-list
|
||||
<section
|
||||
v-else-if="tab === 'alarm'"
|
||||
class="clock-konsta-list clock-alarm-list"
|
||||
class="clock-alarm-list"
|
||||
:aria-label="phone.t('Apps.clock.tabs.alarm')"
|
||||
>
|
||||
<sky-list-item
|
||||
<article
|
||||
v-for="alarm in clock.alarms"
|
||||
:key="alarm.id"
|
||||
link
|
||||
:chevron="alarmsEditing"
|
||||
:title="alarm.time"
|
||||
:subtitle="alarmSubtitle(alarm)"
|
||||
:strong-title="false"
|
||||
title-font-size-ios="text-[38px] font-light"
|
||||
class="clock-alarm-item"
|
||||
@click="openAlarmEditor(alarm.id)"
|
||||
:class="{ 'clock-alarm-item--disabled': !alarm.enabled }"
|
||||
>
|
||||
<template v-if="alarmsEditing" #media>
|
||||
<sky-button
|
||||
rounded
|
||||
small
|
||||
inline
|
||||
variant="danger"
|
||||
class="clock-alarm-remove"
|
||||
:aria-label="phone.t('Apps.clock.alarm.delete')"
|
||||
@click.stop="clock.deleteAlarm(alarm.id)"
|
||||
>
|
||||
<Minus aria-hidden="true" />
|
||||
</sky-button>
|
||||
</template>
|
||||
<template #after>
|
||||
<span v-if="!alarmsEditing" @click.stop>
|
||||
<sky-toggle
|
||||
:checked="alarm.enabled"
|
||||
style="--sky-app-accent: #30d158"
|
||||
:aria-label="`${alarm.time}, ${alarmSubtitle(alarm)}`"
|
||||
@change="clock.toggleAlarm(alarm.id)"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
</sky-list-item>
|
||||
</sky-list>
|
||||
<sky-button
|
||||
v-if="alarmsEditing"
|
||||
rounded
|
||||
small
|
||||
icon-only
|
||||
variant="danger"
|
||||
class="clock-alarm-remove"
|
||||
:aria-label="phone.t('Apps.clock.alarm.delete')"
|
||||
@click="clock.deleteAlarm(alarm.id)"
|
||||
>
|
||||
<Minus aria-hidden="true" />
|
||||
</sky-button>
|
||||
|
||||
<sky-block v-else-if="tab === 'stopwatch'" nested class="clock-tool">
|
||||
<div class="clock-digits">{{ formatStopwatch(stopwatchValue) }}</div>
|
||||
<div class="clock-actions">
|
||||
<button
|
||||
type="button"
|
||||
class="clock-alarm-copy"
|
||||
@click="openAlarmEditor(alarm.id)"
|
||||
>
|
||||
<time class="clock-alarm-time">{{ alarm.time }}</time>
|
||||
<span class="clock-alarm-subtitle">{{
|
||||
alarmSubtitle(alarm)
|
||||
}}</span>
|
||||
</button>
|
||||
|
||||
<ChevronRight
|
||||
v-if="alarmsEditing"
|
||||
class="clock-alarm-chevron"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<sky-toggle
|
||||
v-else
|
||||
:checked="alarm.enabled"
|
||||
style="--sky-app-accent: #30d158"
|
||||
:aria-label="`${alarm.time}, ${alarmSubtitle(alarm)}`"
|
||||
@change="clock.toggleAlarm(alarm.id)"
|
||||
/>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<sky-block
|
||||
v-else-if="tab === 'stopwatch'"
|
||||
nested
|
||||
class="clock-tool clock-stopwatch"
|
||||
>
|
||||
<time class="clock-digits">{{ stopwatchDisplay }}</time>
|
||||
<div class="clock-actions clock-stopwatch-actions">
|
||||
<sky-button
|
||||
rounded
|
||||
variant="secondary"
|
||||
class="clock-action-button"
|
||||
class="clock-action-button clock-action-button--secondary"
|
||||
:disabled="
|
||||
clock.stopwatchStartedAt === null && stopwatchValue <= 0
|
||||
"
|
||||
@click="
|
||||
clock.stopwatchStartedAt === null
|
||||
? clock.resetStopwatch()
|
||||
@@ -330,15 +358,13 @@ onBeforeUnmount(() => {
|
||||
</sky-button>
|
||||
<sky-button
|
||||
rounded
|
||||
:variant="
|
||||
clock.stopwatchStartedAt === null ? 'primary' : 'danger'
|
||||
"
|
||||
:style="
|
||||
clock.stopwatchStartedAt === null
|
||||
? { '--sky-app-accent': '#30d158' }
|
||||
: undefined
|
||||
"
|
||||
variant="secondary"
|
||||
class="clock-action-button"
|
||||
:class="
|
||||
clock.stopwatchStartedAt === null
|
||||
? 'clock-action-button--start'
|
||||
: 'clock-action-button--stop'
|
||||
"
|
||||
@click="
|
||||
clock.stopwatchStartedAt === null
|
||||
? clock.startStopwatch(Date.now())
|
||||
@@ -354,6 +380,7 @@ onBeforeUnmount(() => {
|
||||
}}
|
||||
</sky-button>
|
||||
</div>
|
||||
<div class="clock-tool-divider" aria-hidden="true" />
|
||||
<sky-list dividers class="clock-konsta-list clock-lap-list">
|
||||
<sky-list-item
|
||||
v-for="(lap, index) in clock.laps"
|
||||
@@ -364,7 +391,7 @@ onBeforeUnmount(() => {
|
||||
</sky-list>
|
||||
</sky-block>
|
||||
|
||||
<sky-block v-else nested class="clock-tool clock-timer">
|
||||
<section v-else class="clock-tool clock-timer">
|
||||
<div
|
||||
v-if="clock.timerStartedAt !== null"
|
||||
class="timer-ring"
|
||||
@@ -400,25 +427,47 @@ onBeforeUnmount(() => {
|
||||
:seconds-unit-label="phone.t('Apps.clock.timer.secondsShort')"
|
||||
/>
|
||||
|
||||
<sky-list
|
||||
strong
|
||||
inset
|
||||
class="clock-konsta-list clock-timer-settings clock-timer-note"
|
||||
>
|
||||
<div class="clock-actions clock-timer-actions">
|
||||
<sky-button
|
||||
rounded
|
||||
variant="secondary"
|
||||
class="clock-action-button clock-action-button--secondary clock-action-button--icon"
|
||||
:aria-label="phone.t('Common.reset')"
|
||||
@click="clock.resetTimer()"
|
||||
>
|
||||
<X aria-hidden="true" />
|
||||
</sky-button>
|
||||
<sky-button
|
||||
rounded
|
||||
variant="secondary"
|
||||
class="clock-action-button clock-action-button--start clock-action-button--icon"
|
||||
:disabled="clock.timerStartedAt === null && timerValue <= 0"
|
||||
:aria-label="
|
||||
phone.t(
|
||||
clock.timerStartedAt === null
|
||||
? 'Common.start'
|
||||
: 'Common.pause',
|
||||
)
|
||||
"
|
||||
@click="
|
||||
clock.timerStartedAt === null
|
||||
? clock.startTimer(now)
|
||||
: clock.pauseTimer(now)
|
||||
"
|
||||
>
|
||||
<Play v-if="clock.timerStartedAt === null" aria-hidden="true" />
|
||||
<Pause v-else aria-hidden="true" />
|
||||
</sky-button>
|
||||
</div>
|
||||
|
||||
<sky-list strong inset class="clock-konsta-list clock-timer-settings">
|
||||
<sky-field
|
||||
:label="phone.t('Apps.clock.timer.note')"
|
||||
:label="phone.t('Apps.clock.timer.description')"
|
||||
:placeholder="phone.t('Apps.clock.timer.notePlaceholder')"
|
||||
:value="clock.timerNote"
|
||||
maxlength="80"
|
||||
@input="setTimerNote"
|
||||
/>
|
||||
</sky-list>
|
||||
|
||||
<sky-list
|
||||
strong
|
||||
inset
|
||||
class="clock-konsta-list clock-timer-settings clock-timer-sound"
|
||||
>
|
||||
<sky-list-item
|
||||
link
|
||||
:chevron="false"
|
||||
@@ -433,42 +482,7 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</sky-list-item>
|
||||
</sky-list>
|
||||
|
||||
<div class="clock-actions">
|
||||
<sky-button
|
||||
rounded
|
||||
variant="secondary"
|
||||
class="clock-action-button"
|
||||
@click="clock.resetTimer()"
|
||||
>
|
||||
{{ phone.t('Common.reset') }}
|
||||
</sky-button>
|
||||
<sky-button
|
||||
rounded
|
||||
:variant="clock.timerStartedAt === null ? 'primary' : 'danger'"
|
||||
:style="
|
||||
clock.timerStartedAt === null
|
||||
? { '--sky-app-accent': '#30d158' }
|
||||
: undefined
|
||||
"
|
||||
class="clock-action-button"
|
||||
:disabled="clock.timerStartedAt === null && timerValue <= 0"
|
||||
@click="
|
||||
clock.timerStartedAt === null
|
||||
? clock.startTimer(now)
|
||||
: clock.pauseTimer(now)
|
||||
"
|
||||
>
|
||||
{{
|
||||
phone.t(
|
||||
clock.timerStartedAt === null
|
||||
? 'Common.start'
|
||||
: 'Common.pause',
|
||||
)
|
||||
}}
|
||||
</sky-button>
|
||||
</div>
|
||||
</sky-block>
|
||||
</section>
|
||||
</sky-block>
|
||||
|
||||
<sky-tab-bar
|
||||
@@ -492,6 +506,14 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</sky-tab-button>
|
||||
</sky-tab-bar>
|
||||
|
||||
<AlarmEditor
|
||||
v-if="alarmEditor"
|
||||
:alarm="selectedAlarm"
|
||||
@cancel="alarmEditor = null"
|
||||
@delete="deleteSelectedAlarm"
|
||||
@save="saveAlarm"
|
||||
/>
|
||||
</template>
|
||||
</sky-app-page>
|
||||
</template>
|
||||
|
||||
@@ -1446,7 +1446,7 @@ Locales["en"] = {
|
||||
timer = {
|
||||
time = "Timer duration", hours = "Hours", hoursShort = "hr", minutes = "Minutes", minutesShort = "min",
|
||||
seconds = "Seconds", secondsShort = "sec",
|
||||
note = "Note", notePlaceholder = "Timer", sound = "Sound", ringing = "Timer",
|
||||
description = "Description", note = "Note", notePlaceholder = "Timer", sound = "Sound", ringing = "Timer",
|
||||
},
|
||||
},
|
||||
mail = {
|
||||
|
||||
Reference in New Issue
Block a user