feat: Added translucency effect to menus, dialogs and alerts. Can be turned off from Preferences > Appeareance (#2387) [skip e2e]
This commit is contained in:
@@ -28,6 +28,7 @@ export const PrefDefaults = {
|
|||||||
[PrefKey.NotesHideTags]: false,
|
[PrefKey.NotesHideTags]: false,
|
||||||
[PrefKey.NotesHideEditorIcon]: false,
|
[PrefKey.NotesHideEditorIcon]: false,
|
||||||
[PrefKey.UseSystemColorScheme]: false,
|
[PrefKey.UseSystemColorScheme]: false,
|
||||||
|
[PrefKey.UseTranslucentUI]: true,
|
||||||
[PrefKey.AutoLightThemeIdentifier]: 'Default',
|
[PrefKey.AutoLightThemeIdentifier]: 'Default',
|
||||||
[PrefKey.AutoDarkThemeIdentifier]: NativeFeatureIdentifier.TYPES.DarkTheme,
|
[PrefKey.AutoDarkThemeIdentifier]: NativeFeatureIdentifier.TYPES.DarkTheme,
|
||||||
[PrefKey.NoteAddToParentFolders]: true,
|
[PrefKey.NoteAddToParentFolders]: true,
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export enum PrefKey {
|
|||||||
NotesHideTags = 'hideTags',
|
NotesHideTags = 'hideTags',
|
||||||
NotesHideEditorIcon = 'hideEditorIcon',
|
NotesHideEditorIcon = 'hideEditorIcon',
|
||||||
UseSystemColorScheme = 'useSystemColorScheme',
|
UseSystemColorScheme = 'useSystemColorScheme',
|
||||||
|
UseTranslucentUI = 'useTranslucentUI',
|
||||||
AutoLightThemeIdentifier = 'autoLightThemeIdentifier',
|
AutoLightThemeIdentifier = 'autoLightThemeIdentifier',
|
||||||
AutoDarkThemeIdentifier = 'autoDarkThemeIdentifier',
|
AutoDarkThemeIdentifier = 'autoDarkThemeIdentifier',
|
||||||
NoteAddToParentFolders = 'noteAddToParentFolders',
|
NoteAddToParentFolders = 'noteAddToParentFolders',
|
||||||
@@ -66,6 +67,7 @@ export type PrefValue = {
|
|||||||
[PrefKey.NotesHideTags]: boolean
|
[PrefKey.NotesHideTags]: boolean
|
||||||
[PrefKey.NotesHideEditorIcon]: boolean
|
[PrefKey.NotesHideEditorIcon]: boolean
|
||||||
[PrefKey.UseSystemColorScheme]: boolean
|
[PrefKey.UseSystemColorScheme]: boolean
|
||||||
|
[PrefKey.UseTranslucentUI]: boolean
|
||||||
[PrefKey.AutoLightThemeIdentifier]: string
|
[PrefKey.AutoLightThemeIdentifier]: string
|
||||||
[PrefKey.AutoDarkThemeIdentifier]: string
|
[PrefKey.AutoDarkThemeIdentifier]: string
|
||||||
[PrefKey.NoteAddToParentFolders]: boolean
|
[PrefKey.NoteAddToParentFolders]: boolean
|
||||||
|
|||||||
64
packages/ui-services/src/Theme/Color.spec.ts
Normal file
64
packages/ui-services/src/Theme/Color.spec.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { Color } from './Color'
|
||||||
|
|
||||||
|
describe('Color', () => {
|
||||||
|
it('should throw an error if the color is invalid', () => {
|
||||||
|
expect(() => new Color('#ff')).toThrowError('Invalid color')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse a rgb string', () => {
|
||||||
|
const color = new Color('rgb(255, 0, 0)')
|
||||||
|
expect(color.r).toBe(255)
|
||||||
|
expect(color.g).toBe(0)
|
||||||
|
expect(color.b).toBe(0)
|
||||||
|
expect(color.a).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw error if rgb string is invalid', () => {
|
||||||
|
expect(() => new Color('rgb(255, 0)')).toThrowError('Invalid color')
|
||||||
|
expect(() => new Color('rgb(266, -1, 0)')).toThrowError('Invalid color')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse a hex string', () => {
|
||||||
|
const color = new Color('#ff0000')
|
||||||
|
expect(color.r).toBe(255)
|
||||||
|
expect(color.g).toBe(0)
|
||||||
|
expect(color.b).toBe(0)
|
||||||
|
expect(color.a).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw error if hex string is invalid', () => {
|
||||||
|
expect(() => new Color('#ff')).toThrowError('Invalid color')
|
||||||
|
expect(() => new Color('#ff000')).toThrowError('Invalid color')
|
||||||
|
expect(() => new Color('#ff00000')).toThrowError('Invalid color')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should set the alpha value', () => {
|
||||||
|
const color = new Color('rgb(255, 0, 0)')
|
||||||
|
color.setAlpha(0.5)
|
||||||
|
expect(color.a).toBe(0.5)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw error if alpha value is invalid', () => {
|
||||||
|
const color = new Color('rgb(255, 0, 0)')
|
||||||
|
expect(() => color.setAlpha(-1)).toThrowError('Invalid alpha value')
|
||||||
|
expect(() => color.setAlpha(1.1)).toThrowError('Invalid alpha value')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should convert to string', () => {
|
||||||
|
const color = new Color('rgb(255, 0, 0)')
|
||||||
|
color.setAlpha(0.5)
|
||||||
|
expect(color.toString()).toBe('rgba(255, 0, 0, 0.5)')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return correct isDark value', () => {
|
||||||
|
const autobiographyThemeBG = 'rgb(237, 228, 218)'
|
||||||
|
const darkThemeBG = 'rgb(15, 16, 17)'
|
||||||
|
const solarizedThemeBG = 'rgb(0, 43, 54)'
|
||||||
|
const titaniumThemeBG = 'rgb(238, 239, 241)'
|
||||||
|
|
||||||
|
expect(new Color(autobiographyThemeBG).isDark()).toBe(false)
|
||||||
|
expect(new Color(darkThemeBG).isDark()).toBe(true)
|
||||||
|
expect(new Color(solarizedThemeBG).isDark()).toBe(true)
|
||||||
|
expect(new Color(titaniumThemeBG).isDark()).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
84
packages/ui-services/src/Theme/Color.ts
Normal file
84
packages/ui-services/src/Theme/Color.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
const RGBRegex = /-?\b[0-9]{1,3}\b/g
|
||||||
|
|
||||||
|
export class Color {
|
||||||
|
r: number = 0
|
||||||
|
g: number = 0
|
||||||
|
b: number = 0
|
||||||
|
a: number = 1
|
||||||
|
|
||||||
|
constructor(color: string) {
|
||||||
|
if (color.startsWith('rgb')) {
|
||||||
|
this.setFromRGB(color)
|
||||||
|
} else if (color.startsWith('#')) {
|
||||||
|
this.setFromHex(color)
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid color')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the color from a hex string
|
||||||
|
* @param hex - The hex string to set
|
||||||
|
*/
|
||||||
|
setFromHex(hex: string) {
|
||||||
|
if (!hex.startsWith('#')) {
|
||||||
|
throw new Error('Invalid color')
|
||||||
|
}
|
||||||
|
const hexValue = hex.substring(1)
|
||||||
|
if (hexValue.length !== 3 && hexValue.length !== 6) {
|
||||||
|
throw new Error('Invalid color')
|
||||||
|
}
|
||||||
|
const r = parseInt(hexValue.substring(0, 2), 16)
|
||||||
|
const g = parseInt(hexValue.substring(2, 4), 16)
|
||||||
|
const b = parseInt(hexValue.substring(4, 6), 16)
|
||||||
|
this.r = r
|
||||||
|
this.g = g
|
||||||
|
this.b = b
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the color from an RGB string
|
||||||
|
* @param color - The RGB string to set
|
||||||
|
*/
|
||||||
|
setFromRGB(color: string) {
|
||||||
|
if (!color.startsWith('rgb')) {
|
||||||
|
throw new Error('Invalid color')
|
||||||
|
}
|
||||||
|
const regexMatches = color.match(RGBRegex)
|
||||||
|
if (!regexMatches || regexMatches.length !== 3) {
|
||||||
|
throw new Error('Invalid color')
|
||||||
|
}
|
||||||
|
const [r, g, b] = regexMatches.map((value) => parseInt(value, 10))
|
||||||
|
if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) {
|
||||||
|
throw new Error('Invalid color')
|
||||||
|
}
|
||||||
|
this.r = r
|
||||||
|
this.g = g
|
||||||
|
this.b = b
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the alpha value of the color
|
||||||
|
* @param alpha - The alpha value to set (0-1)
|
||||||
|
*/
|
||||||
|
setAlpha(alpha: number) {
|
||||||
|
if (alpha < 0 || alpha > 1) {
|
||||||
|
throw new Error('Invalid alpha value')
|
||||||
|
}
|
||||||
|
this.a = alpha
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `rgba(${this.r}, ${this.g}, ${this.b}, ${this.a})`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the color is dark
|
||||||
|
* Based on RGB->YIQ equation https://24ways.org/2010/calculating-color-contrast
|
||||||
|
*/
|
||||||
|
isDark() {
|
||||||
|
const yiq = (this.r * 299 + this.g * 587 + this.b * 114) / 1000
|
||||||
|
return yiq <= 128
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
LocalStorageDecryptedContextualPayload,
|
LocalStorageDecryptedContextualPayload,
|
||||||
PrefKey,
|
PrefKey,
|
||||||
ThemeInterface,
|
ThemeInterface,
|
||||||
|
PrefDefaults,
|
||||||
} from '@standardnotes/models'
|
} from '@standardnotes/models'
|
||||||
import {
|
import {
|
||||||
InternalEventBusInterface,
|
InternalEventBusInterface,
|
||||||
@@ -21,6 +22,7 @@ import { AbstractUIServicee } from '../Abstract/AbstractUIService'
|
|||||||
import { GetAllThemesUseCase } from './GetAllThemesUseCase'
|
import { GetAllThemesUseCase } from './GetAllThemesUseCase'
|
||||||
import { Uuid } from '@standardnotes/domain-core'
|
import { Uuid } from '@standardnotes/domain-core'
|
||||||
import { ActiveThemeList } from './ActiveThemeList'
|
import { ActiveThemeList } from './ActiveThemeList'
|
||||||
|
import { Color } from './Color'
|
||||||
|
|
||||||
const CachedThemesKey = 'cachedThemes'
|
const CachedThemesKey = 'cachedThemes'
|
||||||
const TimeBeforeApplyingColorScheme = 5
|
const TimeBeforeApplyingColorScheme = 5
|
||||||
@@ -82,6 +84,8 @@ export class ThemeManager extends AbstractUIServicee {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.toggleTranslucentUIColors()
|
||||||
|
|
||||||
let hasChange = false
|
let hasChange = false
|
||||||
|
|
||||||
const { features, uuids } = this.components.getActiveThemesIdentifiers()
|
const { features, uuids } = this.components.getActiveThemesIdentifiers()
|
||||||
@@ -169,6 +173,8 @@ export class ThemeManager extends AbstractUIServicee {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async handlePreferencesChangeEvent() {
|
private async handlePreferencesChangeEvent() {
|
||||||
|
this.toggleTranslucentUIColors()
|
||||||
|
|
||||||
const useDeviceThemeSettings = this.application.getPreference(PrefKey.UseSystemColorScheme, false)
|
const useDeviceThemeSettings = this.application.getPreference(PrefKey.UseSystemColorScheme, false)
|
||||||
|
|
||||||
const hasPreferenceChanged = useDeviceThemeSettings !== this.lastUseDeviceThemeSettings
|
const hasPreferenceChanged = useDeviceThemeSettings !== this.lastUseDeviceThemeSettings
|
||||||
@@ -339,6 +345,8 @@ export class ThemeManager extends AbstractUIServicee {
|
|||||||
.handleThemeSchemeChange(packageInfo.isDark ?? false, this.getBackgroundColor())
|
.handleThemeSchemeChange(packageInfo.isDark ?? false, this.getBackgroundColor())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.toggleTranslucentUIColors()
|
||||||
}
|
}
|
||||||
document.getElementsByTagName('head')[0].appendChild(link)
|
document.getElementsByTagName('head')[0].appendChild(link)
|
||||||
}
|
}
|
||||||
@@ -356,8 +364,11 @@ export class ThemeManager extends AbstractUIServicee {
|
|||||||
|
|
||||||
this.themesActiveInTheUI.remove(id)
|
this.themesActiveInTheUI.remove(id)
|
||||||
|
|
||||||
if (this.themesActiveInTheUI.isEmpty() && this.application.isNativeMobileWeb()) {
|
if (this.themesActiveInTheUI.isEmpty()) {
|
||||||
this.application.mobileDevice().handleThemeSchemeChange(false, '#ffffff')
|
if (this.application.isNativeMobileWeb()) {
|
||||||
|
this.application.mobileDevice().handleThemeSchemeChange(false, '#ffffff')
|
||||||
|
}
|
||||||
|
this.toggleTranslucentUIColors()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,6 +377,31 @@ export class ThemeManager extends AbstractUIServicee {
|
|||||||
return bgColor.length ? bgColor : '#ffffff'
|
return bgColor.length ? bgColor : '#ffffff'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private shouldUseTranslucentUI() {
|
||||||
|
return this.application.getPreference(PrefKey.UseTranslucentUI, PrefDefaults[PrefKey.UseTranslucentUI])
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleTranslucentUIColors() {
|
||||||
|
if (!this.shouldUseTranslucentUI()) {
|
||||||
|
document.documentElement.style.removeProperty('--popover-background-color')
|
||||||
|
document.documentElement.style.removeProperty('--popover-backdrop-filter')
|
||||||
|
document.body.classList.remove('translucent-ui')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const backgroundColor = new Color(this.getBackgroundColor())
|
||||||
|
const backdropFilter = backgroundColor.isDark()
|
||||||
|
? 'blur(12px) saturate(190%) contrast(70%) brightness(80%)'
|
||||||
|
: 'blur(12px) saturate(190%) contrast(50%) brightness(130%)'
|
||||||
|
const translucentBackgroundColor = backgroundColor.setAlpha(0.65).toString()
|
||||||
|
document.documentElement.style.setProperty('--popover-background-color', translucentBackgroundColor)
|
||||||
|
document.documentElement.style.setProperty('--popover-backdrop-filter', backdropFilter)
|
||||||
|
document.body.classList.add('translucent-ui')
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Syncs the active theme's background color to the 'theme-color' meta tag
|
* Syncs the active theme's background color to the 'theme-color' meta tag
|
||||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name/theme-color
|
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name/theme-color
|
||||||
|
|||||||
@@ -47,5 +47,10 @@
|
|||||||
--sn-stylekit-menu-border: 1px solid #424242;
|
--sn-stylekit-menu-border: 1px solid #424242;
|
||||||
--navigation-item-selected-background-color: var(--background-color);
|
--navigation-item-selected-background-color: var(--background-color);
|
||||||
--normal-button-background-color: var(--sn-stylekit-passive-color-5);
|
--normal-button-background-color: var(--sn-stylekit-passive-color-5);
|
||||||
--menu-border-color: var(--sn-stylekit-border-color);
|
--popover-border-color: var(--sn-stylekit-passive-color-3);
|
||||||
|
--separator-color: var(--sn-stylekit-passive-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.translucent-ui [role='dialog'] {
|
||||||
|
--sn-stylekit-border-color: var(--sn-stylekit-passive-color-3);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,10 +40,13 @@ const AlertDialog = ({
|
|||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'z-[1] w-[95vw] rounded border border-border bg-default px-6 py-5 shadow-xl md:w-auto',
|
'w-[95vw] rounded border border-[--popover-border-color] bg-[--popover-background-color] px-6 py-5 shadow-xl md:w-auto',
|
||||||
!className?.includes('max-w-') && 'max-w-[600px]',
|
!className?.includes('max-w-') && 'max-w-[600px]',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
|
style={{
|
||||||
|
backdropFilter: 'var(--popover-backdrop-filter)',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ const ChallengeModal: FunctionComponent<Props> = ({
|
|||||||
close={cancelChallenge}
|
close={cancelChallenge}
|
||||||
className={{
|
className={{
|
||||||
content: classNames(
|
content: classNames(
|
||||||
'sn-component challenge-modal relative m-0 flex h-full w-full flex-col items-center rounded border-solid border-border bg-default p-0 md:h-auto md:!w-max md:border',
|
'sn-component challenge-modal relative m-0 flex h-full w-full flex-col items-center rounded border-solid border-border bg-default p-0 md:h-auto md:!w-max md:border md:translucent-ui:bg-[--popover-background-color] translucent-ui:[backdrop-filter:var(--popover-backdrop-filter)]',
|
||||||
!isMobileScreen && 'shadow-overlay-light',
|
!isMobileScreen && 'shadow-overlay-light',
|
||||||
isMobileOverlay && 'shadow-overlay-light border border-solid border-border',
|
isMobileOverlay && 'shadow-overlay-light border border-solid border-border',
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -203,7 +203,11 @@ const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment key={groupId}>
|
<Fragment key={groupId}>
|
||||||
<div className={`border-0 border-t border-solid border-border py-1 ${index === 0 ? 'border-t-0' : ''}`}>
|
<div
|
||||||
|
className={`border-0 border-t border-solid border-[--separator-color] py-1 ${
|
||||||
|
index === 0 ? 'border-t-0' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
{group.items.map((menuItem) => {
|
{group.items.map((menuItem) => {
|
||||||
const onClickEditorItem = () => {
|
const onClickEditorItem = () => {
|
||||||
handleMenuSelection(menuItem).catch(console.error)
|
handleMenuSelection(menuItem).catch(console.error)
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ const NewNotePreferences: FunctionComponent<Props> = ({
|
|||||||
<input
|
<input
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'w-full min-w-55 rounded border border-solid border-passive-3 bg-default px-2 py-1.5 text-sm',
|
'w-full min-w-55 rounded border border-solid border-passive-3 bg-default md:translucent-ui:bg-transparent px-2 py-1.5 text-sm',
|
||||||
'focus-within:ring-2 focus-within:ring-info',
|
'focus-within:ring-2 focus-within:ring-info',
|
||||||
)}
|
)}
|
||||||
placeholder="e.g. YYYY-MM-DD"
|
placeholder="e.g. YYYY-MM-DD"
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ const Dropdown = ({
|
|||||||
</VisuallyHidden>
|
</VisuallyHidden>
|
||||||
<Select
|
<Select
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'flex w-full min-w-55 items-center justify-between rounded border border-border bg-default px-3.5 py-1.5 text-sm text-foreground',
|
'flex w-full min-w-55 items-center justify-between rounded border border-passive-3 bg-default md:translucent-ui:bg-transparent px-3.5 py-1.5 text-sm text-foreground',
|
||||||
disabled && 'opacity-50',
|
disabled && 'opacity-50',
|
||||||
classNameOverride.button,
|
classNameOverride.button,
|
||||||
!fullWidth && 'md:w-fit',
|
!fullWidth && 'md:w-fit',
|
||||||
@@ -83,9 +83,12 @@ const Dropdown = ({
|
|||||||
<SelectPopover
|
<SelectPopover
|
||||||
store={select}
|
store={select}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'z-dropdown-menu max-h-[var(--popover-available-height)] w-[var(--popover-anchor-width)] overflow-y-auto rounded border border-border bg-default py-1',
|
'z-dropdown-menu max-h-[var(--popover-available-height)] w-[var(--popover-anchor-width)] overflow-y-auto rounded border border-passive-3 bg-default py-1',
|
||||||
classNameOverride.popover,
|
classNameOverride.popover,
|
||||||
)}
|
)}
|
||||||
|
style={{
|
||||||
|
backdropFilter: 'var(--popover-backdrop-filter)',
|
||||||
|
}}
|
||||||
portal={false}
|
portal={false}
|
||||||
>
|
>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ const FilePreviewModal = observer(({ application, viewControllerManager }: Props
|
|||||||
close={dismiss}
|
close={dismiss}
|
||||||
className={{
|
className={{
|
||||||
content: classNames(
|
content: classNames(
|
||||||
'm-0 flex h-full w-full flex-col rounded bg-[color:var(--modal-background-color)] p-0 shadow-main md:!h-full md:max-h-[90%] md:!w-full md:max-w-[90%]',
|
'm-0 flex h-full w-full flex-col rounded bg-[--popover-background-color] p-0 shadow-main md:!h-full md:max-h-[90%] md:!w-full md:max-w-[90%]',
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
actions={[
|
actions={[
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { DecoratedInputProps } from './DecoratedInputProps'
|
|||||||
|
|
||||||
const getClassNames = (hasLeftDecorations: boolean, hasRightDecorations: boolean, roundedFull?: boolean) => {
|
const getClassNames = (hasLeftDecorations: boolean, hasRightDecorations: boolean, roundedFull?: boolean) => {
|
||||||
return {
|
return {
|
||||||
container: `position-relative flex items-stretch overflow-hidden border border-solid border-passive-3 bg-default text-sm focus-within:ring-2 focus-within:ring-info bg-clip-padding ${
|
container: `position-relative flex items-stretch overflow-hidden border border-solid border-passive-3 bg-default translucent-ui:bg-transparent text-sm focus-within:ring-2 focus-within:ring-info bg-clip-padding ${
|
||||||
!hasLeftDecorations && !hasRightDecorations ? 'px-2 py-1.5' : ''
|
!hasLeftDecorations && !hasRightDecorations ? 'px-2 py-1.5' : ''
|
||||||
} ${roundedFull ? 'rounded-full' : 'rounded'}`,
|
} ${roundedFull ? 'rounded-full' : 'rounded'}`,
|
||||||
input: `focus:ring-none w-full border-0 bg-transparent text-text focus:shadow-none focus:outline-none ${
|
input: `focus:ring-none w-full border-0 bg-transparent text-text focus:shadow-none focus:outline-none ${
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ const LinkedItemsPanel = ({
|
|||||||
<div>
|
<div>
|
||||||
<form
|
<form
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'sticky top-0 z-10 bg-default px-2.5 pt-2.5',
|
'sticky top-0 z-10 bg-default md:translucent-ui:bg-transparent px-2.5 pt-2.5',
|
||||||
linkedResults.length || unlinkedItems.length || notesLinkingToItem.length
|
linkedResults.length || unlinkedItems.length || notesLinkingToItem.length
|
||||||
? 'border-b border-border pb-2.5'
|
? 'border-b border-border pb-2.5'
|
||||||
: 'pb-1',
|
: 'pb-1',
|
||||||
@@ -70,7 +70,10 @@ const LinkedItemsPanel = ({
|
|||||||
>
|
>
|
||||||
<DecoratedInput
|
<DecoratedInput
|
||||||
type="text"
|
type="text"
|
||||||
className={{ container: !isSearching ? 'px-0.5 py-1.5' : 'py-0', input: 'placeholder:text-passive-0' }}
|
className={{
|
||||||
|
container: classNames(!isSearching ? 'px-0.5 py-1.5' : 'py-0', 'md:translucent-ui:bg-default'),
|
||||||
|
input: 'placeholder:text-passive-0',
|
||||||
|
}}
|
||||||
placeholder="Search items to link..."
|
placeholder="Search items to link..."
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={setSearchQuery}
|
onChange={setSearchQuery}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { FunctionComponent } from 'react'
|
|||||||
|
|
||||||
const MenuItemSeparator: FunctionComponent = () => (
|
const MenuItemSeparator: FunctionComponent = () => (
|
||||||
<li className="list-none" role="none">
|
<li className="list-none" role="none">
|
||||||
<div role="separator" className="my-2 h-[1px] bg-border" />
|
<div role="separator" className="my-2 h-[1px] bg-[--separator-color]" />
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const Tooltip = ({ text }: { text: string }) => {
|
|||||||
onMouseEnter={() => setVisible(true)}
|
onMouseEnter={() => setVisible(true)}
|
||||||
onMouseLeave={() => setVisible(false)}
|
onMouseLeave={() => setVisible(false)}
|
||||||
>
|
>
|
||||||
<Icon type={'notes'} className="text-border" size="large" />
|
<Icon type={'notes'} className="text-border translucent-ui:text-[--popover-border-color]" size="large" />
|
||||||
</div>
|
</div>
|
||||||
<Popover
|
<Popover
|
||||||
open={visible}
|
open={visible}
|
||||||
|
|||||||
@@ -104,16 +104,19 @@ const Modal = ({
|
|||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'z-[1] m-0 flex h-full w-full flex-col border-solid border-border bg-default p-0 md:h-auto md:max-h-[85vh] md:w-160 md:rounded md:border md:shadow-main',
|
'z-[1] m-0 flex h-full w-full flex-col border-solid border-[--popover-border-color] bg-[--popover-background-color] p-0 md:h-auto md:max-h-[85vh] md:w-160 md:rounded md:border md:shadow-main',
|
||||||
className?.content,
|
className?.content,
|
||||||
)}
|
)}
|
||||||
|
style={{
|
||||||
|
backdropFilter: 'var(--popover-backdrop-filter)',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{customHeader && !disableCustomHeader ? (
|
{customHeader && !disableCustomHeader ? (
|
||||||
customHeader
|
customHeader
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'flex w-full flex-shrink-0 select-none items-center justify-between rounded-t border-b border-solid border-border bg-default px-2 text-text md:px-4.5 md:py-3',
|
'flex w-full flex-shrink-0 select-none items-center justify-between rounded-t border-b border-solid border-border bg-default px-2 text-text md:px-4.5 md:py-3 md:translucent-ui:bg-transparent',
|
||||||
hasTopInset ? 'pb-1.5 pt-safe-top' : 'py-1.5',
|
hasTopInset ? 'pb-1.5 pt-safe-top' : 'py-1.5',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ const PositionedPopoverContent = ({
|
|||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'absolute left-0 top-0 flex w-full min-w-80 cursor-auto flex-col',
|
'absolute left-0 top-0 flex w-full min-w-80 cursor-auto flex-col',
|
||||||
'overflow-y-auto rounded border border-[--menu-border-color] bg-default shadow-main md:h-auto md:max-w-xs',
|
'overflow-y-auto rounded border border-[--popover-border-color] bg-[--popover-background-color] shadow-main md:h-auto md:max-w-xs',
|
||||||
!disableMobileFullscreenTakeover && 'h-full',
|
!disableMobileFullscreenTakeover && 'h-full',
|
||||||
overrideZIndex ? overrideZIndex : 'z-dropdown-menu',
|
overrideZIndex ? overrideZIndex : 'z-dropdown-menu',
|
||||||
!isDesktopScreen && !disableMobileFullscreenTakeover ? 'pb-safe-bottom pt-safe-top' : '',
|
!isDesktopScreen && !disableMobileFullscreenTakeover ? 'pb-safe-bottom pt-safe-top' : '',
|
||||||
@@ -109,6 +109,7 @@ const PositionedPopoverContent = ({
|
|||||||
{
|
{
|
||||||
...styles,
|
...styles,
|
||||||
...adjustedStyles,
|
...adjustedStyles,
|
||||||
|
backdropFilter: 'var(--popover-backdrop-filter)',
|
||||||
} as CSSProperties
|
} as CSSProperties
|
||||||
}
|
}
|
||||||
ref={mergeRefs([setPopoverElement, addCloseMethod])}
|
ref={mergeRefs([setPopoverElement, addCloseMethod])}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import PreferencesSegment from '../PreferencesComponents/PreferencesSegment'
|
|||||||
import { PremiumFeatureIconName } from '@/Components/Icon/PremiumFeatureIcon'
|
import { PremiumFeatureIconName } from '@/Components/Icon/PremiumFeatureIcon'
|
||||||
import EditorAppearance from './Appearance/EditorAppearance'
|
import EditorAppearance from './Appearance/EditorAppearance'
|
||||||
import { GetAllThemesUseCase } from '@standardnotes/ui-services'
|
import { GetAllThemesUseCase } from '@standardnotes/ui-services'
|
||||||
|
import usePreference from '@/Hooks/usePreference'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
@@ -33,6 +34,11 @@ const Appearance: FunctionComponent<Props> = ({ application }) => {
|
|||||||
application.getPreference(PrefKey.UseSystemColorScheme, PrefDefaults[PrefKey.UseSystemColorScheme]),
|
application.getPreference(PrefKey.UseSystemColorScheme, PrefDefaults[PrefKey.UseSystemColorScheme]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const useTranslucentUI = usePreference(PrefKey.UseTranslucentUI)
|
||||||
|
const toggleTranslucentUI = () => {
|
||||||
|
application.setPreference(PrefKey.UseTranslucentUI, !useTranslucentUI).catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const usecase = new GetAllThemesUseCase(application.items)
|
const usecase = new GetAllThemesUseCase(application.items)
|
||||||
const { thirdParty, native } = usecase.execute({ excludeLayerable: true })
|
const { thirdParty, native } = usecase.execute({ excludeLayerable: true })
|
||||||
@@ -106,6 +112,14 @@ const Appearance: FunctionComponent<Props> = ({ application }) => {
|
|||||||
<PreferencesSegment>
|
<PreferencesSegment>
|
||||||
<Title>Themes</Title>
|
<Title>Themes</Title>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
|
<div className="flex justify-between gap-2 md:items-center">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<Subtitle>Disable translucent UI</Subtitle>
|
||||||
|
<Text>Use opaque style for UI elements instead of translucency</Text>
|
||||||
|
</div>
|
||||||
|
<Switch onChange={toggleTranslucentUI} checked={!useTranslucentUI} />
|
||||||
|
</div>
|
||||||
|
<HorizontalSeparator classes="my-4" />
|
||||||
<div className="flex justify-between gap-2 md:items-center">
|
<div className="flex justify-between gap-2 md:items-center">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<Subtitle>Use system color scheme</Subtitle>
|
<Subtitle>Use system color scheme</Subtitle>
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
|
|||||||
const PreferencesCanvas: FunctionComponent<PreferencesProps & { menu: PreferencesSessionController }> = (props) => (
|
const PreferencesCanvas: FunctionComponent<PreferencesProps & { menu: PreferencesSessionController }> = (props) => (
|
||||||
<div className="flex min-h-0 flex-grow flex-col md:flex-row md:justify-between">
|
<div className="flex min-h-0 flex-grow flex-col md:flex-row md:justify-between">
|
||||||
<PreferencesMenuView menu={props.menu} />
|
<PreferencesMenuView menu={props.menu} />
|
||||||
<div className="min-h-0 flex-grow overflow-auto bg-contrast" tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}>
|
<div
|
||||||
|
className="min-h-0 flex-grow overflow-auto bg-contrast md:bg-[--popover-background-color]"
|
||||||
|
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
||||||
|
>
|
||||||
<PaneSelector {...props} />
|
<PaneSelector {...props} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const PreferencesMenuView: FunctionComponent<Props> = ({ menu }) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border-b border-border bg-default px-5 py-2 md:border-0 md:bg-contrast md:px-0 md:py-0">
|
<div className="border-b border-border bg-default px-5 py-2 md:border-0 md:bg-[--popover-background-color] md:px-0 md:py-0">
|
||||||
<div className="hidden min-w-55 flex-col overflow-y-auto px-3 py-6 md:flex">
|
<div className="hidden min-w-55 flex-col overflow-y-auto px-3 py-6 md:flex">
|
||||||
{menuItems.map((pref) => (
|
{menuItems.map((pref) => (
|
||||||
<PreferencesMenuItem
|
<PreferencesMenuItem
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import Icon from '@/Components/Icon/Icon'
|
|||||||
import FocusModeSwitch from './FocusModeSwitch'
|
import FocusModeSwitch from './FocusModeSwitch'
|
||||||
import ThemesMenuButton from './ThemesMenuButton'
|
import ThemesMenuButton from './ThemesMenuButton'
|
||||||
import { sortThemes } from '@/Utils/SortThemes'
|
import { sortThemes } from '@/Utils/SortThemes'
|
||||||
import HorizontalSeparator from '../Shared/HorizontalSeparator'
|
|
||||||
import { QuickSettingsController } from '@/Controllers/QuickSettingsController'
|
import { QuickSettingsController } from '@/Controllers/QuickSettingsController'
|
||||||
import PanelSettingsSection from './PanelSettingsSection'
|
import PanelSettingsSection from './PanelSettingsSection'
|
||||||
import Menu from '../Menu/Menu'
|
import Menu from '../Menu/Menu'
|
||||||
@@ -21,6 +20,7 @@ import MenuSwitchButtonItem from '../Menu/MenuSwitchButtonItem'
|
|||||||
import MenuRadioButtonItem from '../Menu/MenuRadioButtonItem'
|
import MenuRadioButtonItem from '../Menu/MenuRadioButtonItem'
|
||||||
import { useApplication } from '../ApplicationProvider'
|
import { useApplication } from '../ApplicationProvider'
|
||||||
import { GetAllThemesUseCase } from '@standardnotes/ui-services'
|
import { GetAllThemesUseCase } from '@standardnotes/ui-services'
|
||||||
|
import MenuItemSeparator from '../Menu/MenuItemSeparator'
|
||||||
|
|
||||||
type MenuProps = {
|
type MenuProps = {
|
||||||
quickSettingsMenuController: QuickSettingsController
|
quickSettingsMenuController: QuickSettingsController
|
||||||
@@ -133,7 +133,7 @@ const QuickSettingsMenu: FunctionComponent<MenuProps> = ({ quickSettingsMenuCont
|
|||||||
{component.displayName}
|
{component.displayName}
|
||||||
</MenuSwitchButtonItem>
|
</MenuSwitchButtonItem>
|
||||||
))}
|
))}
|
||||||
<HorizontalSeparator classes="my-2" />
|
<MenuItemSeparator />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="my-1 px-3 text-sm font-semibold uppercase text-text">Appearance</div>
|
<div className="my-1 px-3 text-sm font-semibold uppercase text-text">Appearance</div>
|
||||||
@@ -143,7 +143,7 @@ const QuickSettingsMenu: FunctionComponent<MenuProps> = ({ quickSettingsMenuCont
|
|||||||
{themes.map((theme) => (
|
{themes.map((theme) => (
|
||||||
<ThemesMenuButton uiFeature={theme} key={theme.uniqueIdentifier.value} />
|
<ThemesMenuButton uiFeature={theme} key={theme.uniqueIdentifier.value} />
|
||||||
))}
|
))}
|
||||||
<HorizontalSeparator classes="my-2" />
|
<MenuItemSeparator />
|
||||||
<FocusModeSwitch
|
<FocusModeSwitch
|
||||||
application={application}
|
application={application}
|
||||||
onToggle={setFocusModeEnabled}
|
onToggle={setFocusModeEnabled}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const HistoryModalDialog = forwardRef(({ children, onDismiss }: Props, ref: Forw
|
|||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'absolute z-[1] my-0 flex h-full w-full flex-col rounded-md bg-[color:var(--modal-background-color)]',
|
'absolute z-[1] my-0 flex h-full w-full flex-col rounded-md bg-[--modal-background-color]',
|
||||||
'p-0 pb-safe-bottom pt-safe-top shadow-lg md:max-h-[90%] md:w-[90%] md:max-w-[90%]',
|
'p-0 pb-safe-bottom pt-safe-top shadow-lg md:max-h-[90%] md:w-[90%] md:max-w-[90%]',
|
||||||
'md:left-1/2 md:top-1/2 md:-translate-x-1/2 md:-translate-y-1/2 md:transform',
|
'md:left-1/2 md:top-1/2 md:-translate-x-1/2 md:-translate-y-1/2 md:transform',
|
||||||
getPlatformString(),
|
getPlatformString(),
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ type Props = {
|
|||||||
classes?: string
|
classes?: string
|
||||||
}
|
}
|
||||||
const HorizontalSeparator: FunctionComponent<Props> = ({ classes = '' }) => {
|
const HorizontalSeparator: FunctionComponent<Props> = ({ classes = '' }) => {
|
||||||
return <hr className={`min-h-[1px] w-full border-none bg-border ${classes}`} />
|
return <hr className={`min-h-[1px] w-full border-none bg-[--separator-color] ${classes}`} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HorizontalSeparator
|
export default HorizontalSeparator
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const StyledTooltip = ({
|
|||||||
autoFocusOnShow={!showOnHover}
|
autoFocusOnShow={!showOnHover}
|
||||||
store={tooltip}
|
store={tooltip}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'z-tooltip max-w-max rounded border border-border bg-contrast px-3 py-1.5 text-sm text-foreground shadow',
|
'z-tooltip max-w-max rounded border border-border translucent-ui:border-[--popover-border-color] bg-contrast translucent-ui:bg-[--popover-background-color] [backdrop-filter:var(--popover-backdrop-filter)] px-3 py-1.5 text-sm text-foreground shadow',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
updatePosition={() => {
|
updatePosition={() => {
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ function TextFormatFloatingToolbar({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={toolbarRef}
|
ref={toolbarRef}
|
||||||
className="absolute left-0 top-0 rounded-lg border border-border bg-contrast px-2 py-1 shadow-sm shadow-contrast"
|
className="absolute left-0 top-0 rounded-lg border border-border bg-contrast translucent-ui:bg-[--popover-background-color] translucent-ui:[backdrop-filter:var(--popover-backdrop-filter)] px-2 py-1 shadow-sm shadow-contrast"
|
||||||
>
|
>
|
||||||
{isLink && (
|
{isLink && (
|
||||||
<LinkEditor
|
<LinkEditor
|
||||||
|
|||||||
@@ -56,6 +56,12 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
.sk-panel {
|
||||||
|
border-color: var(--popover-border-color);
|
||||||
|
background-color: var(--popover-background-color);
|
||||||
|
backdrop-filter: var(--popover-backdrop-filter);
|
||||||
|
}
|
||||||
|
|
||||||
.sn-component {
|
.sn-component {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
.sk-panel {
|
.sk-panel {
|
||||||
|
|||||||
@@ -44,5 +44,8 @@
|
|||||||
|
|
||||||
--normal-button-background-color: var(--sn-stylekit-background-color);
|
--normal-button-background-color: var(--sn-stylekit-background-color);
|
||||||
|
|
||||||
--menu-border-color: transparent;
|
--popover-background-color: var(--sn-stylekit-background-color);
|
||||||
|
--popover-border-color: var(--sn-stylekit-border-color);
|
||||||
|
|
||||||
|
--separator-color: var(--sn-stylekit-border-color);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ module.exports = {
|
|||||||
plugins: [
|
plugins: [
|
||||||
plugin(function ({ addVariant }) {
|
plugin(function ({ addVariant }) {
|
||||||
addVariant('pointer-coarse', '@media (pointer: coarse)')
|
addVariant('pointer-coarse', '@media (pointer: coarse)')
|
||||||
|
addVariant('translucent-ui', '.translucent-ui &')
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user