@domicile-desktop/component-library 0.0.0-alpha-77fd63181766 → 0.0.0-alpha-7a29c8f7f7ea

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 CHANGED
@@ -26,6 +26,7 @@ Each export is `@domicile-desktop/component-library/<name>`.
26
26
  | `CountBadge` | Unread count in an accent pill, pinned to the corner of its positioned parent, or `inline` beside text. Shows "9+" past nine. |
27
27
  | `DetailList` | Terms and values in two columns, such as a connection's addresses. Long values wrap in their column. |
28
28
  | `Drilldown` | View that slides a second `Panel` in from the side, with a back button. |
29
+ | `EmptyState` | A panel or section with nothing to show: an icon in a soft circle, what is missing, and a hint on what would fill it. |
29
30
  | `Field` | Label, control and validation message. |
30
31
  | `FilePicker` | Keyboard-first file picker over its positioned parent. |
31
32
  | `IconButton` | Round, quiet button that shows only an icon: a back arrow, a details arrow, a mute button. Drawn in `currentcolor`. |
@@ -34,6 +35,7 @@ Each export is `@domicile-desktop/component-library/<name>`.
34
35
  | `Kbd` | Keyboard key cap. |
35
36
  | `list-walk` | Arrow-key movement of a highlight through a list while focus stays in an input. |
36
37
  | `ListRow` | Row of a panel's `List`: icon, label over a detail, and something at the end. One button, or a `filled` tile holding its own buttons. `disclosure` folds what goes under it behind a toggle. |
38
+ | `Loading` | A spinner and what it waits for: centered in a waiting panel, on one line under a list, or alone at a row's end. |
37
39
  | `ModalDialog` | Modal dialog with `title`, `footer` and `trigger` props. |
38
40
  | `Notice` | Pill that briefly shows a change, such as a zoom level or volume, with an optional level meter. Key it per change. |
39
41
  | `ContextMenu` | Menu opened at a point, such as a right click's. Parts: `Item`, `CheckboxItem`, `RadioGroup` and `RadioItem`, `Submenu`, `Separator`. Items take an `icon` and a `mnemonic`, whose letter chooses them. |
@@ -41,7 +43,7 @@ Each export is `@domicile-desktop/component-library/<name>`.
41
43
  | `Panel` | Titled panel: a `PanelHeader`, then its content a step apart. `PanelAction` puts the content's controls in the header. |
42
44
  | `PanelFooter` | A panel's last line under a rule: a summary and an action. |
43
45
  | `PanelHeader` | A panel's title, with a back button when one level in and controls at the end. |
44
- | `PanelNote` | One line on how a panel stands; `warning` says a refusal plainly, with the service's words in its tooltip. |
46
+ | `PanelNote` | One line on how a panel stands beside its rows; `warning` says a refusal plainly, with the service's words in its tooltip. |
45
47
  | `PanelSection` | A group in a panel under a small label, such as Inboxes or Paired. |
46
48
  | `source-name` | `sourceName`: how a screen cast's source is named. |
47
49
  | `Popover` | Non-modal panel anchored to the control that opened it. `tone="overPhoto"` is a see-through panel for the bar, dark in either theme. |
package/package.json CHANGED
@@ -2,8 +2,8 @@
2
2
  "dependencies": {
3
3
  "@base-ui/react": "^1.8.0",
4
4
  "@cprussin/option-result": "^2.0.1",
5
- "@domicile-desktop/sdk": "0.0.0-alpha-77fd63181766",
6
- "@domicile-desktop/system-apps": "0.0.0-alpha-77fd63181766",
5
+ "@domicile-desktop/sdk": "0.0.0-alpha-7a29c8f7f7ea",
6
+ "@domicile-desktop/system-apps": "0.0.0-alpha-7a29c8f7f7ea",
7
7
  "@pandacss/dev": "^1.12.1",
8
8
  "@phosphor-icons/react": "^2.1.10"
9
9
  },
