@flamingo-stack/openframe-frontend-core 0.0.838 → 0.0.840
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/dist/build/export-map.json +1 -1
- package/dist/{chunk-JFR3OFE6.cjs → chunk-275AQ6LC.cjs} +6 -6
- package/dist/{chunk-5BM7X4GA.cjs → chunk-5GBLXY5F.cjs} +2 -2
- package/dist/{chunk-5L3UKDC6.cjs → chunk-6LDL3UZE.cjs} +9 -9
- package/dist/{chunk-SWMUW3RR.cjs → chunk-744JXTDP.cjs} +19 -19
- package/dist/{chunk-46LATWQR.cjs → chunk-7WKRMJI6.cjs} +5 -5
- package/dist/{chunk-2X5LYDNA.cjs → chunk-AT6D7BAZ.cjs} +5 -5
- package/dist/{chunk-C4OQIYGW.cjs → chunk-CCRXBFNL.cjs} +8 -8
- package/dist/{chunk-LC65AC6M.cjs → chunk-COWMXUHN.cjs} +2 -0
- package/dist/{chunk-5E4YEHHO.cjs → chunk-DGY5KEPY.cjs} +55 -55
- package/dist/{chunk-B4THEQOW.cjs → chunk-DYDQLS2R.cjs} +744 -561
- package/dist/{chunk-LWMWY3JO.cjs → chunk-E7M54JWW.cjs} +14 -14
- package/dist/{chunk-Z7A7IZWL.cjs → chunk-EV6BPF47.cjs} +236 -210
- package/dist/{chunk-7XRFAHJF.cjs → chunk-FHI4I3TQ.cjs} +9 -9
- package/dist/{chunk-ZABZIM2Y.cjs → chunk-FRL2ZN4O.cjs} +3 -3
- package/dist/{chunk-7Z536SPR.cjs → chunk-FW7RKWPY.cjs} +15 -1
- package/dist/{chunk-U4MJ2Z4V.cjs → chunk-GMK4JGC2.cjs} +46 -46
- package/dist/{chunk-NV5AHL4G.cjs → chunk-GUKFMMDZ.cjs} +3 -3
- package/dist/{chunk-JI34QKPJ.cjs → chunk-HXTZZE3R.cjs} +253 -131
- package/dist/{chunk-MMPMQUQM.cjs → chunk-IELPVUPR.cjs} +18 -18
- package/dist/{chunk-64PF3UC7.cjs → chunk-INBFKUZA.cjs} +17 -17
- package/dist/{chunk-APOMNC7H.cjs → chunk-IUDBG3HJ.cjs} +3 -3
- package/dist/{chunk-4JHO2NVI.cjs → chunk-J2IXYCEH.cjs} +26 -10
- package/dist/{chunk-V322TUBY.cjs → chunk-JM2XA7VY.cjs} +28 -28
- package/dist/{chunk-GPXKG34G.cjs → chunk-JUEAZCD6.cjs} +46 -46
- package/dist/{chunk-2QAUQPGC.cjs → chunk-MYAPG3FQ.cjs} +21 -21
- package/dist/{chunk-GRHF5SWL.cjs → chunk-NFNSPLGC.cjs} +3 -3
- package/dist/{chunk-4TUHHKLR.cjs → chunk-Q6WUHYOG.cjs} +72 -72
- package/dist/{chunk-H3WTYAVR.cjs → chunk-RSJYTQEA.cjs} +30 -30
- package/dist/{chunk-MG3HJIGG.cjs → chunk-UAI4VMEF.cjs} +4 -4
- package/dist/{chunk-S5RMRQKT.cjs → chunk-UZPYYHF5.cjs} +18 -18
- package/dist/{chunk-TMD55O5U.cjs → chunk-VBH2RCFM.cjs} +73 -73
- package/dist/{chunk-FNNDGOR3.cjs → chunk-WCK5T3UD.cjs} +43 -43
- package/dist/{chunk-TRZEFT2N.cjs → chunk-WI7EXAZT.cjs} +9 -5
- package/dist/{chunk-7QNGILIY.cjs → chunk-Z35PL3UR.cjs} +32 -32
- package/dist/{chunk-ENK75WN4.cjs → chunk-ZAO5SYBJ.cjs} +4 -4
- package/dist/{cloud-tenants-GLPDB2F5.cjs → cloud-tenants-PV63RZ73.cjs} +19 -19
- package/dist/components/case-studies/index.cjs +33 -33
- package/dist/components/chat/chat-nav-sidebar.d.ts.map +1 -1
- package/dist/components/chat/chat-nav-sidebar.js +17 -3
- package/dist/components/chat/index.cjs +13 -13
- package/dist/components/contact/index.cjs +20 -20
- package/dist/components/docs/index.cjs +23 -23
- package/dist/components/embeds/index.cjs +20 -20
- package/dist/components/faq/index.cjs +21 -21
- package/dist/components/features/command-box.d.ts.map +1 -1
- package/dist/components/features/command-box.js +4 -23
- package/dist/components/features/index.cjs +29 -19
- package/dist/components/features/software/index.d.ts +1 -0
- package/dist/components/features/software/index.d.ts.map +1 -1
- package/dist/components/features/software/index.js +12 -0
- package/dist/components/features/software/software-action-form.d.ts +9 -1
- package/dist/components/features/software/software-action-form.d.ts.map +1 -1
- package/dist/components/features/software/software-action-form.js +18 -1
- package/dist/components/features/software/update-settings-fields.d.ts +43 -0
- package/dist/components/features/software/update-settings-fields.d.ts.map +1 -0
- package/dist/components/features/software/update-settings-fields.js +202 -0
- package/dist/components/help-center-pages/download-apps-page.d.ts +1 -1
- package/dist/components/help-center-pages/download-apps-page.d.ts.map +1 -1
- package/dist/components/help-center-pages/download-apps-page.js +2 -3
- package/dist/components/help-center-pages/index.cjs +122 -123
- package/dist/components/index.cjs +86 -76
- package/dist/components/meeting-scheduler/index.cjs +82 -82
- package/dist/components/navigation/account-popover.d.ts.map +1 -1
- package/dist/components/navigation/account-popover.js +26 -2
- package/dist/components/navigation/app-layout-side-panel.d.ts.map +1 -1
- package/dist/components/navigation/app-layout-side-panel.js +2 -9
- package/dist/components/navigation/index.cjs +22 -22
- package/dist/components/navigation/navigation-sidebar-grouped.d.ts.map +1 -1
- package/dist/components/navigation/navigation-sidebar-grouped.js +30 -6
- package/dist/components/navigation/navigation-sidebar-item.d.ts.map +1 -1
- package/dist/components/navigation/navigation-sidebar-item.js +14 -1
- package/dist/components/navigation/navigation-sidebar.d.ts.map +1 -1
- package/dist/components/navigation/navigation-sidebar.js +72 -3
- package/dist/components/onboarding-guides/index.cjs +22 -22
- package/dist/components/product-demo/fixtures/software-update.d.ts +2 -0
- package/dist/components/product-demo/fixtures/software-update.d.ts.map +1 -1
- package/dist/components/product-demo/fixtures/software-update.js +3 -2
- package/dist/components/product-demo/index.cjs +30 -30
- package/dist/components/related-content/index.cjs +8 -8
- package/dist/components/tickets/index.cjs +24 -24
- package/dist/components/ui/index.cjs +19 -19
- package/dist/components/ui/panel-resize-handle.d.ts +27 -6
- package/dist/components/ui/panel-resize-handle.d.ts.map +1 -1
- package/dist/components/ui/panel-resize-handle.js +27 -3
- package/dist/{devices-GHPW5WCO.cjs → devices-VECG5JNC.cjs} +20 -20
- package/dist/{embeddable-chat-RBC7F4TL.cjs → embeddable-chat-VRYAQQF6.cjs} +90 -90
- package/dist/hooks/index.cjs +11 -7
- package/dist/hooks/ui/index.d.ts +1 -0
- package/dist/hooks/ui/index.d.ts.map +1 -1
- package/dist/hooks/ui/index.js +1 -0
- package/dist/hooks/ui/use-clipped-label-tooltip.d.ts +14 -0
- package/dist/hooks/ui/use-clipped-label-tooltip.d.ts.map +1 -0
- package/dist/hooks/ui/use-clipped-label-tooltip.js +13 -0
- package/dist/hooks/ui/use-is-truncated.d.ts.map +1 -1
- package/dist/hooks/ui/use-is-truncated.js +1 -0
- package/dist/hooks/ui/use-media-query.d.ts.map +1 -1
- package/dist/hooks/ui/use-media-query.js +1 -0
- package/dist/hooks/ui/use-resizable-panel-size.d.ts +1 -0
- package/dist/hooks/ui/use-resizable-panel-size.d.ts.map +1 -1
- package/dist/hooks/ui/use-resizable-panel-size.js +5 -1
- package/dist/index.cjs +38 -24
- package/dist/{knowledge-4TOSY2S5.cjs → knowledge-YJSDJIXT.cjs} +20 -20
- package/dist/{logs-MUODFY6N.cjs → logs-42JG3NZ5.cjs} +26 -26
- package/dist/{policies-TJHO3VLE.cjs → policies-D4QNVJ26.cjs} +23 -23
- package/dist/{remote-session-CN3G7JNR.cjs → remote-session-EE6477O3.cjs} +10 -10
- package/dist/{scripts-X5CMYE2V.cjs → scripts-4VFYBXBE.cjs} +26 -26
- package/dist/{software-update-2UZKHTYM.cjs → software-update-OSDZQ5CP.cjs} +25 -25
- package/dist/{tickets-board-PLYD2NSY.cjs → tickets-board-44HO3LR4.cjs} +10 -10
- package/dist/types/navigation.d.ts +36 -0
- package/dist/types/navigation.d.ts.map +1 -1
- package/dist/utils/notifications.cjs +2 -0
- package/dist/utils/notifications.d.ts +1 -1
- package/dist/utils/notifications.d.ts.map +1 -1
- package/dist/utils/notifications.js +2 -0
- package/package.json +1 -1
- package/src/components/chat/chat-nav-sidebar.tsx +17 -4
- package/src/components/features/command-box.tsx +17 -19
- package/src/components/features/notifications/notification-category-icon.tsx +1 -1
- package/src/components/features/software/index.ts +11 -0
- package/src/components/features/software/software-action-form.tsx +28 -1
- package/src/components/features/software/update-settings-fields.tsx +265 -0
- package/src/components/help-center-pages/download-apps-page.tsx +4 -5
- package/src/components/navigation/account-popover.tsx +31 -2
- package/src/components/navigation/app-layout-side-panel.tsx +2 -7
- package/src/components/navigation/navigation-sidebar-grouped.tsx +37 -6
- package/src/components/navigation/navigation-sidebar-item.tsx +18 -1
- package/src/components/navigation/navigation-sidebar.tsx +116 -7
- package/src/components/product-demo/fixtures/software-update.ts +2 -2
- package/src/components/ui/panel-resize-handle.tsx +50 -8
- package/src/hooks/ui/index.ts +1 -0
- package/src/hooks/ui/use-clipped-label-tooltip.ts +20 -0
- package/src/hooks/ui/use-is-truncated.ts +2 -0
- package/src/hooks/ui/use-media-query.ts +2 -0
- package/src/hooks/ui/use-resizable-panel-size.ts +7 -1
- package/src/types/navigation.ts +37 -0
- package/src/utils/notifications.ts +2 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { InfoCircleIcon } from '../../icons-v2-generated';
|
|
4
|
+
import {
|
|
5
|
+
Alert,
|
|
6
|
+
CheckboxBlock,
|
|
7
|
+
Label,
|
|
8
|
+
RadioGroupBlock,
|
|
9
|
+
type RadioGroupBlockOption,
|
|
10
|
+
Select,
|
|
11
|
+
SelectContent,
|
|
12
|
+
SelectItem,
|
|
13
|
+
SelectTrigger,
|
|
14
|
+
SelectValue,
|
|
15
|
+
} from '../../ui';
|
|
16
|
+
|
|
17
|
+
/** What an update does on a device where the app is running. */
|
|
18
|
+
export type SoftwareUpdateMode = 'WAIT' | 'FORCE';
|
|
19
|
+
|
|
20
|
+
/** "If the app is running": one update's behaviour on a device where the app is open. */
|
|
21
|
+
export interface SoftwareUpdatePolicy {
|
|
22
|
+
mode: SoftwareUpdateMode;
|
|
23
|
+
/** WAIT only: "Countdown after the deadline". Off, the update waits for the user however long it takes. */
|
|
24
|
+
deadlineEnabled: boolean;
|
|
25
|
+
/** WAIT only: seconds after the first try before the countdown starts. */
|
|
26
|
+
deadlineSeconds: number;
|
|
27
|
+
/** Seconds of countdown before the app is closed: after the deadline (WAIT) or right away (FORCE). */
|
|
28
|
+
countdownSeconds: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The Update Software form's "Update Settings": this update, and whether later releases follow it. */
|
|
32
|
+
export interface SoftwareUpdateSettingsValues {
|
|
33
|
+
policy: SoftwareUpdatePolicy;
|
|
34
|
+
/** "Keep it auto-updated": every new version is installed on the same devices after this update. */
|
|
35
|
+
autoUpdate: boolean;
|
|
36
|
+
/** "Same settings for auto-update": auto-updates run with `policy`; off, with `autoUpdatePolicy`. */
|
|
37
|
+
sameForAutoUpdate: boolean;
|
|
38
|
+
autoUpdatePolicy: SoftwareUpdatePolicy;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const DAY = 24 * 60 * 60;
|
|
42
|
+
const MINUTE = 60;
|
|
43
|
+
|
|
44
|
+
export const SOFTWARE_UPDATE_DEADLINE_OPTIONS: ReadonlyArray<{ seconds: number; label: string }> = [
|
|
45
|
+
{ seconds: DAY, label: '1 day after the first try' },
|
|
46
|
+
{ seconds: 3 * DAY, label: '3 days after the first try' },
|
|
47
|
+
{ seconds: 7 * DAY, label: '7 days after the first try' },
|
|
48
|
+
{ seconds: 14 * DAY, label: '14 days after the first try' },
|
|
49
|
+
{ seconds: 30 * DAY, label: '30 days after the first try' },
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
export const SOFTWARE_UPDATE_COUNTDOWN_OPTIONS: ReadonlyArray<{ seconds: number; label: string }> = [
|
|
53
|
+
{ seconds: MINUTE, label: '1 minute' },
|
|
54
|
+
{ seconds: 5 * MINUTE, label: '5 minutes' },
|
|
55
|
+
{ seconds: 15 * MINUTE, label: '15 minutes' },
|
|
56
|
+
{ seconds: 30 * MINUTE, label: '30 minutes' },
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
/** Wait & notify, a countdown 7 days after the first try, 5 minutes long: what the server applies when sent none. */
|
|
60
|
+
export const DEFAULT_SOFTWARE_UPDATE_POLICY: SoftwareUpdatePolicy = {
|
|
61
|
+
mode: 'WAIT',
|
|
62
|
+
deadlineEnabled: true,
|
|
63
|
+
deadlineSeconds: 7 * DAY,
|
|
64
|
+
countdownSeconds: 5 * MINUTE,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const DEFAULT_SOFTWARE_UPDATE_SETTINGS: SoftwareUpdateSettingsValues = {
|
|
68
|
+
policy: DEFAULT_SOFTWARE_UPDATE_POLICY,
|
|
69
|
+
autoUpdate: true,
|
|
70
|
+
sameForAutoUpdate: true,
|
|
71
|
+
autoUpdatePolicy: DEFAULT_SOFTWARE_UPDATE_POLICY,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const MODE_OPTIONS: RadioGroupBlockOption[] = [
|
|
75
|
+
{
|
|
76
|
+
value: 'WAIT',
|
|
77
|
+
label: 'Wait & notify',
|
|
78
|
+
description: 'The user gets Update now or Ask Later. If they close the app, it updates then.',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
value: 'FORCE',
|
|
82
|
+
label: 'Force Update',
|
|
83
|
+
description: 'The user gets a short countdown, then the app closes and updates.',
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
const SAME_SETTINGS_DESCRIPTION: Record<SoftwareUpdateMode, string> = {
|
|
88
|
+
WAIT: 'Auto-updates follow "If the app is running" above. Uncheck to set them separately.',
|
|
89
|
+
FORCE: 'Later releases will also be force-updated.',
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const groupLabelClass = 'text-h5 text-ods-text-secondary';
|
|
93
|
+
|
|
94
|
+
function SecondsSelect({
|
|
95
|
+
label,
|
|
96
|
+
value,
|
|
97
|
+
options,
|
|
98
|
+
onChange,
|
|
99
|
+
}: {
|
|
100
|
+
label: string;
|
|
101
|
+
value: number;
|
|
102
|
+
options: ReadonlyArray<{ seconds: number; label: string }>;
|
|
103
|
+
onChange: (seconds: number) => void;
|
|
104
|
+
}) {
|
|
105
|
+
return (
|
|
106
|
+
<div className="flex min-w-0 flex-col gap-[var(--spacing-system-xxs)]">
|
|
107
|
+
<Label className="text-h4">{label}</Label>
|
|
108
|
+
<Select value={String(value)} onValueChange={next => onChange(Number(next))}>
|
|
109
|
+
<SelectTrigger aria-label={label}>
|
|
110
|
+
<SelectValue />
|
|
111
|
+
</SelectTrigger>
|
|
112
|
+
<SelectContent>
|
|
113
|
+
{options.map(option => (
|
|
114
|
+
<SelectItem key={option.seconds} value={String(option.seconds)}>
|
|
115
|
+
{option.label}
|
|
116
|
+
</SelectItem>
|
|
117
|
+
))}
|
|
118
|
+
</SelectContent>
|
|
119
|
+
</Select>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** "If the app is running" with its deadline and countdown. */
|
|
125
|
+
function UpdatePolicyFields({
|
|
126
|
+
name,
|
|
127
|
+
label,
|
|
128
|
+
policy,
|
|
129
|
+
onChange,
|
|
130
|
+
}: {
|
|
131
|
+
/** The radio group's name: two policies on one page must not share it. */
|
|
132
|
+
name: string;
|
|
133
|
+
label: string;
|
|
134
|
+
policy: SoftwareUpdatePolicy;
|
|
135
|
+
onChange: (policy: SoftwareUpdatePolicy) => void;
|
|
136
|
+
}) {
|
|
137
|
+
const waits = policy.mode === 'WAIT';
|
|
138
|
+
const deadline = waits && policy.deadlineEnabled;
|
|
139
|
+
const countdown = (
|
|
140
|
+
<SecondsSelect
|
|
141
|
+
label="Countdown"
|
|
142
|
+
value={policy.countdownSeconds}
|
|
143
|
+
options={SOFTWARE_UPDATE_COUNTDOWN_OPTIONS}
|
|
144
|
+
onChange={countdownSeconds => onChange({ ...policy, countdownSeconds })}
|
|
145
|
+
/>
|
|
146
|
+
);
|
|
147
|
+
return (
|
|
148
|
+
<div className="flex flex-col gap-[var(--spacing-system-m)]">
|
|
149
|
+
<div className="flex flex-col gap-[var(--spacing-system-xxs)]">
|
|
150
|
+
<span className={groupLabelClass}>{label}</span>
|
|
151
|
+
<RadioGroupBlock
|
|
152
|
+
name={name}
|
|
153
|
+
aria-label={label}
|
|
154
|
+
variant="grouped"
|
|
155
|
+
value={policy.mode}
|
|
156
|
+
onValueChange={mode => onChange({ ...policy, mode: mode as SoftwareUpdateMode })}
|
|
157
|
+
options={MODE_OPTIONS}
|
|
158
|
+
itemClassName="py-[var(--spacing-system-sf)]"
|
|
159
|
+
/>
|
|
160
|
+
</div>
|
|
161
|
+
{waits ? (
|
|
162
|
+
<div className="flex flex-col gap-[var(--spacing-system-xsf)]">
|
|
163
|
+
<CheckboxBlock
|
|
164
|
+
id={`${name}-deadline`}
|
|
165
|
+
checked={policy.deadlineEnabled}
|
|
166
|
+
onCheckedChange={deadlineEnabled => onChange({ ...policy, deadlineEnabled })}
|
|
167
|
+
label="Countdown after the deadline"
|
|
168
|
+
description="If the update still isn’t installed by the deadline, the user gets a countdown and then it installs."
|
|
169
|
+
/>
|
|
170
|
+
{deadline && (
|
|
171
|
+
<div className="grid grid-cols-1 gap-[var(--spacing-system-lf)] content-md:grid-cols-3 content-md:items-start">
|
|
172
|
+
<SecondsSelect
|
|
173
|
+
label="Deadline"
|
|
174
|
+
value={policy.deadlineSeconds}
|
|
175
|
+
options={SOFTWARE_UPDATE_DEADLINE_OPTIONS}
|
|
176
|
+
onChange={deadlineSeconds => onChange({ ...policy, deadlineSeconds })}
|
|
177
|
+
/>
|
|
178
|
+
{countdown}
|
|
179
|
+
</div>
|
|
180
|
+
)}
|
|
181
|
+
</div>
|
|
182
|
+
) : (
|
|
183
|
+
<div className="grid grid-cols-1 gap-[var(--spacing-system-lf)] content-md:grid-cols-3">{countdown}</div>
|
|
184
|
+
)}
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface SoftwareUpdateSettingsFieldsProps {
|
|
190
|
+
value: SoftwareUpdateSettingsValues;
|
|
191
|
+
onChange: (value: SoftwareUpdateSettingsValues) => void;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Update Software's "Update Settings" (design 988:21462, the separate auto-update
|
|
196
|
+
* rules 1034:8173): what happens on a device where the app is running, and
|
|
197
|
+
* whether later releases are installed the same way.
|
|
198
|
+
*/
|
|
199
|
+
export function SoftwareUpdateSettingsFields({ value, onChange }: SoftwareUpdateSettingsFieldsProps) {
|
|
200
|
+
return (
|
|
201
|
+
<section aria-labelledby="software-update-settings" className="flex flex-col gap-[var(--spacing-system-l)]">
|
|
202
|
+
<h2 id="software-update-settings" className="pt-[var(--spacing-system-l)] text-ods-text-primary text-h2">
|
|
203
|
+
Update Settings
|
|
204
|
+
</h2>
|
|
205
|
+
|
|
206
|
+
<UpdatePolicyFields
|
|
207
|
+
name="updatePolicy"
|
|
208
|
+
label="If the app is running"
|
|
209
|
+
policy={value.policy}
|
|
210
|
+
onChange={policy => onChange({ ...value, policy })}
|
|
211
|
+
/>
|
|
212
|
+
|
|
213
|
+
<div className="flex flex-col gap-[var(--spacing-system-xxs)]">
|
|
214
|
+
<span className={groupLabelClass}>Auto-update</span>
|
|
215
|
+
<div className="flex flex-col gap-[var(--spacing-system-sf)]">
|
|
216
|
+
<CheckboxBlock
|
|
217
|
+
id="software-auto-update"
|
|
218
|
+
checked={value.autoUpdate}
|
|
219
|
+
onCheckedChange={autoUpdate => onChange({ ...value, autoUpdate })}
|
|
220
|
+
label="Keep it auto-updated"
|
|
221
|
+
description="After this update, OpenFrame installs each new version from Winget or Brew on the selected devices. When off, only this update runs."
|
|
222
|
+
/>
|
|
223
|
+
{value.autoUpdate && (
|
|
224
|
+
<>
|
|
225
|
+
<CheckboxBlock
|
|
226
|
+
id="software-auto-update-same"
|
|
227
|
+
checked={value.sameForAutoUpdate}
|
|
228
|
+
onCheckedChange={sameForAutoUpdate => onChange({ ...value, sameForAutoUpdate })}
|
|
229
|
+
label="Same settings for auto-update"
|
|
230
|
+
description={SAME_SETTINGS_DESCRIPTION[value.policy.mode]}
|
|
231
|
+
/>
|
|
232
|
+
{!value.sameForAutoUpdate && (
|
|
233
|
+
<div className="rounded-[6px] border border-ods-border p-[var(--spacing-system-m)]">
|
|
234
|
+
<UpdatePolicyFields
|
|
235
|
+
name="autoUpdatePolicy"
|
|
236
|
+
label="For auto-updates"
|
|
237
|
+
policy={value.autoUpdatePolicy}
|
|
238
|
+
onChange={autoUpdatePolicy => onChange({ ...value, autoUpdatePolicy })}
|
|
239
|
+
/>
|
|
240
|
+
</div>
|
|
241
|
+
)}
|
|
242
|
+
</>
|
|
243
|
+
)}
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
{/* Core Alert on the card surface, as a note: it is there from the start, nothing to announce. The icon sits in
|
|
248
|
+
a span so Alert's `[&>svg]` positioning does not apply. */}
|
|
249
|
+
<Alert
|
|
250
|
+
role="note"
|
|
251
|
+
className="flex items-center gap-[var(--spacing-system-m)] rounded-[6px] border-ods-border bg-ods-card p-[var(--spacing-system-m)]"
|
|
252
|
+
>
|
|
253
|
+
<span className="shrink-0 text-ods-text-secondary">
|
|
254
|
+
<InfoCircleIcon size={24} color="currentColor" />
|
|
255
|
+
</span>
|
|
256
|
+
<div className="flex min-w-0 flex-1 flex-col gap-[var(--spacing-system-xxs)]">
|
|
257
|
+
<p className="text-ods-text-primary text-h3">Apps installed by hand get reinstalled</p>
|
|
258
|
+
<p className="text-ods-text-secondary text-h6">
|
|
259
|
+
On the first update, OpenFrame reinstalls them with Winget or Brew so it can keep them updated.
|
|
260
|
+
</p>
|
|
261
|
+
</div>
|
|
262
|
+
</Alert>
|
|
263
|
+
</section>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* lists its platforms; it does not hide them behind tabs, so nothing moves
|
|
8
8
|
* when a visitor looks for another system). In a card the installer is THE
|
|
9
9
|
* action (the other architecture is a quieter button) and each package
|
|
10
|
-
* manager is one
|
|
10
|
+
* manager is one row under it, with its copy button.
|
|
11
11
|
* - The mobile app: the store badges, and the install QR code beside them. The
|
|
12
12
|
* code and the address it encodes are the server's (`mobile.install`): the
|
|
13
13
|
* website's install link, which the server answers with a redirect to the
|
|
@@ -111,7 +111,7 @@ export interface DownloadAppsWebOption {
|
|
|
111
111
|
|
|
112
112
|
const SECTIONS_CLASS = 'flex flex-col gap-[var(--spacing-system-xlf)]';
|
|
113
113
|
const SECTION_CLASS = 'flex flex-col gap-[var(--spacing-system-lf)]';
|
|
114
|
-
const CARDS_GRID_CLASS = 'grid grid-cols-1 gap-[var(--spacing-system-lf)] content-
|
|
114
|
+
const CARDS_GRID_CLASS = 'grid grid-cols-1 gap-[var(--spacing-system-lf)] content-lg:grid-cols-2';
|
|
115
115
|
/** One card takes the row; two or more share it. */
|
|
116
116
|
const cardsGridClass = (cards: number) => (cards > 1 ? CARDS_GRID_CLASS : 'grid grid-cols-1');
|
|
117
117
|
const CARD_CLASS =
|
|
@@ -183,7 +183,7 @@ function installerLabel(download: AppDownload, siblings: number): string {
|
|
|
183
183
|
return siblings > 1 && architecture ? `${label} (${architecture})` : label;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
/** One system's card: its logo and name, its installers side by side, then each package manager as one
|
|
186
|
+
/** One system's card: its logo and name, its installers side by side, then each package manager as one row. */
|
|
187
187
|
function SystemCard({
|
|
188
188
|
system,
|
|
189
189
|
onDownloadAction,
|
|
@@ -243,8 +243,7 @@ function SystemCard({
|
|
|
243
243
|
key={row.id}
|
|
244
244
|
title={row.label ?? undefined}
|
|
245
245
|
command={row.command}
|
|
246
|
-
|
|
247
|
-
commandClassName="text-ods-accent"
|
|
246
|
+
commandClassName="break-words text-ods-accent"
|
|
248
247
|
copyAriaLabel={`Copy the ${row.label ?? 'install'} command`}
|
|
249
248
|
onCopy={() => {
|
|
250
249
|
void copy(row.command as string);
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import { useControllableState } from '@radix-ui/react-use-controllable-state';
|
|
4
4
|
import type { ReactNode } from 'react';
|
|
5
|
+
import { useClippedLabelTooltip } from '../../hooks/ui/use-clipped-label-tooltip';
|
|
5
6
|
import { cn } from '../../utils';
|
|
6
7
|
import { Chevron02DownIcon } from '../icons-v2-generated';
|
|
7
8
|
import { ActionsMenuDropdown, type ActionsMenuGroup, type ActionsMenuItem } from '../ui/actions-menu';
|
|
9
|
+
import { FloatingTooltip } from '../ui/floating-tooltip';
|
|
8
10
|
import { SquareAvatar } from '../ui/square-avatar';
|
|
9
11
|
import { UnreadCountBadge, UnreadDot } from './unread-dot';
|
|
10
12
|
|
|
@@ -124,6 +126,11 @@ export function AccountPopover({
|
|
|
124
126
|
className,
|
|
125
127
|
}: AccountPopoverProps) {
|
|
126
128
|
const unread = items.some(entry => entry.type !== 'divider' && (entry.count ?? 0) > 0);
|
|
129
|
+
// The name and subtitle a narrow sidebar clips: their full text in a tooltip
|
|
130
|
+
// over them, only while clipped and where a pointer can hover. The rail's
|
|
131
|
+
// tooltip is the trigger's `title`.
|
|
132
|
+
const { ref: nameRef, enabled: nameTooltip } = useClippedLabelTooltip<HTMLSpanElement>(name);
|
|
133
|
+
const { ref: subtitleRef, enabled: subtitleTooltip } = useClippedLabelTooltip<HTMLSpanElement>(subtitle);
|
|
127
134
|
const [open = false, setOpen] = useControllableState({
|
|
128
135
|
prop: openProp,
|
|
129
136
|
defaultProp: false,
|
|
@@ -182,14 +189,36 @@ export function AccountPopover({
|
|
|
182
189
|
{/* The chevron follows the name, so it reads as this row's menu and not
|
|
183
190
|
as one more of the buttons a host sets at the row's end. */}
|
|
184
191
|
<span className="flex min-w-0 items-center gap-[var(--spacing-system-xxs)]">
|
|
185
|
-
<
|
|
192
|
+
<FloatingTooltip
|
|
193
|
+
content={name}
|
|
194
|
+
side="top"
|
|
195
|
+
as="span"
|
|
196
|
+
disabled={collapsed || !nameTooltip}
|
|
197
|
+
triggerClassName="min-w-0"
|
|
198
|
+
>
|
|
199
|
+
<span ref={nameRef} className="block truncate text-ods-text-primary text-h4">
|
|
200
|
+
{name}
|
|
201
|
+
</span>
|
|
202
|
+
</FloatingTooltip>
|
|
186
203
|
<Chevron02DownIcon
|
|
187
204
|
size={16}
|
|
188
205
|
className="shrink-0 text-ods-text-secondary transition-transform duration-200 group-data-[state=open]/account:rotate-180"
|
|
189
206
|
aria-hidden="true"
|
|
190
207
|
/>
|
|
191
208
|
</span>
|
|
192
|
-
{subtitle &&
|
|
209
|
+
{subtitle && (
|
|
210
|
+
<FloatingTooltip
|
|
211
|
+
content={subtitle}
|
|
212
|
+
side="top"
|
|
213
|
+
as="span"
|
|
214
|
+
disabled={collapsed || !subtitleTooltip}
|
|
215
|
+
triggerClassName="min-w-0"
|
|
216
|
+
>
|
|
217
|
+
<span ref={subtitleRef} className="block truncate text-ods-text-secondary text-code">
|
|
218
|
+
{subtitle}
|
|
219
|
+
</span>
|
|
220
|
+
</FloatingTooltip>
|
|
221
|
+
)}
|
|
193
222
|
</span>
|
|
194
223
|
</button>
|
|
195
224
|
}
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import { useIsomorphicLayoutEffect } from '../../hooks/ui/use-isomorphic-layout-effect';
|
|
14
14
|
import { breakpoints, useMediaQuery } from '../../hooks/ui/use-media-query';
|
|
15
15
|
import { cn } from '../../utils/cn';
|
|
16
|
-
import {
|
|
16
|
+
import { PanelResizeGrip } from '../ui/panel-resize-handle';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* - `docked`: a column beside the content, resizable.
|
|
@@ -523,12 +523,7 @@ function SidePanelResizeHandle({ state, label, controls }: SidePanelResizeHandle
|
|
|
523
523
|
onKeyDown={handleKeyDown}
|
|
524
524
|
className="group absolute inset-y-0 left-0 z-10 flex w-4 -translate-x-1/2 cursor-col-resize touch-none select-none items-center justify-center outline-none"
|
|
525
525
|
>
|
|
526
|
-
<
|
|
527
|
-
aria-hidden
|
|
528
|
-
className="flex h-8 w-4 items-center justify-center rounded-sm border border-ods-border bg-ods-card text-ods-text-secondary transition-colors group-hover:border-ods-border-hover group-focus-visible:border-ods-accent group-focus-visible:text-ods-accent"
|
|
529
|
-
>
|
|
530
|
-
<Menu01Icon size={12} />
|
|
531
|
-
</span>
|
|
526
|
+
<PanelResizeGrip />
|
|
532
527
|
</div>
|
|
533
528
|
);
|
|
534
529
|
}
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import { cloneElement, isValidElement, type MouseEvent, type ReactNode, useId, useState } from 'react';
|
|
4
4
|
import Link from '../../embed-shims/next-link';
|
|
5
|
+
import { useClippedLabelTooltip } from '../../hooks/ui/use-clipped-label-tooltip';
|
|
5
6
|
import type { NavigationSidebarItem } from '../../types/navigation';
|
|
6
7
|
import { cn } from '../../utils';
|
|
7
8
|
import { UnifiedSkeleton } from '../loading/unified-skeleton';
|
|
9
|
+
import { FloatingTooltip } from '../ui/floating-tooltip';
|
|
8
10
|
import { NavigationItemBadge } from './navigation-item-badge';
|
|
9
11
|
import { UnreadDot } from './unread-dot';
|
|
10
12
|
|
|
@@ -150,8 +152,15 @@ function RowEnd({ count }: { count: number }) {
|
|
|
150
152
|
return count > 0 ? <CountPill count={count} className="ml-auto" /> : null;
|
|
151
153
|
}
|
|
152
154
|
|
|
153
|
-
|
|
155
|
+
/**
|
|
156
|
+
* The row's label column. A label the expanded sidebar clips (a narrow width,
|
|
157
|
+
* a long label) shows its full text in a tooltip over it, as `TruncateText`
|
|
158
|
+
* offers it — only while it IS clipped, and only where a pointer can hover.
|
|
159
|
+
* The rail (`showLabel` false) has its own: the row's `title`.
|
|
160
|
+
*/
|
|
161
|
+
function RowLabel({ item, showLabel = true }: { item: NavigationSidebarItem; showLabel?: boolean }) {
|
|
154
162
|
const unreadCount = item.unreadCount ?? 0;
|
|
163
|
+
const { ref: labelRef, enabled: labelTooltip } = useClippedLabelTooltip<HTMLSpanElement>(item.label);
|
|
155
164
|
return (
|
|
156
165
|
<span
|
|
157
166
|
className={cn(
|
|
@@ -159,7 +168,17 @@ function RowLabel({ item }: { item: NavigationSidebarItem }) {
|
|
|
159
168
|
EXPANDED_ONLY,
|
|
160
169
|
)}
|
|
161
170
|
>
|
|
162
|
-
<
|
|
171
|
+
<FloatingTooltip
|
|
172
|
+
content={item.label}
|
|
173
|
+
side="top"
|
|
174
|
+
as="span"
|
|
175
|
+
disabled={!showLabel || !labelTooltip}
|
|
176
|
+
triggerClassName="min-w-0"
|
|
177
|
+
>
|
|
178
|
+
<span ref={labelRef} className="block min-w-0 truncate text-h4">
|
|
179
|
+
{item.label}
|
|
180
|
+
</span>
|
|
181
|
+
</FloatingTooltip>
|
|
163
182
|
{item.badge && <NavigationItemBadge label={item.badge} compact />}
|
|
164
183
|
<RowEnd count={unreadCount} />
|
|
165
184
|
</span>
|
|
@@ -204,7 +223,7 @@ function GroupedLeafRow({
|
|
|
204
223
|
const content = (
|
|
205
224
|
<>
|
|
206
225
|
<RowIcon item={item} size={20} className={lit ? 'text-ods-accent' : idleIcon('row')} />
|
|
207
|
-
<RowLabel item={item} />
|
|
226
|
+
<RowLabel item={item} showLabel={showLabel} />
|
|
208
227
|
</>
|
|
209
228
|
);
|
|
210
229
|
const shared = {
|
|
@@ -248,6 +267,8 @@ function GroupedChildRow({
|
|
|
248
267
|
}) {
|
|
249
268
|
const name = accessibleName(item);
|
|
250
269
|
const lit = isActive && !disabled;
|
|
270
|
+
// The clipped label's tooltip, as `RowLabel` offers it.
|
|
271
|
+
const { ref: labelRef, enabled: labelTooltip } = useClippedLabelTooltip<HTMLSpanElement>(item.label);
|
|
251
272
|
const className = cn(
|
|
252
273
|
// The icon column flush, under the parent's icon at both widths.
|
|
253
274
|
'group/nav-child relative flex h-11 w-full items-stretch gap-[var(--spacing-system-xxs)] rounded-md text-left',
|
|
@@ -276,7 +297,17 @@ function GroupedChildRow({
|
|
|
276
297
|
!lit && !disabled && isPending && 'after:opacity-100',
|
|
277
298
|
)}
|
|
278
299
|
>
|
|
279
|
-
<
|
|
300
|
+
<FloatingTooltip
|
|
301
|
+
content={item.label}
|
|
302
|
+
side="top"
|
|
303
|
+
as="span"
|
|
304
|
+
disabled={!showLabel || !labelTooltip}
|
|
305
|
+
triggerClassName="min-w-0"
|
|
306
|
+
>
|
|
307
|
+
<span ref={labelRef} className="block min-w-0 truncate text-h4">
|
|
308
|
+
{item.label}
|
|
309
|
+
</span>
|
|
310
|
+
</FloatingTooltip>
|
|
280
311
|
{item.badge && <NavigationItemBadge label={item.badge} compact />}
|
|
281
312
|
<RowEnd count={item.unreadCount ?? 0} />
|
|
282
313
|
</span>
|
|
@@ -365,7 +396,7 @@ function GroupedGroup({
|
|
|
365
396
|
)}
|
|
366
397
|
>
|
|
367
398
|
<RowIcon item={item} size={20} className={lit ? 'text-ods-accent' : idleIcon('row')} />
|
|
368
|
-
<RowLabel item={item} />
|
|
399
|
+
<RowLabel item={item} showLabel={showLabel} />
|
|
369
400
|
</button>
|
|
370
401
|
<div
|
|
371
402
|
id={groupDomId}
|
|
@@ -517,7 +548,7 @@ export function NavigationSidebarActionRow({
|
|
|
517
548
|
const content = (
|
|
518
549
|
<>
|
|
519
550
|
<RowIcon item={entry} size={20} className={lit || (outline && !disabled) ? 'text-ods-accent' : idleIcon('row')} />
|
|
520
|
-
<RowLabel item={entry} />
|
|
551
|
+
<RowLabel item={entry} showLabel={!minimized} />
|
|
521
552
|
</>
|
|
522
553
|
);
|
|
523
554
|
const shared = { className: classes, title: minimized ? name : undefined, 'aria-label': name };
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { cloneElement, memo, type ReactElement, type ReactNode, type MouseEvent } from 'react';
|
|
4
4
|
import Link from '../../embed-shims/next-link';
|
|
5
|
+
import { useClippedLabelTooltip } from '../../hooks/ui/use-clipped-label-tooltip';
|
|
5
6
|
import type { NavigationSidebarItem } from '../../types/navigation';
|
|
6
7
|
import { cn } from '../../utils';
|
|
7
8
|
import { UnifiedSkeleton } from '../loading/unified-skeleton';
|
|
8
9
|
import { BUTTON_KIND_CLASS, Button } from '../ui/button';
|
|
10
|
+
import { FloatingTooltip } from '../ui/floating-tooltip';
|
|
9
11
|
import { useLoadingScope } from '../ui/loading-scope';
|
|
10
12
|
import { NavigationItemBadge } from './navigation-item-badge';
|
|
11
13
|
import { UnreadDot } from './unread-dot';
|
|
@@ -76,6 +78,11 @@ function LoadedNavigationSidebarItemButton({
|
|
|
76
78
|
// The accessible name carries the stamp — "Devices (Beta)" — because `aria-label`
|
|
77
79
|
// replaces the content; the rail's tooltip is the same string.
|
|
78
80
|
const name = item.badge ? `${item.label} (${item.badge})` : item.label;
|
|
81
|
+
// A label the expanded sidebar clips (a narrow width, a long label): its full
|
|
82
|
+
// text in a tooltip over it, as `TruncateText` offers it — only while it IS
|
|
83
|
+
// clipped, since a label that fits would only repeat itself, and only where a
|
|
84
|
+
// pointer can hover. The rail's tooltip is the row's `title` below.
|
|
85
|
+
const { ref: labelRef, enabled: labelTooltip } = useClippedLabelTooltip<HTMLSpanElement>(item.label);
|
|
79
86
|
|
|
80
87
|
const content: ReactNode = (
|
|
81
88
|
<>
|
|
@@ -98,7 +105,17 @@ function LoadedNavigationSidebarItemButton({
|
|
|
98
105
|
)}
|
|
99
106
|
aria-hidden={!showLabel}
|
|
100
107
|
>
|
|
101
|
-
<
|
|
108
|
+
<FloatingTooltip
|
|
109
|
+
content={item.label}
|
|
110
|
+
side="top"
|
|
111
|
+
as="span"
|
|
112
|
+
disabled={!showLabel || !labelTooltip}
|
|
113
|
+
triggerClassName="min-w-0"
|
|
114
|
+
>
|
|
115
|
+
<span ref={labelRef} className="block min-w-0 truncate text-h4">
|
|
116
|
+
{item.label}
|
|
117
|
+
</span>
|
|
118
|
+
</FloatingTooltip>
|
|
102
119
|
{item.badge && <NavigationItemBadge label={item.badge} className="ml-[var(--spacing-system-xs)] shrink-0" />}
|
|
103
120
|
</span>
|
|
104
121
|
|