Open in-page settings inside floating windows
This commit is contained in:
parent
b6dec385dc
commit
b62f6feaea
@ -15,6 +15,11 @@ import alignmentIndicatorSvg from '!!raw-loader!@ui/res/img/alignment-indicators
|
||||
import lockBarIndicatorSvg from '!!raw-loader!@ui/res/img/lock-bar-indicators.svg';
|
||||
import VideoAlignmentType from '../../../common/enums/VideoAlignmentType.enum';
|
||||
import { setVideoAlignmentIndicatorState } from '../../../ui/utils/video-alignment-indicator-handling';
|
||||
import { UwuiWindow } from './UwuiWindow';
|
||||
|
||||
import { createApp } from 'vue';
|
||||
import SettingsWindowContent from '@components/SettingsWindowContent.vue';
|
||||
// import jsonEditorCSS from 'vanilla-jsoneditor/themes/jse-theme-dark.css?inline'
|
||||
|
||||
if (process.env.CHANNEL !== 'stable'){
|
||||
console.info("Loading: UI");
|
||||
@ -293,10 +298,16 @@ class UI {
|
||||
}]
|
||||
},
|
||||
{
|
||||
label: 'todo: open settings'
|
||||
label: 'Extension settings',
|
||||
action: () => this.createSettingsWindow(),
|
||||
},
|
||||
{
|
||||
label: 'todo: first-time "customize/hide menu" option'
|
||||
label: 'Cropping issues?',
|
||||
action: () => this.createSettingsWindow('ui-settings'),
|
||||
},
|
||||
{
|
||||
label: 'Report a problem',
|
||||
action: () => this.createSettingsWindow('about'),
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -393,6 +404,28 @@ class UI {
|
||||
}
|
||||
}
|
||||
|
||||
createSettingsWindow(path?: string) {
|
||||
const iframe = document.createElement('iframe');
|
||||
|
||||
iframe.src = chrome.runtime.getURL(`ui/pages/settings/index.html#ui${path ? `/${path}` : ''}`);
|
||||
iframe.setAttribute('allowtransparency', 'true');
|
||||
Object.assign(iframe.style, {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
border: 'none',
|
||||
background: 'transparent', // important
|
||||
});
|
||||
|
||||
new UwuiWindow({
|
||||
title: 'Ultrawidify settings (iframe)',
|
||||
width: 1200,
|
||||
height: 800,
|
||||
x: 0,
|
||||
y: 0,
|
||||
content: iframe,
|
||||
});
|
||||
}
|
||||
|
||||
setUiVisibility(visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
336
src/ext/lib/uwui/UwuiWindow.ts
Normal file
336
src/ext/lib/uwui/UwuiWindow.ts
Normal file
@ -0,0 +1,336 @@
|
||||
import extensionCss from '@src/main.css?inline';
|
||||
|
||||
|
||||
type WindowContent = string | HTMLElement;
|
||||
type WindowBackgroundMode = 'transparent' | 'blur' | 'solid';
|
||||
|
||||
interface WindowOptions {
|
||||
title?: string;
|
||||
content: WindowContent;
|
||||
background?: WindowBackgroundMode;
|
||||
width?: number;
|
||||
height?: number;
|
||||
x?: number;
|
||||
y?: number;
|
||||
extraStyles?: string[];
|
||||
onFocus?: () => void;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export class UwuiWindow {
|
||||
private static baseZ = 1000000;
|
||||
private static instances: UwuiWindow[] = [];
|
||||
|
||||
private host: HTMLDivElement;
|
||||
private shadow: ShadowRoot;
|
||||
private windowEl: HTMLDivElement;
|
||||
private titleBar: HTMLDivElement;
|
||||
|
||||
private dragging = false;
|
||||
private resizing = false;
|
||||
private dragOffsetX = 0;
|
||||
private dragOffsetY = 0;
|
||||
|
||||
private background: WindowBackgroundMode = 'blur';
|
||||
private extraStyles?: string[];
|
||||
|
||||
private onFocus?: () => void;
|
||||
private onClose?: () => void;
|
||||
|
||||
|
||||
constructor(options: WindowOptions) {
|
||||
this.onFocus = options.onFocus;
|
||||
this.onClose = options.onClose;
|
||||
|
||||
this.host = document.createElement('div');
|
||||
this.host.style.position = 'fixed';
|
||||
this.host.style.inset = '0';
|
||||
this.host.style.pointerEvents = 'none';
|
||||
|
||||
this.background = options.background ?? 'blur';
|
||||
this.extraStyles = options.extraStyles;
|
||||
|
||||
document.body.appendChild(this.host);
|
||||
|
||||
this.shadow = this.host.attachShadow({ mode: 'closed' });
|
||||
this.shadow.innerHTML = this.template(options);
|
||||
|
||||
|
||||
this.windowEl = this.shadow.querySelector('.uw-window')!;
|
||||
this.titleBar = this.shadow.querySelector('.uw-titlebar')!;
|
||||
|
||||
this.setGeometry(
|
||||
options.x ?? 100,
|
||||
options.y ?? 100,
|
||||
options.width ?? 320,
|
||||
options.height ?? 220
|
||||
);
|
||||
|
||||
this.injectContent(options.content);
|
||||
this.bindEvents();
|
||||
|
||||
UwuiWindow.instances.unshift(this);
|
||||
UwuiWindow.recomputeZIndices();
|
||||
|
||||
this.applyBackground();
|
||||
|
||||
this.bindBackgroundMenu();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets window stacking order
|
||||
* @param order stacking order (0 = topmost)
|
||||
*/
|
||||
setOrder(order: number) {
|
||||
const list = UwuiWindow.instances;
|
||||
|
||||
// Remove this window from current position
|
||||
const currentIndex = list.indexOf(this);
|
||||
if (currentIndex !== -1) {
|
||||
list.splice(currentIndex, 1);
|
||||
}
|
||||
|
||||
// Clamp target index
|
||||
const clamped = Math.max(0, Math.min(order, list.length));
|
||||
|
||||
// Insert at new position
|
||||
list.splice(clamped, 0, this);
|
||||
|
||||
// Re-assign z-indexes
|
||||
UwuiWindow.recomputeZIndices();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.onClose?.();
|
||||
|
||||
const list = UwuiWindow.instances;
|
||||
const index = list.indexOf(this);
|
||||
if (index !== -1) {
|
||||
list.splice(index, 1);
|
||||
UwuiWindow.recomputeZIndices();
|
||||
}
|
||||
|
||||
this.host.remove();
|
||||
}
|
||||
|
||||
|
||||
private static recomputeZIndices() {
|
||||
const base = UwuiWindow.baseZ;
|
||||
|
||||
UwuiWindow.instances.forEach((win, index) => {
|
||||
// index 0 = topmost → highest z-index
|
||||
win.host.style.zIndex = String(base + (UwuiWindow.instances.length - index));
|
||||
});
|
||||
}
|
||||
|
||||
private bringToFront = () => {
|
||||
this.setOrder(0);
|
||||
this.onFocus?.();
|
||||
};
|
||||
|
||||
//#region setup
|
||||
private template(options: WindowOptions): string {
|
||||
|
||||
return `
|
||||
<style>
|
||||
${this.generateStyles()}
|
||||
|
||||
${this.extraStyles?.join('\n')}
|
||||
</style>
|
||||
|
||||
<div class="uw-window">
|
||||
<div class="uw-titlebar">
|
||||
<span class="uw-title">${options.title ?? ''}</span>
|
||||
|
||||
<div class="uw-actions">
|
||||
<div class="uw-bg-toggle">
|
||||
<!-- YOUR SVG ICON GOES HERE -->
|
||||
<button type="button" class="uw-bg-button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>layers-outline</title><path d="M12,18.54L19.37,12.8L21,14.07L12,21.07L3,14.07L4.62,12.81L12,18.54M12,16L3,9L12,2L21,9L12,16M12,4.53L6.26,9L12,13.47L17.74,9L12,4.53Z" /></svg>
|
||||
</button>
|
||||
|
||||
<div class="uw-bg-menu">
|
||||
<button data-bg="transparent">Transparent</button>
|
||||
<button data-bg="blur">Blur</button>
|
||||
<button data-bg="solid">Solid</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="uw-close">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>close</title><path d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="uw-content"></div>
|
||||
<div class="uw-resize-handle"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private generateStyles() {
|
||||
let css = extensionCss
|
||||
.trim()
|
||||
.replace(/'html, body \{/g, ':host {')
|
||||
.replace(/'body \{/g, ':host {')
|
||||
;
|
||||
|
||||
// this is bad but I can't be bothered to do it the proper way
|
||||
const cssArr: string[] = css.split('@font-face');
|
||||
const cssRemainder = cssArr[cssArr.length - 1].split('}').slice(1).join('}');
|
||||
|
||||
css = `
|
||||
${cssArr[0]}
|
||||
@font-face {
|
||||
font-family: 'Heebo';
|
||||
src: url(__FONT_HEEBO__) format('truetype-variations');
|
||||
font-weight: 100 900;
|
||||
font-stretch: 75% 125%;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Source Code Pro';
|
||||
src: url(__FONT_SCP__) format('truetype-variations');
|
||||
font-weight: 200 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Source Code Pro';
|
||||
src: url(__FONT_SCPI__) format('truetype-variations');
|
||||
font-weight: 200 900;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
${cssRemainder};
|
||||
`;
|
||||
|
||||
return css
|
||||
.replace('__FONT_HEEBO__', chrome.runtime.getURL('/ui/res/fonts/Heebo.ttf'))
|
||||
.replace('__FONT_SCP__', chrome.runtime.getURL('/ui/res/fonts/SourceCodePro.ttf'))
|
||||
.replace('__FONT_SCPI__', chrome.runtime.getURL('/ui/res/fonts/SourceCodePro-Italic.ttf'))
|
||||
.replaceAll('html,', '')
|
||||
;
|
||||
}
|
||||
|
||||
private injectContent(content: WindowContent) {
|
||||
const container = this.shadow.querySelector('.uw-content')!;
|
||||
if (typeof content === 'string') {
|
||||
container.innerHTML = content;
|
||||
} else {
|
||||
container.appendChild(content);
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region interactivity
|
||||
private bindEvents() {
|
||||
this.windowEl.addEventListener('mousedown', this.bringToFront);
|
||||
|
||||
this.titleBar.addEventListener('mousedown', this.startDrag);
|
||||
|
||||
this.shadow.querySelector('.uw-close')!
|
||||
.addEventListener('click', () => this.destroy());
|
||||
|
||||
this.shadow.querySelector('.uw-resize-handle')!
|
||||
.addEventListener('mousedown', this.startResize);
|
||||
|
||||
window.addEventListener('mousemove', this.onMouseMove);
|
||||
window.addEventListener('mouseup', this.stopActions);
|
||||
}
|
||||
|
||||
private startDrag = (e: MouseEvent) => {
|
||||
const rect = this.windowEl.getBoundingClientRect();
|
||||
this.dragging = true;
|
||||
this.dragOffsetX = e.clientX - rect.left;
|
||||
this.dragOffsetY = e.clientY - rect.top;
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
private startResize = (e: MouseEvent) => {
|
||||
this.resizing = true;
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
private onMouseMove = (e: MouseEvent) => {
|
||||
const rect = this.windowEl.getBoundingClientRect();
|
||||
|
||||
if (this.dragging) {
|
||||
const x = Math.min(
|
||||
window.innerWidth - rect.width,
|
||||
Math.max(0, e.clientX - this.dragOffsetX)
|
||||
);
|
||||
const y = Math.min(
|
||||
window.innerHeight - rect.height,
|
||||
Math.max(0, e.clientY - this.dragOffsetY)
|
||||
);
|
||||
|
||||
this.windowEl.style.left = `${x}px`;
|
||||
this.windowEl.style.top = `${y}px`;
|
||||
}
|
||||
|
||||
if (this.resizing) {
|
||||
const width = Math.max(160, e.clientX - rect.left);
|
||||
const height = Math.max(120, e.clientY - rect.top);
|
||||
|
||||
this.windowEl.style.width = `${width}px`;
|
||||
this.windowEl.style.height = `${height}px`;
|
||||
}
|
||||
};
|
||||
|
||||
private stopActions = () => {
|
||||
this.dragging = false;
|
||||
this.resizing = false;
|
||||
};
|
||||
|
||||
private setGeometry(x: number, y: number, w: number, h: number) {
|
||||
Object.assign(this.windowEl.style, {
|
||||
left: `${x}px`,
|
||||
top: `${y}px`,
|
||||
width: `${w}px`,
|
||||
height: `${h}px`,
|
||||
});
|
||||
}
|
||||
//#endregion interactivity
|
||||
|
||||
//#region bakcground handling
|
||||
setBackground(mode: WindowBackgroundMode) {
|
||||
this.background = mode;
|
||||
this.applyBackground();
|
||||
}
|
||||
|
||||
private applyBackground() {
|
||||
this.windowEl.classList.remove(
|
||||
'uw-window-bg-transparent',
|
||||
'uw-window-bg-blurred',
|
||||
'uw-window-bg-solid'
|
||||
);
|
||||
|
||||
switch (this.background) {
|
||||
case 'transparent':
|
||||
this.windowEl.classList.add('uw-window-bg-transparent');
|
||||
break;
|
||||
case 'blur':
|
||||
this.windowEl.classList.add('uw-window-bg-blurred');
|
||||
break;
|
||||
case 'solid':
|
||||
this.windowEl.classList.add('uw-window-bg-solid');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bindBackgroundMenu() {
|
||||
this.shadow.querySelectorAll('[data-bg]')
|
||||
.forEach(el => {
|
||||
el.addEventListener('click', () => {
|
||||
const mode = el.getAttribute('data-bg') as WindowBackgroundMode;
|
||||
this.setBackground(mode);
|
||||
});
|
||||
});
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
@ -7,14 +7,15 @@
|
||||
@import '@ui/res/styles/buttons.css';
|
||||
|
||||
@import '@ui/res/styles/player-menu.css';
|
||||
@import '@ui/res/styles/player-window.css';
|
||||
|
||||
|
||||
:host {
|
||||
@apply bg-stone-950 text-stone-300;
|
||||
@apply bg-transparent text-stone-300;
|
||||
font-size: 16px;
|
||||
}
|
||||
html, body {
|
||||
@apply bg-stone-950 text-stone-300;
|
||||
@apply bg-transparent text-stone-300;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
|
||||
@ -95,6 +95,15 @@
|
||||
></SiteExtensionSettings>
|
||||
</template>
|
||||
|
||||
<template v-if="settings && selectedTab === 'window.site-extension-settings'" >
|
||||
<h3>Settings for {{site?.host}}</h3>
|
||||
<SiteExtensionSettings
|
||||
:settings="settings"
|
||||
:siteSettings="siteSettings"
|
||||
:isDefaultConfiguration="false"
|
||||
></SiteExtensionSettings>
|
||||
</template>
|
||||
|
||||
<template v-if="settings && selectedTab === 'embedded-extension-settings'" >
|
||||
<h3>Settings for embedded sites</h3>
|
||||
<FrameSiteSettings
|
||||
@ -223,6 +232,15 @@ const AVAILABLE_TABS = {
|
||||
{ id: 'default-extension-settings', label: 'Default settings' }
|
||||
]
|
||||
},
|
||||
'window.site-extension-settings': {
|
||||
id: 'window.site-extension-settings', label: 'Site and Extension options', icon: 'cogs',
|
||||
children: [
|
||||
{ id: 'window.site-extension-settings', label: 'For this site', },
|
||||
{ id: 'embedded-extension-settings', label: 'For embedded sites', disabled: true, badgeCount: 0, },
|
||||
{ id: 'default-extension-settings', label: 'Default settings' },
|
||||
{ id: 'website-extension-settings', label: 'Website exceptions' },
|
||||
]
|
||||
},
|
||||
'default-extension-settings': {
|
||||
id: 'default-extension-settings', label: 'Site and Extension options', icon: 'cogs',
|
||||
children: [
|
||||
@ -258,7 +276,15 @@ const TAB_LOADOUT = {
|
||||
'debugging',
|
||||
],
|
||||
'ui': [
|
||||
'window.player-element-settings'
|
||||
'window.site-extension-settings',
|
||||
'window.player-element-settings',
|
||||
'autodetectionSettings',
|
||||
'ui-settings',
|
||||
'keyboardShortcuts',
|
||||
'changelog',
|
||||
'about',
|
||||
'import-export-settings',
|
||||
'debugging',
|
||||
],
|
||||
'updated': [
|
||||
'updated',
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
@change="settings.saveWithoutReload"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="w-full grid grid-cols-1 min-[1200px]:grid-cols-2 min-[1920px]:grid-cols-3 max-w-[2300px] gap-8">
|
||||
<div class="grow shrink">
|
||||
<h3 class="mb-4">Logger configuration</h3>
|
||||
|
||||
@ -1,14 +1,18 @@
|
||||
<template>
|
||||
<div class="
|
||||
w-full h-[100dvh] overflow-hidden
|
||||
p-1 popup-lg:py-2 popup-lg:px-2 window:py-4 window:px-8
|
||||
flex flex-row justify-center items-center
|
||||
">
|
||||
<div
|
||||
class="
|
||||
w-full h-[100dvh] overflow-hidden
|
||||
flex flex-row justify-center items-center
|
||||
"
|
||||
:class="{
|
||||
'p-1 popup-lg:py-2 popup-lg:px-2 window:py-4 window:px-8 bg-stone-950': role !== 'ui'
|
||||
}"
|
||||
>
|
||||
|
||||
<!-- page content -->
|
||||
<div
|
||||
class="w-full h-[100dvh] overflow-hidden flex flex-col"
|
||||
:class="{'max-w-[1920px]': !isDebugging}"
|
||||
class="w-full h-[100dvh] overflow-hidden flex flex-col"
|
||||
:class="{'max-w-[1920px]': !isDebugging && role !== 'ui'}"
|
||||
>
|
||||
<PopupHead
|
||||
v-if="role === 'popup' && settings && siteSettings && eventBus"
|
||||
@ -18,6 +22,9 @@
|
||||
:eventBus="eventBus"
|
||||
>
|
||||
</PopupHead>
|
||||
<div v-else-if="role === 'ui'" class="flex flex-row font-mono text-[0.8rem] text-stone-500 border-stone-500">
|
||||
url: {{getUrl}}
|
||||
</div>
|
||||
<div v-else>
|
||||
<h1 class="text-[3em] grow-0 shrink-0">Ultrawidify settings</h1>
|
||||
</div>
|
||||
@ -78,6 +85,11 @@ export default defineComponent({
|
||||
isDebugging: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getUrl() {
|
||||
return window.location.href;
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const [segment, ...path] = window.location.hash.split('/');
|
||||
this.initialPath = path;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"; style="position: relative; width: 799px; min-width: 320px; height: 600px; overflow: hidden;">
|
||||
<html lang="en"; style="position: relative; width: 799px; min-width: 320px; height: 600px; overflow: hidden; background-color: transparent;">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
|
||||
169
src/ui/res/styles/player-window.css
Normal file
169
src/ui/res/styles/player-window.css
Normal file
@ -0,0 +1,169 @@
|
||||
@import "tailwindcss";
|
||||
@import "tailwindcss/utilities";
|
||||
|
||||
:host {
|
||||
all: initial;
|
||||
color-scheme: dark light;
|
||||
|
||||
@apply font-mono text-stone-300;
|
||||
}
|
||||
|
||||
.uw-window {
|
||||
@apply fixed flex flex-col pointer-events-auto border-stone-500;
|
||||
|
||||
.uw-content {
|
||||
@apply p-4 flex grow-1 font-sans overflow-hidden;
|
||||
}
|
||||
|
||||
&.uw-window-bg-transparent {
|
||||
|
||||
}
|
||||
|
||||
&.uw-window-bg-blurred {
|
||||
@apply bg-stone-950/75;
|
||||
backdrop-filter: brightness(50%) saturate(50%) blur(16px);
|
||||
}
|
||||
|
||||
&.uw-window-bg-solid {
|
||||
@apply bg-stone-950;
|
||||
}
|
||||
}
|
||||
|
||||
.uw-resize-handle {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: nwse-resize;
|
||||
background:
|
||||
linear-gradient(135deg, transparent 50%, #666 50%);
|
||||
}
|
||||
|
||||
.uw-titlebar {
|
||||
@apply flex flex-row gap-4 items-center cursor-move select-none justify-between
|
||||
bg-stone-950/80 border-b border-b-stone-700;
|
||||
|
||||
.uw-title {
|
||||
@apply px-4 py-2;
|
||||
}
|
||||
|
||||
.uw-actions {
|
||||
@apply flex flex-row;
|
||||
|
||||
button {
|
||||
@apply h-[32px] w-[28px] relative p-0 m-0
|
||||
text-stone-300 hover:text-primary-200 bg-transparent border-none border-transparent px-2;
|
||||
|
||||
svg {
|
||||
@apply h-[24px] w-[24px] fill-current;
|
||||
}
|
||||
|
||||
&.uw-close {
|
||||
@apply w-[40px] px-[8px];
|
||||
|
||||
&:hover {
|
||||
@apply bg-red-600 text-[#fff];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.uw-bg-toggle {
|
||||
@apply relative;
|
||||
|
||||
.uw-bg-menu {
|
||||
@apply absolute top-[100%] right-0 bg-stone-950/75;
|
||||
|
||||
display: none;
|
||||
z-index: 1;
|
||||
|
||||
button {
|
||||
@apply w-full text-left px-4 py-2 hover:bg-stone-800;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&:hover .uw-bg-menu {
|
||||
@apply flex flex-col gap-2
|
||||
min-w-[220px]
|
||||
text-stone-300 hover:text-primary-200
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.settings-categories {
|
||||
@apply
|
||||
relative
|
||||
w-[4.5em] popup:w-[18em] window:w-[24em]
|
||||
mr-[1em]
|
||||
grow-0 shrink-0 flex flex-col
|
||||
|
||||
text-right;
|
||||
|
||||
.tab-column {
|
||||
@apply absolute popup:relative top-0 right-0
|
||||
w-[18em] window:w-full h-full z-[1000]
|
||||
hover:bg-stone-950 popup:hover:bg-transparent
|
||||
border-r border-r-stone-800
|
||||
hover:translate-x-[calc(100%-4.5em)] popup:hover:translate-x-0
|
||||
transition-transform duration-200;
|
||||
|
||||
.tab {
|
||||
@apply
|
||||
flex flex-col
|
||||
cursor-pointer
|
||||
border-r-2 border-r-transparent
|
||||
border-r-primary-400;
|
||||
|
||||
&.active {
|
||||
.main-tab {
|
||||
@apply !bg-transparent !text-primary-300 bg-gradient-to-r from-transparent to-black hover:!text-primary-200 ;
|
||||
}
|
||||
}
|
||||
&.disabled {
|
||||
@apply pointer-events-none;
|
||||
}
|
||||
}
|
||||
|
||||
.main-tab {
|
||||
@apply
|
||||
px-[1em] py-[0.5em]
|
||||
flex flex-row gap-4 justify-end items-center
|
||||
text-[1.125em] text-stone-300 text-right font-mono
|
||||
cursor-pointer
|
||||
hover:bg-stone-800
|
||||
hover:text-primary-200
|
||||
hover:border-r-stone-600;
|
||||
|
||||
.label {
|
||||
@apply grow-0;
|
||||
}
|
||||
}
|
||||
|
||||
.suboption {
|
||||
@apply
|
||||
px-6 py-2
|
||||
uppercase text-stone-400/75 font-mono
|
||||
relative
|
||||
hover:bg-stone-800
|
||||
hover:text-primary-200
|
||||
hover:border-r-stone-600;
|
||||
|
||||
&.active {
|
||||
@apply !bg-transparent !text-primary-300 bg-gradient-to-r from-transparent to-black hover:!text-primary-200;
|
||||
}
|
||||
&.disabled {
|
||||
@apply pointer-events-none text-stone-600/75;
|
||||
}
|
||||
.badge {
|
||||
@apply absolute right-[0.25rem] bottom-0 w-4 h-4 text-stone-950 bg-primary-300 rounded flex justify-center items-center font-bold text-[0.8rem];
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
@apply
|
||||
mb-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user