@@ -34,6 +34,19 @@
34
34
  },
35
35
  "exports": {
36
36
  "./Accordion": "./src/Accordion/Accordion.tsx",
37
+ "./AnimatedBellIcon": "./src/AnimatedBellIcon/AnimatedBellIcon.tsx",
38
+ "./AnimatedBluetoothIcon": "./src/AnimatedBluetoothIcon/AnimatedBluetoothIcon.tsx",
39
+ "./AnimatedChatIcon": "./src/AnimatedChatIcon/AnimatedChatIcon.tsx",
40
+ "./AnimatedCheckIcon": "./src/AnimatedCheckIcon/AnimatedCheckIcon.tsx",
41
+ "./AnimatedEyeIcon": "./src/AnimatedEyeIcon/AnimatedEyeIcon.tsx",
42
+ "./AnimatedGearIcon": "./src/AnimatedGearIcon/AnimatedGearIcon.tsx",
43
+ "./AnimatedKeyboardIcon": "./src/AnimatedKeyboardIcon/AnimatedKeyboardIcon.tsx",
44
+ "./AnimatedLightningIcon": "./src/AnimatedLightningIcon/AnimatedLightningIcon.tsx",
45
+ "./AnimatedMicrophoneIcon": "./src/AnimatedMicrophoneIcon/AnimatedMicrophoneIcon.tsx",
46
+ "./AnimatedSpeakerIcon": "./src/AnimatedSpeakerIcon/AnimatedSpeakerIcon.tsx",
47
+ "./AnimatedSquaresIcon": "./src/AnimatedSquaresIcon/AnimatedSquaresIcon.tsx",
48
+ "./AnimatedThemeIcon": "./src/AnimatedThemeIcon/AnimatedThemeIcon.tsx",
49
+ "./AnimatedWifiIcon": "./src/AnimatedWifiIcon/AnimatedWifiIcon.tsx",
37
50
  "./AppChooser": "./src/AppChooser/AppChooser.tsx",
38
51
  "./Autocomplete": "./src/Autocomplete/Autocomplete.tsx",
39
52
  "./Avatar": "./src/Avatar/Avatar.tsx",
@@ -49,6 +62,7 @@
49
62
  "./DisplayProvider": "./src/Screen/DisplayProvider.tsx",
50
63
  "./Drilldown": "./src/Drilldown/Drilldown.tsx",
51
64
  "./display-source": "./src/Screen/display-source.ts",
65
+ "./EmptyState": "./src/EmptyState/EmptyState.tsx",
52
66
  "./Field": "./src/Field/Field.tsx",
53
67
  "./FilePicker": "./src/FilePicker/FilePicker.tsx",
54
68
  "./file-request": "./src/FilePicker/file-request.ts",
@@ -58,6 +72,7 @@
58
72
  "./Kbd": "./src/Kbd/Kbd.tsx",
59
73
  "./LevelToast": "./src/LevelToast/LevelToast.tsx",
60
74
  "./ListRow": "./src/ListRow/ListRow.tsx",
75
+ "./Loading": "./src/Loading/Loading.tsx",
61
76
  "./list-walk": "./src/list-walk.ts",
62
77
  "./ModalDialog": "./src/ModalDialog/ModalDialog.tsx",
63
78
  "./media-theme-source": "./src/ThemeSwitch/media-theme-source.ts",
@@ -131,5 +146,5 @@
131
146
  "test:unit": "bun test"
132
147
  },
133
148
  "type": "module",
134
- "version": "0.0.0-alpha-77fd63181766"
149
+ "version": "0.0.0-alpha-7a29c8f7f7ea"
135
150
  }
