diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d9417c1..e69079b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -17,7 +17,8 @@ "vscode": { "extensions": [ "oxc.oxc-vscode", - "bradlc.vscode-tailwindcss" + "bradlc.vscode-tailwindcss", + "tekumara.typos-vscode" ] } } diff --git a/client/public/icons.svg b/client/public/icons.svg index 97fc6c1..3e3f6cd 100644 --- a/client/public/icons.svg +++ b/client/public/icons.svg @@ -39,20 +39,21 @@ d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h280v80zm440-160-55-58 102-102H360v-80h327L585-622l55-58 200 200z" /> - + - + - + + + + + \ No newline at end of file diff --git a/client/src/Error.tsx b/client/src/Error.tsx new file mode 100644 index 0000000..e69de29 diff --git a/client/src/Icon.tsx b/client/src/Icon.tsx index d7b59da..d351854 100644 --- a/client/src/Icon.tsx +++ b/client/src/Icon.tsx @@ -1,6 +1,6 @@ import type { VoidProps } from "solid-js"; -export const ICON_NAME = { +export const ICON_ID = { CALENDAR_MONTH: "calendar-month", CALENDAR_MONTH_FILLED: "calendar-month-filled", GROUP: "group", @@ -12,10 +12,11 @@ export const ICON_NAME = { ADD: "add", EDIT: "edit", CLOSE: "close", + ARROW_BACK: "arrow-back", } as const; -type IconName = (typeof ICON_NAME)[keyof typeof ICON_NAME]; -type Properties = VoidProps<{ name: IconName; class?: string }>; +export type IconId = (typeof ICON_ID)[keyof typeof ICON_ID]; +type Properties = VoidProps<{ name: IconId; class?: string }>; export default function Icon(properties: Properties) { return ( diff --git a/client/src/Title.tsx b/client/src/Title.tsx index ea82063..72166b4 100644 --- a/client/src/Title.tsx +++ b/client/src/Title.tsx @@ -6,6 +6,7 @@ import { type Accessor, type ParentProps, type Signal, + type VoidProps, } from "solid-js"; const context = createContext | undefined>(undefined); @@ -29,15 +30,16 @@ export function useTitle(): Accessor { return title; } -export function Title(properties: { children: string }) { - console.debug("Setting title to", properties.children); +//TODO figure out a better API or name as the property has the same name as the component +export function Title(properties: VoidProps<{ title: string }>) { + console.debug("Setting title to", properties.title); const titleSignal = useContext(context); if (!titleSignal) throw new Error("Title must be used within a TitleProvider"); const [, setTitle] = titleSignal; createEffect(() => { - setTitle(properties.children); + setTitle(properties.title); }); - return null; + return undefined; } diff --git a/client/src/TopAppBar.tsx b/client/src/TopAppBar.tsx new file mode 100644 index 0000000..167a2a9 --- /dev/null +++ b/client/src/TopAppBar.tsx @@ -0,0 +1,50 @@ +import type { JSX, VoidProps } from "solid-js"; + +import Icon, { type IconId } from "./Icon"; + +export type ActionButtonProperties = JSX.ButtonHTMLAttributes & { + //TODO the position is redundant in component properties and this property + position: "leading" | "trailing"; + icon: { + name: IconId; + alternativeText: string; + }; +}; + +/** + * @deprecated use the CSS class "action-button" instead as it does not require JS to overwrite CSS + * classes and uses CSS layering instead + */ +export function ActionButton(properties: VoidProps) { + const { icon, ...buttonProperties } = properties; + + /* Before element used to guarantee minimum touch target size on coarse pointer devices */ + return ( + {icon.alternativeText} + + ); +} + +export type TopAppBarProperties = { + title: string; + leadingAction?: JSX.Element; + trailingAction?: JSX.Element; +}; + +export function TopAppBar(properties: VoidProps) { + return ( +
+ {properties.leadingAction} +

+ {properties.title} +

+ {properties.trailingAction} +
+ ); +} diff --git a/client/src/branded.ts b/client/src/branded.ts new file mode 100644 index 0000000..4bbca88 --- /dev/null +++ b/client/src/branded.ts @@ -0,0 +1,2 @@ +export type UserId = string & { __brand: "UserId" }; +export type OrganizationId = string & { __brand: "OrganizationId" }; diff --git a/client/src/index.css b/client/src/index.css index 85bc148..fbfebc5 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -29,4 +29,12 @@ @apply border-outline-variant text-on-surface-variant rounded-full border px-6 py-4; } } + + .action-button { + @apply relative p-3 before:absolute before:top-1/2 before:left-1/2 before:size-[max(100%,44px)] before:-translate-1/2 data-[position=leading]:col-start-1 data-[position=trailing]:col-start-3 pointer-fine:before:hidden; + } + + .floating-action-button { + @apply rounded-medium bg-primary text-on-primary focus-within:outline-primary absolute right-6 bottom-6 size-14 cursor-default content-center justify-items-center outline-offset-4 focus:outline-none; + } } diff --git a/client/src/routes/_app/organizations.tsx b/client/src/routes/_app/organizations/index.tsx similarity index 67% rename from client/src/routes/_app/organizations.tsx rename to client/src/routes/_app/organizations/index.tsx index 8d47453..1c94ebc 100644 --- a/client/src/routes/_app/organizations.tsx +++ b/client/src/routes/_app/organizations/index.tsx @@ -1,10 +1,10 @@ -import { createFileRoute } from "@tanstack/solid-router"; +import { createFileRoute, Link } from "@tanstack/solid-router"; import { createResource, For, type VoidProps } from "solid-js"; -import { Title } from "../../Title"; -import { useUserContext } from "../../userContext"; +import { Title } from "../../../Title"; +import { useUserContext } from "../../../userContext"; -export const Route = createFileRoute("/_app/organizations")({ +export const Route = createFileRoute("/_app/organizations/")({ component: Organizations, }); @@ -17,14 +17,14 @@ type Organization = { function Card(properties: VoidProps<{ organization: Organization }>) { return (
  • - {/* TODO accessbility, the whole content should not be wrapped in a link as that messes withs screen readers */} - - Open - {properties.organization.name} - + Open {properties.organization.name} +
  • ); } @@ -46,7 +46,7 @@ function Organizations() { }); return ( <> - Organizations + {/* TODO overflow, pagination, scrolling */}