feat: Custom accent color (#4897)

* types

* Working, but messy

* Add InputColor component

* types

* Show default theme values when not customized

* Support custom theme on team sign-in page

* Payload validation

* Custom theme on shared documents

* Improve theme validation

* Team -> Workspace in settings
This commit is contained in:
Tom Moor
2023-02-19 10:43:03 -05:00
committed by GitHub
parent 7c05b7326a
commit 70beb7524f
45 changed files with 684 additions and 390 deletions

View File

@@ -817,7 +817,7 @@ ul.checkbox_list li .checkbox {
&[aria-checked=true] {
opacity: 1;
background-image: ${`url(
"data:image/svg+xml,%3Csvg width='14' height='14' viewBox='0 0 14 14' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M3 0C1.34315 0 0 1.34315 0 3V11C0 12.6569 1.34315 14 3 14H11C12.6569 14 14 12.6569 14 11V3C14 1.34315 12.6569 0 11 0H3ZM4.26825 5.85982L5.95873 7.88839L9.70003 2.9C10.0314 2.45817 10.6582 2.36863 11.1 2.7C11.5419 3.03137 11.6314 3.65817 11.3 4.1L6.80002 10.1C6.41275 10.6164 5.64501 10.636 5.2318 10.1402L2.7318 7.14018C2.37824 6.71591 2.43556 6.08534 2.85984 5.73178C3.28412 5.37821 3.91468 5.43554 4.26825 5.85982Z' fill='${props.theme.primary.replace(
"data:image/svg+xml,%3Csvg width='14' height='14' viewBox='0 0 14 14' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M3 0C1.34315 0 0 1.34315 0 3V11C0 12.6569 1.34315 14 3 14H11C12.6569 14 14 12.6569 14 11V3C14 1.34315 12.6569 0 11 0H3ZM4.26825 5.85982L5.95873 7.88839L9.70003 2.9C10.0314 2.45817 10.6582 2.36863 11.1 2.7C11.5419 3.03137 11.6314 3.65817 11.3 4.1L6.80002 10.1C6.41275 10.6164 5.64501 10.636 5.2318 10.1402L2.7318 7.14018C2.37824 6.71591 2.43556 6.08534 2.85984 5.73178C3.28412 5.37821 3.91468 5.43554 4.26825 5.85982Z' fill='${props.theme.accent.replace(
"#",
"%23"
)}' /%3E%3C/svg%3E%0A"

View File

@@ -185,6 +185,7 @@
"Show menu": "Show menu",
"Choose icon": "Choose icon",
"Loading": "Loading",
"Select a color": "Select a color",
"Loading editor": "Loading editor",
"Search": "Search",
"Default access": "Default access",
@@ -305,7 +306,6 @@
"Notifications": "Notifications",
"API Tokens": "API Tokens",
"Details": "Details",
"Team": "Team",
"Security": "Security",
"Features": "Features",
"Members": "Members",
@@ -674,8 +674,14 @@
"Logo updated": "Logo updated",
"Unable to upload new logo": "Unable to upload new logo",
"These settings affect the way that your knowledge base appears to everyone on the team.": "These settings affect the way that your knowledge base appears to everyone on the team.",
"Display": "Display",
"The logo is displayed at the top left of the application.": "The logo is displayed at the top left of the application.",
"The workspace name, usually the same as your company name.": "The workspace name, usually the same as your company name.",
"Theme": "Theme",
"Customize the interface look and feel.": "Customize the interface look and feel.",
"Accent color": "Accent color",
"Accent text color": "Accent text color",
"Behavior": "Behavior",
"Subdomain": "Subdomain",
"Your knowledge base will be accessible at": "Your knowledge base will be accessible at",
"Choose a subdomain to enable a login page just for your team.": "Choose a subdomain to enable a login page just for your team.",
@@ -729,14 +735,12 @@
"Preferences saved": "Preferences saved",
"Delete account": "Delete account",
"Manage settings that affect your personal experience.": "Manage settings that affect your personal experience.",
"Display": "Display",
"Language": "Language",
"Choose the interface language. Community translations are accepted though our <2>translation portal</2>.": "Choose the interface language. Community translations are accepted though our <2>translation portal</2>.",
"Use pointer cursor": "Use pointer cursor",
"Show a hand cursor when hovering over interactive elements.": "Show a hand cursor when hovering over interactive elements.",
"Show line numbers": "Show line numbers",
"Show line numbers on code blocks in documents.": "Show line numbers on code blocks in documents.",
"Behavior": "Behavior",
"Remember previous location": "Remember previous location",
"Automatically return to the document you were last viewing when the app is re-opened.": "Automatically return to the document you were last viewing when the app is re-opened.",
"You may delete your account at any time, note that this is unrecoverable": "You may delete your account at any time, note that this is unrecoverable",

View File

@@ -2,7 +2,9 @@ import { createGlobalStyle } from "styled-components";
import styledNormalize from "styled-normalize";
import { breakpoints, depths } from ".";
type Props = { useCursorPointer?: boolean };
type Props = {
useCursorPointer?: boolean;
};
export default createGlobalStyle<Props>`
${styledNormalize}
@@ -108,7 +110,7 @@ export default createGlobalStyle<Props>`
}
.js-focus-visible .focus-visible {
outline-color: ${(props) => props.theme.primary};
outline-color: ${(props) => props.theme.accent};
outline-offset: -1px;
}
`;

View File

@@ -1,7 +1,8 @@
import { darken, lighten } from "polished";
import { DefaultTheme, Colors } from "styled-components";
import breakpoints from "./breakpoints";
const colors = {
const defaultColors: Colors = {
transparent: "transparent",
almostBlack: "#111319",
lightBlack: "#2F3336",
@@ -13,7 +14,7 @@ const colors = {
smoke: "#F4F7FA",
smokeLight: "#F9FBFC",
smokeDark: "#E8EBED",
white: "#FFF",
white: "#FFFFFF",
white05: "rgba(255, 255, 255, 0.05)",
white10: "rgba(255, 255, 255, 0.1)",
white50: "rgba(255, 255, 255, 0.5)",
@@ -23,7 +24,7 @@ const colors = {
black10: "rgba(0, 0, 0, 0.1)",
black50: "rgba(0, 0, 0, 0.50)",
black75: "rgba(0, 0, 0, 0.75)",
primary: "#0366d6",
accent: "#0366d6",
yellow: "#EDBA07",
warmGrey: "#EDF2F7",
searchHighlight: "#FDEA9B",
@@ -52,178 +53,196 @@ const spacing = {
sidebarMaxWidth: 400,
};
export const base = {
...colors,
...spacing,
fontFamily:
"-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen, Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif",
fontFamilyMono:
"'SFMono-Regular',Consolas,'Liberation Mono', Menlo, Courier,monospace",
fontWeight: 400,
backgroundTransition: "background 100ms ease-in-out",
selected: colors.primary,
buttonBackground: colors.primary,
buttonText: colors.white,
textHighlight: "#FDEA9B",
textHighlightForeground: colors.almostBlack,
code: colors.lightBlack,
codeComment: "#6a737d",
codePunctuation: "#5e6687",
codeNumber: "#d73a49",
codeProperty: "#c08b30",
codeTag: "#3d8fd1",
codeString: "#032f62",
codeSelector: "#6679cc",
codeAttr: "#c76b29",
codeEntity: "#22a2c9",
codeKeyword: "#d73a49",
codeFunction: "#6f42c1",
codeStatement: "#22a2c9",
codePlaceholder: "#3d8fd1",
codeInserted: "#202746",
codeImportant: "#c94922",
noticeInfoBackground: colors.primary,
noticeInfoText: colors.almostBlack,
noticeTipBackground: "#F5BE31",
noticeTipText: colors.almostBlack,
noticeWarningBackground: "#d73a49",
noticeWarningText: colors.almostBlack,
breakpoints,
const buildBaseTheme = (input: Partial<Colors>) => {
const colors = {
...defaultColors,
...input,
};
return {
fontFamily:
"-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen, Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif",
fontFamilyMono:
"'SFMono-Regular',Consolas,'Liberation Mono', Menlo, Courier,monospace",
fontWeight: 400,
backgroundTransition: "background 100ms ease-in-out",
accentText: colors.white,
selected: colors.accent,
textHighlight: "#FDEA9B",
textHighlightForeground: colors.almostBlack,
code: colors.lightBlack,
codeComment: "#6a737d",
codePunctuation: "#5e6687",
codeNumber: "#d73a49",
codeProperty: "#c08b30",
codeTag: "#3d8fd1",
codeString: "#032f62",
codeSelector: "#6679cc",
codeAttr: "#c76b29",
codeEntity: "#22a2c9",
codeKeyword: "#d73a49",
codeFunction: "#6f42c1",
codeStatement: "#22a2c9",
codePlaceholder: "#3d8fd1",
codeInserted: "#202746",
codeImportant: "#c94922",
noticeInfoBackground: colors.accent,
noticeInfoText: colors.almostBlack,
noticeTipBackground: "#F5BE31",
noticeTipText: colors.almostBlack,
noticeWarningBackground: "#d73a49",
noticeWarningText: colors.almostBlack,
breakpoints,
...colors,
...spacing,
};
};
export const light = {
...base,
isDark: false,
background: colors.white,
secondaryBackground: colors.warmGrey,
link: colors.primary,
cursor: colors.almostBlack,
text: colors.almostBlack,
textSecondary: colors.slateDark,
textTertiary: colors.slate,
textDiffInserted: colors.almostBlack,
textDiffInsertedBackground: "rgba(18, 138, 41, 0.16)",
textDiffDeleted: colors.slateDark,
textDiffDeletedBackground: "#ffebe9",
placeholder: "#a2b2c3",
sidebarBackground: colors.warmGrey,
sidebarActiveBackground: "#d7e0ea",
sidebarControlHoverBackground: "rgb(138 164 193 / 20%)",
sidebarDraftBorder: darken("0.25", colors.warmGrey),
sidebarText: "rgb(78, 92, 110)",
backdrop: "rgba(0, 0, 0, 0.2)",
shadow: "rgba(0, 0, 0, 0.2)",
export const buildLightTheme = (input: Partial<Colors>): DefaultTheme => {
const colors = buildBaseTheme(input);
modalBackdrop: colors.black10,
modalBackground: colors.white,
modalShadow:
"0 4px 8px rgb(0 0 0 / 8%), 0 2px 4px rgb(0 0 0 / 0%), 0 30px 40px rgb(0 0 0 / 8%)",
return {
...colors,
isDark: false,
background: colors.white,
secondaryBackground: colors.warmGrey,
link: colors.accent,
cursor: colors.almostBlack,
text: colors.almostBlack,
textSecondary: colors.slateDark,
textTertiary: colors.slate,
textDiffInserted: colors.almostBlack,
textDiffInsertedBackground: "rgba(18, 138, 41, 0.16)",
textDiffDeleted: colors.slateDark,
textDiffDeletedBackground: "#ffebe9",
placeholder: "#a2b2c3",
sidebarBackground: colors.warmGrey,
sidebarActiveBackground: "#d7e0ea",
sidebarControlHoverBackground: "rgb(138 164 193 / 20%)",
sidebarDraftBorder: darken("0.25", colors.warmGrey),
sidebarText: "rgb(78, 92, 110)",
backdrop: "rgba(0, 0, 0, 0.2)",
shadow: "rgba(0, 0, 0, 0.2)",
menuItemSelected: colors.warmGrey,
menuBackground: colors.white,
menuShadow:
"0 0 0 1px rgb(0 0 0 / 2%), 0 4px 8px rgb(0 0 0 / 8%), 0 2px 4px rgb(0 0 0 / 0%), 0 30px 40px rgb(0 0 0 / 8%)",
divider: colors.slateLight,
titleBarDivider: colors.slateLight,
inputBorder: colors.slateLight,
inputBorderFocused: colors.slate,
listItemHoverBackground: colors.warmGrey,
toolbarHoverBackground: colors.black,
toolbarBackground: colors.almostBlack,
toolbarInput: colors.white10,
toolbarItem: colors.white,
tableDivider: colors.smokeDark,
tableSelected: colors.primary,
tableSelectedBackground: "#E5F7FF",
buttonNeutralBackground: colors.white,
buttonNeutralText: colors.almostBlack,
buttonNeutralBorder: darken(0.15, colors.white),
tooltipBackground: colors.almostBlack,
tooltipText: colors.white,
toastBackground: colors.almostBlack,
toastText: colors.white,
quote: colors.slateLight,
codeBackground: colors.smoke,
codeBorder: colors.smokeDark,
embedBorder: colors.slateLight,
horizontalRule: colors.smokeDark,
progressBarBackground: colors.slateLight,
scrollbarBackground: colors.smoke,
scrollbarThumb: darken(0.15, colors.smokeDark),
modalBackdrop: colors.black10,
modalBackground: colors.white,
modalShadow:
"0 4px 8px rgb(0 0 0 / 8%), 0 2px 4px rgb(0 0 0 / 0%), 0 30px 40px rgb(0 0 0 / 8%)",
menuItemSelected: colors.warmGrey,
menuBackground: colors.white,
menuShadow:
"0 0 0 1px rgb(0 0 0 / 2%), 0 4px 8px rgb(0 0 0 / 8%), 0 2px 4px rgb(0 0 0 / 0%), 0 30px 40px rgb(0 0 0 / 8%)",
divider: colors.slateLight,
titleBarDivider: colors.slateLight,
inputBorder: colors.slateLight,
inputBorderFocused: colors.slate,
listItemHoverBackground: colors.warmGrey,
toolbarHoverBackground: colors.black,
toolbarBackground: colors.almostBlack,
toolbarInput: colors.white10,
toolbarItem: colors.white,
tableDivider: colors.smokeDark,
tableSelected: colors.accent,
tableSelectedBackground: "#E5F7FF",
buttonNeutralBackground: colors.white,
buttonNeutralText: colors.almostBlack,
buttonNeutralBorder: darken(0.15, colors.white),
tooltipBackground: colors.almostBlack,
tooltipText: colors.white,
toastBackground: colors.almostBlack,
toastText: colors.white,
quote: colors.slateLight,
codeBackground: colors.smoke,
codeBorder: colors.smokeDark,
embedBorder: colors.slateLight,
horizontalRule: colors.smokeDark,
progressBarBackground: colors.slateLight,
scrollbarBackground: colors.smoke,
scrollbarThumb: darken(0.15, colors.smokeDark),
};
};
export const dark = {
...base,
isDark: true,
background: colors.almostBlack,
secondaryBackground: colors.black50,
link: "#137FFB",
text: colors.almostWhite,
cursor: colors.almostWhite,
textSecondary: lighten(0.1, colors.slate),
textTertiary: colors.slate,
textDiffInserted: colors.almostWhite,
textDiffInsertedBackground: "rgba(63,185,80,0.3)",
textDiffDeleted: darken(0.1, colors.almostWhite),
textDiffDeletedBackground: "rgba(248,81,73,0.15)",
placeholder: colors.slateDark,
sidebarBackground: colors.veryDarkBlue,
sidebarActiveBackground: lighten(0.02, colors.almostBlack),
sidebarControlHoverBackground: colors.white10,
sidebarDraftBorder: darken("0.35", colors.slate),
sidebarText: colors.slate,
backdrop: "rgba(0, 0, 0, 0.5)",
shadow: "rgba(0, 0, 0, 0.6)",
export const buildDarkTheme = (input: Partial<Colors>): DefaultTheme => {
const colors = buildBaseTheme(input);
modalBackdrop: colors.black50,
modalBackground: "#1f2128",
modalShadow:
"0 0 0 1px rgba(0, 0, 0, 0.1), 0 8px 16px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.08)",
return {
...colors,
isDark: true,
background: colors.almostBlack,
secondaryBackground: colors.black50,
link: "#137FFB",
text: colors.almostWhite,
cursor: colors.almostWhite,
textSecondary: lighten(0.1, colors.slate),
textTertiary: colors.slate,
textDiffInserted: colors.almostWhite,
textDiffInsertedBackground: "rgba(63,185,80,0.3)",
textDiffDeleted: darken(0.1, colors.almostWhite),
textDiffDeletedBackground: "rgba(248,81,73,0.15)",
placeholder: colors.slateDark,
sidebarBackground: colors.veryDarkBlue,
sidebarActiveBackground: lighten(0.02, colors.almostBlack),
sidebarControlHoverBackground: colors.white10,
sidebarDraftBorder: darken("0.35", colors.slate),
sidebarText: colors.slate,
backdrop: "rgba(0, 0, 0, 0.5)",
shadow: "rgba(0, 0, 0, 0.6)",
menuItemSelected: lighten(0.1, "#1f2128"),
menuBackground: "#1f2128",
menuShadow:
"0 0 0 1px rgba(0, 0, 0, 0.1), 0 8px 16px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.08)",
divider: lighten(0.1, colors.almostBlack),
titleBarDivider: darken(0.4, colors.slate),
inputBorder: colors.slateDark,
inputBorderFocused: colors.slate,
listItemHoverBackground: colors.white10,
toolbarHoverBackground: colors.slate,
toolbarBackground: colors.white,
toolbarInput: colors.black10,
toolbarItem: colors.lightBlack,
tableDivider: colors.lightBlack,
tableSelected: colors.primary,
tableSelectedBackground: "#002333",
buttonNeutralBackground: colors.almostBlack,
buttonNeutralText: colors.white,
buttonNeutralBorder: colors.slateDark,
tooltipBackground: colors.white,
tooltipText: colors.lightBlack,
toastBackground: colors.white,
toastText: colors.lightBlack,
quote: colors.almostWhite,
code: colors.almostWhite,
codeBackground: colors.black75,
codeBorder: colors.black50,
codeString: "#3d8fd1",
embedBorder: colors.black50,
horizontalRule: lighten(0.1, colors.almostBlack),
noticeInfoText: colors.white,
noticeTipText: colors.white,
noticeWarningText: colors.white,
progressBarBackground: colors.slate,
scrollbarBackground: colors.black,
scrollbarThumb: colors.lightBlack,
modalBackdrop: colors.black50,
modalBackground: "#1f2128",
modalShadow:
"0 0 0 1px rgba(0, 0, 0, 0.1), 0 8px 16px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.08)",
menuItemSelected: lighten(0.1, "#1f2128"),
menuBackground: "#1f2128",
menuShadow:
"0 0 0 1px rgba(0, 0, 0, 0.1), 0 8px 16px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.08)",
divider: lighten(0.1, colors.almostBlack),
titleBarDivider: darken(0.4, colors.slate),
inputBorder: colors.slateDark,
inputBorderFocused: colors.slate,
listItemHoverBackground: colors.white10,
toolbarHoverBackground: colors.slate,
toolbarBackground: colors.white,
toolbarInput: colors.black10,
toolbarItem: colors.lightBlack,
tableDivider: colors.lightBlack,
tableSelected: colors.accent,
tableSelectedBackground: "#002333",
buttonNeutralBackground: colors.almostBlack,
buttonNeutralText: colors.white,
buttonNeutralBorder: colors.slateDark,
tooltipBackground: colors.white,
tooltipText: colors.lightBlack,
toastBackground: colors.white,
toastText: colors.lightBlack,
quote: colors.almostWhite,
code: colors.almostWhite,
codeBackground: colors.black75,
codeBorder: colors.black50,
codeString: "#3d8fd1",
embedBorder: colors.black50,
horizontalRule: lighten(0.1, colors.almostBlack),
noticeInfoText: colors.white,
noticeTipText: colors.white,
noticeWarningText: colors.white,
progressBarBackground: colors.slate,
scrollbarBackground: colors.black,
scrollbarThumb: colors.lightBlack,
};
};
export const lightMobile = light;
export const buildPitchBlackTheme = (input: Partial<Colors>) => {
const colors = buildDarkTheme(input);
export const darkMobile = {
...dark,
background: colors.black,
codeBackground: colors.almostBlack,
return {
...colors,
background: colors.black,
codeBackground: colors.almostBlack,
};
};
export default light;
export const light = buildLightTheme(defaultColors);
export default light as DefaultTheme;

View File

@@ -108,16 +108,28 @@ export enum UserPreference {
export type UserPreferences = { [key in UserPreference]?: boolean };
export type CustomTheme = {
accent: string;
accentText: string;
};
export enum TeamPreference {
/** Whether documents have a separate edit mode instead of seamless editing. */
SeamlessEdit = "seamlessEdit",
/** Whether to use team logo across the app for branding. */
PublicBranding = "publicBranding",
/** Whether viewers should see download options */
/** Whether viewers should see download options. */
ViewersCanExport = "viewersCanExport",
/** The custom theme for the team. */
CustomTheme = "customTheme",
}
export type TeamPreferences = { [key in TeamPreference]?: boolean };
export type TeamPreferences = {
[TeamPreference.SeamlessEdit]?: boolean;
[TeamPreference.PublicBranding]?: boolean;
[TeamPreference.ViewersCanExport]?: boolean;
[TeamPreference.CustomTheme]?: Partial<CustomTheme>;
};
export enum NavigationNodeType {
Collection = "collection",