refactor: widget theme color
This commit is contained in:
@@ -342,4 +342,38 @@ export const compareVersion = (v1: string, v2: string, options?: { lexicographic
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Get the contrasting color for any hex color
|
||||
* (c) 2021 Chris Ferdinandi, MIT License, https://gomakethings.com
|
||||
* Derived from work by Brian Suda, https://24ways.org/2010/calculating-color-contrast/
|
||||
* @param {String} A hexcolor value
|
||||
* @return {String} The contrasting color (black or white)
|
||||
*/
|
||||
export const getContrastColor = (hexcolor: string) => {
|
||||
|
||||
// If a leading # is provided, remove it
|
||||
if (hexcolor.slice(0, 1) === '#') {
|
||||
hexcolor = hexcolor.slice(1);
|
||||
}
|
||||
|
||||
// If a three-character hexcode, make six-character
|
||||
if (hexcolor.length === 3) {
|
||||
hexcolor = hexcolor.split('').map(function (hex) {
|
||||
return hex + hex;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// Convert to RGB value
|
||||
let r = parseInt(hexcolor.substr(0, 2), 16);
|
||||
let g = parseInt(hexcolor.substr(2, 2), 16);
|
||||
let b = parseInt(hexcolor.substr(4, 2), 16);
|
||||
|
||||
// Get YIQ ratio
|
||||
let yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
|
||||
|
||||
// Check contrast
|
||||
return (yiq >= 128) ? 'black' : 'white';
|
||||
|
||||
};
|
||||
@@ -1,25 +1,26 @@
|
||||
import { FC } from 'react';
|
||||
import { useAppSelector } from '../../app/store';
|
||||
import { useTheme } from '../ThemeContext';
|
||||
import IconClose from './close.svg';
|
||||
|
||||
type Props = {
|
||||
handleClose: () => void;
|
||||
themeColor?: string
|
||||
};
|
||||
|
||||
const Index: FC<Props> = ({ handleClose, themeColor = "#1fe1f9" }) => {
|
||||
const Index: FC<Props> = ({ handleClose }) => {
|
||||
const { color, fgColor } = useTheme();
|
||||
const { name, logo } = useAppSelector(store => store.server);
|
||||
return (
|
||||
<header className="relative flex justify-between items-center h-14 px-4" style={{ backgroundColor: themeColor }}>
|
||||
<header className="relative flex justify-between items-center h-14 px-4" style={{ backgroundColor: color }}>
|
||||
<div className="relative w-8 h-8">
|
||||
<img src={logo} alt="logo" className="w-full h-full rounded-full" />
|
||||
</div>
|
||||
<div className="flex-1 px-4 pr-2 text-lg">
|
||||
<span className="text-lg font-bold truncate text-gray-50">{name}</span>
|
||||
<span className="text-lg font-bold truncate text-gray-50" style={{ color: fgColor }}>{name}</span>
|
||||
</div>
|
||||
<button type='button' className='w-6 h-6'>
|
||||
|
||||
<IconClose onClick={handleClose} />
|
||||
<IconClose onClick={handleClose} className={fgColor == "black" ? `fill-black` : "fill-white"} />
|
||||
</button>
|
||||
</header>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { useTheme } from '../../ThemeContext';
|
||||
|
||||
type Props = {
|
||||
uid: number,
|
||||
@@ -9,13 +10,14 @@ type Props = {
|
||||
}
|
||||
|
||||
const Text = ({ content, host, sending }: Props) => {
|
||||
const { color, fgColor } = useTheme();
|
||||
|
||||
return host ?
|
||||
<div className="text-md text-gray-900 bg-gray-100 rounded-lg px-3 py-1.5 break-words" style={{ maxWidth: 'min(((100vw - 56px) - 20px) - 64px, 360px)' }}>
|
||||
{content}
|
||||
</div>
|
||||
:
|
||||
<div className={clsx("text-md text-white bg-[#1fe1f9] rounded-lg px-3 py-1.5 transition-all break-words", sending ? 'opacity-70' : "")} style={{ maxWidth: 'min(((100vw - 56px) - 20px) - 64px, 360px)' }}>
|
||||
<div className={clsx("text-md text-white bg-[#1fe1f9] rounded-lg px-3 py-1.5 transition-all break-words", sending ? 'opacity-70' : "")} style={{ maxWidth: 'min(((100vw - 56px) - 20px) - 64px, 360px)', backgroundColor: color, color: fgColor }}>
|
||||
{content}
|
||||
</div>
|
||||
;
|
||||
|
||||
@@ -17,7 +17,10 @@ const Index = ({ needLogin = false }: Props) => {
|
||||
<div className="pl-14">
|
||||
<div className="rounded-lg" style={{ maxWidth: 'min(((100vw - 56px) - 20px) - 64px, 360px)' }}>
|
||||
<div className="text-md text-gray-900 px-3 py-1.5 bg-gray-100 rounded-lg mb-1.5">
|
||||
👋 Hi there, Nice to meet you! Please login before we have a nice talk 👇
|
||||
{needLogin ?
|
||||
`👋 Hi there, Nice to meet you! Please login before we have a nice talk 👇`
|
||||
:
|
||||
`👋 Hi there, Nice to meet you! `}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,11 +9,10 @@ import useSSE from '../useSSE';
|
||||
|
||||
type Props = {
|
||||
hostId: number,
|
||||
themeColor?: string,
|
||||
handleClose: () => void
|
||||
}
|
||||
|
||||
const Index = ({ handleClose, hostId, themeColor }: Props) => {
|
||||
const Index = ({ handleClose, hostId }: Props) => {
|
||||
// 建立SSE连接
|
||||
useSSE();
|
||||
const { user: loginUser, token, guest: isGuest } = useAppSelector(store => store.authData);
|
||||
@@ -22,7 +21,7 @@ const Index = ({ handleClose, hostId, themeColor }: Props) => {
|
||||
const notLogin = !token || isGuest;
|
||||
return (
|
||||
<aside className="flex flex-col bg-white w-full h-full rounded-md overflow-hidden">
|
||||
<Header themeColor={themeColor} handleClose={handleClose} />
|
||||
<Header handleClose={handleClose} />
|
||||
{/* message list */}
|
||||
<main id='MESSAGE_LIST_CONTAINER' className="relative flex-1 overflow-y-auto scroll-smooth">
|
||||
{/* placeholder */}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { createContext, useContext, ReactNode } from 'react';
|
||||
import { getContrastColor } from '../common/utils';
|
||||
|
||||
const color = decodeURIComponent(new URLSearchParams(location.search).get("themeColor") || "#1fe1f9");
|
||||
const fgColor = getContrastColor(color);
|
||||
const ThemeContext = createContext({ color, fgColor });
|
||||
|
||||
|
||||
function ThemeProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
return <ThemeContext.Provider value={{ color, fgColor }} >{children}</ThemeContext.Provider>;
|
||||
}
|
||||
|
||||
function useTheme() {
|
||||
const context = useContext(ThemeContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useTheme must be used within a ThemeProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
export { ThemeProvider, useTheme };
|
||||
@@ -1,15 +1,14 @@
|
||||
import { useState } from "react";
|
||||
import { useGetServerQuery } from "../app/services/server";
|
||||
|
||||
import { ThemeProvider } from "./ThemeContext";
|
||||
import Icon from "./Icon";
|
||||
import Popup from "./Popup";
|
||||
import useCache from "./useCache";
|
||||
type Props = {
|
||||
hostId: number,
|
||||
themeColor?: string
|
||||
};
|
||||
|
||||
function Widget({ hostId, themeColor }: Props) {
|
||||
function Widget({ hostId }: Props) {
|
||||
const { rehydrated } = useCache();
|
||||
const [visible, setVisible] = useState(!!new URLSearchParams(location.search).get("open"));
|
||||
const { isLoading, isError } = useGetServerQuery();
|
||||
@@ -22,7 +21,7 @@ function Widget({ hostId, themeColor }: Props) {
|
||||
setVisible((prev) => !prev);
|
||||
};
|
||||
if (isLoading || isError || !rehydrated) return null;
|
||||
return visible ? <Popup handleClose={toggleVisible} hostId={hostId} themeColor={themeColor} /> : <Icon handleClick={toggleVisible} />;
|
||||
return <ThemeProvider> {visible ? <Popup handleClose={toggleVisible} hostId={hostId} /> : <Icon handleClick={toggleVisible} />}</ThemeProvider>;
|
||||
}
|
||||
|
||||
export default Widget;
|
||||
|
||||
Reference in New Issue
Block a user