diff --git a/src/components/general/workspace-avatar.tsx b/src/components/general/workspace-avatar.tsx index 4c7bab1..4161d03 100644 --- a/src/components/general/workspace-avatar.tsx +++ b/src/components/general/workspace-avatar.tsx @@ -5,6 +5,7 @@ import { twMerge } from "tailwind-merge"; import profileImageUrls from "@/src/utils/profile-image-urls"; const WorkspaceAvatarSizes = { + xs: { box: "size-[18px] rounded-[4px]", icon: 12 }, sm: { box: "size-8 rounded-default-sm", icon: 16 }, lg: { box: "size-14 rounded-default", icon: 24 }, } as const; diff --git a/src/data/app-data.ts b/src/data/app-data.ts index 3ff3c52..440749a 100644 --- a/src/data/app-data.ts +++ b/src/data/app-data.ts @@ -252,11 +252,16 @@ export const AppData = { * Worker bots the rail lists, most recently active first, before * "See all bots". */ - RailRecentBots: 8, + RailRecentBots: 5, /** - * Stones shown beside "See all bots". + * Workspaces the rail lists, most recently active first, before + * "See all workspaces". */ - RailSeeAllStones: 4, + RailRecentWorkspaces: 5, + /** + * Stones shown beside "See all bots" and "See all workspaces". + */ + RailSeeAllStones: 3, /** * Recent sessions the rail lists under the open bot, before "All * sessions". diff --git a/src/layouts/bots/(partials)/all-workspaces-modal.tsx b/src/layouts/bots/(partials)/all-workspaces-modal.tsx new file mode 100644 index 0000000..3d4c7d1 --- /dev/null +++ b/src/layouts/bots/(partials)/all-workspaces-modal.tsx @@ -0,0 +1,51 @@ +import { useState } from "react"; +import Input from "@moduletrace/twui/form/Input"; +import Span from "@moduletrace/twui/layout/Span"; +import Stack from "@moduletrace/twui/layout/Stack"; +import type { WorkspaceWithActivity } from "@/src/types"; +import AddModalShell from "./add-modal-shell"; +import AllWorkspacesRow from "./all-workspaces-row"; + +type Props = { + open: boolean; + setOpen: (open: boolean) => void; + workspaces: WorkspaceWithActivity[]; +}; + +/** + * Every workspace, latest activity first, with a search by name or + * description. A workspace opens its page. + */ +export default function AllWorkspacesModal({ open, setOpen, workspaces }: Props) { + const [query, setQuery] = useState(""); + const search = query.trim().toLowerCase(); + const found = search + ? workspaces.filter((workspace) => + `${workspace.name} ${workspace.description || ""}`.toLowerCase().includes(search), + ) + : workspaces; + + return ( + + setQuery(e.target.value)} + wrapperWrapperProps={{ className: "mt-4" }} + /> + + {found.map((workspace) => ( + + ))} + {found.length ? null : ( + + No workspace matches "{query.trim()}". + + )} + + + ); +} diff --git a/src/layouts/bots/(partials)/all-workspaces-row.tsx b/src/layouts/bots/(partials)/all-workspaces-row.tsx new file mode 100644 index 0000000..d3d93f0 --- /dev/null +++ b/src/layouts/bots/(partials)/all-workspaces-row.tsx @@ -0,0 +1,37 @@ +import Link from "@moduletrace/twui/layout/Link"; +import Span from "@moduletrace/twui/layout/Span"; +import Stack from "@moduletrace/twui/layout/Stack"; +import WorkspaceAvatar from "@/src/components/general/workspace-avatar"; +import type { WorkspaceWithActivity } from "@/src/types"; +import relativeTime from "@/src/utils/relative-time"; + +type Props = { + workspace: WorkspaceWithActivity; +}; + +/** + * One workspace in the all-workspaces modal: tile, name, description, last + * activity. + */ +export default function AllWorkspacesRow({ workspace }: Props) { + return ( + + + + {workspace.name} + + {workspace.unread ? "New messages" : workspace.description || ""} + + + {workspace.last_activity_at ? ( + + {relativeTime({ at: Number(workspace.last_activity_at) })} + + ) : null} + + ); +} diff --git a/src/layouts/bots/(partials)/rail-bots-section.tsx b/src/layouts/bots/(partials)/rail-bots-section.tsx index fcf8bb8..7a2f2b7 100644 --- a/src/layouts/bots/(partials)/rail-bots-section.tsx +++ b/src/layouts/bots/(partials)/rail-bots-section.tsx @@ -7,6 +7,7 @@ import AllBotsModal from "./all-bots-modal"; import RailBotEntry from "./rail-bot-entry"; import RailSectionLabel from "./rail-section-label"; import RailSeeAllRow from "./rail-see-all-row"; +import RailSeeAllStone from "./rail-see-all-stone"; type Props = { title: string; @@ -55,7 +56,9 @@ export default function RailBotsSection({ title, workers, selected_id, workspace )} {hidden.length ? (