Skip to content

Commit a755386

Browse files
authored
feat: restyling Publish UI (#857)
1 parent d277555 commit a755386

17 files changed

+366
-103
lines changed

js/hang-ui/src/publish/components/CameraSourceButton.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ export default function CameraSourceButton() {
2424
};
2525

2626
return (
27-
<div class="publishSourceButtonContainer">
27+
<div class="publish-ui__source-button-wrapper flex--center">
2828
<Button
2929
title="Camera"
30-
class={`publishSourceButton ${context.cameraActive() ? "active" : ""}`}
30+
class={`publish-ui__source-button flex--center ${context.cameraActive() ? "publish-ui__source-button--active" : ""}`}
3131
onClick={onClick}
3232
>
3333
<Icon.Camera />

js/hang-ui/src/publish/components/FileSourceButton.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ export default function FileSourceButton() {
2323
ref={setFileInputRef}
2424
onChange={onChange}
2525
type="file"
26-
class="hidden"
26+
style="display: none;"
2727
accept="video/*,audio/*,image/*"
2828
/>
2929
<Button
3030
title="Upload File"
31-
class={`publishSourceButton ${context.fileActive() ? "active" : ""}`}
31+
class={`publish-ui__source-button flex--center ${context.fileActive() ? "publish-ui__source-button--active" : ""}`}
3232
onClick={onClick}
3333
>
3434
<Icon.File />

js/hang-ui/src/publish/components/MediaSourceSelector.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ export default function MediaSourceSelector(props: MediaSourceSelectorProps) {
1414
const toggleSourcesVisible = () => setSourcesVisible((visible) => !visible);
1515

1616
return (
17-
<>
17+
<div class="publish-ui__media-selector-wrapper flex--center">
1818
<Button
1919
onClick={toggleSourcesVisible}
20-
class="mediaSourceVisibilityToggle button--media-source-selector"
20+
class="publish-ui__media-selector-toggle button"
2121
title={sourcesVisible() ? "Hide Sources" : "Show Sources"}
2222
>
2323
<Show when={sourcesVisible()} fallback={<Icon.ArrowDown />}>
@@ -27,14 +27,17 @@ export default function MediaSourceSelector(props: MediaSourceSelectorProps) {
2727
<Show when={sourcesVisible()}>
2828
<select
2929
value={props.selectedSource}
30-
class="mediaSourceSelector"
31-
onChange={(e) => props.onSelected?.(e.currentTarget.value as MediaDeviceInfo["deviceId"])}
30+
class="publish-ui__media-selector-dropdown"
31+
onChange={(e) => {
32+
props.onSelected?.(e.currentTarget.value as MediaDeviceInfo["deviceId"]);
33+
setSourcesVisible(false);
34+
}}
3235
>
3336
<For each={props.sources}>
3437
{(source) => <option value={source.deviceId}>{source.label}</option>}
3538
</For>
3639
</select>
3740
</Show>
38-
</>
41+
</div>
3942
);
4043
}

js/hang-ui/src/publish/components/MicrophoneSourceButton.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ export default function MicrophoneSourceButton() {
2424
};
2525

2626
return (
27-
<div class="publishSourceButtonContainer">
27+
<div class="publish-ui__source-button-wrapper flex--center">
2828
<Button
2929
title="Microphone"
30-
class={`publishSourceButton ${context.microphoneActive() ? "active" : ""}`}
30+
class={`publish-ui__source-button flex--center ${context.microphoneActive() ? "publish-ui__source-button--active" : ""}`}
3131
onClick={onClick}
3232
>
3333
<Icon.Microphone />

js/hang-ui/src/publish/components/NothingSourceButton.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,12 @@ export default function NothingSourceButton() {
1111
};
1212

1313
return (
14-
<div class="publishSourceButtonContainer">
15-
<Button
16-
title="No Source"
17-
class={`publishSourceButton ${context.nothingActive() ? "active" : ""}`}
18-
onClick={onClick}
19-
>
20-
<Icon.Ban />
21-
</Button>
22-
</div>
14+
<Button
15+
title="No Source"
16+
class={`publish-ui__source-button flex--center publish-ui__source-button--no-source ${context.nothingActive() ? "publish-ui__source-button--no-source-active" : ""}`}
17+
onClick={onClick}
18+
>
19+
<Icon.Ban />
20+
</Button>
2321
);
2422
}

js/hang-ui/src/publish/components/PublishControls.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import ScreenSourceButton from "./ScreenSourceButton";
77

88
export default function PublishControls() {
99
return (
10-
<div class="publishControlsContainer">
11-
<div class="publishSourceSelectorContainer">
12-
Source:
10+
<div class="publish-ui__controls flex--center flex--space-between">
11+
<div class="publish-ui__source-selector flex--center">
12+
<span class="publish-ui__source-label">Source:</span>
1313
<MicrophoneSourceButton />
1414
<CameraSourceButton />
1515
<ScreenSourceButton />
Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,38 @@
1-
import { Match, Switch } from "solid-js";
1+
import type { PublishStatus } from "../context";
22
import usePublishUIContext from "../hooks/use-publish-ui";
33

4+
type StatusIndicatorConfig = { variant: string; text: string };
5+
6+
const STATUS_MAP: Record<PublishStatus, StatusIndicatorConfig> = {
7+
"no-url": { variant: "error", text: "No URL" },
8+
disconnected: { variant: "error", text: "Disconnected" },
9+
connecting: { variant: "connecting", text: "Connecting..." },
10+
"select-source": { variant: "warning", text: "Select Source" },
11+
"video-only": { variant: "video-only", text: "Video Only" },
12+
"audio-only": { variant: "audio-only", text: "Audio Only" },
13+
live: { variant: "live", text: "Live" },
14+
};
15+
16+
const unknownStatus: StatusIndicatorConfig = { variant: "error", text: "Unknown" };
17+
418
export default function PublishStatusIndicator() {
519
const context = usePublishUIContext();
620

21+
const statusConfig = (): StatusIndicatorConfig => {
22+
const status: PublishStatus = context.publishStatus();
23+
return STATUS_MAP[status] || unknownStatus;
24+
};
25+
726
return (
8-
<output>
9-
<Switch>
10-
<Match when={context.publishStatus() === "no-url"}>🔴 No URL</Match>
11-
<Match when={context.publishStatus() === "disconnected"}>🔴 Disconnected</Match>
12-
<Match when={context.publishStatus() === "connecting"}>🟡 Connecting...</Match>
13-
<Match when={context.publishStatus() === "select-source"}>🟡 Select Source</Match>
14-
<Match when={context.publishStatus() === "video-only"}>🟢 Video Only</Match>
15-
<Match when={context.publishStatus() === "audio-only"}>🟢 Audio Only</Match>
16-
<Match when={context.publishStatus() === "live"}>🟢 Live</Match>
17-
</Switch>
18-
</output>
27+
<div class="publish-ui__status-indicator flex--center">
28+
<span
29+
class={`publish-ui__status-indicator-dot publish-ui__status-indicator-dot--${statusConfig().variant}`}
30+
/>
31+
<span
32+
class={`publish-ui__status-indicator-text publish-ui__status-indicator-text--${statusConfig().variant}`}
33+
>
34+
{statusConfig().text}
35+
</span>
36+
</div>
1937
);
2038
}

js/hang-ui/src/publish/components/ScreenSourceButton.tsx

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,16 @@ export default function ScreenSourceButton() {
77
const onClick = () => {
88
context.hangPublish.source.set("screen");
99
context.hangPublish.invisible.set(false);
10-
context.hangPublish.muted.set(false);
10+
context.hangPublish.muted.set(true);
1111
};
1212

1313
return (
14-
<div class="publishSourceButtonContainer">
15-
<Button
16-
title="Screen"
17-
class={`publishSourceButton ${context.screenActive() ? "active" : ""}`}
18-
onClick={onClick}
19-
>
20-
<Icon.Screen />
21-
</Button>
22-
</div>
14+
<Button
15+
title="Screen"
16+
class={`publish-ui__source-button flex--center ${context.screenActive() ? "publish-ui__source-button--active" : ""}`}
17+
onClick={onClick}
18+
>
19+
<Icon.Screen />
20+
</Button>
2321
);
2422
}

js/hang-ui/src/publish/context.tsx

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@ import type HangPublish from "@moq/hang/publish/element";
22
import type { JSX } from "solid-js";
33
import { createContext, createEffect, createSignal } from "solid-js";
44

5-
type PublishStatus = "no-url" | "disconnected" | "connecting" | "live" | "audio-only" | "video-only" | "select-source";
5+
export type PublishStatus =
6+
| "no-url"
7+
| "disconnected"
8+
| "connecting"
9+
| "live"
10+
| "audio-only"
11+
| "video-only"
12+
| "select-source";
613

714
type PublishUIContextValue = {
815
hangPublish: HangPublish;
@@ -43,7 +50,7 @@ export default function PublishUIContextProvider(props: PublishUIContextProvider
4350
const setFile = (file: File) => {
4451
props.hangPublish.source.set(file);
4552
props.hangPublish.invisible.set(false);
46-
props.hangPublish.muted.set(false);
53+
props.hangPublish.muted.set(true);
4754
};
4855

4956
const value: PublishUIContextValue = {
@@ -64,6 +71,11 @@ export default function PublishUIContextProvider(props: PublishUIContextProvider
6471
createEffect(() => {
6572
const publish = props.hangPublish;
6673

74+
// Initialize with "nothing" active on page load
75+
publish.muted.set(true);
76+
publish.invisible.set(true);
77+
publish.source.set(undefined);
78+
6779
publish.signals.effect((effect) => {
6880
const clearCameraDevices = () => setCameraMediaDevices([]);
6981
const video = effect.get(publish.video);
@@ -107,8 +119,11 @@ export default function PublishUIContextProvider(props: PublishUIContextProvider
107119
});
108120

109121
publish.signals.effect((effect) => {
110-
const selectedSource = effect.get(publish.source);
111-
setNothingActive(selectedSource === undefined);
122+
const source = effect.get(publish.source);
123+
const muted = effect.get(publish.muted);
124+
const invisible = effect.get(publish.invisible);
125+
126+
setNothingActive(source === undefined && muted && invisible);
112127
});
113128

114129
publish.signals.effect((effect) => {
@@ -153,8 +168,13 @@ export default function PublishUIContextProvider(props: PublishUIContextProvider
153168
publish.signals.effect((effect) => {
154169
const url = effect.get(publish.connection.url);
155170
const status = effect.get(publish.connection.status);
156-
const audio = effect.get(publish.broadcast.audio.source);
157-
const video = effect.get(publish.broadcast.video.source);
171+
const audioSource = effect.get(publish.broadcast.audio.source);
172+
const videoSource = effect.get(publish.broadcast.video.source);
173+
const muted = effect.get(publish.muted);
174+
const invisible = effect.get(publish.invisible);
175+
176+
const audio = audioSource && !muted;
177+
const video = videoSource && !invisible;
158178

159179
if (!url) {
160180
setPublishStatus("no-url");
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.publish-ui__controls {
2+
gap: var(--spacing-16);
3+
margin: var(--spacing-8) 0;
4+
border-radius: var(--border-radius-lg);
5+
border: var(--spacing-1) solid var(--color-white-alpha-10);
6+
padding: var(--spacing-16);
7+
}
8+
9+
.publish-ui__source-label {
10+
font-size: 1rem;
11+
font-weight: 600;
12+
letter-spacing: 0.1rem;
13+
color: var(--color-white-alpha-90);
14+
}
15+
16+
.publish-ui__source-selector {
17+
gap: var(--spacing-16);
18+
}

0 commit comments

Comments
 (0)