Files
wehub-resource-sync adf0d17497
publish / version_or_publish (push) Has been cancelled
storybook-build / changes (push) Has been cancelled
storybook-build / :storybook-build (push) Has been cancelled
Sync Gradio Skills to Hugging Face / sync-skills (push) Has been cancelled
functional / changes (push) Has been cancelled
functional / build-frontend (push) Has been cancelled
functional / functional-test-SSR=false (push) Has been cancelled
functional / functional-reload (push) Has been cancelled
js / changes (push) Has been cancelled
js / js-test (push) Has been cancelled
docs-build / changes (push) Has been cancelled
docs-build / docs-build (push) Has been cancelled
docs-build / website-build (push) Has been cancelled
functional / functional-test-SSR=true (push) Has been cancelled
hygiene / hygiene-test (push) Has been cancelled
python / changes (push) Has been cancelled
python / build (push) Has been cancelled
python / test-ubuntu-latest-flaky (push) Has been cancelled
python / test-ubuntu-latest-not-flaky (push) Has been cancelled
python / test-windows-latest-flaky (push) Has been cancelled
python / test-windows-latest-not-flaky (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:17:32 +08:00

439 lines
9.6 KiB
Svelte
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import {
format_date,
generate_calendar_days,
calculate_display_hour,
convert_display_hour_to_24h,
month_names
} from "./utils";
let {
selected_date = $bindable<Date>(),
current_year = $bindable<number>(),
current_month = $bindable<number>(),
selected_hour = $bindable<number>(),
selected_minute = $bindable<number>(),
selected_second = $bindable<number>(),
is_pm = $bindable<boolean>(),
include_time,
position,
onclose,
onclear,
onupdate
}: {
selected_date: Date;
current_year: number;
current_month: number;
selected_hour: number;
selected_minute: number;
selected_second: number;
is_pm: boolean;
include_time: boolean;
position: { top: number; left: number };
onclose?: () => void;
onclear?: () => void;
onupdate?: (formatted: string) => void;
} = $props();
let display_hour = $derived(calculate_display_hour(selected_hour, is_pm));
let calendar_days = $derived(
generate_calendar_days(current_year, current_month)
);
const select_date = (day: number): void => {
selected_date = new Date(
current_year,
current_month,
day,
selected_hour,
selected_minute,
selected_second
);
update_value();
};
const update_value = (): void => {
const formatted = format_date(selected_date, include_time);
onupdate?.(formatted);
};
const update_time = (): void => {
selected_date = new Date(
current_year,
current_month,
selected_date.getDate(),
selected_hour,
selected_minute,
selected_second
);
update_value();
};
const previous_month = (): void => {
if (current_month === 0) {
current_month = 11;
current_year--;
} else {
current_month--;
}
};
const next_month = (): void => {
if (current_month === 11) {
current_month = 0;
current_year++;
} else {
current_month++;
}
};
const toggle_am_pm = (): void => {
is_pm = !is_pm;
if (is_pm && selected_hour < 12) {
selected_hour += 12;
} else if (!is_pm && selected_hour >= 12) {
selected_hour -= 12;
}
update_time();
};
const update_display_hour = (new_hour: number): void => {
selected_hour = convert_display_hour_to_24h(new_hour, is_pm);
update_time();
};
const handle_now = (): void => {
const now = new Date();
selected_date = now;
current_year = now.getFullYear();
current_month = now.getMonth();
selected_hour = now.getHours();
selected_minute = now.getMinutes();
selected_second = now.getSeconds();
is_pm = selected_hour >= 12;
update_value();
};
</script>
<div
class="picker-container"
style="top: {position.top}px; left: {position.left}px;"
>
<div class="picker">
<div class="picker-header">
<button type="button" class="nav-button" onclick={previous_month}
></button
>
<div class="month-year">
{month_names[current_month]}
{current_year}
</div>
<button type="button" class="nav-button" onclick={next_month}></button>
</div>
<div class="calendar-grid">
<div class="weekdays">
<div class="weekday">Su</div>
<div class="weekday">Mo</div>
<div class="weekday">Tu</div>
<div class="weekday">We</div>
<div class="weekday">Th</div>
<div class="weekday">Fr</div>
<div class="weekday">Sa</div>
</div>
<div class="days">
{#each calendar_days as { day, is_current_month, is_next_month }}
<button
type="button"
class="day"
class:other-month={!is_current_month}
class:selected={is_current_month &&
day === selected_date.getDate() &&
current_month === selected_date.getMonth() &&
current_year === selected_date.getFullYear()}
onclick={() => {
if (is_current_month) {
select_date(day);
} else if (is_next_month) {
next_month();
select_date(day);
} else {
previous_month();
select_date(day);
}
}}
>
{day}
</button>
{/each}
</div>
</div>
{#if include_time}
<div class="time-picker">
<div class="time-inputs">
<div class="time-input-group">
<label for="hour">Hour</label>
<input
id="hour"
type="number"
min="1"
max="12"
bind:value={display_hour}
oninput={() => update_display_hour(display_hour)}
/>
</div>
<div class="time-input-group">
<label for="minute">Min</label>
<input
id="minute"
type="number"
min="0"
max="59"
bind:value={selected_minute}
oninput={update_time}
/>
</div>
<div class="time-input-group">
<label for="second">Sec</label>
<input
id="second"
type="number"
min="0"
max="59"
bind:value={selected_second}
oninput={update_time}
/>
</div>
<div class="time-input-group">
<span class="am-pm-label">Period</span>
<button
type="button"
class="am-pm-toggle"
onclick={toggle_am_pm}
aria-label="Toggle AM/PM"
>
{is_pm ? "PM" : "AM"}
</button>
</div>
</div>
</div>
{/if}
<div class="picker-actions">
<button type="button" class="action-button" onclick={onclear}>
Clear
</button>
<div class="picker-actions-right">
<button type="button" class="action-button" onclick={handle_now}>
Now
</button>
<button type="button" class="action-button" onclick={onclose}>
Done
</button>
</div>
</div>
</div>
</div>
<style>
.picker-container {
position: fixed;
z-index: 9999;
box-shadow: var(--shadow-drop-lg);
border-radius: var(--radius-lg);
background: var(--background-fill-primary);
border: 1px solid var(--border-color-primary);
}
.picker {
padding: var(--size-3);
min-width: 280px;
}
.picker-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--size-3);
}
.nav-button {
background: none;
border: none;
font-size: var(--text-lg);
padding: var(--size-1);
border-radius: var(--radius-sm);
transition: var(--button-transition);
cursor: pointer;
color: var(--body-text-color-subdued);
}
.nav-button:hover {
background: var(--button-secondary-background-fill-hover);
color: var(--body-text-color);
}
.month-year {
font-weight: var(--weight-semibold);
font-size: var(--text-base);
color: var(--body-text-color);
}
.calendar-grid {
margin-bottom: var(--size-3);
}
.weekdays {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 1px;
margin-bottom: var(--size-2);
}
.weekday {
text-align: center;
font-size: var(--text-sm);
font-weight: var(--weight-semibold);
color: var(--body-text-color-subdued);
padding: var(--size-1);
}
.days {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 1px;
}
.day {
aspect-ratio: 1;
border: none;
background: none;
border-radius: var(--radius-sm);
font-size: var(--text-sm);
transition: var(--button-transition);
color: var(--body-text-color);
cursor: pointer;
}
.day:hover {
background: var(--button-secondary-background-fill-hover);
}
.day.other-month {
color: var(--body-text-color-subdued);
}
.day.selected {
background: var(--button-primary-background-fill);
color: var(--button-primary-text-color);
}
.day.selected:hover {
background: var(--button-primary-background-fill-hover);
}
.time-picker {
border-top: 1px solid var(--border-color-primary);
padding-top: var(--size-3);
margin-bottom: var(--size-3);
}
.time-inputs {
display: flex;
gap: var(--size-2);
justify-content: center;
}
.time-input-group {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--size-1);
}
.time-input-group label {
font-size: var(--text-xs);
color: var(--body-text-color-subdued);
font-weight: var(--weight-semibold);
}
.am-pm-label {
font-size: var(--text-xs);
color: var(--body-text-color-subdued);
font-weight: var(--weight-semibold);
}
.time-input-group input {
width: 50px;
padding: var(--size-1);
border: 1px solid var(--input-border-color);
border-radius: var(--radius-sm);
text-align: center;
font-size: var(--text-sm);
background: var(--input-background-fill);
color: var(--body-text-color);
}
.time-input-group input:focus {
outline: none;
border-color: var(--input-border-color-focus);
box-shadow: var(--input-shadow-focus);
}
.am-pm-toggle {
width: 50px;
padding: var(--size-1);
border: 1px solid var(--button-primary-border-color);
border-radius: var(--radius-sm);
text-align: center;
font-size: var(--text-sm);
background: var(--button-primary-background-fill);
color: var(--button-primary-text-color);
cursor: pointer;
transition: var(--button-transition);
}
.am-pm-toggle:hover {
background: var(--button-primary-background-fill-hover);
border-color: var(--button-primary-border-color-hover);
}
.am-pm-toggle:focus {
outline: none;
border-color: var(--button-primary-border-color-focus);
box-shadow: var(--button-primary-shadow-focus);
}
.picker-actions {
display: flex;
gap: var(--size-2);
justify-content: space-between;
align-items: center;
border-top: 1px solid var(--border-color-primary);
padding-top: var(--size-3);
}
.picker-actions-right {
display: flex;
gap: var(--size-2);
}
.action-button {
padding: var(--size-1) var(--size-3);
border: 1px solid var(--button-secondary-border-color);
border-radius: var(--radius-sm);
background: var(--button-secondary-background-fill);
color: var(--button-secondary-text-color);
font-size: var(--text-sm);
transition: var(--button-transition);
cursor: pointer;
}
.action-button:hover {
background: var(--button-secondary-background-fill-hover);
border-color: var(--button-secondary-border-color-hover);
}
</style>