PTT fixes, TDS expansions, performance hooks, state event renderers
PTT fixes (BUG-7/8/9): - BUG-7: Fix isEditable to use ownerDocument.body (works in EC iframe context) - BUG-8: Release mic if callEmbed changes during active PTT (cleanup fn) - BUG-9: Wire iframe blur/focus listeners for stuck-mic prevention PiP bug fix (BUG-11): - Track prevPipMode ref so position only resets when first entering pip mode, not on every callVisible change (user drag position preserved) GIF improvements (BUG-15): - Show user-visible error text in toolbar on GIF send failure - Also surface size-limit rejection with a 4-second auto-clearing message Performance: - useInterval: replace useMemo with useEffect for setInterval (React StrictMode safe) - usePan: add unmount cleanup effect to remove document mousemove/mouseup listeners Feature: timeline state event renderers (low effort, high value): - Added renderers for RoomEncryption, RoomJoinRules, RoomGuestAccess, RoomCanonicalAlias - These were silently falling through to the hidden-events fallback TDS (Lotus Terminal Design System): - Fix: correct 8 escaped template literals in GIF picker light-mode block - Add data-emoji-board attribute to EmojiBoardLayout for stable CSS targeting - Add Tooltip panel styles (dark + light mode) - Add Switch toggle styles (dark + light mode) - Add Spinner stroke colors (dark + light mode) - Add EmojiBoard panel styles (dark + light mode) - Add PopOut/Menu/floating panel styles (dark + light mode)
This commit is contained in:
@@ -415,20 +415,26 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
|
|||||||
const activeDragCleanupRef = React.useRef<(() => void) | null>(null);
|
const activeDragCleanupRef = React.useRef<(() => void) | null>(null);
|
||||||
React.useEffect(() => () => { activeDragCleanupRef.current?.(); }, []);
|
React.useEffect(() => () => { activeDragCleanupRef.current?.(); }, []);
|
||||||
|
|
||||||
|
// Track previous pipMode to only reset position when first entering pip (not on callVisible changes)
|
||||||
|
const prevPipModeRef = React.useRef(false);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const el = callEmbedRef.current;
|
const el = callEmbedRef.current;
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
if (pipMode) {
|
if (pipMode) {
|
||||||
el.style.top = 'auto'; el.style.left = 'auto';
|
if (!prevPipModeRef.current) {
|
||||||
el.style.bottom = '72px'; el.style.right = '16px';
|
el.style.top = 'auto'; el.style.left = 'auto';
|
||||||
el.style.width = '280px'; el.style.height = '158px';
|
el.style.bottom = '72px'; el.style.right = '16px';
|
||||||
el.style.borderRadius = '12px'; el.style.overflow = 'hidden';
|
el.style.width = '280px'; el.style.height = '158px';
|
||||||
el.style.zIndex = '99'; el.style.boxShadow = '0 8px 32px rgba(0,0,0,0.55)';
|
el.style.borderRadius = '12px'; el.style.overflow = 'hidden';
|
||||||
el.style.border = '1px solid rgba(255,255,255,0.1)'; el.style.visibility = 'visible';
|
el.style.zIndex = '99'; el.style.boxShadow = '0 8px 32px rgba(0,0,0,0.55)';
|
||||||
|
el.style.border = '1px solid rgba(255,255,255,0.1)';
|
||||||
|
}
|
||||||
|
el.style.visibility = 'visible';
|
||||||
} else {
|
} else {
|
||||||
['top','left','bottom','right','width','height','borderRadius','overflow','zIndex','boxShadow','border'].forEach(p => { (el.style as any)[p] = ''; });
|
['top','left','bottom','right','width','height','borderRadius','overflow','zIndex','boxShadow','border'].forEach(p => { (el.style as any)[p] = ''; });
|
||||||
el.style.visibility = callVisible ? '' : 'hidden';
|
el.style.visibility = callVisible ? '' : 'hidden';
|
||||||
}
|
}
|
||||||
|
prevPipModeRef.current = pipMode;
|
||||||
}, [pipMode, callVisible]);
|
}, [pipMode, callVisible]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ import {
|
|||||||
} from './components';
|
} from './components';
|
||||||
import { EmojiBoardTab, EmojiType } from './types';
|
import { EmojiBoardTab, EmojiType } from './types';
|
||||||
import { VirtualTile } from '../virtualizer';
|
import { VirtualTile } from '../virtualizer';
|
||||||
|
import { useSetting } from '../../state/hooks/settings';
|
||||||
|
import { settingsAtom } from '../../state/settings';
|
||||||
|
|
||||||
const RECENT_GROUP_ID = 'recent_group';
|
const RECENT_GROUP_ID = 'recent_group';
|
||||||
const SEARCH_GROUP_ID = 'search_group';
|
const SEARCH_GROUP_ID = 'search_group';
|
||||||
@@ -503,6 +505,7 @@ export function EmojiBoard({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<EmojiBoardLayout
|
<EmojiBoardLayout
|
||||||
|
data-emoji-board=""
|
||||||
header={
|
header={
|
||||||
<Box direction="Column" gap="200">
|
<Box direction="Column" gap="200">
|
||||||
{onTabChange && <EmojiBoardTabs tab={tab} onTabChange={onTabChange} />}
|
{onTabChange && <EmojiBoardTabs tab={tab} onTabChange={onTabChange} />}
|
||||||
|
|||||||
@@ -97,20 +97,21 @@ export function CallControls({ callEmbed }: CallControlsProps) {
|
|||||||
setCords(undefined);
|
setCords(undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const pttActiveRef = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!pttMode) return;
|
if (!pttMode) return;
|
||||||
const iframeWindow = callEmbed.iframe.contentWindow;
|
const iframeWindow = callEmbed.iframe.contentWindow;
|
||||||
|
|
||||||
const onKeyDown = (e: KeyboardEvent) => {
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
if (e.code !== pttKey || e.repeat) return;
|
if (e.code !== pttKey || e.repeat) return;
|
||||||
// Don't intercept keys typed into a text input or editable element
|
|
||||||
const target = e.target as HTMLElement;
|
const target = e.target as HTMLElement;
|
||||||
// Skip PTT if key is pressed inside any text-input or editable surface
|
// BUG-7: use ownerDocument.body so isEditable works inside the EC iframe
|
||||||
const isEditable = (el: HTMLElement): boolean => {
|
const isEditable = (el: HTMLElement): boolean => {
|
||||||
const tag = el.tagName;
|
const tag = el.tagName;
|
||||||
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return true;
|
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return true;
|
||||||
let node: HTMLElement | null = el;
|
let node: HTMLElement | null = el;
|
||||||
while (node && node !== document.body) {
|
while (node && node !== el.ownerDocument.body) {
|
||||||
if (node.contentEditable === 'true') return true;
|
if (node.contentEditable === 'true') return true;
|
||||||
if (node.contentEditable === 'false') return false;
|
if (node.contentEditable === 'false') return false;
|
||||||
node = node.parentElement;
|
node = node.parentElement;
|
||||||
@@ -120,29 +121,34 @@ export function CallControls({ callEmbed }: CallControlsProps) {
|
|||||||
if (isEditable(target)) return;
|
if (isEditable(target)) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!microphoneRef.current) callEmbed.control.setMicrophone(true);
|
if (!microphoneRef.current) callEmbed.control.setMicrophone(true);
|
||||||
|
pttActiveRef.current = true;
|
||||||
setPttActive(true);
|
setPttActive(true);
|
||||||
};
|
};
|
||||||
const onKeyUp = (e: KeyboardEvent) => {
|
const onKeyUp = (e: KeyboardEvent) => {
|
||||||
if (e.code !== pttKey) return;
|
if (e.code !== pttKey) return;
|
||||||
callEmbed.control.setMicrophone(false);
|
callEmbed.control.setMicrophone(false);
|
||||||
|
pttActiveRef.current = false;
|
||||||
setPttActive(false);
|
setPttActive(false);
|
||||||
};
|
};
|
||||||
// Release PTT when the tab loses focus to prevent stuck-on mic
|
|
||||||
const onBlur = () => {
|
const onBlur = () => {
|
||||||
callEmbed.control.setMicrophone(false);
|
callEmbed.control.setMicrophone(false);
|
||||||
|
pttActiveRef.current = false;
|
||||||
setPttActive(false);
|
setPttActive(false);
|
||||||
};
|
};
|
||||||
// Re-mute on focus restore: EC can re-assert audio_enabled:true on audio-context resume
|
|
||||||
const onFocus = () => {
|
const onFocus = () => {
|
||||||
callEmbed.control.setMicrophone(false);
|
callEmbed.control.setMicrophone(false);
|
||||||
|
pttActiveRef.current = false;
|
||||||
setPttActive(false);
|
setPttActive(false);
|
||||||
};
|
};
|
||||||
window.addEventListener('keydown', onKeyDown);
|
window.addEventListener('keydown', onKeyDown);
|
||||||
window.addEventListener('keyup', onKeyUp);
|
window.addEventListener('keyup', onKeyUp);
|
||||||
window.addEventListener('blur', onBlur);
|
window.addEventListener('blur', onBlur);
|
||||||
window.addEventListener('focus', onFocus);
|
window.addEventListener('focus', onFocus);
|
||||||
|
// BUG-9: also wire iframe blur/focus so stuck-mic release works when focus moves to iframe
|
||||||
iframeWindow?.addEventListener('keydown', onKeyDown);
|
iframeWindow?.addEventListener('keydown', onKeyDown);
|
||||||
iframeWindow?.addEventListener('keyup', onKeyUp);
|
iframeWindow?.addEventListener('keyup', onKeyUp);
|
||||||
|
iframeWindow?.addEventListener('blur', onBlur);
|
||||||
|
iframeWindow?.addEventListener('focus', onFocus);
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('keydown', onKeyDown);
|
window.removeEventListener('keydown', onKeyDown);
|
||||||
window.removeEventListener('keyup', onKeyUp);
|
window.removeEventListener('keyup', onKeyUp);
|
||||||
@@ -150,6 +156,14 @@ export function CallControls({ callEmbed }: CallControlsProps) {
|
|||||||
window.removeEventListener('focus', onFocus);
|
window.removeEventListener('focus', onFocus);
|
||||||
iframeWindow?.removeEventListener('keydown', onKeyDown);
|
iframeWindow?.removeEventListener('keydown', onKeyDown);
|
||||||
iframeWindow?.removeEventListener('keyup', onKeyUp);
|
iframeWindow?.removeEventListener('keyup', onKeyUp);
|
||||||
|
iframeWindow?.removeEventListener('blur', onBlur);
|
||||||
|
iframeWindow?.removeEventListener('focus', onFocus);
|
||||||
|
// BUG-8: if callEmbed changes while PTT is active, release mic on cleanup
|
||||||
|
if (pttActiveRef.current) {
|
||||||
|
callEmbed.control.setMicrophone(false);
|
||||||
|
pttActiveRef.current = false;
|
||||||
|
setPttActive(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// microphone intentionally read via microphoneRef — excluded from deps to avoid listener churn
|
// microphone intentionally read via microphoneRef — excluded from deps to avoid listener churn
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
|||||||
@@ -241,6 +241,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
const { gifApiKey } = useClientConfig();
|
const { gifApiKey } = useClientConfig();
|
||||||
const gifBtnRef = useRef<HTMLButtonElement>(null);
|
const gifBtnRef = useRef<HTMLButtonElement>(null);
|
||||||
const [hideStickerBtn, setHideStickerBtn] = useState(document.body.clientWidth < 500);
|
const [hideStickerBtn, setHideStickerBtn] = useState(document.body.clientWidth < 500);
|
||||||
|
const [gifError, setGifError] = React.useState<string | null>(null);
|
||||||
|
|
||||||
const isComposing = useComposingCheck();
|
const isComposing = useComposingCheck();
|
||||||
|
|
||||||
@@ -469,7 +470,11 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
if (!contentType.startsWith('image/')) return;
|
if (!contentType.startsWith('image/')) return;
|
||||||
|
|
||||||
const blob = await res.blob();
|
const blob = await res.blob();
|
||||||
if (blob.size > 20 * 1024 * 1024) return; // 20 MB cap
|
if (blob.size > 20 * 1024 * 1024) {
|
||||||
|
setGifError('GIF is too large (max 20 MB).');
|
||||||
|
setTimeout(() => setGifError(null), 4000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const uploadRes = await mx.uploadContent(
|
const uploadRes = await mx.uploadContent(
|
||||||
new File([blob], 'image.gif', { type: 'image/gif' }),
|
new File([blob], 'image.gif', { type: 'image/gif' }),
|
||||||
@@ -485,6 +490,8 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('GIF send failed', e);
|
console.error('GIF send failed', e);
|
||||||
|
setGifError('Failed to send GIF. Please try again.');
|
||||||
|
setTimeout(() => setGifError(null), 4000);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[mx, roomId]
|
[mx, roomId]
|
||||||
@@ -774,6 +781,11 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
)}
|
)}
|
||||||
</UseStateProvider>
|
</UseStateProvider>
|
||||||
)}
|
)}
|
||||||
|
{gifError && (
|
||||||
|
<Text size="T100" style={{ color: 'var(--tc-danger-normal)', padding: '2px 6px', alignSelf: 'center', whiteSpace: 'nowrap' }}>
|
||||||
|
{gifError}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={handleShareLocation}
|
onClick={handleShareLocation}
|
||||||
variant="SurfaceVariant"
|
variant="SurfaceVariant"
|
||||||
|
|||||||
@@ -1586,6 +1586,70 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
|||||||
</Event>
|
</Event>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
[StateEvent.RoomEncryption]: (mEventId, mEvent, item) => {
|
||||||
|
const highlighted = focusItem?.index === item && focusItem.highlight;
|
||||||
|
const senderId = mEvent.getSender() ?? '';
|
||||||
|
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
||||||
|
const timeJSX = (
|
||||||
|
<Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} hour24Clock={hour24Clock} dateFormatString={dateFormatString} />
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Event key={mEvent.getId()} data-message-item={item} data-message-id={mEventId} room={room} mEvent={mEvent} highlight={highlighted} messageSpacing={messageSpacing} canDelete={canRedact || mEvent.getSender() === mx.getUserId()} hideReadReceipts={hideActivity} showDeveloperTools={showDeveloperTools}>
|
||||||
|
<EventContent messageLayout={messageLayout} time={timeJSX} iconSrc={Icons.Lock}
|
||||||
|
content={<Box grow="Yes" direction="Column"><Text size="T300" priority="300"><b>{senderName}</b>{' enabled end-to-end encryption'}</Text></Box>}
|
||||||
|
/>
|
||||||
|
</Event>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[StateEvent.RoomJoinRules]: (mEventId, mEvent, item) => {
|
||||||
|
const highlighted = focusItem?.index === item && focusItem.highlight;
|
||||||
|
const senderId = mEvent.getSender() ?? '';
|
||||||
|
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
||||||
|
const joinRule = mEvent.getContent<{ join_rule?: string }>().join_rule ?? 'unknown';
|
||||||
|
const ruleLabel: Record<string, string> = { public: 'public', invite: 'invite-only', knock: 'knock', restricted: 'restricted' };
|
||||||
|
const timeJSX = (
|
||||||
|
<Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} hour24Clock={hour24Clock} dateFormatString={dateFormatString} />
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Event key={mEvent.getId()} data-message-item={item} data-message-id={mEventId} room={room} mEvent={mEvent} highlight={highlighted} messageSpacing={messageSpacing} canDelete={canRedact || mEvent.getSender() === mx.getUserId()} hideReadReceipts={hideActivity} showDeveloperTools={showDeveloperTools}>
|
||||||
|
<EventContent messageLayout={messageLayout} time={timeJSX} iconSrc={Icons.Settings}
|
||||||
|
content={<Box grow="Yes" direction="Column"><Text size="T300" priority="300"><b>{senderName}</b>{` set room join rule to ${ruleLabel[joinRule] ?? joinRule}`}</Text></Box>}
|
||||||
|
/>
|
||||||
|
</Event>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[StateEvent.RoomGuestAccess]: (mEventId, mEvent, item) => {
|
||||||
|
const highlighted = focusItem?.index === item && focusItem.highlight;
|
||||||
|
const senderId = mEvent.getSender() ?? '';
|
||||||
|
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
||||||
|
const access = mEvent.getContent<{ guest_access?: string }>().guest_access ?? 'unknown';
|
||||||
|
const timeJSX = (
|
||||||
|
<Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} hour24Clock={hour24Clock} dateFormatString={dateFormatString} />
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Event key={mEvent.getId()} data-message-item={item} data-message-id={mEventId} room={room} mEvent={mEvent} highlight={highlighted} messageSpacing={messageSpacing} canDelete={canRedact || mEvent.getSender() === mx.getUserId()} hideReadReceipts={hideActivity} showDeveloperTools={showDeveloperTools}>
|
||||||
|
<EventContent messageLayout={messageLayout} time={timeJSX} iconSrc={Icons.Settings}
|
||||||
|
content={<Box grow="Yes" direction="Column"><Text size="T300" priority="300"><b>{senderName}</b>{access === 'can_join' ? ' allowed guest access' : ' disabled guest access'}</Text></Box>}
|
||||||
|
/>
|
||||||
|
</Event>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[StateEvent.RoomCanonicalAlias]: (mEventId, mEvent, item) => {
|
||||||
|
const highlighted = focusItem?.index === item && focusItem.highlight;
|
||||||
|
const senderId = mEvent.getSender() ?? '';
|
||||||
|
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
||||||
|
const alias = mEvent.getContent<{ alias?: string }>().alias;
|
||||||
|
const timeJSX = (
|
||||||
|
<Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} hour24Clock={hour24Clock} dateFormatString={dateFormatString} />
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Event key={mEvent.getId()} data-message-item={item} data-message-id={mEventId} room={room} mEvent={mEvent} highlight={highlighted} messageSpacing={messageSpacing} canDelete={canRedact || mEvent.getSender() === mx.getUserId()} hideReadReceipts={hideActivity} showDeveloperTools={showDeveloperTools}>
|
||||||
|
<EventContent messageLayout={messageLayout} time={timeJSX} iconSrc={Icons.Hash}
|
||||||
|
content={<Box grow="Yes" direction="Column"><Text size="T300" priority="300"><b>{senderName}</b>{alias ? ` set room address to ${alias}` : ' removed room address'}</Text></Box>}
|
||||||
|
/>
|
||||||
|
</Event>
|
||||||
|
);
|
||||||
|
},
|
||||||
[StateEvent.GroupCallMemberPrefix]: (mEventId, mEvent, item) => {
|
[StateEvent.GroupCallMemberPrefix]: (mEventId, mEvent, item) => {
|
||||||
const highlighted = focusItem?.index === item && focusItem.highlight;
|
const highlighted = focusItem?.index === item && focusItem.highlight;
|
||||||
const senderId = mEvent.getSender() ?? '';
|
const senderId = mEvent.getSender() ?? '';
|
||||||
|
|||||||
@@ -1,24 +1,11 @@
|
|||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
export type IntervalCallback = () => void;
|
export type IntervalCallback = () => void;
|
||||||
|
|
||||||
/**
|
export const useInterval = (callback: IntervalCallback, ms: number): void => {
|
||||||
* @param callback interval callback.
|
useEffect(() => {
|
||||||
* @param ms interval time in milliseconds. negative value will stop the interval.
|
|
||||||
* @returns interval id or undefined if not running.
|
|
||||||
*/
|
|
||||||
export const useInterval = (callback: IntervalCallback, ms: number): number | undefined => {
|
|
||||||
const id = useMemo(() => {
|
|
||||||
if (ms < 0) return undefined;
|
if (ms < 0) return undefined;
|
||||||
return window.setInterval(callback, ms);
|
const id = window.setInterval(callback, ms);
|
||||||
|
return () => window.clearInterval(id);
|
||||||
}, [callback, ms]);
|
}, [callback, ms]);
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => () => {
|
|
||||||
window.clearInterval(id);
|
|
||||||
},
|
|
||||||
[id]
|
|
||||||
);
|
|
||||||
|
|
||||||
return id;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,6 +54,13 @@ export const usePan = (active: boolean) => {
|
|||||||
if (!active) setPan(INITIAL_PAN);
|
if (!active) setPan(INITIAL_PAN);
|
||||||
}, [active]);
|
}, [active]);
|
||||||
|
|
||||||
|
// Clean up document listeners if component unmounts during an active drag
|
||||||
|
useEffect(() => () => {
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
document.removeEventListener('mouseup', handleMouseUp);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pan,
|
pan,
|
||||||
cursor,
|
cursor,
|
||||||
|
|||||||
+118
-8
@@ -688,8 +688,8 @@ globalStyle(`html[data-theme="light"] body.${lotusTerminalBodyClass} [data-url-p
|
|||||||
|
|
||||||
// ── GIF picker light TDS (dark-mode rules already exist via [data-gif-terminal]) ──
|
// ── GIF picker light TDS (dark-mode rules already exist via [data-gif-terminal]) ──
|
||||||
globalStyle(
|
globalStyle(
|
||||||
`html[data-theme="light"] body.\${lotusTerminalBodyClass} [data-gif-terminal] input,` +
|
`html[data-theme="light"] body.${lotusTerminalBodyClass} [data-gif-terminal] input,` +
|
||||||
`html[data-theme="light"] body.\${lotusTerminalBodyClass} [data-gif-terminal] form`, {
|
`html[data-theme="light"] body.${lotusTerminalBodyClass} [data-gif-terminal] form`, {
|
||||||
background: '#f4f6fa !important' as any,
|
background: '#f4f6fa !important' as any,
|
||||||
color: '#111827 !important' as any,
|
color: '#111827 !important' as any,
|
||||||
border: '1px solid rgba(196,78,0,0.28) !important' as any,
|
border: '1px solid rgba(196,78,0,0.28) !important' as any,
|
||||||
@@ -697,23 +697,133 @@ globalStyle(
|
|||||||
fontSize: '12px !important' as any,
|
fontSize: '12px !important' as any,
|
||||||
boxShadow: 'none !important' as any,
|
boxShadow: 'none !important' as any,
|
||||||
});
|
});
|
||||||
globalStyle(`html[data-theme="light"] body.\${lotusTerminalBodyClass} [data-gif-terminal] input:focus`, {
|
globalStyle(`html[data-theme="light"] body.${lotusTerminalBodyClass} [data-gif-terminal] input:focus`, {
|
||||||
borderColor: 'rgba(196,78,0,0.60) !important' as any,
|
borderColor: 'rgba(196,78,0,0.60) !important' as any,
|
||||||
boxShadow: '0 0 0 2px rgba(196,78,0,0.12) !important' as any,
|
boxShadow: '0 0 0 2px rgba(196,78,0,0.12) !important' as any,
|
||||||
outline: 'none !important' as any,
|
outline: 'none !important' as any,
|
||||||
});
|
});
|
||||||
globalStyle(`html[data-theme="light"] body.\${lotusTerminalBodyClass} [data-gif-terminal] input::placeholder`, {
|
globalStyle(`html[data-theme="light"] body.${lotusTerminalBodyClass} [data-gif-terminal] input::placeholder`, {
|
||||||
color: 'rgba(196,78,0,0.45) !important' as any,
|
color: 'rgba(196,78,0,0.45) !important' as any,
|
||||||
});
|
});
|
||||||
globalStyle(`html[data-theme="light"] body.\${lotusTerminalBodyClass} [data-gif-terminal] svg,` +
|
globalStyle(`html[data-theme="light"] body.${lotusTerminalBodyClass} [data-gif-terminal] svg,` +
|
||||||
`html[data-theme="light"] body.\${lotusTerminalBodyClass} [data-gif-terminal] button[type="reset"]`, {
|
`html[data-theme="light"] body.${lotusTerminalBodyClass} [data-gif-terminal] button[type="reset"]`, {
|
||||||
display: 'none !important' as any,
|
display: 'none !important' as any,
|
||||||
});
|
});
|
||||||
globalStyle(`html[data-theme="light"] body.\${lotusTerminalBodyClass} [data-gif-terminal] ::-webkit-scrollbar-track`, {
|
globalStyle(`html[data-theme="light"] body.${lotusTerminalBodyClass} [data-gif-terminal] ::-webkit-scrollbar-track`, {
|
||||||
background: '#e2e7ef',
|
background: '#e2e7ef',
|
||||||
});
|
});
|
||||||
globalStyle(`html[data-theme="light"] body.\${lotusTerminalBodyClass} [data-gif-terminal] ::-webkit-scrollbar-thumb`, {
|
globalStyle(`html[data-theme="light"] body.${lotusTerminalBodyClass} [data-gif-terminal] ::-webkit-scrollbar-thumb`, {
|
||||||
background: 'rgba(196,78,0,0.35)',
|
background: 'rgba(196,78,0,0.35)',
|
||||||
borderRadius: '2px',
|
borderRadius: '2px',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Tooltip TDS ──────────────────────────────────────────────────────────────
|
||||||
|
globalStyle(`body.${lotusTerminalBodyClass} ._6plmi2g > div`, {
|
||||||
|
background: '#060c14 !important' as any,
|
||||||
|
border: '1px solid rgba(0,212,255,0.30) !important' as any,
|
||||||
|
color: '#b0cbe8 !important' as any,
|
||||||
|
fontFamily: "'JetBrains Mono', 'Cascadia Code', 'Fira Code', monospace !important" as any,
|
||||||
|
fontSize: '11px !important' as any,
|
||||||
|
borderRadius: '4px !important' as any,
|
||||||
|
boxShadow: '0 4px 12px rgba(0,0,0,0.7), 0 0 0 1px rgba(0,212,255,0.08) !important' as any,
|
||||||
|
});
|
||||||
|
globalStyle(`html[data-theme="light"] body.${lotusTerminalBodyClass} ._6plmi2g > div`, {
|
||||||
|
background: '#f0f4fc !important' as any,
|
||||||
|
border: '1px solid rgba(0,98,184,0.30) !important' as any,
|
||||||
|
color: '#2d3d56 !important' as any,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Switch toggle TDS ─────────────────────────────────────────────────────────
|
||||||
|
globalStyle(`body.${lotusTerminalBodyClass} [role="switch"]`, {
|
||||||
|
background: 'rgba(0,212,255,0.12) !important' as any,
|
||||||
|
border: '1px solid rgba(0,212,255,0.30) !important' as any,
|
||||||
|
borderRadius: '99px !important' as any,
|
||||||
|
transition: 'background 0.2s, border-color 0.2s',
|
||||||
|
});
|
||||||
|
globalStyle(`body.${lotusTerminalBodyClass} [role="switch"][aria-checked="true"]`, {
|
||||||
|
background: 'rgba(255,107,0,0.22) !important' as any,
|
||||||
|
borderColor: 'rgba(255,107,0,0.60) !important' as any,
|
||||||
|
boxShadow: '0 0 8px rgba(255,107,0,0.20) !important' as any,
|
||||||
|
});
|
||||||
|
globalStyle(`html[data-theme="light"] body.${lotusTerminalBodyClass} [role="switch"]`, {
|
||||||
|
background: 'rgba(0,98,184,0.10) !important' as any,
|
||||||
|
borderColor: 'rgba(0,98,184,0.35) !important' as any,
|
||||||
|
});
|
||||||
|
globalStyle(`html[data-theme="light"] body.${lotusTerminalBodyClass} [role="switch"][aria-checked="true"]`, {
|
||||||
|
background: 'rgba(196,78,0,0.18) !important' as any,
|
||||||
|
borderColor: 'rgba(196,78,0,0.55) !important' as any,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Spinner TDS ───────────────────────────────────────────────────────────────
|
||||||
|
globalStyle(`body.${lotusTerminalBodyClass} ._31czpko`, {
|
||||||
|
stroke: 'rgba(0,212,255,0.20) !important' as any,
|
||||||
|
});
|
||||||
|
globalStyle(`body.${lotusTerminalBodyClass} ._31czpkp`, {
|
||||||
|
stroke: 'rgba(255,107,0,0.85) !important' as any,
|
||||||
|
});
|
||||||
|
globalStyle(`html[data-theme="light"] body.${lotusTerminalBodyClass} ._31czpko`, {
|
||||||
|
stroke: 'rgba(0,98,184,0.20) !important' as any,
|
||||||
|
});
|
||||||
|
globalStyle(`html[data-theme="light"] body.${lotusTerminalBodyClass} ._31czpkp`, {
|
||||||
|
stroke: 'rgba(196,78,0,0.85) !important' as any,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── EmojiBoard TDS ────────────────────────────────────────────────────────────
|
||||||
|
globalStyle(`body.${lotusTerminalBodyClass} [data-emoji-board]`, {
|
||||||
|
background: '#060c14 !important' as any,
|
||||||
|
border: '1px solid rgba(0,212,255,0.20) !important' as any,
|
||||||
|
borderRadius: '4px !important' as any,
|
||||||
|
boxShadow: '0 8px 32px rgba(0,0,0,0.75), 0 0 0 1px rgba(0,212,255,0.08) !important' as any,
|
||||||
|
color: '#b0cbe8 !important' as any,
|
||||||
|
});
|
||||||
|
globalStyle(`body.${lotusTerminalBodyClass} [data-emoji-board] input`, {
|
||||||
|
background: 'rgba(0,212,255,0.06) !important' as any,
|
||||||
|
border: '1px solid rgba(0,212,255,0.22) !important' as any,
|
||||||
|
color: '#b0cbe8 !important' as any,
|
||||||
|
fontFamily: "'JetBrains Mono', 'Cascadia Code', 'Fira Code', monospace !important" as any,
|
||||||
|
borderRadius: '4px !important' as any,
|
||||||
|
});
|
||||||
|
globalStyle(`body.${lotusTerminalBodyClass} [data-emoji-board] input:focus`, {
|
||||||
|
borderColor: 'rgba(255,107,0,0.60) !important' as any,
|
||||||
|
boxShadow: '0 0 0 2px rgba(255,107,0,0.12) !important' as any,
|
||||||
|
outline: 'none !important' as any,
|
||||||
|
});
|
||||||
|
globalStyle(`html[data-theme="light"] body.${lotusTerminalBodyClass} [data-emoji-board]`, {
|
||||||
|
background: '#f0f4fc !important' as any,
|
||||||
|
border: '1px solid rgba(0,98,184,0.22) !important' as any,
|
||||||
|
color: '#2d3d56 !important' as any,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── PopOut / Menu / floating panels TDS ──────────────────────────────────────
|
||||||
|
globalStyle(`body.${lotusTerminalBodyClass} ._1v6ozra1`, {
|
||||||
|
background: '#060c14 !important' as any,
|
||||||
|
border: '1px solid rgba(0,212,255,0.22) !important' as any,
|
||||||
|
borderRadius: '6px !important' as any,
|
||||||
|
boxShadow: '0 8px 32px rgba(0,0,0,0.80), 0 0 0 1px rgba(0,212,255,0.06) !important' as any,
|
||||||
|
color: '#b0cbe8 !important' as any,
|
||||||
|
});
|
||||||
|
globalStyle(`body.${lotusTerminalBodyClass} ._1fptcza0`, {
|
||||||
|
background: 'transparent !important' as any,
|
||||||
|
});
|
||||||
|
globalStyle(`body.${lotusTerminalBodyClass} ._13tt0gb6`, {
|
||||||
|
color: '#b0cbe8 !important' as any,
|
||||||
|
borderRadius: '3px !important' as any,
|
||||||
|
});
|
||||||
|
globalStyle(`body.${lotusTerminalBodyClass} ._13tt0gb6:hover`, {
|
||||||
|
background: 'rgba(0,212,255,0.08) !important' as any,
|
||||||
|
color: '#00d4ff !important' as any,
|
||||||
|
});
|
||||||
|
globalStyle(`html[data-theme="light"] body.${lotusTerminalBodyClass} ._1v6ozra1`, {
|
||||||
|
background: '#f0f4fc !important' as any,
|
||||||
|
border: '1px solid rgba(0,98,184,0.22) !important' as any,
|
||||||
|
boxShadow: '0 8px 32px rgba(0,0,0,0.15) !important' as any,
|
||||||
|
color: '#2d3d56 !important' as any,
|
||||||
|
});
|
||||||
|
globalStyle(`html[data-theme="light"] body.${lotusTerminalBodyClass} ._13tt0gb6`, {
|
||||||
|
color: '#2d3d56 !important' as any,
|
||||||
|
});
|
||||||
|
globalStyle(`html[data-theme="light"] body.${lotusTerminalBodyClass} ._13tt0gb6:hover`, {
|
||||||
|
background: 'rgba(0,98,184,0.08) !important' as any,
|
||||||
|
color: '#0062b8 !important' as any,
|
||||||
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user