Your shifts
Customer support, Dubai · Week of 19 October
- Monday 19 OctoberMorning · 06:00 to 14:00
- Tuesday 20 OctoberMorning · 06:00 to 14:00
- Wednesday 21 OctoberNo shift chosen
- Thursday 22 OctoberEvening · 14:00 to 22:00
- Friday 23 OctoberNo shift chosen
shift-picker.tsx
"use client";
import { useState } from "react";
import { LuMoon, LuSun, LuSunset } from "react-icons/lu";
import { Button, Dialog, RadioField, RadioGroup } from "@nexera-ui/react";
const SHIFTS = [
{
id: "morning",
label: "Morning",
time: "06:00 to 14:00",
icon: <LuSun aria-hidden="true" />,
left: 2,
},
{
id: "evening",
label: "Evening",
time: "14:00 to 22:00",
icon: <LuSunset aria-hidden="true" />,
left: 1,
},
{
id: "night",
label: "Night",
time: "22:00 to 06:00 (+15% night rate)",
icon: <LuMoon aria-hidden="true" />,
left: 3,
},
];
const WEEK = [
{ day: "Monday 19 October", shift: "morning" },
{ day: "Tuesday 20 October", shift: "morning" },
{ day: "Wednesday 21 October", shift: null },
{ day: "Thursday 22 October", shift: "evening" },
{ day: "Friday 23 October", shift: null },
];
/** Shift picker: a rota for the week where open days ask the employee to choose a morning, evening or night shift. */
export default function ShiftPicker() {
const [rota, setRota] = useState(WEEK);
const [editing, setEditing] = useState<string | null>(null);
const [choice, setChoice] = useState<string>("morning");
const shift = (id: string | null) => SHIFTS.find((s) => s.id === id);
return (
<div className="@container bg-page px-4 py-8 @3xl:px-8">
<section
aria-labelledby="rota-title"
className="mx-auto grid w-full max-w-2xl gap-4 rounded-xl border border-default bg-surface p-4 shadow-sm @3xl:p-6"
>
<header className="grid gap-1">
<h2 id="rota-title" className="text-heading-h3 text-primary">
Your shifts
</h2>
<p className="text-body-default text-secondary">
Customer support, Dubai · Week of 19 October
</p>
</header>
<ul className="m-0 grid list-none gap-2 p-0">
{rota.map((r) => {
const s = shift(r.shift);
return (
<li
key={r.day}
className="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-default p-3"
>
<div className="flex items-center gap-3">
<span className="text-muted">{s?.icon}</span>
<div className="grid">
<span className="text-body-strong text-primary">{r.day}</span>
<span
className={`text-body-small ${s ? "text-secondary" : "text-status-warning-fg"}`}
>
{s ? `${s.label} · ${s.time}` : "No shift chosen"}
</span>
</div>
</div>
<Button
variant={s ? "ghost" : "primary"}
aria-label={`${s ? "Change" : "Choose"} shift for ${r.day}`}
onPress={() => {
setChoice(r.shift ?? "morning");
setEditing(r.day);
}}
>
{s ? "Change" : "Choose shift"}
</Button>
</li>
);
})}
</ul>
</section>
<Dialog
title={`Choose a shift for ${editing ?? ""}`}
isOpen={editing !== null}
onOpenChange={(open) => {
if (!open) setEditing(null);
}}
primaryLabel="Confirm shift"
onPrimaryAction={() => {
setRota((w) => w.map((r) => (r.day === editing ? { ...r, shift: choice } : r)));
}}
secondaryLabel="Cancel"
isDismissable
>
<RadioGroup label="Shift" value={choice} onChange={setChoice}>
{SHIFTS.map((s) => (
<RadioField
key={s.id}
value={s.id}
label={s.label}
description={`${s.time} · ${s.left} ${s.left === 1 ? "place" : "places"} left`}
/>
))}
</RadioGroup>
</Dialog>
</div>
);
}
More scheduling and calendar blocks
- Month calendarEvents across a month, with a 'more' overflow.
- Week calendarHour-by-hour week view with events.
- Team availabilityWho is free when, side by side.
- Book a meetingPick a day, then a free time slot.
- Event detailsA popover with time, place and people.
- Leave requestRequest time off and see the balance.
- Leave approvalReview a request and approve or decline.