@flamingo-stack/openframe-frontend-core 0.0.731 → 0.0.733
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-RAHRDJBP.cjs → chunk-3HPB4H4C.cjs} +4 -4
- package/dist/{chunk-2UNQJVYN.cjs → chunk-3KGICRH7.cjs} +1457 -1187
- package/dist/{chunk-A4HNF4MP.cjs → chunk-3UTPZW36.cjs} +89 -89
- package/dist/{chunk-GID62YEN.cjs → chunk-AAFW4STC.cjs} +3 -3
- package/dist/{chunk-XDBHGWFH.cjs → chunk-ATMBJBOI.cjs} +16 -16
- package/dist/{chunk-YDGBRI3Y.cjs → chunk-AV5533DH.cjs} +15 -15
- package/dist/{chunk-BFHLGEJK.cjs → chunk-BP6R5WDW.cjs} +63 -60
- package/dist/{chunk-CLILWI6O.cjs → chunk-CJVAA7KL.cjs} +32 -32
- package/dist/{chunk-YJFN2W7A.cjs → chunk-DUVOQHQI.cjs} +7 -7
- package/dist/{chunk-E3N24573.cjs → chunk-DUXLZPMJ.cjs} +9 -9
- package/dist/{chunk-TH4N726A.cjs → chunk-E26I4XH6.cjs} +17 -17
- package/dist/{chunk-Y4QRK3CB.cjs → chunk-EMG3T55N.cjs} +7 -7
- package/dist/{chunk-JK75ZZP3.cjs → chunk-EQORT7NK.cjs} +59 -59
- package/dist/chunk-FCGLSWKR.cjs +417 -0
- package/dist/{chunk-MANNIJ42.cjs → chunk-FICJS5OA.cjs} +6 -0
- package/dist/{chunk-QP6QS3YU.cjs → chunk-FNRMDDVN.cjs} +48 -50
- package/dist/{chunk-KLXHCNDC.cjs → chunk-FTTLBMHU.cjs} +3 -3
- package/dist/{chunk-JPZ4Y6X3.cjs → chunk-GB3CHAW7.cjs} +93 -93
- package/dist/{chunk-7DUXR4HL.cjs → chunk-GVTDVS5U.cjs} +3 -3
- package/dist/{chunk-ZV6473Z4.cjs → chunk-HSWHOCZ3.cjs} +20 -20
- package/dist/{chunk-53XZ6MJ5.cjs → chunk-IC4FT3A6.cjs} +28 -28
- package/dist/{chunk-6V4GEE55.cjs → chunk-J7JKPTGO.cjs} +12 -12
- package/dist/{chunk-FKKUQK4C.cjs → chunk-L4ZNR5V7.cjs} +43 -39
- package/dist/{chunk-NYY25425.cjs → chunk-MYBTNOS6.cjs} +2 -2
- package/dist/chunk-NTJE6SM6.cjs +28 -0
- package/dist/{chunk-RBOIGJYF.cjs → chunk-P3R23TOE.cjs} +35 -35
- package/dist/chunk-PX7FY655.cjs +114 -0
- package/dist/{chunk-YUXRIR45.cjs → chunk-SA7LDEVJ.cjs} +98 -50
- package/dist/{chunk-32ENYVFA.cjs → chunk-TIMCHP4X.cjs} +42 -42
- package/dist/{chunk-3SWLL4IM.cjs → chunk-U4T5NLZD.cjs} +8 -8
- package/dist/{chunk-HI7EFAFQ.cjs → chunk-XDWY6HAW.cjs} +28 -28
- package/dist/{chunk-25H2SZH7.cjs → chunk-XY632BCH.cjs} +4 -4
- package/dist/{chunk-HY2KTQSL.cjs → chunk-YIXKH2JO.cjs} +236 -236
- package/dist/{chunk-4YPV4OI4.cjs → chunk-ZBNA6TCW.cjs} +82 -82
- package/dist/{chunk-HLIJ3U2T.cjs → chunk-ZJNYWNCB.cjs} +23 -21
- package/dist/{chunk-HEOD765R.cjs → chunk-ZRYKDN2J.cjs} +32 -379
- package/dist/{cloud-tenants-SC4FNAC2.cjs → cloud-tenants-3GOGR3EN.cjs} +22 -22
- package/dist/components/agent-identity.d.ts +8 -0
- package/dist/components/agent-identity.d.ts.map +1 -1
- package/dist/components/agent-identity.js +13 -1
- package/dist/components/case-studies/index.cjs +35 -35
- package/dist/components/chat/embeddable-chat.d.ts +16 -0
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- package/dist/components/chat/embeddable-chat.js +5 -2
- package/dist/components/chat/index.cjs +16 -16
- package/dist/components/chat/utils/icon-library.d.ts.map +1 -1
- package/dist/components/chat/utils/icon-library.js +6 -0
- package/dist/components/contact/index.cjs +23 -23
- package/dist/components/docs/index.cjs +26 -26
- package/dist/components/embeds/index.cjs +23 -23
- package/dist/components/faq/index.cjs +23 -23
- package/dist/components/features/index.cjs +22 -22
- package/dist/components/help-center-pages/index.cjs +138 -138
- package/dist/components/index.cjs +89 -82
- package/dist/components/meeting-scheduler/index.cjs +67 -67
- package/dist/components/navigation/index.cjs +25 -25
- package/dist/components/onboarding-guides/index.cjs +25 -25
- package/dist/components/product-demo/index.cjs +8 -8
- package/dist/components/product-demo/screens/remote-desktop-picture.d.ts.map +1 -1
- package/dist/components/product-demo/screens/remote-desktop-picture.js +2 -1
- package/dist/components/related-content/index.cjs +10 -10
- package/dist/components/tickets/index.cjs +26 -26
- package/dist/components/ui/device-wallpaper.d.ts +7 -0
- package/dist/components/ui/device-wallpaper.d.ts.map +1 -0
- package/dist/components/ui/device-wallpaper.js +5 -0
- package/dist/components/ui/feature-list.d.ts +9 -4
- package/dist/components/ui/feature-list.d.ts.map +1 -1
- package/dist/components/ui/feature-list.js +26 -35
- package/dist/components/ui/index.cjs +28 -22
- package/dist/components/ui/index.d.ts +3 -1
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +3 -1
- package/dist/components/ui/notification-stack.d.ts +33 -0
- package/dist/components/ui/notification-stack.d.ts.map +1 -0
- package/dist/components/ui/notification-stack.js +78 -0
- package/dist/components/ui/numbered-step.d.ts +29 -0
- package/dist/components/ui/numbered-step.d.ts.map +1 -0
- package/dist/components/ui/numbered-step.js +57 -0
- package/dist/components/ui/phone-frame.d.ts +14 -9
- package/dist/components/ui/phone-frame.d.ts.map +1 -1
- package/dist/components/ui/phone-frame.js +50 -6
- package/dist/components/ui/phone-glyphs/lock-camera-glyph.d.ts +5 -0
- package/dist/components/ui/phone-glyphs/lock-camera-glyph.d.ts.map +1 -0
- package/dist/components/ui/phone-glyphs/lock-camera-glyph.js +14 -0
- package/dist/components/ui/phone-glyphs/lock-flashlight-glyph.d.ts +5 -0
- package/dist/components/ui/phone-glyphs/lock-flashlight-glyph.d.ts.map +1 -0
- package/dist/components/ui/phone-glyphs/lock-flashlight-glyph.js +14 -0
- package/dist/components/ui/phone-glyphs/status-bar-glyphs.d.ts +5 -0
- package/dist/components/ui/phone-glyphs/status-bar-glyphs.d.ts.map +1 -0
- package/dist/components/ui/phone-glyphs/status-bar-glyphs.js +68 -0
- package/dist/components/ui/searchable-select.d.ts +29 -3
- package/dist/components/ui/searchable-select.d.ts.map +1 -1
- package/dist/components/ui/searchable-select.js +101 -45
- package/dist/components/ui/snap-carousel.d.ts +5 -8
- package/dist/components/ui/snap-carousel.d.ts.map +1 -1
- package/dist/components/ui/snap-carousel.js +2 -27
- package/dist/components/ui/tab-navigation.d.ts +8 -1
- package/dist/components/ui/tab-navigation.d.ts.map +1 -1
- package/dist/components/ui/tab-navigation.js +30 -20
- package/dist/{devices-7MHSJNXS.cjs → devices-POZXTP7X.cjs} +23 -23
- package/dist/{embeddable-chat-VPIG3F7E.cjs → embeddable-chat-I3Y5GCCR.cjs} +96 -93
- package/dist/hooks/index.cjs +9 -9
- package/dist/hooks/ui/use-autoplay.d.ts +50 -13
- package/dist/hooks/ui/use-autoplay.d.ts.map +1 -1
- package/dist/hooks/ui/use-autoplay.js +76 -13
- package/dist/hooks/ui/use-in-view.d.ts +8 -0
- package/dist/hooks/ui/use-in-view.d.ts.map +1 -1
- package/dist/hooks/ui/use-in-view.js +21 -36
- package/dist/hooks/use-agent-identities.d.ts +6 -0
- package/dist/hooks/use-agent-identities.d.ts.map +1 -1
- package/dist/hooks/use-scenario-player.d.ts +26 -9
- package/dist/hooks/use-scenario-player.d.ts.map +1 -1
- package/dist/hooks/use-scenario-player.js +12 -9
- package/dist/index.cjs +58 -51
- package/dist/{knowledge-4DREGSOZ.cjs → knowledge-AP34IMXN.cjs} +23 -23
- package/dist/{logs-BWXNT2KP.cjs → logs-CEVMIOPW.cjs} +30 -30
- package/dist/{policies-MGFUI23M.cjs → policies-AMBMRCGB.cjs} +26 -26
- package/dist/{remote-session-O6QGXV2N.cjs → remote-session-Z4YZFG5E.cjs} +10 -9
- package/dist/{software-update-7LF7XEYC.cjs → software-update-Z3MWMSHI.cjs} +26 -26
- package/dist/{tickets-board-KNXK747O.cjs → tickets-board-JOSYLOS7.cjs} +13 -13
- package/package.json +1 -1
- package/src/components/agent-identity.tsx +20 -1
- package/src/components/chat/embeddable-chat.tsx +29 -2
- package/src/components/chat/utils/icon-library.tsx +7 -0
- package/src/components/product-demo/screens/remote-desktop-picture.tsx +2 -1
- package/src/components/ui/device-wallpaper.ts +7 -0
- package/src/components/ui/feature-list.tsx +24 -30
- package/src/components/ui/index.ts +3 -1
- package/src/components/ui/notification-stack.tsx +133 -0
- package/src/components/ui/numbered-step.tsx +81 -0
- package/src/components/ui/phone-frame.tsx +83 -19
- package/src/components/ui/phone-glyphs/lock-camera-glyph.tsx +14 -0
- package/src/components/ui/phone-glyphs/lock-flashlight-glyph.tsx +14 -0
- package/src/components/ui/phone-glyphs/status-bar-glyphs.tsx +45 -0
- package/src/components/ui/searchable-select.tsx +124 -32
- package/src/components/ui/snap-carousel.tsx +6 -38
- package/src/components/ui/tab-navigation.tsx +49 -21
- package/src/hooks/ui/use-autoplay.ts +144 -27
- package/src/hooks/ui/use-in-view.ts +31 -38
- package/src/hooks/use-agent-identities.ts +6 -0
- package/src/hooks/use-scenario-player.ts +44 -20
- package/dist/chunk-2WXLQ5R5.cjs +0 -99
- package/dist/chunk-BE2WAMG5.cjs +0 -54
- package/dist/chunk-LCTDHLDL.cjs +0 -43
- package/dist/components/ui/playback-toggle.d.ts +0 -24
- package/dist/components/ui/playback-toggle.d.ts.map +0 -1
- package/dist/components/ui/playback-toggle.js +0 -32
- package/src/components/ui/playback-toggle.tsx +0 -53
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { createContext, useContext, useEffect, useMemo, type ReactNode } from 'react';
|
|
3
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, type ReactNode } from 'react';
|
|
4
4
|
import {
|
|
5
5
|
AGENT_IDENTITIES_API_PATH,
|
|
6
6
|
useAgentIdentities,
|
|
7
7
|
type AgentIdentityIcon,
|
|
8
8
|
type AgentIdentityIcons,
|
|
9
9
|
} from '../hooks/use-agent-identities';
|
|
10
|
+
import { HEX_PATTERN } from '../utils/ods-color-utils';
|
|
10
11
|
|
|
11
12
|
export { AGENT_IDENTITIES_API_PATH, type AgentIdentityIcon, type AgentIdentityIcons };
|
|
12
13
|
|
|
@@ -107,3 +108,21 @@ export function useAgentIdentityIcon(slug: string | null | undefined): AgentIden
|
|
|
107
108
|
}, [unprovided, slug]);
|
|
108
109
|
return identities?.get(slug ?? '');
|
|
109
110
|
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Each agent identity's accent, as a lookup by slug: the hex colour its
|
|
114
|
+
* identity stores, or undefined when it names none (the caller then draws the platform's
|
|
115
|
+
* accent). THE way anything is drawn in an agent's colour: never a colour
|
|
116
|
+
* chosen per agent in a component. A lookup, not one value, because a row of
|
|
117
|
+
* agents (a tab per agent) needs every one of them.
|
|
118
|
+
*/
|
|
119
|
+
export function useAgentAccents(): (slug: string | null | undefined) => string | undefined {
|
|
120
|
+
const identities = useContext(AgentIdentityContext);
|
|
121
|
+
return useCallback(
|
|
122
|
+
slug => {
|
|
123
|
+
const accent = identities?.get(slug ?? '')?.accent;
|
|
124
|
+
return typeof accent === 'string' && HEX_PATTERN.test(accent) ? accent : undefined;
|
|
125
|
+
},
|
|
126
|
+
[identities],
|
|
127
|
+
);
|
|
128
|
+
}
|
|
@@ -481,6 +481,22 @@ export interface EmbeddableChatProps {
|
|
|
481
481
|
items: ChatContextItem[];
|
|
482
482
|
onRemove?: (item: ChatContextItem) => void;
|
|
483
483
|
};
|
|
484
|
+
/**
|
|
485
|
+
* Host content pinned between the thread and the composer: the thread
|
|
486
|
+
* scrolls above it, the composer stays below, and it keeps its place while
|
|
487
|
+
* the thread grows (e.g. a guided tour the host attaches to one
|
|
488
|
+
* conversation, deciding by `mingoState.activeDialogId`). Rendered wherever
|
|
489
|
+
* the composer is - a conversation or the new-chat surface - and never on
|
|
490
|
+
* the chat list. Sized by its content: a tall block is the host's to cap or
|
|
491
|
+
* scroll. `null`/unset leaves the layout as it was.
|
|
492
|
+
*/
|
|
493
|
+
aboveComposer?: React.ReactNode;
|
|
494
|
+
/**
|
|
495
|
+
* v2: what the list's "What to ask Mingo?" opens. Default: a new chat, the
|
|
496
|
+
* same as "Start New Chat"; a host with its own suggestions surface (a
|
|
497
|
+
* guided tour in a conversation of its own) points it there instead.
|
|
498
|
+
*/
|
|
499
|
+
onWhatToAsk?: () => void;
|
|
484
500
|
}
|
|
485
501
|
|
|
486
502
|
// =============================================================================
|
|
@@ -1045,6 +1061,8 @@ function EmbeddableChatInner({
|
|
|
1045
1061
|
guidePendingPrompt,
|
|
1046
1062
|
onGuidePromptConsumed,
|
|
1047
1063
|
contextMemory,
|
|
1064
|
+
aboveComposer,
|
|
1065
|
+
onWhatToAsk,
|
|
1048
1066
|
handleRef,
|
|
1049
1067
|
}: EmbeddableChatProps & { handleRef?: React.Ref<EmbeddableChatHandle> }) {
|
|
1050
1068
|
// `shell` 'none' / 'inline' means the consumer hosts us inside their own panel
|
|
@@ -2397,8 +2415,8 @@ function EmbeddableChatInner({
|
|
|
2397
2415
|
onNewChat: leaveArchiveThen(handleNewChat),
|
|
2398
2416
|
onOpenArchive: headerOnOpenArchive && (archiveOpen ? closeArchive : headerOnOpenArchive),
|
|
2399
2417
|
archiveActive: archiveOpen,
|
|
2400
|
-
// The suggestions a new chat opens on.
|
|
2401
|
-
onWhatToAsk: leaveArchiveThen(handleNewChat),
|
|
2418
|
+
// The suggestions a new chat opens on, unless the host has a surface of its own.
|
|
2419
|
+
onWhatToAsk: leaveArchiveThen(onWhatToAsk ?? handleNewChat),
|
|
2402
2420
|
statusOf: dialogStatusOf,
|
|
2403
2421
|
onRequestRename: mingoCaps.canRename ? setRenameTarget : undefined,
|
|
2404
2422
|
onRequestArchive: mingoCaps.canArchive ? setArchiveTarget : undefined,
|
|
@@ -2987,6 +3005,15 @@ function EmbeddableChatInner({
|
|
|
2987
3005
|
</div>
|
|
2988
3006
|
</div>
|
|
2989
3007
|
|
|
3008
|
+
{/* Host block pinned above the composer (`aboveComposer`):
|
|
3009
|
+
the thread's own bottom padding separates them, the
|
|
3010
|
+
composer's horizontal inset is repeated so the two line up. */}
|
|
3011
|
+
{aboveComposer != null && aboveComposer !== false && (
|
|
3012
|
+
<div className="shrink-0 px-[var(--spacing-system-m)] pb-[var(--spacing-system-m)]">
|
|
3013
|
+
{aboveComposer}
|
|
3014
|
+
</div>
|
|
3015
|
+
)}
|
|
3016
|
+
|
|
2990
3017
|
<ChatAttachmentChipStrip
|
|
2991
3018
|
attachments={stagedAttachments}
|
|
2992
3019
|
onRemove={removeAttachment}
|
|
@@ -90,6 +90,13 @@ export const ICON_ALIASES: Readonly<Record<string, IconRef>> = {
|
|
|
90
90
|
video: { set: 'VideoRecorderIcon' },
|
|
91
91
|
headphones: { set: 'HeadphoneIcon' },
|
|
92
92
|
|
|
93
|
+
// The two LLM brands offered by `ICON_OPTIONS`. Their brand-colored glyphs
|
|
94
|
+
// are the only picker entries that hardcode a fill (#000 / #D97757), so they
|
|
95
|
+
// ignored the chip accent and sat black and orange in a cyan grid; the grey
|
|
96
|
+
// variants fill with `currentColor` like every other brand logo here.
|
|
97
|
+
'openai-logo': { set: 'OpenaiLogoGreyIcon' },
|
|
98
|
+
'anthropic-logo': { set: 'AnthropicLogoGreyIcon' },
|
|
99
|
+
|
|
93
100
|
// Figma-canonical aliases (node 7363:205938) — kept for Storybook
|
|
94
101
|
// fixtures and any older feed entries still using the long names.
|
|
95
102
|
'clickup-logo': CLICKUP_GREY,
|
|
@@ -13,6 +13,7 @@ import { StopIcon } from '../../icons-v2-generated/media-playback/stop-icon';
|
|
|
13
13
|
import { CheckCircleIcon } from '../../icons-v2-generated/signs-and-symbols/check-circle-icon';
|
|
14
14
|
import { MinusIcon } from '../../icons-v2-generated/signs-and-symbols/minus-icon';
|
|
15
15
|
import { XmarkIcon } from '../../icons-v2-generated/signs-and-symbols/xmark-icon';
|
|
16
|
+
import { DEVICE_WALLPAPER_CLASS } from '../../ui/device-wallpaper';
|
|
16
17
|
import type { RemoteSessionFixture } from '../fixtures/remote-session';
|
|
17
18
|
|
|
18
19
|
function DesktopShortcut({ icon, label }: { icon: ReactNode; label: string }) {
|
|
@@ -56,7 +57,7 @@ function TaskbarApp({ icon, active = false }: { icon: ReactNode; active?: boolea
|
|
|
56
57
|
*/
|
|
57
58
|
export function RemoteDesktopPicture({ desktop }: { desktop: RemoteSessionFixture['desktop'] }) {
|
|
58
59
|
return (
|
|
59
|
-
<div className=
|
|
60
|
+
<div className={`absolute inset-0 flex flex-col overflow-hidden ${DEVICE_WALLPAPER_CLASS}`}>
|
|
60
61
|
<div className="relative min-h-0 flex-1">
|
|
61
62
|
{/* A narrow box shows the window alone: the shortcuts would leave it no room. */}
|
|
62
63
|
<div className="absolute left-[var(--spacing-system-sf)] top-[var(--spacing-system-sf)] hidden flex-col gap-[var(--spacing-system-sf)] content-md:flex">
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE wallpaper of a drawn device screen (the remote desktop in the product
|
|
3
|
+
* demo, the phone's lock screen): one gradient across the two brand colours,
|
|
4
|
+
* so every device a page draws is lit the same way.
|
|
5
|
+
*/
|
|
6
|
+
export const DEVICE_WALLPAPER_CLASS =
|
|
7
|
+
'bg-gradient-to-br from-ods-flamingo-cyan-secondary via-ods-bg-surface to-ods-flamingo-pink-secondary';
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type React from 'react';
|
|
4
4
|
import { Button } from './button';
|
|
5
|
+
import { NumberedStep } from './numbered-step';
|
|
5
6
|
|
|
6
7
|
export interface FeatureListItemData {
|
|
7
8
|
/** Shown in the boxed slot. Not used by the `numbered` variant. */
|
|
@@ -21,11 +22,16 @@ export interface FeatureListProps {
|
|
|
21
22
|
*/
|
|
22
23
|
variant?: 'boxed' | 'numbered';
|
|
23
24
|
/**
|
|
24
|
-
* `numbered` only. The step being shown now:
|
|
25
|
-
*
|
|
26
|
-
* demo. Omit for a plain list where every step is equal.
|
|
25
|
+
* `numbered` only. The step being shown now: its marker and its title take
|
|
26
|
+
* the accent and its description reads at full strength, so the list reads as
|
|
27
|
+
* "you are here" beside a timed demo. Omit for a plain list where every step is equal.
|
|
27
28
|
*/
|
|
28
29
|
activeIndex?: number;
|
|
30
|
+
/**
|
|
31
|
+
* `numbered` only. The colour of the step being shown (a CSS colour, such as
|
|
32
|
+
* the hex of the agent whose demo it is). Omitted, it is the platform's accent.
|
|
33
|
+
*/
|
|
34
|
+
accent?: string;
|
|
29
35
|
/**
|
|
30
36
|
* `numbered` only. Makes every step a button: choosing one reports its index
|
|
31
37
|
* (a demo jumps to that step). Omit for a list that is only read.
|
|
@@ -39,6 +45,7 @@ export function FeatureList({
|
|
|
39
45
|
iconBoxSize = 72,
|
|
40
46
|
variant = 'boxed',
|
|
41
47
|
activeIndex,
|
|
48
|
+
accent,
|
|
42
49
|
onSelect,
|
|
43
50
|
}: FeatureListProps) {
|
|
44
51
|
if (variant === 'numbered') {
|
|
@@ -47,32 +54,19 @@ export function FeatureList({
|
|
|
47
54
|
<ol className={`flex flex-col gap-[var(--spacing-system-lf)] ${className}`}>
|
|
48
55
|
{items.map((item, index) => {
|
|
49
56
|
const current = dims && index === activeIndex;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
</span>
|
|
61
|
-
<div className="flex min-w-0 flex-col gap-[var(--spacing-system-xxs)]">
|
|
62
|
-
<h3 className="text-ods-text-primary text-h4">{item.title}</h3>
|
|
63
|
-
<p className="text-ods-text-secondary text-h6">{item.description}</p>
|
|
64
|
-
</div>
|
|
65
|
-
</>
|
|
57
|
+
// Every numbered step on a page is the one `NumberedStep`.
|
|
58
|
+
const step = (
|
|
59
|
+
<NumberedStep
|
|
60
|
+
number={index + 1}
|
|
61
|
+
title={item.title}
|
|
62
|
+
description={item.description}
|
|
63
|
+
current={current}
|
|
64
|
+
accent={accent}
|
|
65
|
+
className={onSelect ? 'w-full' : undefined}
|
|
66
|
+
/>
|
|
66
67
|
);
|
|
67
|
-
const layout = 'flex items-start gap-[var(--spacing-system-sf)]';
|
|
68
68
|
return (
|
|
69
|
-
<li
|
|
70
|
-
key={index}
|
|
71
|
-
aria-current={current ? 'step' : undefined}
|
|
72
|
-
className={`transition-opacity duration-200 motion-reduce:transition-none ${
|
|
73
|
-
dims && !current ? 'opacity-55 focus-within:opacity-100 hover:opacity-100' : ''
|
|
74
|
-
}`}
|
|
75
|
-
>
|
|
69
|
+
<li key={index} aria-current={current ? 'step' : undefined}>
|
|
76
70
|
{onSelect ? (
|
|
77
71
|
// The unified Button with no box of its own (`wrap`): it brings the
|
|
78
72
|
// target, the focus ring and the keyboard semantics; the row is the look.
|
|
@@ -81,12 +75,12 @@ export function FeatureList({
|
|
|
81
75
|
size="wrap"
|
|
82
76
|
fullWidth
|
|
83
77
|
onClick={() => onSelect(index)}
|
|
84
|
-
className=
|
|
78
|
+
className="items-start justify-start whitespace-normal text-left hover:bg-transparent active:bg-transparent"
|
|
85
79
|
>
|
|
86
|
-
{
|
|
80
|
+
{step}
|
|
87
81
|
</Button>
|
|
88
82
|
) : (
|
|
89
|
-
|
|
83
|
+
step
|
|
90
84
|
)}
|
|
91
85
|
</li>
|
|
92
86
|
);
|
|
@@ -45,7 +45,6 @@ export * from './modal';
|
|
|
45
45
|
export * from './modal-v2';
|
|
46
46
|
export * from './modal-guarded-close';
|
|
47
47
|
export * from './admin-form-drawer';
|
|
48
|
-
export * from './playback-toggle';
|
|
49
48
|
export * from './admin-form-modal';
|
|
50
49
|
export * from './separator';
|
|
51
50
|
export * from './sheet';
|
|
@@ -238,6 +237,9 @@ export { LazyMount, type LazyMountProps } from './lazy-mount';
|
|
|
238
237
|
export * from './app-window-frame';
|
|
239
238
|
export * from './product-screen-frame';
|
|
240
239
|
export * from './phone-frame';
|
|
240
|
+
export * from './notification-stack';
|
|
241
|
+
export * from './numbered-step';
|
|
242
|
+
export * from './device-wallpaper';
|
|
241
243
|
export * from './analyzing-state';
|
|
242
244
|
export * from './incident-feed';
|
|
243
245
|
export * from './status-line';
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { AnimatePresence, motion, useReducedMotion } from 'framer-motion';
|
|
4
|
+
import { useEffect, useState, type ReactNode } from 'react';
|
|
5
|
+
import { cn } from '../../utils/cn';
|
|
6
|
+
|
|
7
|
+
export interface NotificationStackItem {
|
|
8
|
+
key: string;
|
|
9
|
+
node: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface NotificationStackProps {
|
|
13
|
+
/** What has arrived so far, OLDEST FIRST. The newest is drawn on top. */
|
|
14
|
+
items: readonly NotificationStackItem[];
|
|
15
|
+
/** How many are shown whole when a new one has just arrived. Default 3. */
|
|
16
|
+
maxExpanded?: number;
|
|
17
|
+
/** How long they all stay whole after an arrival before the oldest starts to fold. Default 1800. */
|
|
18
|
+
lingerMs?: number;
|
|
19
|
+
/** The pause between one fold and the next: they go one at a time, never together. Default 900. */
|
|
20
|
+
foldEveryMs?: number;
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The most card edges drawn for the folded pile: past two a pile only reads as "more". */
|
|
25
|
+
const MAX_PILE_EDGES = 2;
|
|
26
|
+
|
|
27
|
+
// Gentle on purpose: a notification eases into place and the ones under it
|
|
28
|
+
// drift down with it. Soft springs with no overshoot; nothing snaps or bounces.
|
|
29
|
+
const ARRIVE = { type: 'spring', stiffness: 90, damping: 22, mass: 1 } as const;
|
|
30
|
+
const PUSH = { type: 'spring', stiffness: 60, damping: 20, mass: 1.1 } as const;
|
|
31
|
+
const FOLD = { type: 'spring', stiffness: 140, damping: 28, mass: 1 } as const;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Notifications as a phone keeps them: the most recent on top, the list
|
|
35
|
+
* starting where its host puts it (the lock screen: right under the clock). A
|
|
36
|
+
* new one DROPS IN FROM ABOVE into the top place and PUSHES the ones still
|
|
37
|
+
* whole DOWN. They stay
|
|
38
|
+
* whole for a moment (`lingerMs`, long enough to see that one more joined
|
|
39
|
+
* them), then fold away ONE AT A TIME, oldest first (`foldEveryMs` apart):
|
|
40
|
+
* each slips behind the ones above it and leaves its edge under the newest,
|
|
41
|
+
* until only the newest is whole on a pile of edges. Nothing that arrived is
|
|
42
|
+
* taken away, and the eye follows every one of them.
|
|
43
|
+
*
|
|
44
|
+
* The caller passes only what has arrived, oldest first; each `node` is the
|
|
45
|
+
* notification itself (`NotificationTile`). Under reduced motion the stack
|
|
46
|
+
* changes in place, with no travel.
|
|
47
|
+
*/
|
|
48
|
+
export function NotificationStack({
|
|
49
|
+
items,
|
|
50
|
+
maxExpanded = 3,
|
|
51
|
+
lingerMs = 1800,
|
|
52
|
+
foldEveryMs = 900,
|
|
53
|
+
className,
|
|
54
|
+
}: NotificationStackProps) {
|
|
55
|
+
const reducedMotion = useReducedMotion();
|
|
56
|
+
const count = items.length;
|
|
57
|
+
const opened = Math.min(maxExpanded, count);
|
|
58
|
+
|
|
59
|
+
// How many are whole now. A new arrival joins the ones still whole (the ones
|
|
60
|
+
// already folded stay folded); a list that restarts opens afresh. State
|
|
61
|
+
// derived from the prop during render, React's own pattern for it.
|
|
62
|
+
const [seenCount, setSeenCount] = useState(count);
|
|
63
|
+
const [whole, setWhole] = useState(opened);
|
|
64
|
+
const [justArrived, setJustArrived] = useState(false);
|
|
65
|
+
if (seenCount !== count) {
|
|
66
|
+
setSeenCount(count);
|
|
67
|
+
setWhole(count > seenCount ? Math.min(opened, whole + (count - seenCount)) : opened);
|
|
68
|
+
setJustArrived(true);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Fold the oldest whole one: the first after the linger, each next one a beat later.
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (whole <= 1) return undefined;
|
|
74
|
+
const timer = setTimeout(
|
|
75
|
+
() => {
|
|
76
|
+
setWhole(current => current - 1);
|
|
77
|
+
setJustArrived(false);
|
|
78
|
+
},
|
|
79
|
+
justArrived ? lingerMs : foldEveryMs,
|
|
80
|
+
);
|
|
81
|
+
return () => clearTimeout(timer);
|
|
82
|
+
}, [whole, justArrived, lingerMs, foldEveryMs]);
|
|
83
|
+
|
|
84
|
+
const shown = items.slice(-Math.max(1, Math.min(whole, count))).reverse();
|
|
85
|
+
const folded = Math.min(MAX_PILE_EDGES, Math.max(0, count - shown.length));
|
|
86
|
+
const still = { duration: 0 };
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div className={cn('flex w-full flex-col gap-1.5 pb-2.5', className)}>
|
|
90
|
+
<AnimatePresence initial={false} mode="popLayout">
|
|
91
|
+
{shown.map((item, index) => (
|
|
92
|
+
<motion.div
|
|
93
|
+
key={item.key}
|
|
94
|
+
layout={reducedMotion ? false : 'position'}
|
|
95
|
+
// It drops in from above, into the top place.
|
|
96
|
+
initial={{ opacity: 0, y: reducedMotion ? 0 : -20, scale: reducedMotion ? 1 : 0.98 }}
|
|
97
|
+
animate={{ opacity: 1, y: 0, scale: 1, transition: reducedMotion ? still : ARRIVE }}
|
|
98
|
+
// Folding: it slips up behind the one above it.
|
|
99
|
+
exit={{
|
|
100
|
+
opacity: 0,
|
|
101
|
+
y: reducedMotion ? 0 : -28,
|
|
102
|
+
scale: reducedMotion ? 1 : 0.94,
|
|
103
|
+
transition: reducedMotion ? still : FOLD,
|
|
104
|
+
}}
|
|
105
|
+
// The push: how the ones already there make room.
|
|
106
|
+
transition={reducedMotion ? still : PUSH}
|
|
107
|
+
// `isolate`: the pile's edges sit behind the notification, never behind the screen.
|
|
108
|
+
className="relative isolate min-w-0"
|
|
109
|
+
>
|
|
110
|
+
{/* The pile: one edge per folded notification, under the last whole one. */}
|
|
111
|
+
{index === shown.length - 1 && (
|
|
112
|
+
<AnimatePresence initial={false}>
|
|
113
|
+
{Array.from({ length: folded }, (_, edge) => (
|
|
114
|
+
<motion.span
|
|
115
|
+
key={edge}
|
|
116
|
+
aria-hidden
|
|
117
|
+
initial={{ opacity: 0, y: 0 }}
|
|
118
|
+
animate={{ opacity: 1 - (edge + 1) * 0.3, y: (edge + 1) * 6 }}
|
|
119
|
+
exit={{ opacity: 0, y: 0 }}
|
|
120
|
+
transition={reducedMotion ? still : FOLD}
|
|
121
|
+
className="absolute inset-0 rounded-2xl border border-ods-border bg-ods-card"
|
|
122
|
+
style={{ scaleX: 1 - (edge + 1) * 0.06, zIndex: -1 - edge }}
|
|
123
|
+
/>
|
|
124
|
+
))}
|
|
125
|
+
</AnimatePresence>
|
|
126
|
+
)}
|
|
127
|
+
{item.node}
|
|
128
|
+
</motion.div>
|
|
129
|
+
))}
|
|
130
|
+
</AnimatePresence>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { cn } from '../../utils/cn';
|
|
2
|
+
|
|
3
|
+
export interface StepNumberProps {
|
|
4
|
+
/** The step's number, as shown (1-based). */
|
|
5
|
+
number: number;
|
|
6
|
+
/** The step being shown now: its ring and number take the accent. */
|
|
7
|
+
current?: boolean;
|
|
8
|
+
/** The lit colour (a CSS colour, such as the hex an agent's identity stores). Omitted, it is the platform's accent. */
|
|
9
|
+
accent?: string;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* THE number of a numbered step, wherever a page counts steps: a bold number
|
|
15
|
+
* in a ring. Grey at rest, the accent when it is the current one.
|
|
16
|
+
*/
|
|
17
|
+
export function StepNumber({ number, current = false, accent, className }: StepNumberProps) {
|
|
18
|
+
return (
|
|
19
|
+
<span
|
|
20
|
+
className={cn(
|
|
21
|
+
'grid h-6 w-6 shrink-0 place-items-center rounded-full border-2 font-bold transition-colors duration-200 text-h6 motion-reduce:transition-none',
|
|
22
|
+
current ? !accent && 'border-ods-accent text-ods-accent' : 'border-ods-text-secondary text-ods-text-secondary',
|
|
23
|
+
className,
|
|
24
|
+
)}
|
|
25
|
+
style={current && accent ? { borderColor: accent, color: accent } : undefined}
|
|
26
|
+
>
|
|
27
|
+
{number}
|
|
28
|
+
</span>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface NumberedStepProps extends StepNumberProps {
|
|
33
|
+
title: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
/** The title's element. Default `h3`. */
|
|
36
|
+
titleAs?: 'h3' | 'h4' | 'p';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* THE head of a numbered step: its number (`StepNumber`), its title and one
|
|
41
|
+
* line under it. One look everywhere steps are counted (a list of steps beside
|
|
42
|
+
* a demo, the head of a column of a flow). At rest the title reads at full
|
|
43
|
+
* strength and the description steps back; the current one's number and title
|
|
44
|
+
* take the accent and its description reads at full strength.
|
|
45
|
+
*/
|
|
46
|
+
export function NumberedStep({
|
|
47
|
+
number,
|
|
48
|
+
title,
|
|
49
|
+
description,
|
|
50
|
+
current = false,
|
|
51
|
+
accent,
|
|
52
|
+
titleAs: Title = 'h3',
|
|
53
|
+
className,
|
|
54
|
+
}: NumberedStepProps) {
|
|
55
|
+
return (
|
|
56
|
+
<div className={cn('flex items-start gap-[var(--spacing-system-xs)]', className)}>
|
|
57
|
+
<StepNumber number={number} current={current} accent={accent} />
|
|
58
|
+
<div className="flex min-w-0 flex-col gap-[var(--spacing-system-xxs)]">
|
|
59
|
+
<Title
|
|
60
|
+
className={cn(
|
|
61
|
+
'm-0 transition-colors duration-200 text-h3 motion-reduce:transition-none',
|
|
62
|
+
current ? !accent && 'text-ods-accent' : 'text-ods-text-primary',
|
|
63
|
+
)}
|
|
64
|
+
style={current && accent ? { color: accent } : undefined}
|
|
65
|
+
>
|
|
66
|
+
{title}
|
|
67
|
+
</Title>
|
|
68
|
+
{description && (
|
|
69
|
+
<p
|
|
70
|
+
className={cn(
|
|
71
|
+
'm-0 transition-colors duration-200 text-h4 motion-reduce:transition-none',
|
|
72
|
+
current ? 'text-ods-text-primary' : 'text-ods-text-secondary',
|
|
73
|
+
)}
|
|
74
|
+
>
|
|
75
|
+
{description}
|
|
76
|
+
</p>
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -1,39 +1,103 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useSyncExternalStore, type ReactNode } from 'react';
|
|
2
4
|
import { cn } from '../../utils/cn';
|
|
5
|
+
import { DEVICE_WALLPAPER_CLASS } from './device-wallpaper';
|
|
6
|
+
import { LockCameraGlyph } from './phone-glyphs/lock-camera-glyph';
|
|
7
|
+
import { LockFlashlightGlyph } from './phone-glyphs/lock-flashlight-glyph';
|
|
8
|
+
import { StatusBarGlyphs } from './phone-glyphs/status-bar-glyphs';
|
|
3
9
|
|
|
4
10
|
export interface PhoneFrameProps {
|
|
5
|
-
/**
|
|
11
|
+
/** A fixed lock-screen date line ("Thursday, October 1"). Omitted, it is today's, in the visitor's own locale. */
|
|
6
12
|
dateLabel?: string;
|
|
7
|
-
/**
|
|
13
|
+
/** A fixed lock-screen clock ("5:09"). Omitted, it is the visitor's own time, kept current. */
|
|
8
14
|
timeLabel?: string;
|
|
9
|
-
/** The
|
|
15
|
+
/** The notifications, right under the clock, the newest first (`NotificationStack`). */
|
|
10
16
|
children?: ReactNode;
|
|
11
17
|
className?: string;
|
|
12
18
|
}
|
|
13
19
|
|
|
20
|
+
/** How often the clock is re-read. The label only changes with the minute, so most readings change nothing. */
|
|
21
|
+
const CLOCK_TICK_MS = 1000;
|
|
22
|
+
|
|
23
|
+
function subscribeToClock(onTick: () => void): () => void {
|
|
24
|
+
const timer = setInterval(onTick, CLOCK_TICK_MS);
|
|
25
|
+
return () => clearInterval(timer);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** "9:41": the hour and minute as a lock screen shows them, with no AM/PM. */
|
|
29
|
+
function readClock(): string {
|
|
30
|
+
const parts = new Intl.DateTimeFormat(undefined, { hour: 'numeric', minute: '2-digit' }).formatToParts(new Date());
|
|
31
|
+
const part = (type: string) => parts.find(p => p.type === type)?.value ?? '';
|
|
32
|
+
return `${part('hour')}:${part('minute')}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** "Monday, October 12". */
|
|
36
|
+
function readDate(): string {
|
|
37
|
+
return new Intl.DateTimeFormat(undefined, { weekday: 'long', month: 'long', day: 'numeric' }).format(new Date());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// The server cannot know the visitor's time: it (and the hydrating render) draw none, in the same box.
|
|
41
|
+
const noTime = () => '';
|
|
42
|
+
|
|
43
|
+
/** One of the lock screen's two round shortcuts. A drawing: it does nothing. */
|
|
44
|
+
function LockShortcut({ children }: { children: ReactNode }) {
|
|
45
|
+
return (
|
|
46
|
+
<span className="grid size-[34px] place-items-center rounded-full bg-ods-text-primary/35 text-ods-text-primary backdrop-blur-md">
|
|
47
|
+
{children}
|
|
48
|
+
</span>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
14
52
|
/**
|
|
15
|
-
* A phone lock screen: island, date
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
53
|
+
* A phone lock screen: the status bar and island, the date and clock on the
|
|
54
|
+
* device wallpaper (`DEVICE_WALLPAPER_CLASS`, the one every drawn device
|
|
55
|
+
* shares), the notifications stacked under the clock, then the two
|
|
56
|
+
* shortcuts and the home indicator. Decorative chrome for showing what lands
|
|
57
|
+
* on a phone.
|
|
58
|
+
*
|
|
59
|
+
* The clock is REAL: the visitor's own date and time, kept current, unless a
|
|
60
|
+
* caller fixes them. The server draws neither (it cannot know them); their
|
|
61
|
+
* lines keep their height, so nothing moves when they appear.
|
|
19
62
|
*
|
|
20
|
-
* It is sized by its class (default
|
|
63
|
+
* It is sized by its class (default 299 x 617), never scaled, so the text
|
|
21
64
|
* inside stays the size the page set.
|
|
22
65
|
*/
|
|
23
66
|
export function PhoneFrame({ dateLabel, timeLabel, children, className }: PhoneFrameProps) {
|
|
67
|
+
const liveTime = useSyncExternalStore(subscribeToClock, readClock, noTime);
|
|
68
|
+
const liveDate = useSyncExternalStore(subscribeToClock, readDate, noTime);
|
|
69
|
+
const time = timeLabel ?? liveTime;
|
|
70
|
+
const date = dateLabel ?? liveDate;
|
|
24
71
|
return (
|
|
25
72
|
<div
|
|
26
|
-
className={cn('h-[
|
|
73
|
+
className={cn('h-[617px] w-[299px] shrink-0 rounded-[48px] border border-ods-border bg-black p-2.5', className)}
|
|
27
74
|
>
|
|
28
|
-
<div className=
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
75
|
+
<div className={cn('relative flex h-full flex-col overflow-hidden rounded-[38px]', DEVICE_WALLPAPER_CLASS)}>
|
|
76
|
+
<div
|
|
77
|
+
className="grid shrink-0 grid-cols-[1fr_auto_1fr] items-center px-5 pt-2.5 text-ods-text-primary"
|
|
78
|
+
aria-hidden
|
|
79
|
+
>
|
|
80
|
+
<span className="font-semibold tabular-nums text-h6">{time}</span>
|
|
81
|
+
<span className="h-6 w-20 rounded-full bg-black" />
|
|
82
|
+
<StatusBarGlyphs className="h-2.5 w-[54px] justify-self-end" />
|
|
83
|
+
</div>
|
|
84
|
+
<div className="flex shrink-0 flex-col items-center pt-4 text-ods-text-primary">
|
|
85
|
+
<span className="min-h-[var(--font-line-space-h4-body)] text-h4">{date}</span>
|
|
86
|
+
<span className="h-[68px] text-[64px] font-bold tabular-nums leading-[68px] tracking-tight">{time}</span>
|
|
87
|
+
</div>
|
|
88
|
+
{/* Right under the clock, growing down; clipped at the foot, so a tall stack never runs over the shortcuts. */}
|
|
89
|
+
<div className="flex min-h-0 w-full flex-1 flex-col justify-start overflow-hidden px-2.5 pb-3 pt-4">
|
|
90
|
+
{children}
|
|
91
|
+
</div>
|
|
92
|
+
<div className="flex shrink-0 items-center justify-between px-8" aria-hidden>
|
|
93
|
+
<LockShortcut>
|
|
94
|
+
<LockFlashlightGlyph className="size-[34px]" />
|
|
95
|
+
</LockShortcut>
|
|
96
|
+
<LockShortcut>
|
|
97
|
+
<LockCameraGlyph className="size-[34px]" />
|
|
98
|
+
</LockShortcut>
|
|
99
|
+
</div>
|
|
100
|
+
<span className="mx-auto mb-2 mt-3 h-1 w-24 shrink-0 rounded-full bg-ods-text-primary" aria-hidden />
|
|
37
101
|
</div>
|
|
38
102
|
</div>
|
|
39
103
|
);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// The camera shortcut of the drawn phone's lock screen, as the design draws it. It belongs to `PhoneFrame` and
|
|
2
|
+
// nothing else, so it is not part of the icon set. It takes the text colour around it.
|
|
3
|
+
|
|
4
|
+
/** The camera shortcut's glyph, in its 34px round button's own box. */
|
|
5
|
+
export function LockCameraGlyph({ className }: { className?: string }) {
|
|
6
|
+
return (
|
|
7
|
+
<svg viewBox="0 0 34.0257 34.0257" fill="none" aria-hidden="true" className={className}>
|
|
8
|
+
<path
|
|
9
|
+
d="M10.4883 23.8176H23.5348C25.0193 23.8176 25.7758 23.0753 25.7758 21.6051V14.1326C25.7758 12.6624 25.0193 11.9273 23.5348 11.9273H21.4936C20.9297 11.9273 20.7585 11.8131 20.4373 11.4563L19.8663 10.8139C19.5095 10.4214 19.1455 10.2073 18.4104 10.2073H15.5627C14.8276 10.2073 14.4636 10.4214 14.1068 10.8139L13.5358 11.4563C13.2146 11.806 13.0362 11.9273 12.4795 11.9273H10.4883C8.99665 11.9273 8.24726 12.6624 8.24726 14.1326V21.6051C8.24726 23.0753 8.99665 23.8176 10.4883 23.8176ZM17.0115 21.7122C14.849 21.7122 13.1147 19.985 13.1147 17.8082C13.1147 15.6386 14.849 13.9114 17.0115 13.9114C19.174 13.9114 20.9012 15.6386 20.9012 17.8082C20.9012 19.985 19.1669 21.7122 17.0115 21.7122ZM21.3794 15.1818C21.3794 14.6965 21.8076 14.2682 22.3072 14.2682C22.7996 14.2682 23.2207 14.6965 23.2207 15.1818C23.2207 15.6885 22.7996 16.0953 22.3072 16.1025C21.8076 16.1025 21.3794 15.6956 21.3794 15.1818ZM17.0115 20.6273C18.5674 20.6273 19.8235 19.3784 19.8235 17.8082C19.8235 16.2452 18.5674 14.9891 17.0115 14.9891C15.4556 14.9891 14.1924 16.2452 14.1924 17.8082C14.1924 19.3784 15.4628 20.6273 17.0115 20.6273Z"
|
|
10
|
+
fill="currentColor"
|
|
11
|
+
/>
|
|
12
|
+
</svg>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// The flashlight shortcut of the drawn phone's lock screen, as the design draws it. It belongs to `PhoneFrame` and
|
|
2
|
+
// nothing else, so it is not part of the icon set. It takes the text colour around it.
|
|
3
|
+
|
|
4
|
+
/** The flashlight shortcut's glyph, in its 34px round button's own box. */
|
|
5
|
+
export function LockFlashlightGlyph({ className }: { className?: string }) {
|
|
6
|
+
return (
|
|
7
|
+
<svg viewBox="0 0 34.0257 34.0257" fill="none" aria-hidden="true" className={className}>
|
|
8
|
+
<path
|
|
9
|
+
d="M13.8216 11.1597H20.8833V10.8248C20.8833 9.94557 20.4507 9.5269 19.6133 9.5269H15.0916C14.2543 9.5269 13.8216 9.94557 13.8216 10.8248V11.1597ZM16.5151 25.1505H18.1898C19.0272 25.1505 19.4528 24.7318 19.4528 23.8526V16.442C19.4528 15.7093 19.6273 15.179 19.8924 14.7743L20.346 14.1044C20.667 13.609 20.8833 13.1415 20.8833 12.5483V11.9762H13.8216V12.5483C13.8216 13.1415 14.038 13.609 14.3589 14.1044L14.8055 14.7743C15.0777 15.179 15.2451 15.7093 15.2451 16.442V23.8526C15.2451 24.7318 15.6778 25.1505 16.5151 25.1505ZM17.3525 19.7845C16.7803 19.7845 16.4105 19.3658 16.4105 18.7796V16.8677C16.4105 16.2746 16.7803 15.8768 17.3525 15.8908C17.9177 15.8978 18.2945 16.2955 18.2945 16.8677V18.7796C18.2945 19.3658 17.9177 19.7845 17.3525 19.7845ZM17.3525 19.289C17.6595 19.289 17.9247 19.0308 17.9247 18.7168C17.9247 18.4028 17.6595 18.1377 17.3525 18.1377C17.0385 18.1377 16.7733 18.4028 16.7733 18.7168C16.7733 19.0308 17.0385 19.289 17.3525 19.289Z"
|
|
10
|
+
fill="currentColor"
|
|
11
|
+
/>
|
|
12
|
+
</svg>
|
|
13
|
+
);
|
|
14
|
+
}
|