@hanzo/ui 8.27.8 → 8.27.10
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.
- package/README.md +2 -1
- package/dist/backends/gui/3d-card.cjs +100 -0
- package/dist/backends/gui/3d-card.d.ts +59 -0
- package/dist/backends/gui/3d-card.d.ts.map +1 -0
- package/dist/backends/gui/3d-card.js +92 -0
- package/dist/backends/gui/3d-card.js.map +1 -0
- package/dist/backends/gui/3d-marquee.cjs +225 -0
- package/dist/backends/gui/3d-marquee.d.ts +114 -0
- package/dist/backends/gui/3d-marquee.d.ts.map +1 -0
- package/dist/backends/gui/3d-marquee.js +190 -0
- package/dist/backends/gui/3d-marquee.js.map +1 -0
- package/dist/backends/gui/3d-pin.cjs +51 -0
- package/dist/backends/gui/3d-pin.d.ts +14 -0
- package/dist/backends/gui/3d-pin.d.ts.map +1 -0
- package/dist/backends/gui/3d-pin.js +49 -0
- package/dist/backends/gui/3d-pin.js.map +1 -0
- package/dist/backends/gui/advanced-chart.cjs +61 -0
- package/dist/backends/gui/advanced-chart.d.ts +38 -0
- package/dist/backends/gui/advanced-chart.d.ts.map +1 -0
- package/dist/backends/gui/advanced-chart.js +59 -0
- package/dist/backends/gui/advanced-chart.js.map +1 -0
- package/dist/backends/gui/alert.cjs +106 -0
- package/dist/backends/gui/alert.d.ts +39 -0
- package/dist/backends/gui/alert.d.ts.map +1 -0
- package/dist/backends/gui/alert.js +102 -0
- package/dist/backends/gui/alert.js.map +1 -0
- package/dist/backends/gui/android.cjs +52 -0
- package/dist/backends/gui/android.d.ts +16 -0
- package/dist/backends/gui/android.d.ts.map +1 -0
- package/dist/backends/gui/android.js +50 -0
- package/dist/backends/gui/android.js.map +1 -0
- package/dist/backends/gui/animated-beam.cjs +97 -0
- package/dist/backends/gui/animated-beam.d.ts +57 -0
- package/dist/backends/gui/animated-beam.d.ts.map +1 -0
- package/dist/backends/gui/animated-beam.js +62 -0
- package/dist/backends/gui/animated-beam.js.map +1 -0
- package/dist/backends/gui/animated-cursor.cjs +183 -0
- package/dist/backends/gui/animated-cursor.d.ts +42 -0
- package/dist/backends/gui/animated-cursor.d.ts.map +1 -0
- package/dist/backends/gui/animated-cursor.js +181 -0
- package/dist/backends/gui/animated-cursor.js.map +1 -0
- package/dist/backends/gui/animated-testimonials.cjs +109 -0
- package/dist/backends/gui/animated-testimonials.d.ts +23 -0
- package/dist/backends/gui/animated-testimonials.d.ts.map +1 -0
- package/dist/backends/gui/animated-testimonials.js +74 -0
- package/dist/backends/gui/animated-testimonials.js.map +1 -0
- package/dist/backends/gui/animated-tooltip.cjs +100 -0
- package/dist/backends/gui/animated-tooltip.d.ts +15 -0
- package/dist/backends/gui/animated-tooltip.d.ts.map +1 -0
- package/dist/backends/gui/animated-tooltip.js +65 -0
- package/dist/backends/gui/animated-tooltip.js.map +1 -0
- package/dist/backends/gui/announcement.cjs +54 -0
- package/dist/backends/gui/announcement.d.ts +26 -0
- package/dist/backends/gui/announcement.d.ts.map +1 -0
- package/dist/backends/gui/announcement.js +52 -0
- package/dist/backends/gui/announcement.js.map +1 -0
- package/dist/backends/gui/apple-cards-carousel.cjs +198 -0
- package/dist/backends/gui/apple-cards-carousel.d.ts +45 -0
- package/dist/backends/gui/apple-cards-carousel.d.ts.map +1 -0
- package/dist/backends/gui/apple-cards-carousel.js +162 -0
- package/dist/backends/gui/apple-cards-carousel.js.map +1 -0
- package/dist/backends/gui/apple-hello-effect.cjs +38 -0
- package/dist/backends/gui/apple-hello-effect.d.ts +9 -0
- package/dist/backends/gui/apple-hello-effect.d.ts.map +1 -0
- package/dist/backends/gui/apple-hello-effect.js +36 -0
- package/dist/backends/gui/apple-hello-effect.js.map +1 -0
- package/dist/backends/gui/banner.cjs +108 -0
- package/dist/backends/gui/banner.d.ts +33 -0
- package/dist/backends/gui/banner.d.ts.map +1 -0
- package/dist/backends/gui/banner.js +106 -0
- package/dist/backends/gui/banner.js.map +1 -0
- package/dist/backends/gui/calendar.cjs +152 -0
- package/dist/backends/gui/calendar.d.ts +41 -0
- package/dist/backends/gui/calendar.d.ts.map +1 -0
- package/dist/backends/gui/calendar.js +151 -0
- package/dist/backends/gui/calendar.js.map +1 -0
- package/dist/backends/gui/choicebox.cjs +50 -0
- package/dist/backends/gui/choicebox.d.ts +33 -0
- package/dist/backends/gui/choicebox.d.ts.map +1 -0
- package/dist/backends/gui/choicebox.js +49 -0
- package/dist/backends/gui/choicebox.js.map +1 -0
- package/dist/backends/gui/code-block.cjs +120 -0
- package/dist/backends/gui/code-block.d.ts +22 -0
- package/dist/backends/gui/code-block.d.ts.map +1 -0
- package/dist/backends/gui/code-block.js +118 -0
- package/dist/backends/gui/code-block.js.map +1 -0
- package/dist/backends/gui/code-editor.cjs +195 -0
- package/dist/backends/gui/code-editor.d.ts +43 -0
- package/dist/backends/gui/code-editor.d.ts.map +1 -0
- package/dist/backends/gui/code-editor.js +160 -0
- package/dist/backends/gui/code-editor.js.map +1 -0
- package/dist/backends/gui/code-tabs.cjs +21 -0
- package/dist/backends/gui/code-tabs.d.ts +22 -0
- package/dist/backends/gui/code-tabs.d.ts.map +1 -0
- package/dist/backends/gui/code-tabs.js +19 -0
- package/dist/backends/gui/code-tabs.js.map +1 -0
- package/dist/backends/gui/color-picker.cjs +141 -0
- package/dist/backends/gui/color-picker.d.ts +25 -0
- package/dist/backends/gui/color-picker.d.ts.map +1 -0
- package/dist/backends/gui/color-picker.js +139 -0
- package/dist/backends/gui/color-picker.js.map +1 -0
- package/dist/backends/gui/combobox.cjs +42 -0
- package/dist/backends/gui/combobox.d.ts +39 -0
- package/dist/backends/gui/combobox.d.ts.map +1 -0
- package/dist/backends/gui/combobox.js +40 -0
- package/dist/backends/gui/combobox.js.map +1 -0
- package/dist/backends/gui/company-profile.cjs +43 -0
- package/dist/backends/gui/company-profile.d.ts +33 -0
- package/dist/backends/gui/company-profile.d.ts.map +1 -0
- package/dist/backends/gui/company-profile.js +41 -0
- package/dist/backends/gui/company-profile.js.map +1 -0
- package/dist/backends/gui/comparison.cjs +43 -0
- package/dist/backends/gui/comparison.d.ts +16 -0
- package/dist/backends/gui/comparison.d.ts.map +1 -0
- package/dist/backends/gui/comparison.js +41 -0
- package/dist/backends/gui/comparison.js.map +1 -0
- package/dist/backends/gui/credit-card.cjs +80 -0
- package/dist/backends/gui/credit-card.d.ts +21 -0
- package/dist/backends/gui/credit-card.d.ts.map +1 -0
- package/dist/backends/gui/credit-card.js +78 -0
- package/dist/backends/gui/credit-card.js.map +1 -0
- package/dist/backends/gui/crypto-market.cjs +128 -0
- package/dist/backends/gui/crypto-market.d.ts +52 -0
- package/dist/backends/gui/crypto-market.d.ts.map +1 -0
- package/dist/backends/gui/crypto-market.js +126 -0
- package/dist/backends/gui/crypto-market.js.map +1 -0
- package/dist/backends/gui/crypto-screener.cjs +55 -0
- package/dist/backends/gui/crypto-screener.d.ts +42 -0
- package/dist/backends/gui/crypto-screener.d.ts.map +1 -0
- package/dist/backends/gui/crypto-screener.js +53 -0
- package/dist/backends/gui/crypto-screener.js.map +1 -0
- package/dist/backends/gui/cursor.cjs +40 -0
- package/dist/backends/gui/cursor.d.ts +16 -0
- package/dist/backends/gui/cursor.d.ts.map +1 -0
- package/dist/backends/gui/cursor.js +38 -0
- package/dist/backends/gui/cursor.js.map +1 -0
- package/dist/backends/gui/data-table.cjs +211 -0
- package/dist/backends/gui/data-table.d.ts +71 -0
- package/dist/backends/gui/data-table.d.ts.map +1 -0
- package/dist/backends/gui/data-table.js +175 -0
- package/dist/backends/gui/data-table.js.map +1 -0
- package/dist/backends/gui/date-picker.cjs +66 -0
- package/dist/backends/gui/date-picker.d.ts +29 -0
- package/dist/backends/gui/date-picker.d.ts.map +1 -0
- package/dist/backends/gui/date-picker.js +64 -0
- package/dist/backends/gui/date-picker.js.map +1 -0
- package/dist/backends/gui/desktop-spotlight.cjs +46 -0
- package/dist/backends/gui/desktop-spotlight.d.ts +34 -0
- package/dist/backends/gui/desktop-spotlight.d.ts.map +1 -0
- package/dist/backends/gui/desktop-spotlight.js +44 -0
- package/dist/backends/gui/desktop-spotlight.js.map +1 -0
- package/dist/backends/gui/desktop-window.cjs +180 -0
- package/dist/backends/gui/desktop-window.d.ts +51 -0
- package/dist/backends/gui/desktop-window.d.ts.map +1 -0
- package/dist/backends/gui/desktop-window.js +177 -0
- package/dist/backends/gui/desktop-window.js.map +1 -0
- package/dist/backends/gui/desktop.cjs +238 -0
- package/dist/backends/gui/desktop.d.ts +109 -0
- package/dist/backends/gui/desktop.d.ts.map +1 -0
- package/dist/backends/gui/desktop.js +199 -0
- package/dist/backends/gui/desktop.js.map +1 -0
- package/dist/backends/gui/dialog-stack.cjs +26 -0
- package/dist/backends/gui/dialog-stack.d.ts +23 -0
- package/dist/backends/gui/dialog-stack.d.ts.map +1 -0
- package/dist/backends/gui/dialog-stack.js +24 -0
- package/dist/backends/gui/dialog-stack.js.map +1 -0
- package/dist/backends/gui/dock.cjs +99 -0
- package/dist/backends/gui/dock.d.ts +41 -0
- package/dist/backends/gui/dock.d.ts.map +1 -0
- package/dist/backends/gui/dock.js +96 -0
- package/dist/backends/gui/dock.js.map +1 -0
- package/dist/backends/gui/dropzone.cjs +142 -0
- package/dist/backends/gui/dropzone.d.ts +12 -0
- package/dist/backends/gui/dropzone.d.ts.map +1 -0
- package/dist/backends/gui/dropzone.js +140 -0
- package/dist/backends/gui/dropzone.js.map +1 -0
- package/dist/backends/gui/editor.cjs +80 -0
- package/dist/backends/gui/editor.d.ts +23 -0
- package/dist/backends/gui/editor.d.ts.map +1 -0
- package/dist/backends/gui/editor.js +45 -0
- package/dist/backends/gui/editor.js.map +1 -0
- package/dist/backends/gui/empty.cjs +71 -0
- package/dist/backends/gui/empty.d.ts +43 -0
- package/dist/backends/gui/empty.d.ts.map +1 -0
- package/dist/backends/gui/empty.js +63 -0
- package/dist/backends/gui/empty.js.map +1 -0
- package/dist/backends/gui/financials.cjs +45 -0
- package/dist/backends/gui/financials.d.ts +36 -0
- package/dist/backends/gui/financials.d.ts.map +1 -0
- package/dist/backends/gui/financials.js +43 -0
- package/dist/backends/gui/financials.js.map +1 -0
- package/dist/backends/gui/forex-market.cjs +21 -0
- package/dist/backends/gui/forex-market.d.ts +17 -0
- package/dist/backends/gui/forex-market.d.ts.map +1 -0
- package/dist/backends/gui/forex-market.js +19 -0
- package/dist/backends/gui/forex-market.js.map +1 -0
- package/dist/backends/gui/forex-screener.cjs +57 -0
- package/dist/backends/gui/forex-screener.d.ts +45 -0
- package/dist/backends/gui/forex-screener.d.ts.map +1 -0
- package/dist/backends/gui/forex-screener.js +55 -0
- package/dist/backends/gui/forex-screener.js.map +1 -0
- package/dist/backends/gui/gantt.cjs +50 -0
- package/dist/backends/gui/gantt.d.ts +19 -0
- package/dist/backends/gui/gantt.d.ts.map +1 -0
- package/dist/backends/gui/gantt.js +49 -0
- package/dist/backends/gui/gantt.js.map +1 -0
- package/dist/backends/gui/index.cjs +108 -1
- package/dist/backends/gui/index.d.ts +41 -0
- package/dist/backends/gui/index.d.ts.map +1 -1
- package/dist/backends/gui/index.js +41 -0
- package/dist/backends/gui/index.js.map +1 -1
- package/dist/canvas.cjs +3 -3
- package/dist/canvas.js +3 -3
- package/dist/canvas.js.map +1 -1
- package/dist/gallery.html +2 -2
- package/dist/grid.cjs +19 -43
- package/dist/grid.d.ts +36 -1
- package/dist/grid.d.ts.map +1 -1
- package/dist/grid.js +19 -43
- package/dist/grid.js.map +1 -1
- package/dist/index.cjs +70 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/index.node.cjs +70 -1
- package/dist/index.node.js +3 -0
- package/dist/product/ChunkGuard.cjs +58 -0
- package/dist/product/ChunkGuard.d.ts +7 -0
- package/dist/product/ChunkGuard.d.ts.map +1 -0
- package/dist/product/ChunkGuard.js +56 -0
- package/dist/product/ChunkGuard.js.map +1 -0
- package/dist/product/DetailPane.cjs +46 -0
- package/dist/product/DetailPane.d.ts +47 -0
- package/dist/product/DetailPane.d.ts.map +1 -0
- package/dist/product/DetailPane.js +43 -0
- package/dist/product/DetailPane.js.map +1 -0
- package/dist/product/Fact.cjs +19 -0
- package/dist/product/Fact.d.ts +23 -0
- package/dist/product/Fact.d.ts.map +1 -0
- package/dist/product/Fact.js +17 -0
- package/dist/product/Fact.js.map +1 -0
- package/dist/product/chunkGuard.logic.cjs +54 -0
- package/dist/product/chunkGuard.logic.d.ts +26 -0
- package/dist/product/chunkGuard.logic.d.ts.map +1 -0
- package/dist/product/chunkGuard.logic.js +49 -0
- package/dist/product/chunkGuard.logic.js.map +1 -0
- package/dist/product/index.cjs +10 -0
- package/dist/product/index.d.ts +3 -0
- package/dist/product/index.d.ts.map +1 -1
- package/dist/product/index.js +10 -0
- package/dist/product/index.js.map +1 -1
- package/package.json +5 -3
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* DatePicker — a button that opens a calendar in a popover, for picking one
|
|
5
|
+
* date (`mode="single"`, the default) or a range (`mode="range"`), with an
|
|
6
|
+
* optional list of quick presets shown above the calendar.
|
|
7
|
+
*
|
|
8
|
+
* The trigger reads the current pick, or `placeholder` while empty. `selected`
|
|
9
|
+
* and `onSelect` name the value the same way `Calendar` does, since this is a
|
|
10
|
+
* `Calendar` with a trigger and a panel in front of it, not a different
|
|
11
|
+
* contract. Picking a single date closes the popover; a range stays open
|
|
12
|
+
* until both ends are set. `disabled` marks days that cannot be picked, same
|
|
13
|
+
* contract as `Calendar`.
|
|
14
|
+
*/
|
|
15
|
+
import { SizableText, YStack } from '@hanzo/gui';
|
|
16
|
+
import { Calendar as CalendarIcon } from '@hanzogui/lucide-icons-2';
|
|
17
|
+
import { useState } from 'react';
|
|
18
|
+
import { Button } from './button.js';
|
|
19
|
+
import { Calendar } from './calendar.js';
|
|
20
|
+
import { Popover, PopoverContent, PopoverTrigger } from './popover.js';
|
|
21
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './select.js';
|
|
22
|
+
import { slot } from './slot.js';
|
|
23
|
+
const MONTHS = [
|
|
24
|
+
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
|
25
|
+
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
|
|
26
|
+
];
|
|
27
|
+
const format = (d) => `${MONTHS[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`;
|
|
28
|
+
const sameDay = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
29
|
+
export function DatePicker(props) {
|
|
30
|
+
const { placeholder = 'Pick a date', disabled, className, width = 280 } = props;
|
|
31
|
+
const isRange = props.mode === 'range';
|
|
32
|
+
const [open, setOpen] = useState(false);
|
|
33
|
+
const [ownDate, setOwnDate] = useState(isRange ? undefined : props.selected);
|
|
34
|
+
const [ownRange, setOwnRange] = useState(isRange ? props.selected : undefined);
|
|
35
|
+
const date = !isRange ? (props.selected ?? ownDate) : undefined;
|
|
36
|
+
const range = isRange ? (props.selected ?? ownRange) : undefined;
|
|
37
|
+
const selectDate = (d) => {
|
|
38
|
+
setOwnDate(d);
|
|
39
|
+
props.onSelect?.(d);
|
|
40
|
+
setOpen(false);
|
|
41
|
+
};
|
|
42
|
+
const selectRange = (r) => {
|
|
43
|
+
setOwnRange(r);
|
|
44
|
+
props.onSelect?.(r);
|
|
45
|
+
};
|
|
46
|
+
const label = isRange
|
|
47
|
+
? range?.from
|
|
48
|
+
? range.to && !sameDay(range.to, range.from)
|
|
49
|
+
? `${format(range.from)} - ${format(range.to)}`
|
|
50
|
+
: format(range.from)
|
|
51
|
+
: placeholder
|
|
52
|
+
: date
|
|
53
|
+
? format(date)
|
|
54
|
+
: placeholder;
|
|
55
|
+
const empty = isRange ? !range?.from : !date;
|
|
56
|
+
const presets = !isRange ? props.presets : undefined;
|
|
57
|
+
return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { ...slot('date-picker-trigger'), "data-empty": empty || undefined, variant: "outline", width: width, justify: "flex-start", className: className, children: [_jsx(CalendarIcon, { size: 16 }), empty ? _jsx(SizableText, { color: "$quiet", children: label }) : label] }) }), _jsxs(PopoverContent, { ...slot('date-picker-content'), width: "auto", p: 0, align: "start", children: [presets && presets.length > 0 && (_jsx(YStack, { ...slot('date-picker-presets'), p: "$2", borderBottomWidth: 1, borderColor: "$borderColor", children: _jsxs(Select, { onValueChange: (value) => {
|
|
58
|
+
const days = Number(value);
|
|
59
|
+
const picked = new Date();
|
|
60
|
+
picked.setDate(picked.getDate() + days);
|
|
61
|
+
selectDate(picked);
|
|
62
|
+
}, children: [_jsx(SelectTrigger, { ...slot('date-picker-preset-trigger'), children: _jsx(SelectValue, { placeholder: "Select" }) }), _jsx(SelectContent, { children: presets.map((preset) => (_jsx(SelectItem, { value: String(preset.days), children: preset.label }, preset.days))) })] }) })), isRange ? (_jsx(Calendar, { mode: "range", selected: range, onSelect: selectRange, defaultMonth: range?.from, numberOfMonths: props.numberOfMonths ?? 2, disabled: disabled, initialFocus: true })) : (_jsx(Calendar, { mode: "single", selected: date, onSelect: selectDate, disabled: disabled, initialFocus: true }))] })] }));
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=date-picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date-picker.js","sourceRoot":"","sources":["../../../src/backends/gui/date-picker.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use client';"use strict";
|
|
2
|
+
'use client';
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.Spotlight = Spotlight;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
/**
|
|
7
|
+
* Spotlight — a macOS-style search dialog: type to filter a flat list of
|
|
8
|
+
* items grouped by category, arrow keys move the cursor, Enter runs the
|
|
9
|
+
* highlighted row, Escape or a backdrop click closes it.
|
|
10
|
+
*
|
|
11
|
+
* The search, the grouping, the cursor and the keyboard bindings are not
|
|
12
|
+
* reimplemented here — they are `Command`'s, the same state machine the
|
|
13
|
+
* command palette runs on. This file is composition: a `CommandDialog` wired
|
|
14
|
+
* to `isOpen`/`onClose`, with `SpotlightItem`s spread across `CommandGroup`s.
|
|
15
|
+
*/
|
|
16
|
+
const react_1 = require("react");
|
|
17
|
+
const gui_1 = require("@hanzo/gui");
|
|
18
|
+
const lucide_icons_2_1 = require("@hanzogui/lucide-icons-2");
|
|
19
|
+
const command_1 = require("./command.cjs");
|
|
20
|
+
const NO_CATEGORY = 'Results';
|
|
21
|
+
/** Items in first-seen category order — the grouping `Object.entries` alone won't give. */
|
|
22
|
+
function groupByCategory(items) {
|
|
23
|
+
const order = [];
|
|
24
|
+
const groups = new Map();
|
|
25
|
+
for (const item of items) {
|
|
26
|
+
const category = item.category || NO_CATEGORY;
|
|
27
|
+
if (!groups.has(category)) {
|
|
28
|
+
groups.set(category, []);
|
|
29
|
+
order.push(category);
|
|
30
|
+
}
|
|
31
|
+
groups.get(category).push(item);
|
|
32
|
+
}
|
|
33
|
+
return order.map((category) => [category, groups.get(category)]);
|
|
34
|
+
}
|
|
35
|
+
function Spotlight({ isOpen, onClose, items = [], onSelect, placeholder = 'Search...', }) {
|
|
36
|
+
const groups = (0, react_1.useMemo)(() => groupByCategory(items), [items]);
|
|
37
|
+
const pick = (item) => {
|
|
38
|
+
item.action?.();
|
|
39
|
+
onSelect?.(item);
|
|
40
|
+
onClose();
|
|
41
|
+
};
|
|
42
|
+
return ((0, jsx_runtime_1.jsxs)(command_1.CommandDialog, { "data-slot": "spotlight", open: isOpen, onOpenChange: (open) => {
|
|
43
|
+
if (!open)
|
|
44
|
+
onClose();
|
|
45
|
+
}, title: "Spotlight", description: placeholder, children: [(0, jsx_runtime_1.jsx)(command_1.CommandInput, { placeholder: placeholder, autoFocus: true }), (0, jsx_runtime_1.jsxs)(command_1.CommandList, { children: [(0, jsx_runtime_1.jsx)(command_1.CommandEmpty, { children: "No results found" }), groups.map(([category, categoryItems]) => ((0, jsx_runtime_1.jsx)(command_1.CommandGroup, { heading: category, children: categoryItems.map((item) => ((0, jsx_runtime_1.jsx)(command_1.CommandItem, { "data-slot": "spotlight-item", value: item.title, keywords: item.keywords, onSelect: () => pick(item), children: (0, jsx_runtime_1.jsxs)(gui_1.XStack, { items: "center", gap: "$3", flex: 1, minW: 0, children: [(0, jsx_runtime_1.jsx)(gui_1.XStack, { items: "center", justify: "center", width: 32, height: 32, rounded: "$3", bg: "$hover", children: item.icon ?? (0, jsx_runtime_1.jsx)(lucide_icons_2_1.Search, { size: 16, opacity: 0.7 }) }), (0, jsx_runtime_1.jsxs)(gui_1.YStack, { flex: 1, minW: 0, children: [(0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$3", fontWeight: "500", numberOfLines: 1, children: item.title }), item.subtitle ? ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", color: "$quiet", numberOfLines: 1, children: item.subtitle })) : null] })] }) }, item.id))) }, category)))] })] }));
|
|
46
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spotlight — a macOS-style search dialog: type to filter a flat list of
|
|
3
|
+
* items grouped by category, arrow keys move the cursor, Enter runs the
|
|
4
|
+
* highlighted row, Escape or a backdrop click closes it.
|
|
5
|
+
*
|
|
6
|
+
* The search, the grouping, the cursor and the keyboard bindings are not
|
|
7
|
+
* reimplemented here — they are `Command`'s, the same state machine the
|
|
8
|
+
* command palette runs on. This file is composition: a `CommandDialog` wired
|
|
9
|
+
* to `isOpen`/`onClose`, with `SpotlightItem`s spread across `CommandGroup`s.
|
|
10
|
+
*/
|
|
11
|
+
import { type ReactNode } from 'react';
|
|
12
|
+
export interface SpotlightItem {
|
|
13
|
+
id: string;
|
|
14
|
+
title: string;
|
|
15
|
+
subtitle?: string;
|
|
16
|
+
icon?: ReactNode;
|
|
17
|
+
category?: string;
|
|
18
|
+
keywords?: string[];
|
|
19
|
+
action?: () => void;
|
|
20
|
+
}
|
|
21
|
+
export interface SpotlightProps {
|
|
22
|
+
/** Controls visibility of the spotlight dialog. */
|
|
23
|
+
isOpen: boolean;
|
|
24
|
+
/** Called when the spotlight is closed — Escape, backdrop, or a selection. */
|
|
25
|
+
onClose: () => void;
|
|
26
|
+
/** The searchable items, in the order groups should fall when unfiltered. */
|
|
27
|
+
items?: SpotlightItem[];
|
|
28
|
+
/** Called with the picked item, after its own `action` (if any) runs. */
|
|
29
|
+
onSelect?: (item: SpotlightItem) => void;
|
|
30
|
+
/** Search input placeholder text. */
|
|
31
|
+
placeholder?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function Spotlight({ isOpen, onClose, items, onSelect, placeholder, }: SpotlightProps): import("react").JSX.Element;
|
|
34
|
+
//# sourceMappingURL=desktop-spotlight.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"desktop-spotlight.d.ts","sourceRoot":"","sources":["../../../src/backends/gui/desktop-spotlight.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,OAAO,EAAW,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAa/C,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,MAAM,EAAE,OAAO,CAAA;IACf,8EAA8E;IAC9E,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,aAAa,EAAE,CAAA;IACvB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAA;IACxC,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAmBD,wBAAgB,SAAS,CAAC,EACxB,MAAM,EACN,OAAO,EACP,KAAU,EACV,QAAQ,EACR,WAAyB,GAC1B,EAAE,cAAc,+BA6DhB"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Spotlight — a macOS-style search dialog: type to filter a flat list of
|
|
5
|
+
* items grouped by category, arrow keys move the cursor, Enter runs the
|
|
6
|
+
* highlighted row, Escape or a backdrop click closes it.
|
|
7
|
+
*
|
|
8
|
+
* The search, the grouping, the cursor and the keyboard bindings are not
|
|
9
|
+
* reimplemented here — they are `Command`'s, the same state machine the
|
|
10
|
+
* command palette runs on. This file is composition: a `CommandDialog` wired
|
|
11
|
+
* to `isOpen`/`onClose`, with `SpotlightItem`s spread across `CommandGroup`s.
|
|
12
|
+
*/
|
|
13
|
+
import { useMemo } from 'react';
|
|
14
|
+
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
15
|
+
import { Search as SearchIcon } from '@hanzogui/lucide-icons-2';
|
|
16
|
+
import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from './command.js';
|
|
17
|
+
const NO_CATEGORY = 'Results';
|
|
18
|
+
/** Items in first-seen category order — the grouping `Object.entries` alone won't give. */
|
|
19
|
+
function groupByCategory(items) {
|
|
20
|
+
const order = [];
|
|
21
|
+
const groups = new Map();
|
|
22
|
+
for (const item of items) {
|
|
23
|
+
const category = item.category || NO_CATEGORY;
|
|
24
|
+
if (!groups.has(category)) {
|
|
25
|
+
groups.set(category, []);
|
|
26
|
+
order.push(category);
|
|
27
|
+
}
|
|
28
|
+
groups.get(category).push(item);
|
|
29
|
+
}
|
|
30
|
+
return order.map((category) => [category, groups.get(category)]);
|
|
31
|
+
}
|
|
32
|
+
export function Spotlight({ isOpen, onClose, items = [], onSelect, placeholder = 'Search...', }) {
|
|
33
|
+
const groups = useMemo(() => groupByCategory(items), [items]);
|
|
34
|
+
const pick = (item) => {
|
|
35
|
+
item.action?.();
|
|
36
|
+
onSelect?.(item);
|
|
37
|
+
onClose();
|
|
38
|
+
};
|
|
39
|
+
return (_jsxs(CommandDialog, { "data-slot": "spotlight", open: isOpen, onOpenChange: (open) => {
|
|
40
|
+
if (!open)
|
|
41
|
+
onClose();
|
|
42
|
+
}, title: "Spotlight", description: placeholder, children: [_jsx(CommandInput, { placeholder: placeholder, autoFocus: true }), _jsxs(CommandList, { children: [_jsx(CommandEmpty, { children: "No results found" }), groups.map(([category, categoryItems]) => (_jsx(CommandGroup, { heading: category, children: categoryItems.map((item) => (_jsx(CommandItem, { "data-slot": "spotlight-item", value: item.title, keywords: item.keywords, onSelect: () => pick(item), children: _jsxs(XStack, { items: "center", gap: "$3", flex: 1, minW: 0, children: [_jsx(XStack, { items: "center", justify: "center", width: 32, height: 32, rounded: "$3", bg: "$hover", children: item.icon ?? _jsx(SearchIcon, { size: 16, opacity: 0.7 }) }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$3", fontWeight: "500", numberOfLines: 1, children: item.title }), item.subtitle ? (_jsx(SizableText, { size: "$1", color: "$quiet", numberOfLines: 1, children: item.subtitle })) : null] })] }) }, item.id))) }, category)))] })] }));
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=desktop-spotlight.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"desktop-spotlight.js","sourceRoot":"","sources":["../../../src/backends/gui/desktop-spotlight.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
'use client';"use strict";
|
|
2
|
+
'use client';
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.DesktopWindowControls = DesktopWindowControls;
|
|
5
|
+
exports.DesktopWindow = DesktopWindow;
|
|
6
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
7
|
+
/**
|
|
8
|
+
* DesktopWindow — a macOS-style floating panel: a title bar with traffic-light
|
|
9
|
+
* controls (close/minimize/maximize), a draggable title bar, a resizable
|
|
10
|
+
* bottom-right corner, and a double-click (or the maximize button) to fill the
|
|
11
|
+
* viewport and restore.
|
|
12
|
+
*
|
|
13
|
+
* Dragging and resizing both run on `drag()` from `./gesture` — the one
|
|
14
|
+
* pointer/responder contract this backend already uses for the color picker's
|
|
15
|
+
* saturation field and hue bar. Position and size are plain numeric state; the
|
|
16
|
+
* frame's box is set through `style` because it is a live pixel rectangle, not
|
|
17
|
+
* a themed surface — everything else on the frame (background, border, radius,
|
|
18
|
+
* shadow) is a token read through `variant`.
|
|
19
|
+
*/
|
|
20
|
+
const gui_1 = require("@hanzo/gui");
|
|
21
|
+
const react_1 = require("react");
|
|
22
|
+
const gesture_1 = require("./gesture.cjs");
|
|
23
|
+
const slot_1 = require("./slot.cjs");
|
|
24
|
+
const TITLE_BAR_H = 40;
|
|
25
|
+
const CONTROL = 12;
|
|
26
|
+
const RESIZE_HANDLE = 16;
|
|
27
|
+
const box = (el) => typeof HTMLElement !== 'undefined' && el instanceof HTMLElement ? el : null;
|
|
28
|
+
const WindowContext = /* @__PURE__ */ (0, gui_1.createStyledContext)({
|
|
29
|
+
windowType: 'default',
|
|
30
|
+
});
|
|
31
|
+
const Frame = (0, gui_1.styled)(gui_1.YStack, {
|
|
32
|
+
name: 'DesktopWindow',
|
|
33
|
+
context: WindowContext,
|
|
34
|
+
borderWidth: 1,
|
|
35
|
+
rounded: '$5',
|
|
36
|
+
overflow: 'hidden',
|
|
37
|
+
shadowColor: '$shadowColor',
|
|
38
|
+
shadowRadius: 24,
|
|
39
|
+
variants: {
|
|
40
|
+
windowType: {
|
|
41
|
+
default: { bg: '$panel', borderColor: '$borderColor' },
|
|
42
|
+
dark: { bg: '$color1', borderColor: '$borderColor' },
|
|
43
|
+
light: { bg: '$color12', borderColor: '$borderColor' },
|
|
44
|
+
transparent: { bg: '$panel', borderColor: '$borderColor', opacity: 0.92 },
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
defaultVariants: { windowType: 'default' },
|
|
48
|
+
});
|
|
49
|
+
const TitleText = (0, gui_1.styled)(gui_1.SizableText, {
|
|
50
|
+
name: 'DesktopWindowTitleText',
|
|
51
|
+
context: WindowContext,
|
|
52
|
+
size: '$2',
|
|
53
|
+
fontWeight: '600',
|
|
54
|
+
variants: {
|
|
55
|
+
windowType: {
|
|
56
|
+
default: { color: '$ink' },
|
|
57
|
+
dark: { color: '$white1' },
|
|
58
|
+
light: { color: '$color1' },
|
|
59
|
+
transparent: { color: '$ink' },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
/** One traffic-light dot: close (red), minimize (yellow) or maximize (green). */
|
|
64
|
+
const Light = (0, gui_1.styled)(gui_1.XStack, {
|
|
65
|
+
name: 'DesktopWindowLight',
|
|
66
|
+
width: CONTROL,
|
|
67
|
+
height: CONTROL,
|
|
68
|
+
rounded: 999,
|
|
69
|
+
items: 'center',
|
|
70
|
+
justify: 'center',
|
|
71
|
+
cursor: 'pointer',
|
|
72
|
+
variants: {
|
|
73
|
+
tone: {
|
|
74
|
+
close: { bg: '$red9', hoverStyle: { bg: '$red10' } },
|
|
75
|
+
minimize: { bg: '$yellow9', hoverStyle: { bg: '$yellow10' } },
|
|
76
|
+
maximize: { bg: '$green9', hoverStyle: { bg: '$green10' } },
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
/** The three traffic-light dots on their own, for a title bar built by hand. */
|
|
81
|
+
function DesktopWindowControls({ onClose, onMinimize, onMaximize, isMaximized = false, }) {
|
|
82
|
+
return ((0, jsx_runtime_1.jsxs)(gui_1.XStack, { ...(0, slot_1.slot)('desktop-window-controls'), items: "center", gap: "$2", children: [(0, jsx_runtime_1.jsx)(Light, { ...(0, slot_1.slot)('desktop-window-close'), tone: "close", onPress: onClose, ...(0, gesture_1.touch)(CONTROL, 44, 'both'), "aria-label": "Close window", role: "button" }), (0, jsx_runtime_1.jsx)(Light, { ...(0, slot_1.slot)('desktop-window-minimize'), tone: "minimize", onPress: onMinimize, ...(0, gesture_1.touch)(CONTROL, 44, 'both'), "aria-label": "Minimize window", role: "button" }), (0, jsx_runtime_1.jsx)(Light, { ...(0, slot_1.slot)('desktop-window-maximize'), tone: "maximize", onPress: onMaximize, ...(0, gesture_1.touch)(CONTROL, 44, 'both'), "aria-label": isMaximized ? 'Restore window' : 'Maximize window', role: "button" })] }));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A macOS-style window: `title` names it, `onClose`/`onMinimize`/`onMaximize`
|
|
86
|
+
* wire the traffic lights, `initialPosition`/`initialSize` place it, and
|
|
87
|
+
* `windowType` picks its surface. Position and size stay in this component —
|
|
88
|
+
* a caller that needs several windows keeps one of these per window and an
|
|
89
|
+
* `onFocus` that raises its `zIndex`.
|
|
90
|
+
*/
|
|
91
|
+
function DesktopWindow({ title, icon, children, onClose, onMinimize, onMaximize, onFocus, initialPosition, initialSize, minWidth = 200, minHeight = 150, maxWidth, maxHeight, windowType = 'default', resizable = true, draggable = true, hideControls = false, zIndex = 1000, }) {
|
|
92
|
+
const frameRef = (0, react_1.useRef)(null);
|
|
93
|
+
const [position, setPosition] = (0, react_1.useState)(() => initialPosition ?? { x: 100, y: 100 });
|
|
94
|
+
const [size, setSize] = (0, react_1.useState)(() => initialSize ?? { width: 800, height: 600 });
|
|
95
|
+
const [isMaximized, setIsMaximized] = (0, react_1.useState)(false);
|
|
96
|
+
const restoreRef = (0, react_1.useRef)(null);
|
|
97
|
+
const lastPointer = (0, react_1.useRef)({ x: 0, y: 0 });
|
|
98
|
+
const startSize = (0, react_1.useRef)(size);
|
|
99
|
+
const titleId = `desktop-window-title-${title.replace(/\s+/g, '-').toLowerCase()}`;
|
|
100
|
+
const handleMaximize = (0, react_1.useCallback)(() => {
|
|
101
|
+
setIsMaximized((was) => {
|
|
102
|
+
if (was) {
|
|
103
|
+
if (restoreRef.current) {
|
|
104
|
+
setPosition(restoreRef.current.position);
|
|
105
|
+
setSize(restoreRef.current.size);
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
restoreRef.current = { position, size };
|
|
110
|
+
setPosition({ x: 0, y: 0 });
|
|
111
|
+
setSize({
|
|
112
|
+
width: typeof window !== 'undefined' ? window.innerWidth : size.width,
|
|
113
|
+
height: typeof window !== 'undefined' ? window.innerHeight : size.height,
|
|
114
|
+
});
|
|
115
|
+
return true;
|
|
116
|
+
});
|
|
117
|
+
onMaximize?.();
|
|
118
|
+
}, [position, size, onMaximize]);
|
|
119
|
+
const beginDrag = (0, react_1.useCallback)((e) => {
|
|
120
|
+
lastPointer.current = { x: (0, gesture_1.dragPos)(e, true), y: (0, gesture_1.dragPos)(e, false) };
|
|
121
|
+
}, []);
|
|
122
|
+
const moveDrag = (0, react_1.useCallback)((e) => {
|
|
123
|
+
const x = (0, gesture_1.dragPos)(e, true);
|
|
124
|
+
const y = (0, gesture_1.dragPos)(e, false);
|
|
125
|
+
const dx = x - lastPointer.current.x;
|
|
126
|
+
const dy = y - lastPointer.current.y;
|
|
127
|
+
lastPointer.current = { x, y };
|
|
128
|
+
setPosition((p) => ({ x: p.x + dx, y: p.y + dy }));
|
|
129
|
+
}, []);
|
|
130
|
+
const dragGesture = (0, gesture_1.drag)({
|
|
131
|
+
begin: beginDrag,
|
|
132
|
+
move: moveDrag,
|
|
133
|
+
end: () => { },
|
|
134
|
+
enabled: draggable && !isMaximized,
|
|
135
|
+
});
|
|
136
|
+
const beginResize = (0, react_1.useCallback)((e) => {
|
|
137
|
+
lastPointer.current = { x: (0, gesture_1.dragPos)(e, true), y: (0, gesture_1.dragPos)(e, false) };
|
|
138
|
+
startSize.current = size;
|
|
139
|
+
}, [size]);
|
|
140
|
+
const moveResize = (0, react_1.useCallback)((e) => {
|
|
141
|
+
const x = (0, gesture_1.dragPos)(e, true);
|
|
142
|
+
const y = (0, gesture_1.dragPos)(e, false);
|
|
143
|
+
const dx = x - lastPointer.current.x;
|
|
144
|
+
const dy = y - lastPointer.current.y;
|
|
145
|
+
const width = Math.max(minWidth, Math.min(startSize.current.width + dx, maxWidth ?? Infinity));
|
|
146
|
+
const height = Math.max(minHeight, Math.min(startSize.current.height + dy, maxHeight ?? Infinity));
|
|
147
|
+
setSize({ width, height });
|
|
148
|
+
}, [minWidth, minHeight, maxWidth, maxHeight]);
|
|
149
|
+
const resizeGesture = (0, gesture_1.drag)({
|
|
150
|
+
begin: beginResize,
|
|
151
|
+
move: moveResize,
|
|
152
|
+
end: () => { },
|
|
153
|
+
enabled: resizable && !isMaximized,
|
|
154
|
+
});
|
|
155
|
+
// Escape closes the window, same as a real one — only while it has focus.
|
|
156
|
+
(0, react_1.useEffect)(() => {
|
|
157
|
+
const el = box(frameRef.current);
|
|
158
|
+
if (!el)
|
|
159
|
+
return;
|
|
160
|
+
const onKeyDown = (e) => {
|
|
161
|
+
if (e.key === 'Escape')
|
|
162
|
+
onClose?.();
|
|
163
|
+
};
|
|
164
|
+
el.addEventListener('keydown', onKeyDown);
|
|
165
|
+
return () => el.removeEventListener('keydown', onKeyDown);
|
|
166
|
+
}, [onClose]);
|
|
167
|
+
return ((0, jsx_runtime_1.jsxs)(Frame, { ref: frameRef, ...(0, slot_1.slot)('desktop-window'), windowType: windowType, role: "dialog", "aria-labelledby": titleId, tabIndex: -1, onPointerDown: onFocus, style: {
|
|
168
|
+
position: 'fixed',
|
|
169
|
+
left: 0,
|
|
170
|
+
top: 0,
|
|
171
|
+
transform: `translate(${position.x}px, ${position.y}px)`,
|
|
172
|
+
width: size.width,
|
|
173
|
+
height: size.height,
|
|
174
|
+
minWidth,
|
|
175
|
+
minHeight,
|
|
176
|
+
maxWidth: isMaximized ? undefined : maxWidth,
|
|
177
|
+
maxHeight: isMaximized ? undefined : maxHeight,
|
|
178
|
+
zIndex,
|
|
179
|
+
}, children: [(0, jsx_runtime_1.jsxs)(gui_1.XStack, { ...(0, slot_1.slot)('desktop-window-title-bar'), height: TITLE_BAR_H, px: "$3", items: "center", justify: "space-between", borderBottomWidth: 1, borderColor: "$borderColor", cursor: draggable && !isMaximized ? 'grab' : 'default', onDoubleClick: handleMaximize, ...dragGesture, children: [hideControls ? ((0, jsx_runtime_1.jsx)(gui_1.XStack, { width: CONTROL * 3 + 16 })) : ((0, jsx_runtime_1.jsx)(DesktopWindowControls, { onClose: onClose, onMinimize: onMinimize, onMaximize: handleMaximize, isMaximized: isMaximized })), (0, jsx_runtime_1.jsxs)(gui_1.XStack, { items: "center", gap: "$2", flex: 1, justify: "center", children: [icon, (0, jsx_runtime_1.jsx)(TitleText, { id: titleId, windowType: windowType, numberOfLines: 1, children: title })] }), (0, jsx_runtime_1.jsx)(gui_1.XStack, { width: CONTROL * 3 + 16 })] }), (0, jsx_runtime_1.jsx)(gui_1.YStack, { ...(0, slot_1.slot)('desktop-window-content'), flex: 1, overflow: "scroll", children: children }), resizable && !isMaximized ? ((0, jsx_runtime_1.jsx)(gui_1.XStack, { ...(0, slot_1.slot)('desktop-window-resize-handle'), position: "absolute", b: 0, r: 0, width: RESIZE_HANDLE, height: RESIZE_HANDLE, cursor: "se-resize", ...resizeGesture })) : null] }));
|
|
180
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export type DesktopWindowType = 'default' | 'dark' | 'light' | 'transparent';
|
|
3
|
+
export type DesktopWindowControlsProps = {
|
|
4
|
+
onClose?: () => void;
|
|
5
|
+
onMinimize?: () => void;
|
|
6
|
+
onMaximize?: () => void;
|
|
7
|
+
isMaximized?: boolean;
|
|
8
|
+
};
|
|
9
|
+
/** The three traffic-light dots on their own, for a title bar built by hand. */
|
|
10
|
+
export declare function DesktopWindowControls({ onClose, onMinimize, onMaximize, isMaximized, }: DesktopWindowControlsProps): import("react").JSX.Element;
|
|
11
|
+
export type DesktopWindowProps = {
|
|
12
|
+
/** Text shown in the title bar. */
|
|
13
|
+
title: string;
|
|
14
|
+
icon?: ReactNode;
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
onClose?: () => void;
|
|
17
|
+
onMinimize?: () => void;
|
|
18
|
+
onMaximize?: () => void;
|
|
19
|
+
/** Fires on pointer-down anywhere on the frame — wire it to bring-to-front. */
|
|
20
|
+
onFocus?: () => void;
|
|
21
|
+
initialPosition?: {
|
|
22
|
+
x: number;
|
|
23
|
+
y: number;
|
|
24
|
+
};
|
|
25
|
+
initialSize?: {
|
|
26
|
+
width: number;
|
|
27
|
+
height: number;
|
|
28
|
+
};
|
|
29
|
+
minWidth?: number;
|
|
30
|
+
minHeight?: number;
|
|
31
|
+
maxWidth?: number;
|
|
32
|
+
maxHeight?: number;
|
|
33
|
+
/** Style variant — the frame's background, border and title-text color. */
|
|
34
|
+
windowType?: DesktopWindowType;
|
|
35
|
+
/** Drag the corner to resize. Defaults to on. */
|
|
36
|
+
resizable?: boolean;
|
|
37
|
+
/** Drag the title bar to move. Defaults to on. */
|
|
38
|
+
draggable?: boolean;
|
|
39
|
+
/** Hide the three traffic-light dots. */
|
|
40
|
+
hideControls?: boolean;
|
|
41
|
+
zIndex?: number;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* A macOS-style window: `title` names it, `onClose`/`onMinimize`/`onMaximize`
|
|
45
|
+
* wire the traffic lights, `initialPosition`/`initialSize` place it, and
|
|
46
|
+
* `windowType` picks its surface. Position and size stay in this component —
|
|
47
|
+
* a caller that needs several windows keeps one of these per window and an
|
|
48
|
+
* `onFocus` that raises its `zIndex`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function DesktopWindow({ title, icon, children, onClose, onMinimize, onMaximize, onFocus, initialPosition, initialSize, minWidth, minHeight, maxWidth, maxHeight, windowType, resizable, draggable, hideControls, zIndex, }: DesktopWindowProps): import("react").JSX.Element;
|
|
51
|
+
//# sourceMappingURL=desktop-window.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"desktop-window.d.ts","sourceRoot":"","sources":["../../../src/backends/gui/desktop-window.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAA4C,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAIhF,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,aAAa,CAAA;AAqE5E,MAAM,MAAM,0BAA0B,GAAG;IACvC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB,CAAA;AAED,gFAAgF;AAChF,wBAAgB,qBAAqB,CAAC,EACpC,OAAO,EACP,UAAU,EACV,UAAU,EACV,WAAmB,GACpB,EAAE,0BAA0B,+BA6B5B;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,eAAe,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1C,WAAW,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,2EAA2E;IAC3E,UAAU,CAAC,EAAE,iBAAiB,CAAA;IAC9B,iDAAiD;IACjD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,kDAAkD;IAClD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,yCAAyC;IACzC,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,OAAO,EACP,UAAU,EACV,UAAU,EACV,OAAO,EACP,eAAe,EACf,WAAW,EACX,QAAc,EACd,SAAe,EACf,QAAQ,EACR,SAAS,EACT,UAAsB,EACtB,SAAgB,EAChB,SAAgB,EAChB,YAAoB,EACpB,MAAa,GACd,EAAE,kBAAkB,+BA+JpB"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* DesktopWindow — a macOS-style floating panel: a title bar with traffic-light
|
|
5
|
+
* controls (close/minimize/maximize), a draggable title bar, a resizable
|
|
6
|
+
* bottom-right corner, and a double-click (or the maximize button) to fill the
|
|
7
|
+
* viewport and restore.
|
|
8
|
+
*
|
|
9
|
+
* Dragging and resizing both run on `drag()` from `./gesture` — the one
|
|
10
|
+
* pointer/responder contract this backend already uses for the color picker's
|
|
11
|
+
* saturation field and hue bar. Position and size are plain numeric state; the
|
|
12
|
+
* frame's box is set through `style` because it is a live pixel rectangle, not
|
|
13
|
+
* a themed surface — everything else on the frame (background, border, radius,
|
|
14
|
+
* shadow) is a token read through `variant`.
|
|
15
|
+
*/
|
|
16
|
+
import { SizableText, XStack, YStack, createStyledContext, styled } from '@hanzo/gui';
|
|
17
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
18
|
+
import { drag, dragPos, touch } from './gesture.js';
|
|
19
|
+
import { slot } from './slot.js';
|
|
20
|
+
const TITLE_BAR_H = 40;
|
|
21
|
+
const CONTROL = 12;
|
|
22
|
+
const RESIZE_HANDLE = 16;
|
|
23
|
+
const box = (el) => typeof HTMLElement !== 'undefined' && el instanceof HTMLElement ? el : null;
|
|
24
|
+
const WindowContext = /* @__PURE__ */ createStyledContext({
|
|
25
|
+
windowType: 'default',
|
|
26
|
+
});
|
|
27
|
+
const Frame = styled(YStack, {
|
|
28
|
+
name: 'DesktopWindow',
|
|
29
|
+
context: WindowContext,
|
|
30
|
+
borderWidth: 1,
|
|
31
|
+
rounded: '$5',
|
|
32
|
+
overflow: 'hidden',
|
|
33
|
+
shadowColor: '$shadowColor',
|
|
34
|
+
shadowRadius: 24,
|
|
35
|
+
variants: {
|
|
36
|
+
windowType: {
|
|
37
|
+
default: { bg: '$panel', borderColor: '$borderColor' },
|
|
38
|
+
dark: { bg: '$color1', borderColor: '$borderColor' },
|
|
39
|
+
light: { bg: '$color12', borderColor: '$borderColor' },
|
|
40
|
+
transparent: { bg: '$panel', borderColor: '$borderColor', opacity: 0.92 },
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
defaultVariants: { windowType: 'default' },
|
|
44
|
+
});
|
|
45
|
+
const TitleText = styled(SizableText, {
|
|
46
|
+
name: 'DesktopWindowTitleText',
|
|
47
|
+
context: WindowContext,
|
|
48
|
+
size: '$2',
|
|
49
|
+
fontWeight: '600',
|
|
50
|
+
variants: {
|
|
51
|
+
windowType: {
|
|
52
|
+
default: { color: '$ink' },
|
|
53
|
+
dark: { color: '$white1' },
|
|
54
|
+
light: { color: '$color1' },
|
|
55
|
+
transparent: { color: '$ink' },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
/** One traffic-light dot: close (red), minimize (yellow) or maximize (green). */
|
|
60
|
+
const Light = styled(XStack, {
|
|
61
|
+
name: 'DesktopWindowLight',
|
|
62
|
+
width: CONTROL,
|
|
63
|
+
height: CONTROL,
|
|
64
|
+
rounded: 999,
|
|
65
|
+
items: 'center',
|
|
66
|
+
justify: 'center',
|
|
67
|
+
cursor: 'pointer',
|
|
68
|
+
variants: {
|
|
69
|
+
tone: {
|
|
70
|
+
close: { bg: '$red9', hoverStyle: { bg: '$red10' } },
|
|
71
|
+
minimize: { bg: '$yellow9', hoverStyle: { bg: '$yellow10' } },
|
|
72
|
+
maximize: { bg: '$green9', hoverStyle: { bg: '$green10' } },
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
/** The three traffic-light dots on their own, for a title bar built by hand. */
|
|
77
|
+
export function DesktopWindowControls({ onClose, onMinimize, onMaximize, isMaximized = false, }) {
|
|
78
|
+
return (_jsxs(XStack, { ...slot('desktop-window-controls'), items: "center", gap: "$2", children: [_jsx(Light, { ...slot('desktop-window-close'), tone: "close", onPress: onClose, ...touch(CONTROL, 44, 'both'), "aria-label": "Close window", role: "button" }), _jsx(Light, { ...slot('desktop-window-minimize'), tone: "minimize", onPress: onMinimize, ...touch(CONTROL, 44, 'both'), "aria-label": "Minimize window", role: "button" }), _jsx(Light, { ...slot('desktop-window-maximize'), tone: "maximize", onPress: onMaximize, ...touch(CONTROL, 44, 'both'), "aria-label": isMaximized ? 'Restore window' : 'Maximize window', role: "button" })] }));
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* A macOS-style window: `title` names it, `onClose`/`onMinimize`/`onMaximize`
|
|
82
|
+
* wire the traffic lights, `initialPosition`/`initialSize` place it, and
|
|
83
|
+
* `windowType` picks its surface. Position and size stay in this component —
|
|
84
|
+
* a caller that needs several windows keeps one of these per window and an
|
|
85
|
+
* `onFocus` that raises its `zIndex`.
|
|
86
|
+
*/
|
|
87
|
+
export function DesktopWindow({ title, icon, children, onClose, onMinimize, onMaximize, onFocus, initialPosition, initialSize, minWidth = 200, minHeight = 150, maxWidth, maxHeight, windowType = 'default', resizable = true, draggable = true, hideControls = false, zIndex = 1000, }) {
|
|
88
|
+
const frameRef = useRef(null);
|
|
89
|
+
const [position, setPosition] = useState(() => initialPosition ?? { x: 100, y: 100 });
|
|
90
|
+
const [size, setSize] = useState(() => initialSize ?? { width: 800, height: 600 });
|
|
91
|
+
const [isMaximized, setIsMaximized] = useState(false);
|
|
92
|
+
const restoreRef = useRef(null);
|
|
93
|
+
const lastPointer = useRef({ x: 0, y: 0 });
|
|
94
|
+
const startSize = useRef(size);
|
|
95
|
+
const titleId = `desktop-window-title-${title.replace(/\s+/g, '-').toLowerCase()}`;
|
|
96
|
+
const handleMaximize = useCallback(() => {
|
|
97
|
+
setIsMaximized((was) => {
|
|
98
|
+
if (was) {
|
|
99
|
+
if (restoreRef.current) {
|
|
100
|
+
setPosition(restoreRef.current.position);
|
|
101
|
+
setSize(restoreRef.current.size);
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
restoreRef.current = { position, size };
|
|
106
|
+
setPosition({ x: 0, y: 0 });
|
|
107
|
+
setSize({
|
|
108
|
+
width: typeof window !== 'undefined' ? window.innerWidth : size.width,
|
|
109
|
+
height: typeof window !== 'undefined' ? window.innerHeight : size.height,
|
|
110
|
+
});
|
|
111
|
+
return true;
|
|
112
|
+
});
|
|
113
|
+
onMaximize?.();
|
|
114
|
+
}, [position, size, onMaximize]);
|
|
115
|
+
const beginDrag = useCallback((e) => {
|
|
116
|
+
lastPointer.current = { x: dragPos(e, true), y: dragPos(e, false) };
|
|
117
|
+
}, []);
|
|
118
|
+
const moveDrag = useCallback((e) => {
|
|
119
|
+
const x = dragPos(e, true);
|
|
120
|
+
const y = dragPos(e, false);
|
|
121
|
+
const dx = x - lastPointer.current.x;
|
|
122
|
+
const dy = y - lastPointer.current.y;
|
|
123
|
+
lastPointer.current = { x, y };
|
|
124
|
+
setPosition((p) => ({ x: p.x + dx, y: p.y + dy }));
|
|
125
|
+
}, []);
|
|
126
|
+
const dragGesture = drag({
|
|
127
|
+
begin: beginDrag,
|
|
128
|
+
move: moveDrag,
|
|
129
|
+
end: () => { },
|
|
130
|
+
enabled: draggable && !isMaximized,
|
|
131
|
+
});
|
|
132
|
+
const beginResize = useCallback((e) => {
|
|
133
|
+
lastPointer.current = { x: dragPos(e, true), y: dragPos(e, false) };
|
|
134
|
+
startSize.current = size;
|
|
135
|
+
}, [size]);
|
|
136
|
+
const moveResize = useCallback((e) => {
|
|
137
|
+
const x = dragPos(e, true);
|
|
138
|
+
const y = dragPos(e, false);
|
|
139
|
+
const dx = x - lastPointer.current.x;
|
|
140
|
+
const dy = y - lastPointer.current.y;
|
|
141
|
+
const width = Math.max(minWidth, Math.min(startSize.current.width + dx, maxWidth ?? Infinity));
|
|
142
|
+
const height = Math.max(minHeight, Math.min(startSize.current.height + dy, maxHeight ?? Infinity));
|
|
143
|
+
setSize({ width, height });
|
|
144
|
+
}, [minWidth, minHeight, maxWidth, maxHeight]);
|
|
145
|
+
const resizeGesture = drag({
|
|
146
|
+
begin: beginResize,
|
|
147
|
+
move: moveResize,
|
|
148
|
+
end: () => { },
|
|
149
|
+
enabled: resizable && !isMaximized,
|
|
150
|
+
});
|
|
151
|
+
// Escape closes the window, same as a real one — only while it has focus.
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
const el = box(frameRef.current);
|
|
154
|
+
if (!el)
|
|
155
|
+
return;
|
|
156
|
+
const onKeyDown = (e) => {
|
|
157
|
+
if (e.key === 'Escape')
|
|
158
|
+
onClose?.();
|
|
159
|
+
};
|
|
160
|
+
el.addEventListener('keydown', onKeyDown);
|
|
161
|
+
return () => el.removeEventListener('keydown', onKeyDown);
|
|
162
|
+
}, [onClose]);
|
|
163
|
+
return (_jsxs(Frame, { ref: frameRef, ...slot('desktop-window'), windowType: windowType, role: "dialog", "aria-labelledby": titleId, tabIndex: -1, onPointerDown: onFocus, style: {
|
|
164
|
+
position: 'fixed',
|
|
165
|
+
left: 0,
|
|
166
|
+
top: 0,
|
|
167
|
+
transform: `translate(${position.x}px, ${position.y}px)`,
|
|
168
|
+
width: size.width,
|
|
169
|
+
height: size.height,
|
|
170
|
+
minWidth,
|
|
171
|
+
minHeight,
|
|
172
|
+
maxWidth: isMaximized ? undefined : maxWidth,
|
|
173
|
+
maxHeight: isMaximized ? undefined : maxHeight,
|
|
174
|
+
zIndex,
|
|
175
|
+
}, children: [_jsxs(XStack, { ...slot('desktop-window-title-bar'), height: TITLE_BAR_H, px: "$3", items: "center", justify: "space-between", borderBottomWidth: 1, borderColor: "$borderColor", cursor: draggable && !isMaximized ? 'grab' : 'default', onDoubleClick: handleMaximize, ...dragGesture, children: [hideControls ? (_jsx(XStack, { width: CONTROL * 3 + 16 })) : (_jsx(DesktopWindowControls, { onClose: onClose, onMinimize: onMinimize, onMaximize: handleMaximize, isMaximized: isMaximized })), _jsxs(XStack, { items: "center", gap: "$2", flex: 1, justify: "center", children: [icon, _jsx(TitleText, { id: titleId, windowType: windowType, numberOfLines: 1, children: title })] }), _jsx(XStack, { width: CONTROL * 3 + 16 })] }), _jsx(YStack, { ...slot('desktop-window-content'), flex: 1, overflow: "scroll", children: children }), resizable && !isMaximized ? (_jsx(XStack, { ...slot('desktop-window-resize-handle'), position: "absolute", b: 0, r: 0, width: RESIZE_HANDLE, height: RESIZE_HANDLE, cursor: "se-resize", ...resizeGesture })) : null] }));
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=desktop-window.js.map
|