@@ -0,0 +1,66 @@
1
+ import { css } from "../../styled-system/css";
2
+ import { IconFrame } from "../_icon/IconFrame";
3
+ import { IconPart } from "../_icon/IconPart";
4
+ import { BELL_FILL } from "../_icon/phosphor-parts";
5
+ import { useCue } from "../_icon/useCue";
6
+
7
+ /** Where the bell hangs from. */
8
+ const PIVOT = [128, 24] as const;
9
+
10
+ type Props = {
11
+ /** Swings the bell each time it changes, such as to a higher unread count. */
12
+ cue?: unknown;
13
+ /** Names the icon; without one it is decorative. */
14
+ label?: string | undefined;
15
+ /** Strikes the bell through, such as while do not disturb is on. */
16
+ off?: boolean | undefined;
17
+ size: number;
18
+ };
19
+
20
+ /**
21
+ * Phosphor's filled `BellSimple`. It swings, its clapper lagging, on each
22
+ * cue, and nudges while the control it is in is pointed at. A slash draws
23
+ * across it while it is `off`.
24
+ */
25
+ export const AnimatedBellIcon = ({ cue, label, off = false, size }: Props) => {
26
+ const cued = useCue(cue);
27
+ return (
28
+ <IconFrame label={label} size={size} slash={{ shown: off, weight: "fill" }}>
29
+ <IconPart
30
+ cue={cued}
31
+ cueStyles={bodySwingStyles}
32
+ d={BELL_FILL.body}
33
+ hoverStyles={bodyNudgeStyles}
34
+ name="body"
35
+ origin={PIVOT}
36
+ />
37
+ <IconPart
38
+ cue={cued}
39
+ cueStyles={clapperSwingStyles}
40
+ d={BELL_FILL.clapper}
41
+ name="clapper"
42
+ origin={PIVOT}
43
+ />
44
+ </IconFrame>
45
+ );
46
+ };
47
+
48
+ const bodySwingStyles = css({
49
+ "&[data-cued]": {
50
+ animation:
51
+ "iconBellSwing calc({durations.slowest} + {durations.normal}) {easings.linear}",
52
+ },
53
+ });
54
+
55
+ const clapperSwingStyles = css({
56
+ "&[data-cued]": {
57
+ animation:
58
+ "iconBellClapper calc({durations.slowest} + {durations.normal}) {easings.linear} {durations.fastest}",
59
+ },
60
+ });
61
+
62
+ const bodyNudgeStyles = css({
63
+ _controlHover: {
64
+ animation: "iconBellNudge {durations.slowest} {easings.in-out}",
65
+ },
66
+ });
@@ -0,0 +1,84 @@
1
+ import { css } from "../../styled-system/css";
2
+ import { IconFrame } from "../_icon/IconFrame";
3
+ import { IconPart } from "../_icon/IconPart";
4
+ import { BLUETOOTH_BOLD } from "../_icon/phosphor-parts";
5
+
6
+ type Props = {
7
+ /** Names the icon; without one it is decorative. */
8
+ label?: string | undefined;
9
+ size: number;
10
+ /** Off is slashed; connected shows the dots beside the rune. */
11
+ state: "connected" | "off" | "on";
12
+ };
13
+
14
+ /**
15
+ * Phosphor's bold `Bluetooth`, `BluetoothConnected` and `BluetoothSlash` in
16
+ * one. The dots slide out of the rune when a device connects, back in when
17
+ * it goes, and blink while the control the icon is in is pointed at.
18
+ */
19
+ export const AnimatedBluetoothIcon = ({ label, size, state }: Props) => {
20
+ const dots = state === "connected" ? "shown" : "hidden";
21
+ return (
22
+ <IconFrame
23
+ label={label}
24
+ size={size}
25
+ slash={{ shown: state === "off", weight: "bold" }}
26
+ >
27
+ <IconPart d={BLUETOOTH_BOLD.rune} name="rune" />
28
+ <IconPart
29
+ d={BLUETOOTH_BOLD.dotStart}
30
+ hoverStyles={state === "connected" ? dotBlinkStyles : undefined}
31
+ name="dot"
32
+ origin={[56, 128]}
33
+ state={dots}
34
+ stateStyles={dotStartStyles}
35
+ />
36
+ <IconPart
37
+ d={BLUETOOTH_BOLD.dotEnd}
38
+ hoverStyles={state === "connected" ? dotBlinkStyles : undefined}
39
+ name="dot"
40
+ order={1}
41
+ origin={[208, 128]}
42
+ state={dots}
43
+ stateStyles={dotEndStyles}
44
+ />
45
+ </IconFrame>
46
+ );
47
+ };
48
+
49
+ const dotStartStyles = css({
50
+ "&[data-state=hidden]": {
51
+ opacity: 0,
52
+ transform: "translateX(28px) scale(0.3)",
53
+ transition:
54
+ "opacity {durations.normal} {easings.in}, transform {durations.normal} {easings.in}",
55
+ },
56
+ "&[data-state=shown]": {
57
+ opacity: 1,
58
+ transform: "none",
59
+ transition:
60
+ "opacity {durations.slow} {easings.out}, transform {durations.slower} {easings.outBack}",
61
+ },
62
+ });
63
+
64
+ const dotEndStyles = css({
65
+ "&[data-state=hidden]": {
66
+ opacity: 0,
67
+ transform: "translateX(-28px) scale(0.3)",
68
+ transition:
69
+ "opacity {durations.normal} {easings.in}, transform {durations.normal} {easings.in}",
70
+ },
71
+ "&[data-state=shown]": {
72
+ opacity: 1,
73
+ transform: "none",
74
+ transition:
75
+ "opacity {durations.slow} {easings.out}, transform {durations.slower} {easings.outBack}",
76
+ },
77
+ });
78
+
79
+ const dotBlinkStyles = css({
80
+ _controlHover: {
81
+ animation:
82
+ "iconDotBlink {durations.slower} {easings.in-out} calc(var(--part-order) * {durations.fast}) both",
83
+ },
84
+ });
@@ -0,0 +1,69 @@
1
+ import { css } from "../../styled-system/css";
2
+ import { IconFrame } from "../_icon/IconFrame";
3
+ import { IconPart } from "../_icon/IconPart";
4
+ import { CHAT_BOLD } from "../_icon/phosphor-parts";
5
+ import { useCue } from "../_icon/useCue";
6
+
7
+ /** Where each line starts writing from. */
8
+ const LINE_STARTS = [
9
+ [84, 108],
10
+ [84, 148],
11
+ ] as const;
12
+
13
+ type Props = {
14
+ /** Plays each time it changes, such as to a higher unread count. */
15
+ cue?: unknown;
16
+ /** Names the icon; without one it is decorative. */
17
+ label?: string | undefined;
18
+ size: number;
19
+ };
20
+
21
+ /**
22
+ * Phosphor's bold `ChatCircleText`. On each cue the bubble tips on its tail
23
+ * and its lines write in, one after the other; pointed at, it tips.
24
+ */
25
+ export const AnimatedChatIcon = ({ cue, label, size }: Props) => {
26
+ const cued = useCue(cue);
27
+ return (
28
+ <IconFrame label={label} size={size}>
29
+ <IconPart
30
+ cue={cued}
31
+ cueStyles={bubbleTipStyles}
32
+ d={CHAT_BOLD.bubble}
33
+ hoverStyles={bubbleHoverStyles}
34
+ name="bubble"
35
+ origin={[32, 228]}
36
+ />
37
+ {CHAT_BOLD.lines.map((d, index) => (
38
+ <IconPart
39
+ cue={cued}
40
+ cueStyles={lineWriteStyles}
41
+ d={d}
42
+ key={d}
43
+ name="line"
44
+ order={index}
45
+ origin={LINE_STARTS[index]}
46
+ />
47
+ ))}
48
+ </IconFrame>
49
+ );
50
+ };
51
+
52
+ const bubbleTipStyles = css({
53
+ "&[data-cued]": {
54
+ animation: "iconBubbleTip {durations.slowest} {easings.in-out}",
55
+ },
56
+ });
57
+
58
+ const bubbleHoverStyles = css({
59
+ _controlHover: {
60
+ animation: "iconBubbleTip {durations.slower} {easings.in-out}",
61
+ },
62
+ });
63
+
64
+ const lineWriteStyles = css({
65
+ "&[data-cued]": {
66
+ animation:
67
+ "iconLineWrite {durations.slow} {easings.out} calc({durations.faster} + var(--part-order) * {durations.fast}) both",
68
+ },
69
+ });
@@ -0,0 +1,24 @@
1
+ import { css } from "../../styled-system/css";
2
+ import { IconFrame } from "../_icon/IconFrame";
3
+ import { IconPart } from "../_icon/IconPart";
4
+ import { CHECK_BOLD } from "../_icon/phosphor-parts";
5
+
6
+ type Props = {
7
+ /** Names the icon; without one it is decorative. */
8
+ label?: string | undefined;
9
+ size: number;
10
+ };
11
+
12
+ /**
13
+ * Phosphor's bold `Check`. It draws from its start when it appears, such as
14
+ * when something has finished.
15
+ */
16
+ export const AnimatedCheckIcon = ({ label, size }: Props) => (
17
+ <IconFrame label={label} size={size}>
18
+ <IconPart d={CHECK_BOLD.check} name="check" stateStyles={drawStyles} />
19
+ </IconFrame>
20
+ );
21
+
22
+ const drawStyles = css({
23
+ animation: "iconCheckDraw {durations.slow} {easings.out}",
24
+ });
@@ -0,0 +1,96 @@
1
+ import { css } from "../../styled-system/css";
2
+ import { IconFrame } from "../_icon/IconFrame";
3
+ import { IconPart } from "../_icon/IconPart";
4
+ import { EYE_BOLD } from "../_icon/phosphor-parts";
5
+
6
+ type Props = {
7
+ /** Names the icon; without one it is decorative. */
8
+ label?: string | undefined;
9
+ /** Phosphor's `Eye` while open, `EyeClosed` while shut. */
10
+ open: boolean;
11
+ size: number;
12
+ };
13
+
14
+ /**
15
+ * Phosphor's bold `Eye` and `EyeClosed` in one. Shutting blinks the eye
16
+ * closed; opening widens it and the pupil comes back. Pointed at while open,
17
+ * the pupil looks one way and the other.
18
+ */
19
+ export const AnimatedEyeIcon = ({ label, open, size }: Props) => {
20
+ const openState = open ? "shown" : "hidden";
21
+ return (
22
+ <IconFrame label={label} size={size}>
23
+ <IconPart
24
+ d={EYE_BOLD.outline}
25
+ name="outline"
26
+ state={openState}
27
+ stateStyles={outlineStyles}
28
+ />
29
+ <IconPart
30
+ d={EYE_BOLD.pupil}
31
+ hoverStyles={open ? pupilGlanceStyles : undefined}
32
+ name="pupil"
33
+ state={openState}
34
+ stateStyles={pupilStyles}
35
+ />
36
+ <IconPart
37
+ d={EYE_BOLD.closed}
38
+ name="closed"
39
+ state={open ? "hidden" : "shown"}
40
+ stateStyles={closedStyles}
41
+ />
42
+ </IconFrame>
43
+ );
44
+ };
45
+
46
+ // Shutting squeezes the outline flat, then lets it go.
47
+ const outlineStyles = css({
48
+ "&[data-state=hidden]": {
49
+ opacity: 0,
50
+ transform: "scaleY(0.08)",
51
+ transition:
52
+ "transform {durations.fast} {easings.in}, opacity {durations.fastest} {easings.linear} {durations.fast}",
53
+ },
54
+ "&[data-state=shown]": {
55
+ opacity: 1,
56
+ transform: "none",
57
+ transition:
58
+ "transform {durations.slow} {easings.outBack} {durations.faster}, opacity {durations.fastest} {easings.linear} {durations.faster}",
59
+ },
60
+ });
61
+
62
+ const pupilStyles = css({
63
+ "&[data-state=hidden]": {
64
+ opacity: 0,
65
+ transform: "scaleY(0.08)",
66
+ transition:
67
+ "opacity {durations.fast} {easings.in}, transform {durations.fast} {easings.in}",
68
+ },
69
+ "&[data-state=shown]": {
70
+ opacity: 1,
71
+ transform: "none",
72
+ transition:
73
+ "opacity {durations.slow} {easings.out} {durations.normal}, transform {durations.slow} {easings.outBack} {durations.normal}",
74
+ },
75
+ });
76
+
77
+ const closedStyles = css({
78
+ "&[data-state=hidden]": {
79
+ opacity: 0,
80
+ transform: "translateY(-10px) scaleY(0.5)",
81
+ transition:
82
+ "opacity {durations.fast} {easings.in}, transform {durations.fast} {easings.in}",
83
+ },
84
+ "&[data-state=shown]": {
85
+ opacity: 1,
86
+ transform: "none",
87
+ transition:
88
+ "opacity {durations.normal} {easings.out} {durations.fast}, transform {durations.normal} {easings.out} {durations.fast}",
89
+ },
90
+ });
91
+
92
+ const pupilGlanceStyles = css({
93
+ _controlHover: {
94
+ animation: "iconPupilGlance calc({durations.slowest} * 2) {easings.in-out}",
95
+ },
96
+ });
@@ -0,0 +1,26 @@
1
+ import { css } from "../../styled-system/css";
2
+ import { IconFrame } from "../_icon/IconFrame";
3
+ import { IconPart } from "../_icon/IconPart";
4
+ import { GEAR_REGULAR } from "../_icon/phosphor-parts";
5
+
6
+ type Props = {
7
+ /** Names the icon; without one it is decorative. */
8
+ label?: string | undefined;
9
+ size: number;
10
+ };
11
+
12
+ /**
13
+ * Phosphor's regular `GearSix`. It turns one tooth and settles while the
14
+ * control it is in is pointed at.
15
+ */
16
+ export const AnimatedGearIcon = ({ label, size }: Props) => (
17
+ <IconFrame label={label} size={size}>
18
+ <IconPart d={GEAR_REGULAR.gear} hoverStyles={gearTurnStyles} name="gear" />
19
+ </IconFrame>
20
+ );
21
+
22
+ const gearTurnStyles = css({
23
+ _controlHover: {
24
+ animation: "iconGearTurn {durations.slowest} {easings.outBack}",
25
+ },
26
+ });
@@ -0,0 +1,50 @@
1
+ import { css } from "../../styled-system/css";
2
+ import { IconFrame } from "../_icon/IconFrame";
3
+ import { IconPart } from "../_icon/IconPart";
4
+ import { KEYBOARD_BOLD } from "../_icon/phosphor-parts";
5
+ import { useCue } from "../_icon/useCue";
6
+
7
+ type Props = {
8
+ /** Plays each time it changes, such as to the keymap in use. */
9
+ cue?: unknown;
10
+ /** Names the icon; without one it is decorative. */
11
+ label?: string | undefined;
12
+ size: number;
13
+ };
14
+
15
+ /**
16
+ * Phosphor's bold `Keyboard`. On each cue its keys press down in a wave, top
17
+ * row first; pointed at, one key presses.
18
+ */
19
+ export const AnimatedKeyboardIcon = ({ cue, label, size }: Props) => {
20
+ const cued = useCue(cue);
21
+ return (
22
+ <IconFrame label={label} size={size}>
23
+ <IconPart d={KEYBOARD_BOLD.frame} name="frame" />
24
+ {KEYBOARD_BOLD.keys.map((d, index) => (
25
+ <IconPart
26
+ cue={cued}
27
+ cueStyles={keyWaveStyles}
28
+ d={d}
29
+ hoverStyles={index === 1 ? keyHoverStyles : undefined}
30
+ key={d}
31
+ name="key"
32
+ order={index}
33
+ />
34
+ ))}
35
+ </IconFrame>
36
+ );
37
+ };
38
+
39
+ const keyWaveStyles = css({
40
+ "&[data-cued]": {
41
+ animation:
42
+ "iconKeyPress {durations.slow} {easings.in-out} calc(var(--part-order) * {durations.fastest}) both",
43
+ },
44
+ });
45
+
46
+ const keyHoverStyles = css({
47
+ _controlHover: {
48
+ animation: "iconKeyPress {durations.slow} {easings.in-out}",
49
+ },
50
+ });
@@ -0,0 +1,27 @@
1
+ import { css } from "../../styled-system/css";
2
+ import { IconFrame } from "../_icon/IconFrame";
3
+ import { IconPart } from "../_icon/IconPart";
4
+ import { LIGHTNING_FILL } from "../_icon/phosphor-parts";
5
+
6
+ type Props = {
7
+ /** Names the icon; without one it is decorative. */
8
+ label?: string | undefined;
9
+ size: number;
10
+ /** Where it sits, inside an outer drawing such as a battery. */
11
+ x?: number | undefined;
12
+ y?: number | undefined;
13
+ };
14
+
15
+ /**
16
+ * Phosphor's filled `Lightning`. It strikes down from its top when it
17
+ * appears, such as when charging starts.
18
+ */
19
+ export const AnimatedLightningIcon = ({ label, size, x, y }: Props) => (
20
+ <IconFrame label={label} size={size} x={x} y={y}>
21
+ <IconPart d={LIGHTNING_FILL.bolt} name="bolt" stateStyles={strikeStyles} />
22
+ </IconFrame>
23
+ );
24
+
25
+ const strikeStyles = css({
26
+ animation: "iconBoltStrike {durations.slowest} {easings.linear}",
27
+ });
@@ -0,0 +1,53 @@
1
+ import { css } from "../../styled-system/css";
2
+ import { IconFrame } from "../_icon/IconFrame";
3
+ import { IconPart } from "../_icon/IconPart";
4
+ import { MICROPHONE_FILL, MICROPHONE_REGULAR } from "../_icon/phosphor-parts";
5
+ import { useCue } from "../_icon/useCue";
6
+
7
+ const PARTS = { fill: MICROPHONE_FILL, regular: MICROPHONE_REGULAR };
8
+
9
+ type Props = {
10
+ /** Names the icon; without one it is decorative. */
11
+ label?: string | undefined;
12
+ muted: boolean;
13
+ size: number;
14
+ weight: keyof typeof PARTS;
15
+ };
16
+
17
+ /**
18
+ * Phosphor's `Microphone` and `MicrophoneSlash` in one. Muting draws the
19
+ * slash; unmuting, or pointing at the control the icon is in, bobs the
20
+ * capsule.
21
+ */
22
+ export const AnimatedMicrophoneIcon = ({
23
+ label,
24
+ muted,
25
+ size,
26
+ weight,
27
+ }: Props) => {
28
+ const unmuted = useCue(muted ? undefined : "live");
29
+ return (
30
+ <IconFrame label={label} size={size} slash={{ shown: muted, weight }}>
31
+ <IconPart
32
+ cue={unmuted}
33
+ cueStyles={capsuleBobStyles}
34
+ d={PARTS[weight].capsule}
35
+ hoverStyles={muted ? undefined : capsuleHoverStyles}
36
+ name="capsule"
37
+ />
38
+ <IconPart d={PARTS[weight].stand} name="stand" />
39
+ </IconFrame>
40
+ );
41
+ };
42
+
43
+ const capsuleBobStyles = css({
44
+ "&[data-cued]": {
45
+ animation: "iconCapsuleBob {durations.slowest} {easings.in-out}",
46
+ },
47
+ });
48
+
49
+ const capsuleHoverStyles = css({
50
+ _controlHover: {
51
+ animation: "iconCapsuleBob {durations.slowest} {easings.in-out}",
52
+ },
53
+ });