Start time grid #62
@ -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() {
|
||||
|
||||
@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
57
frontend/src/studentportalen/ComponentLibrary/Sidebar.tsx
Normal file
57
frontend/src/studentportalen/ComponentLibrary/Sidebar.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
18
frontend/src/studentportalen/ComponentLibrary/data.ts
Normal file
18
frontend/src/studentportalen/ComponentLibrary/data.ts
Normal 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;
|
||||
Loading…
x
Reference in New Issue
Block a user