Start time grid #62

Merged
stne3960 merged 30 commits from start_time_grid into main 2026-01-16 14:17:09 +01:00
13 changed files with 737 additions and 461 deletions
Showing only changes of commit e966572c2b - Show all commits

View File

@ -1,5 +1,5 @@
import { BrowserRouter, Route, Routes } from "react-router";
import ComponentLibrary from "./studentportalen/ComponentLibrary.tsx";
import ComponentLibrary from "./studentportalen/ComponentLibrary/ComponentLibrary";
import Layout from "./studentportalen/Layout.tsx";
export default function Studentportalen() {

View File

@ -1,460 +0,0 @@
import { useState, useEffect } from "react";
import Button from "../components/Button/Button";
import TextInput from "../components/TextInput/TextInput";
import { SearchIcon } from "../components/Icon/Icon";
import ListItem from "../components/ListItem/ListItem";
import SearchResultList from "../components/SearchResultList/SearchResultList";
import Combobox from "../components/Combobox/Combobox";
import ListCard from "../components/ListCard/ListCard";
import ParticipantPicker from "../components/ParticipantPicker/ParticipantPicker";
interface Person {
value: string;
label: string;
subtitle: string;
}
const peopleOptions: Person[] = [
{ value: "1", label: "Lennart Johansson", subtitle: "lejo1891" },
{ value: "2", label: "Mats Rubarth", subtitle: "matsrub1891" },
{ value: "3", label: "Daniel Tjernström", subtitle: "datj1891" },
{ value: "4", label: "Johan Mjällby", subtitle: "jomj1891" },
{ value: "5", label: "Krister Nordin", subtitle: "krno1891" },
{ value: "6", label: "Kurre Hamrin", subtitle: "kuha1891" },
];
const getPersonValue = (person: Person) => person.value;
const getPersonLabel = (person: Person) => person.label;
const getPersonSubtitle = (person: Person) => person.subtitle;
export default function ComponentLibrary() {
const [darkMode, setDarkMode] = useState(() => {
return document.documentElement.classList.contains("dark");
});
const [selectedPerson, setSelectedPerson] = useState<string>("");
const [selectedPeople, setSelectedPeople] = useState<string[]>([]);
const [participants, setParticipants] = useState<string[]>([]);
useEffect(() => {
if (darkMode) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
}, [darkMode]);
return (
<>
<h1>Component Library</h1>
<section className="mt-lg">
<h2 className="mb-md">Dark Mode</h2>
<Button variant="primary" onClick={() => setDarkMode(!darkMode)}>
{darkMode ? "Light Mode" : "Dark Mode"}
</Button>
</section>
<section className="mt-lg">
<h2 className="mb-md">Button Variants</h2>
<div className="flex flex-wrap gap-md">
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="red">Red</Button>
<Button variant="green">Green</Button>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Button Sizes</h2>
<div className="flex flex-wrap items-center gap-md">
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Text Input Sizes</h2>
<div className="flex flex-wrap items-center gap-md">
<TextInput
size="sm"
placeholder="Small"
label="Small input"
hideLabel
/>
<TextInput
size="md"
placeholder="Medium"
label="Medium input"
hideLabel
/>
<TextInput
size="lg"
placeholder="Large"
label="Large input"
hideLabel
/>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Text Input with Icon</h2>
<div className="flex flex-wrap items-center gap-md">
<TextInput
size="sm"
placeholder="Small with icon"
Icon={SearchIcon}
label="Small search"
hideLabel
/>
<TextInput
size="md"
placeholder="Medium with icon"
Icon={SearchIcon}
label="Medium search"
hideLabel
/>
<TextInput
size="lg"
placeholder="Large with icon"
Icon={SearchIcon}
label="Large search"
hideLabel
/>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Text Input States</h2>
<div className="flex flex-wrap items-center gap-md">
<TextInput placeholder="Default" label="Default state" hideLabel />
<TextInput
placeholder="Error state"
error
label="Error state"
hideLabel
/>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Text Input With/Without Placeholder</h2>
<div className="flex flex-wrap items-center gap-md">
<TextInput
placeholder="Placeholder"
label="With placeholder"
hideLabel
/>
<TextInput label="Without placeholder" hideLabel />
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Text Input Width Options</h2>
<div className="flex flex-col gap-md">
<TextInput
placeholder="Full width"
fullWidth
label="Full width input"
hideLabel
/>
<TextInput
placeholder="Custom width"
customWidth="300px"
label="Custom width input"
hideLabel
/>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Text Input with Label</h2>
<div className="flex flex-wrap items-start gap-md">
<TextInput label="Email" placeholder="Enter your email" />
<TextInput label="Password" placeholder="Enter password" error />
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Text Input with Label and Message</h2>
<div className="flex flex-wrap items-start gap-md">
<TextInput
label="Email"
placeholder="Enter your email"
error
message="This field is required"
/>
<TextInput
label="Username"
placeholder="Choose a username"
error
message="Must be at least 3 characters"
/>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">List Item</h2>
<div className="max-w-96 border border-base-ink-soft rounded-(--border-radius-md) overflow-hidden">
<ListItem title="Lennart Johansson" subtitle="lejo1891" />
<ListItem title="Mats Rubarth" subtitle="matsrub1891" />
<ListItem title="Daniel Tjernström" subtitle="datj1891" selected />
<ListItem title="Johan Mjällby" subtitle="jomj1891" />
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">List Item - Title Only</h2>
<div className="max-w-96 border border-base-ink-soft rounded-(--border-radius-md) overflow-hidden">
<ListItem title="Krister Nordin" />
<ListItem title="Kurre Hamrin" selected />
<ListItem title="Per Karlsson" />
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">SearchResultList</h2>
<div className="max-w-96">
<SearchResultList
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
selectedValues={["3"]}
focusedIndex={1}
noResultsText="Inga resultat"
/>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">SearchResultList - Empty</h2>
<div className="max-w-96">
<SearchResultList
options={[]}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
noResultsText="Inga resultat"
/>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Combobox - Single Select</h2>
<div className="flex flex-col gap-md">
<Combobox
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
value={selectedPerson}
onChange={(v) => setSelectedPerson(v as string)}
placeholder="Sök..."
label="Välj person"
/>
<p className="body-light-sm text-base-ink-placeholder">
Selected:{" "}
{selectedPerson
? peopleOptions.find((p) => p.value === selectedPerson)?.label
: "None"}
</p>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Combobox - Multi Select</h2>
<div className="flex flex-col gap-md">
<Combobox
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
value={selectedPeople}
onChange={(v) => setSelectedPeople(v as string[])}
placeholder="Sök..."
label="Välj personer"
multiple
/>
<p className="body-light-sm text-base-ink-placeholder">
Selected:{" "}
{selectedPeople.length > 0
? selectedPeople
.map((v) => peopleOptions.find((p) => p.value === v)?.label)
.join(", ")
: "None"}
</p>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Combobox - Sizes</h2>
<div className="flex flex-wrap items-start gap-md">
<Combobox
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
placeholder="Small"
size="sm"
label="Small"
hideLabel
/>
<Combobox
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
placeholder="Medium"
size="md"
label="Medium"
hideLabel
/>
<Combobox
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
placeholder="Large"
size="lg"
label="Large"
hideLabel
/>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Combobox - Custom Width</h2>
<div className="flex flex-col gap-md">
<Combobox
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
placeholder="Sök..."
customWidth="350px"
label="Custom width"
hideLabel
/>
<Combobox
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
placeholder="Sök..."
label="Full width"
fullWidth
/>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">ListCard</h2>
<div className="flex flex-col gap-md max-w-96">
<ListCard title="Lennart Johansson" onRemove={() => {}} />
<ListCard title="Mats Rubarth" onRemove={() => {}} />
<ListCard title="Daniel Tjernström" />
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">ParticipantPicker</h2>
<ParticipantPicker
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
value={participants}
onChange={setParticipants}
placeholder="Sök deltagare..."
label="Välj deltagare"
/>
</section>
<section className="mt-lg">
<h2 className="mb-md">ParticipantPicker - Sizes</h2>
<div className="flex flex-wrap items-start gap-md">
<ParticipantPicker
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
value={participants}
onChange={setParticipants}
placeholder="Small"
size="sm"
label="Small"
hideLabel
/>
<ParticipantPicker
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
value={participants}
onChange={setParticipants}
placeholder="Medium"
size="md"
label="Medium"
hideLabel
/>
<ParticipantPicker
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
value={participants}
onChange={setParticipants}
placeholder="Large"
size="lg"
label="Large"
hideLabel
/>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">ParticipantPicker - Custom Width</h2>
<div className="flex flex-col gap-md">
<ParticipantPicker
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
value={participants}
onChange={setParticipants}
placeholder="Sök..."
customWidth="350px"
label="Custom width"
hideLabel
/>
<ParticipantPicker
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
value={participants}
onChange={setParticipants}
placeholder="Sök..."
label="Full width"
fullWidth
/>
</div>
</section>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
</>
);
}

View File

@ -0,0 +1,26 @@
import Button from "../../components/Button/Button";
export default function ButtonSection() {
return (
<>
<section>
<h2 className="mb-md">Button Variants</h2>
<div className="flex flex-wrap gap-md">
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="red">Red</Button>
<Button variant="green">Green</Button>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Button Sizes</h2>
<div className="flex flex-wrap items-center gap-md">
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
</div>
</section>
</>
);
}

View File

@ -0,0 +1,125 @@
import { useState } from "react";
import Combobox from "../../components/Combobox/Combobox";
import {
peopleOptions,
getPersonValue,
getPersonLabel,
getPersonSubtitle,
} from "./data";
export default function ComboboxSection() {
const [selectedPerson, setSelectedPerson] = useState<string>("");
const [selectedPeople, setSelectedPeople] = useState<string[]>([]);
return (
<>
<section>
<h2 className="mb-md">Combobox - Single Select</h2>
<div className="flex flex-col gap-md">
<Combobox
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
value={selectedPerson}
onChange={(v) => setSelectedPerson(v as string)}
placeholder="Sök..."
label="Välj person"
/>
<p className="body-light-sm text-base-ink-placeholder">
Selected:{" "}
{selectedPerson
? peopleOptions.find((p) => p.value === selectedPerson)?.label
: "None"}
</p>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Combobox - Multi Select</h2>
<div className="flex flex-col gap-md">
<Combobox
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
value={selectedPeople}
onChange={(v) => setSelectedPeople(v as string[])}
placeholder="Sök..."
label="Välj personer"
multiple
/>
<p className="body-light-sm text-base-ink-placeholder">
Selected:{" "}
{selectedPeople.length > 0
? selectedPeople
.map((v) => peopleOptions.find((p) => p.value === v)?.label)
.join(", ")
: "None"}
</p>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Combobox - Sizes</h2>
<div className="flex flex-wrap items-start gap-md">
<Combobox
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
placeholder="Small"
size="sm"
label="Small"
hideLabel
/>
<Combobox
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
placeholder="Medium"
size="md"
label="Medium"
hideLabel
/>
<Combobox
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
placeholder="Large"
size="lg"
label="Large"
hideLabel
/>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Combobox - Custom Width</h2>
<div className="flex flex-col gap-md">
<Combobox
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
placeholder="Sök..."
customWidth="350px"
label="Custom width"
hideLabel
/>
<Combobox
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
placeholder="Sök..."
label="Full width"
fullWidth
/>
</div>
</section>
</>
);
}

View File

@ -0,0 +1,64 @@
import { useState, useEffect } from "react";
import Sidebar, { type ComponentCategory } from "./Sidebar";
import ButtonSection from "./ButtonSection";
import TextInputSection from "./TextInputSection";
import DropdownSection from "./DropdownSection";
import ListItemSection from "./ListItemSection";
import SearchResultListSection from "./SearchResultListSection";
import ComboboxSection from "./ComboboxSection";
import ListCardSection from "./ListCardSection";
import ParticipantPickerSection from "./ParticipantPickerSection";
export default function ComponentLibrary() {
const [darkMode, setDarkMode] = useState(() => {
return document.documentElement.classList.contains("dark");
});
const [selectedCategory, setSelectedCategory] =
useState<ComponentCategory>("Button");
useEffect(() => {
if (darkMode) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
}, [darkMode]);
const renderContent = () => {
switch (selectedCategory) {
case "Button":
return <ButtonSection />;
case "TextInput":
return <TextInputSection />;
case "Dropdown":
return <DropdownSection />;
case "ListItem":
return <ListItemSection />;
case "SearchResultList":
return <SearchResultListSection />;
case "Combobox":
return <ComboboxSection />;
case "ListCard":
return <ListCardSection />;
case "ParticipantPicker":
return <ParticipantPickerSection />;
default:
return null;
}
};
return (
<div className="flex flex-1 min-h-0">
<Sidebar
selectedCategory={selectedCategory}
onSelectCategory={setSelectedCategory}
darkMode={darkMode}
onToggleDarkMode={() => setDarkMode(!darkMode)}
/>
<main className="flex-1 overflow-y-auto p-lg">
<h1 className="mb-lg">{selectedCategory}</h1>
{renderContent()}
</main>
</div>
);
}

View File

@ -0,0 +1,137 @@
import { useState } from "react";
import Dropdown from "../../components/Dropdown/Dropdown";
import { peopleOptions, getPersonValue, getPersonLabel } from "./data";
export default function DropdownSection() {
const [selectedPerson, setSelectedPerson] = useState<string>("");
return (
<>
<section>
<h2 className="mb-md">Dropdown Sizes</h2>
<div className="flex flex-wrap items-center gap-md">
<Dropdown
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
placeholder="Small"
size="sm"
label="Small dropdown"
hideLabel
/>
<Dropdown
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
placeholder="Medium"
size="md"
label="Medium dropdown"
hideLabel
/>
<Dropdown
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
placeholder="Large"
size="lg"
label="Large dropdown"
hideLabel
/>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Dropdown States</h2>
<div className="flex flex-wrap items-center gap-md">
<Dropdown
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
placeholder="Default"
label="Default state"
hideLabel
/>
<Dropdown
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
placeholder="Error state"
error
label="Error state"
hideLabel
/>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Dropdown with Label</h2>
<div className="flex flex-wrap items-start gap-md">
<Dropdown
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
placeholder="Select person"
label="Person"
value={selectedPerson}
onChange={(value) => setSelectedPerson(value)}
/>
<Dropdown
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
placeholder="Select person"
label="Person (error)"
error
/>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Dropdown with Label and Message</h2>
<div className="flex flex-wrap items-start gap-md">
<Dropdown
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
placeholder="Select person"
label="Person"
message="Please select a person"
/>
<Dropdown
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
placeholder="Select person"
label="Person"
error
message="This field is required"
/>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Dropdown Width Options</h2>
<div className="flex flex-col gap-md">
<Dropdown
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
placeholder="Full width"
fullWidth
label="Full width dropdown"
hideLabel
/>
<Dropdown
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
placeholder="Custom width"
customWidth="300px"
label="Custom width dropdown"
hideLabel
/>
</div>
</section>
</>
);
}

View File

@ -0,0 +1,14 @@
import ListCard from "../../components/ListCard/ListCard";
export default function ListCardSection() {
return (
<section>
<h2 className="mb-md">ListCard</h2>
<div className="flex flex-col gap-md max-w-96">
<ListCard title="Lennart Johansson" onRemove={() => {}} />
<ListCard title="Mats Rubarth" onRemove={() => {}} />
<ListCard title="Daniel Tjernström" />
</div>
</section>
);
}

View File

@ -0,0 +1,26 @@
import ListItem from "../../components/ListItem/ListItem";
export default function ListItemSection() {
return (
<>
<section>
<h2 className="mb-md">List Item</h2>
<div className="max-w-96 border border-base-ink-soft rounded-(--border-radius-md) overflow-hidden">
<ListItem title="Lennart Johansson" subtitle="lejo1891" />
<ListItem title="Mats Rubarth" subtitle="matsrub1891" />
<ListItem title="Daniel Tjernström" subtitle="datj1891" selected />
<ListItem title="Johan Mjällby" subtitle="jomj1891" />
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">List Item - Title Only</h2>
<div className="max-w-96 border border-base-ink-soft rounded-(--border-radius-md) overflow-hidden">
<ListItem title="Krister Nordin" />
<ListItem title="Kurre Hamrin" selected />
<ListItem title="Per Karlsson" />
</div>
</section>
</>
);
}

View File

@ -0,0 +1,101 @@
import { useState } from "react";
import ParticipantPicker from "../../components/ParticipantPicker/ParticipantPicker";
import {
peopleOptions,
getPersonValue,
getPersonLabel,
getPersonSubtitle,
} from "./data";
export default function ParticipantPickerSection() {
const [participants, setParticipants] = useState<string[]>([]);
return (
<>
<section>
<h2 className="mb-md">ParticipantPicker</h2>
<ParticipantPicker
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
value={participants}
onChange={setParticipants}
placeholder="Sök deltagare..."
label="Välj deltagare"
/>
</section>
<section className="mt-lg">
<h2 className="mb-md">ParticipantPicker - Sizes</h2>
<div className="flex flex-wrap items-start gap-md">
<ParticipantPicker
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
value={participants}
onChange={setParticipants}
placeholder="Small"
size="sm"
label="Small"
hideLabel
/>
<ParticipantPicker
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
value={participants}
onChange={setParticipants}
placeholder="Medium"
size="md"
label="Medium"
hideLabel
/>
<ParticipantPicker
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
value={participants}
onChange={setParticipants}
placeholder="Large"
size="lg"
label="Large"
hideLabel
/>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">ParticipantPicker - Custom Width</h2>
<div className="flex flex-col gap-md">
<ParticipantPicker
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
value={participants}
onChange={setParticipants}
placeholder="Sök..."
customWidth="350px"
label="Custom width"
hideLabel
/>
<ParticipantPicker
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
value={participants}
onChange={setParticipants}
placeholder="Sök..."
label="Full width"
fullWidth
/>
</div>
</section>
</>
);
}

View File

@ -0,0 +1,40 @@
import SearchResultList from "../../components/SearchResultList/SearchResultList";
import {
peopleOptions,
getPersonValue,
getPersonLabel,
getPersonSubtitle,
} from "./data";
export default function SearchResultListSection() {
return (
<>
<section>
<h2 className="mb-md">SearchResultList</h2>
<div className="max-w-96">
<SearchResultList
options={peopleOptions}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
getOptionSubtitle={getPersonSubtitle}
selectedValues={["3"]}
focusedIndex={1}
noResultsText="Inga resultat"
/>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">SearchResultList - Empty</h2>
<div className="max-w-96">
<SearchResultList
options={[]}
getOptionValue={getPersonValue}
getOptionLabel={getPersonLabel}
noResultsText="Inga resultat"
/>
</div>
</section>
</>
);
}

View File

@ -0,0 +1,57 @@
import clsx from "clsx";
import Button from "../../components/Button/Button";
export const componentCategories = [
"Button",
"TextInput",
"Dropdown",
"ListItem",
"SearchResultList",
"Combobox",
"ListCard",
"ParticipantPicker",
] as const;
export type ComponentCategory = (typeof componentCategories)[number];
interface SidebarProps {
selectedCategory: ComponentCategory;
onSelectCategory: (category: ComponentCategory) => void;
darkMode: boolean;
onToggleDarkMode: () => void;
}
export default function Sidebar({
selectedCategory,
onSelectCategory,
darkMode,
onToggleDarkMode,
}: SidebarProps) {
return (
<nav className="w-64 shrink-0 border-r border-base-ink-soft bg-base-canvas p-md overflow-y-auto">
<h1 className="mb-md">Components</h1>
<div className="mb-lg">
<Button variant="secondary" size="sm" onClick={onToggleDarkMode}>
{darkMode ? "Light Mode" : "Dark Mode"}
</Button>
</div>
<ul className="flex flex-col gap-xs">
{componentCategories.map((category) => (
<li key={category}>
<button
onClick={() => onSelectCategory(category)}
className={clsx(
"w-full text-left px-md py-sm rounded-(--border-radius-sm) body-normal-md cursor-pointer",
selectedCategory === category
? "bg-primary text-base-canvas"
: "text-base-ink-strong hover:bg-base-ink-soft",
)}
>
{category}
</button>
</li>
))}
</ul>
</nav>
);
}

View File

@ -0,0 +1,128 @@
import TextInput from "../../components/TextInput/TextInput";
import { SearchIcon } from "../../components/Icon/Icon";
export default function TextInputSection() {
return (
<>
<section>
<h2 className="mb-md">Text Input Sizes</h2>
<div className="flex flex-wrap items-center gap-md">
<TextInput
size="sm"
placeholder="Small"
label="Small input"
hideLabel
/>
<TextInput
size="md"
placeholder="Medium"
label="Medium input"
hideLabel
/>
<TextInput
size="lg"
placeholder="Large"
label="Large input"
hideLabel
/>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Text Input with Icon</h2>
<div className="flex flex-wrap items-center gap-md">
<TextInput
size="sm"
placeholder="Small with icon"
Icon={SearchIcon}
label="Small search"
hideLabel
/>
<TextInput
size="md"
placeholder="Medium with icon"
Icon={SearchIcon}
label="Medium search"
hideLabel
/>
<TextInput
size="lg"
placeholder="Large with icon"
Icon={SearchIcon}
label="Large search"
hideLabel
/>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Text Input States</h2>
<div className="flex flex-wrap items-center gap-md">
<TextInput placeholder="Default" label="Default state" hideLabel />
<TextInput
placeholder="Error state"
error
label="Error state"
hideLabel
/>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Text Input With/Without Placeholder</h2>
<div className="flex flex-wrap items-center gap-md">
<TextInput
placeholder="Placeholder"
label="With placeholder"
hideLabel
/>
<TextInput label="Without placeholder" hideLabel />
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Text Input Width Options</h2>
<div className="flex flex-col gap-md">
<TextInput
placeholder="Full width"
fullWidth
label="Full width input"
hideLabel
/>
<TextInput
placeholder="Custom width"
customWidth="300px"
label="Custom width input"
hideLabel
/>
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Text Input with Label</h2>
<div className="flex flex-wrap items-start gap-md">
<TextInput label="Email" placeholder="Enter your email" />
<TextInput label="Password" placeholder="Enter password" error />
</div>
</section>
<section className="mt-lg">
<h2 className="mb-md">Text Input with Label and Message</h2>
<div className="flex flex-wrap items-start gap-md">
<TextInput
label="Email"
placeholder="Enter your email"
error
message="This field is required"
/>
<TextInput
label="Username"
placeholder="Choose a username"
error
message="Must be at least 3 characters"
/>
</div>
</section>
</>
);
}

View File

@ -0,0 +1,18 @@
export interface Person {
value: string;
label: string;
subtitle: string;
}
export const peopleOptions: Person[] = [
{ value: "1", label: "Lennart Johansson", subtitle: "lejo1891" },
{ value: "2", label: "Mats Rubarth", subtitle: "matsrub1891" },
{ value: "3", label: "Daniel Tjernström", subtitle: "datj1891" },
{ value: "4", label: "Johan Mjällby", subtitle: "jomj1891" },
{ value: "5", label: "Krister Nordin", subtitle: "krno1891" },
{ value: "6", label: "Kurre Hamrin", subtitle: "kuha1891" },
];
export const getPersonValue = (person: Person) => person.value;
export const getPersonLabel = (person: Person) => person.label;
export const getPersonSubtitle = (person: Person) => person.subtitle;