@rightkit/workspace-ui 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE-APACHE +201 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +45 -2
  5. package/dist/client.d.ts +190 -0
  6. package/dist/client.js +2 -0
  7. package/dist/env.d.ts +59 -0
  8. package/dist/hooks/useDrive.d.ts +26 -0
  9. package/dist/hooks/useDrive.js +158 -0
  10. package/dist/host.d.ts +109 -0
  11. package/dist/host.js +33 -0
  12. package/dist/icons.d.ts +34 -0
  13. package/dist/icons.js +33 -0
  14. package/dist/index.d.ts +23 -0
  15. package/dist/index.js +24 -0
  16. package/dist/lib/avatar.d.ts +4 -0
  17. package/dist/lib/avatar.js +13 -0
  18. package/dist/lib/calendarUtils.d.ts +11 -0
  19. package/dist/lib/calendarUtils.js +42 -0
  20. package/dist/lib/vcard.d.ts +12 -0
  21. package/dist/lib/vcard.js +86 -0
  22. package/dist/views/CalendarEventFormModal.d.ts +10 -0
  23. package/dist/views/CalendarEventFormModal.js +19 -0
  24. package/dist/views/CalendarEventPopover.d.ts +10 -0
  25. package/dist/views/CalendarEventPopover.js +10 -0
  26. package/dist/views/CalendarGrids.d.ts +16 -0
  27. package/dist/views/CalendarGrids.js +106 -0
  28. package/dist/views/CalendarView.d.ts +3 -0
  29. package/dist/views/CalendarView.js +332 -0
  30. package/dist/views/ContactsView.d.ts +3 -0
  31. package/dist/views/ContactsView.js +227 -0
  32. package/dist/views/ContactsView.types.d.ts +8 -0
  33. package/dist/views/ContactsView.types.js +7 -0
  34. package/dist/views/DriveFileMenu.d.ts +14 -0
  35. package/dist/views/DriveFileMenu.js +32 -0
  36. package/dist/views/DrivePreviewModal.d.ts +9 -0
  37. package/dist/views/DrivePreviewModal.js +11 -0
  38. package/dist/views/DriveView.d.ts +3 -0
  39. package/dist/views/DriveView.js +185 -0
  40. package/dist/views/MeetView.d.ts +1 -0
  41. package/dist/views/MeetView.js +81 -0
  42. package/dist/views/TasksView.d.ts +3 -0
  43. package/dist/views/TasksView.js +156 -0
  44. package/dist/views/driveFileGlyph.d.ts +7 -0
  45. package/dist/views/driveFileGlyph.js +19 -0
  46. package/dist/views/nav/CalendarNav.d.ts +1 -0
  47. package/dist/views/nav/CalendarNav.js +93 -0
  48. package/dist/views/nav/ContactsNav.d.ts +1 -0
  49. package/dist/views/nav/ContactsNav.js +22 -0
  50. package/dist/views/nav/DriveNav.d.ts +1 -0
  51. package/dist/views/nav/DriveNav.js +44 -0
  52. package/dist/views/nav/TasksNav.d.ts +1 -0
  53. package/dist/views/nav/TasksNav.js +22 -0
  54. package/package.json +70 -3
