Collab UI v2 (#2618)

This PR implements
https://www.figma.com/file/pLq7dvhx2mFeWFOedXpUQ5/Project-%E2%80%93-Collaboration-UI?type=design&node-id=666%3A8610&t=XFGk6KEtTCFXBvCQ-1

Release notes:
- [x] Redesigned collaboration UI
- [x] Added voice support
This commit is contained in:
Mikayla Maki
2023-06-28 09:14:39 -07:00
committed by GitHub
368 changed files with 4748 additions and 439 deletions
+2
View File
@@ -23,6 +23,7 @@ import feedback from "./feedback"
import welcome from "./welcome"
import copilot from "./copilot"
import assistant from "./assistant"
import { titlebar } from "./titlebar"
export default function app(colorScheme: ColorScheme): Object {
return {
@@ -36,6 +37,7 @@ export default function app(colorScheme: ColorScheme): Object {
incomingCallNotification: incomingCallNotification(colorScheme),
picker: picker(colorScheme),
workspace: workspace(colorScheme),
titlebar: titlebar(colorScheme),
copilot: copilot(colorScheme),
welcome: welcome(colorScheme),
contextMenu: contextMenu(colorScheme),
+266
View File
@@ -0,0 +1,266 @@
import { ColorScheme } from "../common";
import { icon_button, toggleable_icon_button } from "../component/icon_button"
import { toggleable_text_button } from "../component/text_button"
import { interactive, toggleable } from "../element"
import { withOpacity } from "../theme/color";
import { background, border, foreground, text } from "./components";
const ITEM_SPACING = 8
const TITLEBAR_HEIGHT = 32
function build_spacing(
container_height: number,
element_height: number,
spacing: number
) {
return {
group: spacing,
item: spacing / 2,
half_item: spacing / 4,
marginY: (container_height - element_height) / 2,
marginX: (container_height - element_height) / 2,
}
}
function call_controls(theme: ColorScheme) {
const button_height = 18
const space = build_spacing(TITLEBAR_HEIGHT, button_height, ITEM_SPACING);
const marginY = {
top: space.marginY,
bottom: space.marginY,
}
return {
toggle_microphone_button: toggleable_icon_button(theme, {
margin: {
...marginY,
left: space.group,
right: space.half_item,
},
active_color: 'negative'
}),
toggle_speakers_button: toggleable_icon_button(theme, {
margin: {
...marginY,
left: space.half_item,
right: space.half_item
},
}),
screen_share_button: toggleable_icon_button(theme, {
margin: {
...marginY,
left: space.half_item,
right: space.group
},
active_color: 'accent'
}),
muted: foreground(theme.lowest, "negative"),
speaking: foreground(theme.lowest, "accent"),
}
}
/**
* Opens the User Menu when toggled
*
* When logged in shows the user's avatar and a chevron,
* When logged out only shows a chevron.
*/
function user_menu(theme: ColorScheme) {
const button_height = 18
const space = build_spacing(TITLEBAR_HEIGHT, button_height, ITEM_SPACING);
const build_button = ({ online }: { online: boolean }) => {
const button = toggleable({
base: interactive({
base: {
cornerRadius: 6,
height: button_height,
width: online ? 37 : 24,
padding: {
top: 2,
bottom: 2,
left: 6,
right: 6,
},
margin: {
left: space.item,
right: space.item,
},
...text(theme.lowest, "sans", { size: "xs" }),
background: background(theme.lowest),
},
state: {
hovered: {
...text(theme.lowest, "sans", "hovered", {
size: "xs",
}),
background: background(theme.lowest, "hovered"),
},
clicked: {
...text(theme.lowest, "sans", "pressed", {
size: "xs",
}),
background: background(theme.lowest, "pressed"),
},
},
}),
state: {
active: {
default: {
...text(theme.lowest, "sans", "active", { size: "xs" }),
background: background(theme.middle),
},
hovered: {
...text(theme.lowest, "sans", "active", { size: "xs" }),
background: background(theme.middle, "hovered"),
},
clicked: {
...text(theme.lowest, "sans", "active", { size: "xs" }),
background: background(theme.middle, "pressed"),
},
},
}
});
return {
user_menu: button,
avatar: {
icon_width: 16,
icon_height: 16,
corner_radius: 4,
outer_width: 16,
outer_corner_radius: 16
},
icon: {
margin: {
top: 2,
left: online ? space.item : 0,
right: space.group,
bottom: 2,
},
width: 11,
height: 11,
color: foreground(theme.lowest)
}
}
}
return {
userMenuButtonOnline: build_button({ online: true }),
userMenuButtonOffline: build_button({ online: false }),
}
}
export function titlebar(theme: ColorScheme) {
const avatarWidth = 15
const avatarOuterWidth = avatarWidth + 4
const followerAvatarWidth = 14
const followerAvatarOuterWidth = followerAvatarWidth + 4
return {
item_spacing: ITEM_SPACING,
facePileSpacing: 2,
height: TITLEBAR_HEIGHT,
background: background(theme.lowest),
border: border(theme.lowest, { bottom: true }),
padding: {
left: 80,
right: 0,
},
// Project
title: text(theme.lowest, "sans", "variant"),
highlight_color: text(theme.lowest, "sans", "active").color,
// Collaborators
leaderAvatar: {
width: avatarWidth,
outerWidth: avatarOuterWidth,
cornerRadius: avatarWidth / 2,
outerCornerRadius: avatarOuterWidth / 2,
},
followerAvatar: {
width: followerAvatarWidth,
outerWidth: followerAvatarOuterWidth,
cornerRadius: followerAvatarWidth / 2,
outerCornerRadius: followerAvatarOuterWidth / 2,
},
inactiveAvatarGrayscale: true,
followerAvatarOverlap: 8,
leaderSelection: {
margin: {
top: 4,
bottom: 4,
},
padding: {
left: 2,
right: 2,
top: 2,
bottom: 2,
},
cornerRadius: 6,
},
avatarRibbon: {
height: 3,
width: 14,
// TODO: Chore: Make avatarRibbon colors driven by the theme rather than being hard coded.
},
sign_in_button: toggleable_text_button(theme, {}),
offlineIcon: {
color: foreground(theme.lowest, "variant"),
width: 16,
margin: {
left: ITEM_SPACING,
},
padding: {
right: 4,
},
},
// When the collaboration server is out of date, show a warning
outdatedWarning: {
...text(theme.lowest, "sans", "warning", { size: "xs" }),
background: withOpacity(background(theme.lowest, "warning"), 0.3),
border: border(theme.lowest, "warning"),
margin: {
left: ITEM_SPACING,
},
padding: {
left: 8,
right: 8,
},
cornerRadius: 6,
},
leave_call_button: icon_button(theme, {
margin: {
left: ITEM_SPACING / 2,
right: ITEM_SPACING
},
}),
...call_controls(theme),
toggle_contacts_button: toggleable_icon_button(theme, {
margin: {
left: ITEM_SPACING,
},
}),
// Jewel that notifies you that there are new contact requests
toggleContactsBadge: {
cornerRadius: 3,
padding: 2,
margin: { top: 3, left: 3 },
border: border(theme.lowest),
background: foreground(theme.lowest, "accent"),
},
shareButton: toggleable_text_button(theme, {}),
user_menu: user_menu(theme)
}
}
+3 -209
View File
@@ -1,6 +1,5 @@
import { ColorScheme } from "../theme/colorScheme"
import { withOpacity } from "../theme/color"
import { toggleable } from "../element"
import {
background,
border,
@@ -12,56 +11,11 @@ import {
import statusBar from "./statusBar"
import tabBar from "./tabBar"
import { interactive } from "../element"
import merge from "ts-deepmerge"
import { titlebar } from "./titlebar"
export default function workspace(colorScheme: ColorScheme) {
const layer = colorScheme.lowest
const isLight = colorScheme.isLight
const itemSpacing = 8
const titlebarButton = toggleable({
base: interactive({
base: {
cornerRadius: 6,
padding: {
top: 1,
bottom: 1,
left: 8,
right: 8,
},
...text(layer, "sans", "variant", { size: "xs" }),
background: background(layer, "variant"),
border: border(layer),
},
state: {
hovered: {
...text(layer, "sans", "variant", "hovered", {
size: "xs",
}),
background: background(layer, "variant", "hovered"),
border: border(layer, "variant", "hovered"),
},
clicked: {
...text(layer, "sans", "variant", "pressed", {
size: "xs",
}),
background: background(layer, "variant", "pressed"),
border: border(layer, "variant", "pressed"),
},
},
}),
state: {
active: {
default: {
...text(layer, "sans", "variant", "active", { size: "xs" }),
background: background(layer, "variant", "active"),
border: border(layer, "variant", "active"),
},
},
},
})
const avatarWidth = 18
const avatarOuterWidth = avatarWidth + 4
const followerAvatarWidth = 14
const followerAvatarOuterWidth = followerAvatarWidth + 4
return {
background: background(colorScheme.lowest),
@@ -171,167 +125,7 @@ export default function workspace(colorScheme: ColorScheme) {
width: 1,
},
statusBar: statusBar(colorScheme),
titlebar: {
itemSpacing,
facePileSpacing: 2,
height: 33, // 32px + 1px border. It's important the content area of the titlebar is evenly sized to vertically center avatar images.
background: background(layer),
border: border(layer, { bottom: true }),
padding: {
left: 80,
right: itemSpacing,
},
// Project
title: text(layer, "sans", "variant"),
highlight_color: text(layer, "sans", "active").color,
// Collaborators
leaderAvatar: {
width: avatarWidth,
outerWidth: avatarOuterWidth,
cornerRadius: avatarWidth / 2,
outerCornerRadius: avatarOuterWidth / 2,
},
followerAvatar: {
width: followerAvatarWidth,
outerWidth: followerAvatarOuterWidth,
cornerRadius: followerAvatarWidth / 2,
outerCornerRadius: followerAvatarOuterWidth / 2,
},
inactiveAvatarGrayscale: true,
followerAvatarOverlap: 8,
leaderSelection: {
margin: {
top: 4,
bottom: 4,
},
padding: {
left: 2,
right: 2,
top: 2,
bottom: 2,
},
cornerRadius: 6,
},
avatarRibbon: {
height: 3,
width: 12,
// TODO: Chore: Make avatarRibbon colors driven by the theme rather than being hard coded.
},
// Sign in buttom
// FlatButton, Variant
signInPrompt: merge(titlebarButton, {
inactive: {
default: {
margin: {
left: itemSpacing,
},
},
},
}),
// Offline Indicator
offlineIcon: {
color: foreground(layer, "variant"),
width: 16,
margin: {
left: itemSpacing,
},
padding: {
right: 4,
},
},
// Notice that the collaboration server is out of date
outdatedWarning: {
...text(layer, "sans", "warning", { size: "xs" }),
background: withOpacity(background(layer, "warning"), 0.3),
border: border(layer, "warning"),
margin: {
left: itemSpacing,
},
padding: {
left: 8,
right: 8,
},
cornerRadius: 6,
},
callControl: interactive({
base: {
cornerRadius: 6,
color: foreground(layer, "variant"),
iconWidth: 12,
buttonWidth: 20,
},
state: {
hovered: {
background: background(layer, "variant", "hovered"),
color: foreground(layer, "variant", "hovered"),
},
},
}),
toggleContactsButton: toggleable({
base: interactive({
base: {
margin: { left: itemSpacing },
cornerRadius: 6,
color: foreground(layer, "variant"),
iconWidth: 14,
buttonWidth: 20,
},
state: {
clicked: {
background: background(layer, "variant", "pressed"),
},
hovered: {
background: background(layer, "variant", "hovered"),
},
},
}),
state: {
active: {
default: {
background: background(layer, "on", "default"),
},
hovered: {
background: background(layer, "on", "hovered"),
},
clicked: {
background: background(layer, "on", "pressed"),
},
},
},
}),
userMenuButton: merge(titlebarButton, {
inactive: {
default: {
buttonWidth: 20,
iconWidth: 12,
},
},
active: {
// posiewic: these properties are not currently set on main
default: {
iconWidth: 12,
button_width: 20,
},
},
}),
toggleContactsBadge: {
cornerRadius: 3,
padding: 2,
margin: { top: 3, left: 3 },
border: border(layer),
background: foreground(layer, "accent"),
},
shareButton: {
...titlebarButton,
},
},
titlebar: titlebar(colorScheme),
toolbar: {
height: 34,
background: background(colorScheme.highest),