Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: TimeField component #529

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"dependencies": {
"@docsearch/css": "3.5.2",
"@docsearch/js": "3.5.2",
"@internationalized/date": "3.4.0",
"@kobalte/core": "workspace:*",
"@solidjs/meta": "0.29.3",
"@solidjs/router": "0.12.4",
Expand Down
91 changes: 91 additions & 0 deletions apps/docs/src/examples/time-field.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
.time-field {
display: flex;
flex-direction: column;
gap: 4px;
}

.time-field__label {
color: hsl(240 6% 10%);
font-size: 14px;
font-weight: 500;
user-select: none;
}

.time-field__field {
display: flex;
padding: 4px 8px;
border-radius: 6px;
width: 150px;
white-space: nowrap;
forced-color-adjust: none;
background-color: #fff;
border: 1px solid hsl(240 6% 90%);
color: hsl(240 4% 16%);
font-size: 14px;
}

.time-field__field:focus-within {
outline: 2px solid hsl(200 98% 39%);
outline-offset: -1px;
}

.time-field__segment {
padding: 0 2px;
font-variant-numeric: tabular-nums;
text-align: end;
}

.time-field__segment[data-type="literal"] {
padding: 0;
}

.time-field__segment[data-placeholder] {
color: hsl(240 4% 46%);
font-style: italic;
}

.time-field__segment:focus {
color: #fff !important;
background: hsl(200 98% 39%);
outline: none;
border-radius: 4px;
}

.time-field__segment::selection {
background-color: transparent;
}

.time-field__description {
color: hsl(240 5% 26%);
font-size: 12px;
user-select: none;
}

.time-field__error-message {
color: hsl(0 72% 51%);
font-size: 12px;
user-select: none;
}

[data-kb-theme="dark"] .time-field__label {
color: hsl(240 5% 84%);
}

[data-kb-theme="dark"] .time-field__field {
background-color: hsl(240 4% 16%);
border: 1px solid hsl(240 5% 34%);
color: hsl(0 100% 100% / 0.9);
}

.time-field__field[data-invalid] {
border-color: hsl(0 72% 51%);
outline-color: hsl(0 72% 51%);
}

[data-kb-theme="dark"] .time-field__segment[data-placeholder] {
color: hsl(0 100% 100% / 0.5);
}

[data-kb-theme="dark"] .time-field__description {
color: hsl(240 5% 65%);
}
273 changes: 273 additions & 0 deletions apps/docs/src/examples/time-field.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
import {
Time,
getLocalTimeZone,
parseZonedDateTime,
toCalendarDateTime,
today,
} from "@internationalized/date";
import { createSignal } from "solid-js";
import { createDateFormatter } from "../../../../packages/core/src/i18n";
import { TimeField } from "../../../../packages/core/src/time-field";
import style from "./time-field.module.css";

export function BasicExample() {
return (
<TimeField class={style["time-field"]}>
<TimeField.Label class={style["time-field__label"]}>
Event time
</TimeField.Label>
<TimeField.Field class={style["time-field__field"]}>
{(segment) => (
<TimeField.Segment
class={style["time-field__segment"]}
segment={segment()}
/>
)}
</TimeField.Field>
</TimeField>
);
}

export function DefaultValueExample() {
return (
<TimeField class={style["time-field"]} defaultValue={new Time(11, 45)}>
<TimeField.Field class={style["time-field__field"]}>
{(segment) => (
<TimeField.Segment
class={style["time-field__segment"]}
segment={segment()}
/>
)}
</TimeField.Field>
</TimeField>
);
}

export function ControlledValueExample() {
const [value, setValue] = createSignal(new Time(9, 45));

const dateFormatter = createDateFormatter({
hour12: true,
timeStyle: "short",
});

return (
<>
<TimeField
class={style["time-field"]}
value={value()}
onChange={setValue}
>
<TimeField.Field class={style["time-field__field"]}>
{(segment) => (
<TimeField.Segment
class={style["time-field__segment"]}
segment={segment()}
/>
)}
</TimeField.Field>
</TimeField>
<p class="not-prose text-sm mt-4">
Selected time:{" "}
{value()
? dateFormatter().format(
toCalendarDateTime(today(getLocalTimeZone()), value()).toDate(
getLocalTimeZone(),
),
)
: "––"}
</p>
</>
);
}

export function TimeZoneExample() {
return (
<TimeField
class={style["time-field"]}
defaultValue={parseZonedDateTime("2022-11-07T00:45[America/Los_Angeles]")}
>
<TimeField.Field class={style["time-field__field"]}>
{(segment) => (
<TimeField.Segment
class={style["time-field__segment"]}
segment={segment()}
/>
)}
</TimeField.Field>
</TimeField>
);
}

export function GranularityExample() {
return (
<TimeField class={style["time-field"]} granularity="second">
<TimeField.Field class={style["time-field__field"]}>
{(segment) => (
<TimeField.Segment
class={style["time-field__segment"]}
segment={segment()}
/>
)}
</TimeField.Field>
</TimeField>
);
}

export function MinMaxExample() {
return (
<TimeField
class={style["time-field"]}
defaultValue={new Time(9, 45)}
minValue={new Time(9)}
maxValue={new Time(17)}
>
<TimeField.Field class={style["time-field__field"]}>
{(segment) => (
<TimeField.Segment
class={style["time-field__segment"]}
segment={segment()}
/>
)}
</TimeField.Field>
<TimeField.ErrorMessage class={style["time-field__error-message"]}>
Select time between 9 AM and 5 PM.
</TimeField.ErrorMessage>
</TimeField>
);
}

export function PlaceholderValueExample() {
return (
<TimeField class={style["time-field"]} placeholderValue={new Time(9)}>
<TimeField.Field class={style["time-field__field"]}>
{(segment) => (
<TimeField.Segment
class={style["time-field__segment"]}
segment={segment()}
/>
)}
</TimeField.Field>
</TimeField>
);
}

export function HideTimeZoneExample() {
return (
<TimeField
class={style["time-field"]}
defaultValue={parseZonedDateTime("2022-11-07T00:45[America/Los_Angeles]")}
hideTimeZone
>
<TimeField.Field class={style["time-field__field"]}>
{(segment) => (
<TimeField.Segment
class={style["time-field__segment"]}
segment={segment()}
/>
)}
</TimeField.Field>
</TimeField>
);
}

export function HourCycleExample() {
return (
<TimeField class={style["time-field"]} hourCycle={24}>
<TimeField.Field class={style["time-field__field"]}>
{(segment) => (
<TimeField.Segment
class={style["time-field__segment"]}
segment={segment()}
/>
)}
</TimeField.Field>
</TimeField>
);
}

export function DescriptionExample() {
return (
<TimeField class={style["time-field"]}>
<TimeField.Label class={style["time-field__label"]}>Time</TimeField.Label>
<TimeField.Field class={style["time-field__field"]}>
{(segment) => (
<TimeField.Segment
class={style["time-field__segment"]}
segment={segment()}
/>
)}
</TimeField.Field>
<TimeField.Description class={style["time-field__description"]}>
Select a meeting time.
</TimeField.Description>
</TimeField>
);
}

export function ErrorMessageExample() {
const [value, setValue] = createSignal(undefined);

return (
<TimeField
class={style["time-field"]}
value={value()}
onChange={setValue}
validationState={value() === undefined ? "invalid" : "valid"}
>
<TimeField.Label class={style["time-field__label"]}>Time</TimeField.Label>
<TimeField.Field class={style["time-field__field"]}>
{(segment) => (
<TimeField.Segment
class={style["time-field__segment"]}
segment={segment()}
/>
)}
</TimeField.Field>
<TimeField.ErrorMessage class={style["time-field__error-message"]}>
Please select a time.
</TimeField.ErrorMessage>
</TimeField>
);
}

export function HTMLFormExample() {
let formRef: HTMLFormElement | undefined;

const onSubmit = (e: SubmitEvent) => {
e.preventDefault();
e.stopPropagation();

const formData = new FormData(formRef);

alert(JSON.stringify(Object.fromEntries(formData), null, 2));
};

return (
<form
ref={formRef}
onSubmit={onSubmit}
class="flex flex-col items-center space-y-6"
>
<TimeField class={style["time-field"]} name="time">
<TimeField.Field class={style["time-field__field"]}>
{(segment) => (
<TimeField.Segment
class={style["time-field__segment"]}
segment={segment()}
/>
)}
</TimeField.Field>
<TimeField.HiddenInput />
</TimeField>
<div class="flex space-x-2">
<button type="reset" class="kb-button">
Reset
</button>
<button type="submit" class="kb-button-primary">
Submit
</button>
</div>
</form>
);
}
5 changes: 5 additions & 0 deletions apps/docs/src/routes/docs/core.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,11 @@ const CORE_NAV_SECTIONS: NavSection[] = [
title: "Text Field",
href: "/docs/core/components/text-field",
},
{
title: "Time Field",
href: "/docs/core/components/time-field",
status: "new",
},
{
title: "Toast",
href: "/docs/core/components/toast",
Expand Down
Loading
Loading