@@ -0,0 +1,156 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Tasks view (Google Tasks). Extracted from App.tsx (M5) — a prop-less,
3
+ // memoized component that reads its state from the host state and owns its handlers
4
+ // + load-on-mount, so it re-renders only when its own slices change.
5
+ import { memo, useEffect } from "react";
6
+ import { Icon } from "../icons.js";
7
+ import { Button, IconButton, SkeletonRows, Input, Select, Checkbox } from "@rightkit/react-shell/react";
8
+ import { useWorkspaceClient, useWorkspaceHost, useWorkspaceState } from "../host.js";
9
+ function TasksViewInner() {
10
+ const { listTasks, listTaskLists, createTaskList, renameTaskList, deleteTaskList, clearCompletedTasks, setTaskStatus, createTaskIn, deleteTask } = useWorkspaceClient();
11
+ const showDialog = useWorkspaceHost().dialog;
12
+ const activeAccount = useWorkspaceState((s) => s.activeAccount);
13
+ const setErr = useWorkspaceHost().reportError;
14
+ const showUndo = useWorkspaceHost().notify;
15
+ const gtasks = useWorkspaceState((s) => s.gtasks);
16
+ const setGtasks = useWorkspaceState((s) => s.setGtasks);
17
+ const taskLists = useWorkspaceState((s) => s.taskLists);
18
+ const setTaskLists = useWorkspaceState((s) => s.setTaskLists);
19
+ const taskListId = useWorkspaceState((s) => s.taskListId);
20
+ const setTaskListId = useWorkspaceState((s) => s.setTaskListId);
21
+ const taskDue = useWorkspaceState((s) => s.taskDue);
22
+ const setTaskDue = useWorkspaceState((s) => s.setTaskDue);
23
+ const showCompletedTasks = useWorkspaceState((s) => s.showCompletedTasks);
24
+ const setShowCompletedTasks = useWorkspaceState((s) => s.setShowCompletedTasks);
25
+ const listMgrOpen = useWorkspaceState((s) => s.listMgrOpen);
26
+ const setListMgrOpen = useWorkspaceState((s) => s.setListMgrOpen);
27
+ const taskFormOpen = useWorkspaceState((s) => s.taskFormOpen);
28
+ const setTaskFormOpen = useWorkspaceState((s) => s.setTaskFormOpen);
29
+ const taskCreate = useWorkspaceState((s) => s.taskCreate);
30
+ const setTaskCreate = useWorkspaceState((s) => s.setTaskCreate);
31
+ const tasksBusy = useWorkspaceState((s) => s.tasksBusy);
32
+ const setTasksBusy = useWorkspaceState((s) => s.setTasksBusy);
33
+ async function reloadTasks(showCompleted = showCompletedTasks) {
34
+ if (!activeAccount)
35
+ return;
36
+ try {
37
+ setGtasks(await listTasks(activeAccount, showCompleted));
38
+ }
39
+ catch (e) {
40
+ setErr(String(e));
41
+ }
42
+ }
43
+ /** Create / rename / delete task lists (Tasks list manager). */
44
+ async function newTaskList() {
45
+ if (!activeAccount)
46
+ return;
47
+ const title = await showDialog({ kind: "prompt", message: "New list name" });
48
+ if (!title || !title.trim())
49
+ return;
50
+ try {
51
+ await createTaskList(activeAccount, title.trim());
52
+ setTaskLists(await listTaskLists(activeAccount));
53
+ showUndo("List created");
54
+ }
55
+ catch (e) {
56
+ setErr(String(e));
57
+ }
58
+ }
59
+ async function renameTaskListRow(l) {
60
+ if (!activeAccount)
61
+ return;
62
+ const title = await showDialog({ kind: "prompt", message: "Rename list", initial: l.title });
63
+ if (!title || !title.trim() || title.trim() === l.title)
64
+ return;
65
+ try {
66
+ await renameTaskList(activeAccount, l.id, title.trim());
67
+ setTaskLists(await listTaskLists(activeAccount));
68
+ reloadTasks();
69
+ }
70
+ catch (e) {
71
+ setErr(String(e));
72
+ }
73
+ }
74
+ async function deleteTaskListRow(l) {
75
+ if (!activeAccount)
76
+ return;
77
+ if ((await showDialog({ kind: "confirm", message: `Delete the list "${l.title}" and all its tasks? This cannot be undone.` })) !== "ok")
78
+ return;
79
+ try {
80
+ await deleteTaskList(activeAccount, l.id);
81
+ setTaskLists(await listTaskLists(activeAccount));
82
+ reloadTasks();
83
+ showUndo("List deleted");
84
+ }
85
+ catch (e) {
86
+ setErr(String(e));
87
+ }
88
+ }
89
+ async function clearCompletedRow(l) {
90
+ if (!activeAccount)
91
+ return;
92
+ try {
93
+ await clearCompletedTasks(activeAccount, l.id);
94
+ reloadTasks();
95
+ showUndo(`Cleared completed in ${l.title}`);
96
+ }
97
+ catch (e) {
98
+ setErr(String(e));
99
+ }
100
+ }
101
+ function toggleTask(t) {
102
+ if (!activeAccount)
103
+ return;
104
+ const done = t.status === "completed";
105
+ setGtasks((prev) => prev.map((x) => (x.id === t.id ? { ...x, status: done ? "needsAction" : "completed" } : x)));
106
+ setTaskStatus(activeAccount, t.list_id, t.id, !done).catch((e) => setErr(String(e)));
107
+ }
108
+ function deleteTaskRow(t) {
109
+ if (!activeAccount)
110
+ return;
111
+ deleteTask(activeAccount, t.list_id, t.id)
112
+ .then(() => setGtasks((prev) => prev.filter((x) => x.id !== t.id)))
113
+ .catch((e) => setErr(String(e)));
114
+ }
115
+ async function submitTask() {
116
+ if (!activeAccount || !taskCreate.title.trim()) {
117
+ setErr("Task needs a title.");
118
+ return;
119
+ }
120
+ const list = taskListId || taskLists[0]?.id || "@default";
121
+ const due = taskDue ? new Date(taskDue).toISOString() : "";
122
+ try {
123
+ await createTaskIn(activeAccount, list, taskCreate.title, taskCreate.notes, due);
124
+ setTaskFormOpen(false);
125
+ setTaskCreate({ title: "", notes: "" });
126
+ setTaskDue("");
127
+ await reloadTasks();
128
+ }
129
+ catch (e) {
130
+ setErr(String(e));
131
+ }
132
+ }
133
+ // Load lists + tasks on mount / account change (was the `view === "tasks"`
134
+ // branch of App's workspace effect).
135
+ useEffect(() => {
136
+ setTaskFormOpen(false);
137
+ if (!activeAccount)
138
+ return;
139
+ setTasksBusy(true);
140
+ listTaskLists(activeAccount).then(setTaskLists).catch(() => { });
141
+ listTasks(activeAccount)
142
+ .then(setGtasks)
143
+ .catch((e) => setErr(String(e)))
144
+ .finally(() => setTasksBusy(false));
145
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- setTaskFormOpen/setTasksBusy/setGtasks/setErr are store setters with unstable identity; tracking them would reload task lists on every render instead of only on account change.
146
+ }, [activeAccount]);
147
+ return (_jsxs("div", { className: "ws-panel", children: [_jsxs("div", { className: "ws-head", children: [_jsx("h2", { children: "Tasks" }), _jsx("div", { className: "ws-inline-toggle", title: "Include completed tasks", children: _jsx(Checkbox, { label: "Show completed", checked: showCompletedTasks, onChange: (on) => {
148
+ setShowCompletedTasks(on);
149
+ reloadTasks(on);
150
+ } }) }), _jsx(Button, { variant: "secondary", size: "sm", onClick: () => setListMgrOpen((o) => !o), children: "Lists" }), _jsx(Button, { variant: "secondary", size: "sm", onClick: () => {
151
+ setTaskCreate({ title: "", notes: "" });
152
+ setTaskDue("");
153
+ setTaskFormOpen((o) => !o);
154
+ }, children: "+ New task" })] }), listMgrOpen && (_jsxs("div", { className: "ws-form list-mgr", children: [_jsxs("div", { className: "list-mgr-head", children: [_jsx("span", { children: "Task lists" }), _jsx(Button, { variant: "primary", size: "sm", onClick: newTaskList, children: "+ New list" })] }), taskLists.map((l) => (_jsxs("div", { className: "list-mgr-row", children: [_jsx("span", { className: "ws-title", children: l.title }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => clearCompletedRow(l), children: "Clear completed" }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => renameTaskListRow(l), children: "Rename" }), _jsx(Button, { variant: "danger", size: "sm", onClick: () => deleteTaskListRow(l), children: "Delete" })] }, l.id))), taskLists.length === 0 && _jsx("p", { className: "hint", children: "No lists yet." })] })), taskFormOpen && (_jsxs("div", { className: "ws-form", children: [_jsx(Input, { placeholder: "Task title", value: taskCreate.title, onChange: (e) => setTaskCreate({ ...taskCreate, title: e.target.value }) }), _jsx("textarea", { placeholder: "Notes (optional)", value: taskCreate.notes, onChange: (e) => setTaskCreate({ ...taskCreate, notes: e.target.value }) }), _jsxs("label", { className: "ws-form-row", children: [_jsx("span", { children: "Due" }), _jsx(Input, { type: "date", value: taskDue, onChange: (e) => setTaskDue(e.target.value) })] }), taskLists.length > 1 && (_jsx(Select, { label: "Task list", value: taskListId || taskLists[0]?.id || "", options: taskLists.map((l) => ({ value: l.id, label: l.title })), onChange: setTaskListId })), _jsxs("div", { className: "ws-form-foot", children: [_jsx(Button, { variant: "primary", onClick: submitTask, children: "Create" }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setTaskFormOpen(false), children: "Cancel" })] })] })), tasksBusy ? (_jsx(SkeletonRows, {})) : gtasks.length === 0 ? (_jsx("p", { className: "hint", children: "No open tasks." })) : (_jsx("div", { className: "ws-list", children: gtasks.map((t) => (_jsxs("div", { className: "ws-row task-row", children: [_jsx(Checkbox, { label: _jsx("span", { className: "sr-only", children: "Complete" }), checked: t.status === "completed", onChange: () => toggleTask(t) }), _jsx("span", { className: `ws-title ${t.status === "completed" ? "task-done" : ""}`, children: t.title || "(untitled)" }), _jsxs("span", { className: "ws-sub", children: [t.list_title, t.due ? ` · due ${t.due.slice(0, 10)}` : ""] }), _jsx(IconButton, { label: "Delete task", onClick: () => deleteTaskRow(t), children: _jsx(Icon, { name: "trash", size: 14 }) })] }, t.id))) }))] }));
155
+ }
156
+ export const TasksView = memo(TasksViewInner);
@@ -0,0 +1,7 @@
1
+ import type { DriveFile } from "../client.js";
2
+ import type { WorkspaceIconName } from "../icons.js";
3
+ /** File-type to recognizable glyph plus Google's semantic color, or null for themed. */
4
+ export declare function fileGlyph(f: DriveFile): {
5
+ icon: WorkspaceIconName;
6
+ color: string | null;
7
+ };
@@ -0,0 +1,19 @@
1
+ /** File-type to recognizable glyph plus Google's semantic color, or null for themed. */
2
+ export function fileGlyph(f) {
3
+ if (f.is_folder)
4
+ return { icon: "folder", color: null };
5
+ const m = f.mime_type;
6
+ if (m.includes("application/vnd.google-apps.document"))
7
+ return { icon: "doc", color: "#4285F4" };
8
+ if (m.includes("spreadsheet"))
9
+ return { icon: "doc", color: "#0F9D58" };
10
+ if (m.includes("presentation"))
11
+ return { icon: "doc", color: "#F4B400" };
12
+ if (m === "application/pdf")
13
+ return { icon: "doc", color: "#DB4437" };
14
+ if (m.startsWith("image/"))
15
+ return { icon: "image", color: null };
16
+ if (m.startsWith("video/"))
17
+ return { icon: "video", color: null };
18
+ return { icon: "draft", color: null };
19
+ }
@@ -0,0 +1 @@
1
+ export declare function CalendarNav(): import("react").JSX.Element;
@@ -0,0 +1,93 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Calendar's contextual sidebar: Create + a mini-month date picker + a "My
3
+ // calendars" toggle list. Mirrors calendar.google.com's left rail. The mini
4
+ // month drives the main grid's date cursor; the toggles drive which calendars
5
+ // overlay via the shared host state.
6
+ import { Button, IconButton, NavList } from "@rightkit/react-shell/react";
7
+ import { Icon } from "../../icons.js";
8
+ import { DAY, startOfDay } from "../../lib/calendarUtils.js";
9
+ import { useState } from "react";
10
+ import { useWorkspaceHost, useWorkspaceState } from "../../host.js";
11
+ const ALL_ID = "__all";
12
+ const DOW = ["S", "M", "T", "W", "T", "F", "S"];
13
+ function MiniMonth() {
14
+ const calCursor = useWorkspaceState((s) => s.calCursor);
15
+ const setCalCursor = useWorkspaceState((s) => s.setCalCursor);
16
+ const d = new Date(calCursor);
17
+ const first = new Date(d.getFullYear(), d.getMonth(), 1);
18
+ const gridStart = first.getTime() - first.getDay() * DAY;
19
+ const [today] = useState(() => startOfDay(Date.now()));
20
+ const cursorDay = startOfDay(calCursor);
21
+ const month = d.getMonth();
22
+ function shiftMonth(dir) {
23
+ setCalCursor(new Date(d.getFullYear(), d.getMonth() + dir, Math.min(d.getDate(), 28)).getTime());
24
+ }
25
+ return (_jsxs("div", { className: "mini-month", children: [_jsxs("div", { className: "mini-head", children: [_jsx("span", { className: "mini-label", children: d.toLocaleDateString([], { month: "long", year: "numeric" }) }), _jsxs("div", { className: "mini-nav", children: [_jsx(IconButton, { label: "Previous month", className: "mini-arrow", title: "Previous month", onClick: () => shiftMonth(-1), children: _jsx(Icon, { name: "chevron-left", size: 15 }) }), _jsx(IconButton, { label: "Next month", className: "mini-arrow", title: "Next month", onClick: () => shiftMonth(1), children: _jsx(Icon, { name: "chevron-right", size: 15 }) })] })] }), _jsx("div", { className: "mini-dow", children: DOW.map((w, i) => _jsx("span", { children: w }, i)) }), _jsx("div", { className: "mini-grid", children: Array.from({ length: 42 }, (_, i) => {
26
+ const ms = gridStart + i * DAY;
27
+ const cell = new Date(ms);
28
+ const cls = [
29
+ "mini-day",
30
+ cell.getMonth() === month ? "" : "out",
31
+ ms === today ? "today" : "",
32
+ ms === cursorDay ? "sel" : "",
33
+ ].filter(Boolean).join(" ");
34
+ return (_jsx(Button, { variant: "ghost", className: cls, onClick: () => setCalCursor(ms), children: cell.getDate() }, i));
35
+ }) })] }));
36
+ }
37
+ export function CalendarNav() {
38
+ const setPref = useWorkspaceHost().savePreference;
39
+ const calendars = useWorkspaceState((s) => s.calendars);
40
+ const selectedCal = useWorkspaceState((s) => s.selectedCal);
41
+ const shownCals = useWorkspaceState((s) => s.shownCals);
42
+ const setShownCals = useWorkspaceState((s) => s.setShownCals);
43
+ const setEventSeed = useWorkspaceState((s) => s.setEventSeed);
44
+ const allIds = calendars.map((c) => c.id);
45
+ const allShown = calendars.length > 0 && shownCals.length === calendars.length;
46
+ function persist(next) {
47
+ setShownCals(next);
48
+ setPref("shownCals", next);
49
+ }
50
+ /** Checkbox: add/remove one calendar from the overlay set (fine multi-select). */
51
+ function toggle(calId) {
52
+ setShownCals((prev) => {
53
+ const next = prev.includes(calId) ? prev.filter((c) => c !== calId) : [...prev, calId];
54
+ setPref("shownCals", next);
55
+ return next;
56
+ });
57
+ }
58
+ /** Row click: show only this calendar (like clicking a single inbox in Mail). */
59
+ function solo(calId) {
60
+ persist([calId]);
61
+ }
62
+ /** "All calendars": combine every calendar into one view (like All inboxes). */
63
+ function showAll() {
64
+ persist(allIds);
65
+ }
66
+ const calendarGroups = [
67
+ {
68
+ items: [
69
+ {
70
+ id: ALL_ID,
71
+ label: "All calendars",
72
+ ariaLabel: "Combine all calendars into one view",
73
+ icon: _jsx("span", { className: "cal-swatch cal-swatch-all" }),
74
+ },
75
+ ...calendars.map((c) => ({
76
+ id: c.id,
77
+ label: c.summary,
78
+ ariaLabel: `Show only ${c.summary}`,
79
+ icon: _jsx("span", { className: "cal-swatch", style: { background: c.background_color || "var(--copper)" } }),
80
+ })),
81
+ ],
82
+ },
83
+ ];
84
+ return (_jsxs("div", { className: "appnav", children: [_jsxs(Button, { variant: "primary", className: "nav-new", title: "Create event", onClick: () => setEventSeed({ summary: "", withMeet: false }), children: [_jsx(Icon, { name: "plus", size: 20 }), _jsx("span", { children: "Create" })] }), _jsx(MiniMonth, {}), _jsx("div", { className: "labels-head", children: _jsx("span", { children: "My calendars" }) }), calendars.length === 0 ? (_jsx("p", { className: "hint nav-hint", children: "No calendars yet." })) : (_jsx(NavList, { label: "My calendars", groups: calendarGroups, activeId: allShown ? ALL_ID : shownCals.length === 1 ? shownCals[0] : "", onNavigate: (id) => (id === ALL_ID ? showAll() : solo(id)), itemCheck: (item) => {
85
+ const c = calendars.find((x) => x.id === item.id);
86
+ if (!c)
87
+ return undefined;
88
+ return {
89
+ checked: shownCals.length ? shownCals.includes(c.id) : c.id === selectedCal,
90
+ onChange: () => toggle(c.id),
91
+ };
92
+ } }))] }));
93
+ }
@@ -0,0 +1 @@
1
+ export declare function ContactsNav(): import("react").JSX.Element;
@@ -0,0 +1,22 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Contacts' contextual sidebar (interim): Contacts/groups nav. The list, detail
3
+ // and CRUD live in ContactsView for now; Phase 3 lifts those into a shared hook
4
+ // and fills this nav with Create + import/export. Kept minimal so the column
5
+ // reads as intentional rather than empty.
6
+ import { useEffect } from "react";
7
+ import { NavList } from "@rightkit/react-shell/react";
8
+ import { Icon } from "../../icons.js";
9
+ import { useWorkspaceClient, useWorkspaceState } from "../../host.js";
10
+ export function ContactsNav() {
11
+ const { listContactGroups } = useWorkspaceClient();
12
+ const activeAccount = useWorkspaceState((s) => s.activeAccount);
13
+ const contactGroups = useWorkspaceState((s) => s.contactGroups);
14
+ const setContactGroups = useWorkspaceState((s) => s.setContactGroups);
15
+ useEffect(() => {
16
+ if (!activeAccount)
17
+ return;
18
+ listContactGroups(activeAccount).then(setContactGroups).catch(() => setContactGroups([]));
19
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- setContactGroups is a store setter with unstable identity; tracking it would refetch contact groups on every render instead of only on account change.
20
+ }, [activeAccount]);
21
+ return (_jsxs("div", { className: "appnav", children: [_jsx(NavList, { label: "Contacts", groups: [{ items: [{ id: "contacts", label: "Contacts", ariaLabel: "Contacts", icon: _jsx(Icon, { name: "people", size: 18, className: "f-ic" }) }] }], activeId: "contacts", onNavigate: () => { }, itemProps: (_item, { active }) => ({ className: `nav-shelf${active ? " active" : ""}` }) }), contactGroups.length > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { className: "labels-head", children: _jsx("span", { children: "Labels" }) }), _jsx("div", { className: "nav-shelves", children: contactGroups.map((g) => (_jsxs("div", { className: "nav-shelf", title: g.name, children: [_jsx(Icon, { name: "tag", size: 16, className: "f-ic" }), _jsx("span", { className: "f-name", children: g.name }), g.member_count > 0 && _jsx("span", { className: "badge", children: g.member_count })] }, g.resource_name))) })] })), _jsx("p", { className: "hint nav-hint", children: "Search, import/export and dedupe are in the panel." })] }));
22
+ }
@@ -0,0 +1 @@
1
+ export declare function DriveNav(): import("react").JSX.Element;
@@ -0,0 +1,44 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Drive's contextual sidebar: the "New" button + shelf nav (My Drive / Shared /
3
+ // Recent / Starred / Trash) + storage meter. Mirrors drive.google.com's left
4
+ // rail. Shares Drive state with the file grid via useDrive, so picking a shelf
5
+ // here re-lists the grid.
6
+ import { useRef, useState } from "react";
7
+ import { Button, ContextMenu, NavList, Input } from "@rightkit/react-shell/react";
8
+ import { Icon } from "../../icons.js";
9
+ import { formatBytes } from "@rightkit/settings-ui/core";
10
+ import { useDrive } from "../../hooks/useDrive.js";
11
+ import { useWorkspaceState } from "../../host.js";
12
+ const SHELVES = [
13
+ { key: "my", label: "My Drive", icon: "drive" },
14
+ { key: "shared", label: "Shared with me", icon: "people" },
15
+ { key: "recent", label: "Recent", icon: "clock" },
16
+ { key: "starred", label: "Starred", icon: "star" },
17
+ { key: "trash", label: "Trash", icon: "trash" },
18
+ ];
19
+ export function DriveNav() {
20
+ const drive = useDrive();
21
+ const quota = useWorkspaceState((s) => s.driveQuota);
22
+ const [newAnchor, setNewAnchor] = useState(null);
23
+ const fileRef = useRef(null);
24
+ const used = quota ? Number(quota.usage) : 0;
25
+ const limit = quota && quota.limit ? Number(quota.limit) : 0;
26
+ const pct = limit > 0 ? Math.min(100, Math.round((used / limit) * 100)) : 0;
27
+ return (_jsxs("div", { className: "appnav", children: [_jsxs("div", { className: "nav-new-wrap", children: [_jsxs(Button, { variant: "primary", className: "nav-new", onClick: (e) => setNewAnchor((a) => (a ? null : e.currentTarget)), title: "New", children: [_jsx(Icon, { name: "plus", size: 20 }), _jsx("span", { children: "New" })] }), newAnchor && (_jsx(ContextMenu, { anchor: newAnchor, placement: "bottom-start", label: "New", showLead: true, onClose: () => setNewAnchor(null), items: [
28
+ { id: "folder", label: "New folder", icon: _jsx(Icon, { name: "folder", size: 15 }), run: () => drive.newFolder() },
29
+ { id: "upload", label: "Upload files", icon: _jsx(Icon, { name: "upload", size: 15 }), run: () => fileRef.current?.click() },
30
+ ] })), _jsx(Input, { ref: fileRef, type: "file", multiple: true, style: { display: "none" }, onChange: (e) => {
31
+ if (e.target.files && e.target.files.length)
32
+ drive.upload(e.target.files);
33
+ e.target.value = "";
34
+ } })] }), _jsx(NavList, { label: "Drive shelves", groups: [
35
+ {
36
+ items: SHELVES.map((s) => ({
37
+ id: s.key,
38
+ label: s.label,
39
+ ariaLabel: s.label,
40
+ icon: _jsx(Icon, { name: s.icon, size: 18, className: "f-ic" }),
41
+ })),
42
+ },
43
+ ], activeId: drive.driveShelf, onNavigate: (id) => drive.selectShelf(id), itemProps: (_item, { active }) => ({ className: `nav-shelf${active ? " active" : ""}` }) }), quota && (_jsxs("div", { className: "nav-storage", children: [_jsx("div", { className: "storage-bar", children: _jsx("span", { className: "storage-fill", style: { width: `${pct}%` } }) }), _jsxs("div", { className: "storage-text", children: [formatBytes(used), " ", limit > 0 ? `of ${formatBytes(limit)} used` : "used"] })] }))] }));
44
+ }
@@ -0,0 +1 @@
1
+ export declare function TasksNav(): import("react").JSX.Element;
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Tasks' contextual sidebar (interim): the task-list picker. The task items +
3
+ // add/complete live in TasksView; this nav just switches the active list via the
4
+ // shared host state.
5
+ import { NavList } from "@rightkit/react-shell/react";
6
+ import { Icon } from "../../icons.js";
7
+ import { useWorkspaceState } from "../../host.js";
8
+ export function TasksNav() {
9
+ const taskLists = useWorkspaceState((s) => s.taskLists);
10
+ const taskListId = useWorkspaceState((s) => s.taskListId);
11
+ const setTaskListId = useWorkspaceState((s) => s.setTaskListId);
12
+ return (_jsxs("div", { className: "appnav", children: [_jsx("div", { className: "labels-head", children: _jsx("span", { children: "Lists" }) }), taskLists.length === 0 ? (_jsx("p", { className: "hint nav-hint", children: "No task lists yet." })) : (_jsx(NavList, { label: "Task lists", groups: [
13
+ {
14
+ items: taskLists.map((l) => ({
15
+ id: l.id,
16
+ label: l.title,
17
+ ariaLabel: l.title,
18
+ icon: _jsx(Icon, { name: "check", size: 17, className: "f-ic" }),
19
+ })),
20
+ },
21
+ ], activeId: taskListId ?? "", onNavigate: setTaskListId, itemProps: (_item, { active }) => ({ className: `nav-shelf${active ? " active" : ""}` }) }))] }));
22
+ }
package/package.json CHANGED
@@ -1,6 +1,73 @@
1
1
  {
2
2
  "name": "@rightkit/workspace-ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "description": "Calendar, contacts, drive, tasks and meeting views and their navs, over a typed workspace client and state the host supplies.",
5
+ "license": "MIT OR Apache-2.0",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./client": {
15
+ "types": "./dist/client.d.ts",
16
+ "import": "./dist/client.js"
17
+ },
18
+ "./host": {
19
+ "types": "./dist/host.d.ts",
20
+ "import": "./dist/host.js"
21
+ },
22
+ "./lib/avatar": {
23
+ "types": "./dist/lib/avatar.d.ts",
24
+ "import": "./dist/lib/avatar.js"
25
+ },
26
+ "./lib/calendarUtils": {
27
+ "types": "./dist/lib/calendarUtils.d.ts",
28
+ "import": "./dist/lib/calendarUtils.js"
29
+ },
30
+ "./package.json": "./package.json"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "README.md",
35
+ "CHANGELOG.md",
36
+ "LICENSE-MIT",
37
+ "LICENSE-APACHE"
38
+ ],
39
+ "sideEffects": [
40
+ "*.css"
41
+ ],
42
+ "dependencies": {
43
+ "@rightkit/react-shell": "^0.7.3",
44
+ "@rightkit/settings-ui": "^0.7.1",
45
+ "lucide-react": "^1.57.0"
46
+ },
47
+ "peerDependencies": {
48
+ "react": ">=18 <21"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^26.6.6",
52
+ "@types/react": "^19.3.0",
53
+ "@types/react-dom": "^19.3.0",
54
+ "react": "^19.3.1",
55
+ "react-dom": "^19.3.1",
56
+ "tsx": "^4.23.15",
57
+ "typescript": "^7.0.2",
58
+ "vite": "^8.3.4"
59
+ },
60
+ "publishConfig": {
61
+ "registry": "https://registry.npmjs.org",
62
+ "access": "public"
63
+ },
64
+ "repository": {
65
+ "type": "git",
66
+ "url": "git+https://github.com/bogusyogi/rightkit.git",
67
+ "directory": "packages/workspace-ui"
68
+ },
69
+ "scripts": {
70
+ "build": "tsc -p tsconfig.json && node scripts/copy-assets.mjs",
71
+ "typecheck": "tsc -p tsconfig.json --noEmit"
72
+ }
6
73
  }