diff --git a/package.json b/package.json index 841ae79..e10da2e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@steffo/bluelib-react", - "version": "4.3.0", + "version": "4.4.0", "description": "React bindings for Bluelib", "keywords": [ "react", diff --git a/src/components/BaseElement.tsx b/src/components/BaseElement.tsx index 3d0dc45..244d68b 100644 --- a/src/components/BaseElement.tsx +++ b/src/components/BaseElement.tsx @@ -17,7 +17,7 @@ export interface BaseElementProps extends React.HTMLProps { } -export function BaseElement({kind = "div", bluelibClassNames, disabled = false, builtinColor, customColor, ...props}: BaseElementProps): JSX.Element { +export const BaseElement = React.forwardRef(({kind = "div", bluelibClassNames, disabled = false, builtinColor, customColor, ...props}: BaseElementProps, ref): JSX.Element => { // Set the Bluelib color if(customColor) { props.style = {...props.style, ...Colors.colorToBluelibStyle("color", customColor)} @@ -36,5 +36,8 @@ export function BaseElement({kind = "div", bluelibClassNames, disabled = false, bluelibClassNames = BluelibMapper.rootToModule(bluelibClassNames) props.className = mergeClassNames(props.className, bluelibClassNames) + // Set the ref on the child element + props.ref = ref + return React.createElement(kind, props) -} +}) diff --git a/src/components/Bluelib.tsx b/src/components/Bluelib.tsx index f3fed62..13aba9e 100644 --- a/src/components/Bluelib.tsx +++ b/src/components/Bluelib.tsx @@ -1,101 +1,52 @@ import * as React from "react" -import * as ReactDOM from "react-dom" -import {BluelibTheme} from "../types" import * as Types from "../types" -import * as Colors from "../utils/Colors" -import Color from "color" -import mergeClassNames from "classnames" +import * as Splitter from "../utils/Splitter" + import {BaseElement} from "./BaseElement" -import PaperTheme from "../bluelib/src/targets/paper.module.css" -import RoyalBlueTheme from "../bluelib/src/targets/royalblue.module.css" -import HackerTheme from "../bluelib/src/targets/hacker.module.css" -import SophonTheme from "../bluelib/src/targets/sophon.module.css" -import GestioneAmberTheme from "../bluelib/src/targets/amber.module.css" +import {useBluelib, UseBluelibOptions} from "../hooks/useBluelib" -const BuiltinThemes = { - "paper": PaperTheme, - "royalblue": RoyalBlueTheme, - "hacker": HackerTheme, - "sophon": SophonTheme, - "amber": GestioneAmberTheme, -} +export interface BluelibProps extends Types.BluelibHTMLProps, UseBluelibOptions {} -export interface BluelibProps extends Types.BluelibHTMLProps { - theme: BluelibTheme, +export const Bluelib = (props: BluelibProps): JSX.Element => { - backgroundColor?: typeof Color, - foregroundColor?: typeof Color, - accentColor?: typeof Color, - linkColor?: typeof Color, - brokenColor?: typeof Color, - visitedColor?: typeof Color, - downloadColor?: typeof Color, - redColor?: typeof Color, - orangeColor?: typeof Color, - yellowColor?: typeof Color, - limeColor?: typeof Color, - cyanColor?: typeof Color, - blueColor?: typeof Color, - magentaColor?: typeof Color, - grayColor?: typeof Color, - polarity?: number, -} + const [useBluelibOptions, baseElementProps] = Splitter.splitInTwo(props, [ + "theme", + "backgroundColor", + "foregroundColor", + "accentColor", + "linkColor", + "brokenColor", + "visitedColor", + "downloadColor", + "redColor", + "orangeColor", + "yellowColor", + "limeColor", + "cyanColor", + "blueColor", + "magentaColor", + "grayColor", + "polarity", + ]) - -export function Bluelib({ - theme, - backgroundColor, - foregroundColor, - accentColor, - linkColor, - brokenColor, - visitedColor, - downloadColor, - redColor, - orangeColor, - yellowColor, - limeColor, - cyanColor, - blueColor, - magentaColor, - grayColor, - polarity, - ...props -}: BluelibProps): JSX.Element { - - props.className = mergeClassNames(props.className, BuiltinThemes[theme]["bluelib"]) - - if(backgroundColor) props.style = {...props.style, ...Colors.colorToBluelibStyle("background", backgroundColor)} - if(foregroundColor) props.style = {...props.style, ...Colors.colorToBluelibStyle("foreground", foregroundColor)} - if(accentColor) props.style = {...props.style, ...Colors.colorToBluelibStyle("accent", accentColor)} - if(linkColor) props.style = {...props.style, ...Colors.colorToBluelibStyle("link", linkColor)} - if(brokenColor) props.style = {...props.style, ...Colors.colorToBluelibStyle("broken", brokenColor)} - if(visitedColor) props.style = {...props.style, ...Colors.colorToBluelibStyle("visited", visitedColor)} - if(downloadColor) props.style = {...props.style, ...Colors.colorToBluelibStyle("download", downloadColor)} - if(redColor) props.style = {...props.style, ...Colors.colorToBluelibStyle("red", redColor)} - if(orangeColor) props.style = {...props.style, ...Colors.colorToBluelibStyle("orange", orangeColor)} - if(yellowColor) props.style = {...props.style, ...Colors.colorToBluelibStyle("yellow", yellowColor)} - if(limeColor) props.style = {...props.style, ...Colors.colorToBluelibStyle("lime", limeColor)} - if(cyanColor) props.style = {...props.style, ...Colors.colorToBluelibStyle("cyan", cyanColor)} - if(blueColor) props.style = {...props.style, ...Colors.colorToBluelibStyle("blue", blueColor)} - if(magentaColor) props.style = {...props.style, ...Colors.colorToBluelibStyle("magenta", magentaColor)} - if(grayColor) props.style = {...props.style, ...Colors.colorToBluelibStyle("gray", grayColor)} - if(polarity) { - // @ts-ignore - props.style["--bluelib-polarity"] = polarity - } - - return ( - + const ref = React.useRef(null) + const element = React.useMemo( + () => ( + + ), + [ref, baseElementProps] ) -} + useBluelib(ref, useBluelibOptions) + + return element +} Bluelib.Element = BaseElement - diff --git a/src/hooks/useBluelib.tsx b/src/hooks/useBluelib.tsx new file mode 100644 index 0000000..ebb0a1a --- /dev/null +++ b/src/hooks/useBluelib.tsx @@ -0,0 +1,83 @@ +import * as React from "react" +import { BluelibTheme } from "../types" +import mergeClassNames from "classnames" +import Color from "color"; +import * as Colors from "../utils/Colors" + +import PaperTheme from "../bluelib/src/targets/paper.module.css" +import RoyalBlueTheme from "../bluelib/src/targets/royalblue.module.css" +import HackerTheme from "../bluelib/src/targets/hacker.module.css" +import SophonTheme from "../bluelib/src/targets/sophon.module.css" +import GestioneAmberTheme from "../bluelib/src/targets/amber.module.css" + + +const BuiltinThemes = { + "paper": PaperTheme, + "royalblue": RoyalBlueTheme, + "hacker": HackerTheme, + "sophon": SophonTheme, + "amber": GestioneAmberTheme, +} + + +export interface UseBluelibOptions { + theme?: BluelibTheme, + backgroundColor?: typeof Color, + foregroundColor?: typeof Color, + accentColor?: typeof Color, + linkColor?: typeof Color, + brokenColor?: typeof Color, + visitedColor?: typeof Color, + downloadColor?: typeof Color, + redColor?: typeof Color, + orangeColor?: typeof Color, + yellowColor?: typeof Color, + limeColor?: typeof Color, + cyanColor?: typeof Color, + blueColor?: typeof Color, + magentaColor?: typeof Color, + grayColor?: typeof Color, + polarity?: number, +} + + +export function useBluelib(ref: React.RefObject, options: UseBluelibOptions) { + React.useEffect( + () => { + const target = ref.current + if(!target) return + + let extraClassName: string = "" + if(options.theme) extraClassName = BuiltinThemes[options.theme]["bluelib"] + + let extraStyle: {[_: string]: number} = {} + if(options.backgroundColor) extraStyle = {...extraStyle, ...Colors.colorToBluelibStyle("background", options.backgroundColor)} + if(options.foregroundColor) extraStyle = {...extraStyle, ...Colors.colorToBluelibStyle("foreground", options.foregroundColor)} + if(options.accentColor) extraStyle = {...extraStyle, ...Colors.colorToBluelibStyle("accent", options.accentColor)} + if(options.linkColor) extraStyle = {...extraStyle, ...Colors.colorToBluelibStyle("link", options.linkColor)} + if(options.brokenColor) extraStyle = {...extraStyle, ...Colors.colorToBluelibStyle("broken", options.brokenColor)} + if(options.visitedColor) extraStyle = {...extraStyle, ...Colors.colorToBluelibStyle("visited", options.visitedColor)} + if(options.downloadColor) extraStyle = {...extraStyle, ...Colors.colorToBluelibStyle("download", options.downloadColor)} + if(options.redColor) extraStyle = {...extraStyle, ...Colors.colorToBluelibStyle("red", options.redColor)} + if(options.orangeColor) extraStyle = {...extraStyle, ...Colors.colorToBluelibStyle("orange", options.orangeColor)} + if(options.yellowColor) extraStyle = {...extraStyle, ...Colors.colorToBluelibStyle("yellow", options.yellowColor)} + if(options.limeColor) extraStyle = {...extraStyle, ...Colors.colorToBluelibStyle("lime", options.limeColor)} + if(options.cyanColor) extraStyle = {...extraStyle, ...Colors.colorToBluelibStyle("cyan", options.cyanColor)} + if(options.blueColor) extraStyle = {...extraStyle, ...Colors.colorToBluelibStyle("blue", options.blueColor)} + if(options.magentaColor) extraStyle = {...extraStyle, ...Colors.colorToBluelibStyle("magenta", options.magentaColor)} + if(options.grayColor) extraStyle = {...extraStyle, ...Colors.colorToBluelibStyle("gray", options.grayColor)} + + target.classList.forEach((className) => { + if(Object.values(BuiltinThemes).map(a => a["bluelib"]).includes(className)) { + target.classList.remove(className) + } + }) + target.classList.add(extraClassName) + + Object.entries(extraStyle).forEach(([k, v]) => { + target.style.setProperty(k, v.toString(), "") + }) + }, + [ref, options] + ) +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index fa6d22f..56de9f9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -59,3 +59,4 @@ export {Bluelib as default} from "./components/Bluelib" export {usePromise} from "./hooks/usePromise" export {useFormState} from "./hooks/useFormState" +export {useBluelib} from "./hooks/useBluelib" diff --git a/src/utils/Splitter.js b/src/utils/Splitter.js new file mode 100644 index 0000000..b6c0245 --- /dev/null +++ b/src/utils/Splitter.js @@ -0,0 +1,28 @@ +// FIXME: Converting this to TypeScript breaks the transpiler. Why? I have absolutely no idea. JS is a mess. + +export function split(obj, ...keyGroups) { + const results = keyGroups.map((_) => {return {}}) + const other = {} + + Object.entries(obj).forEach(([k, v]) => { + let matched = false + keyGroups.forEach((keyGroup, index) => { + if(keyGroup.includes(k)) { + results[index][k] = v + matched = true + } + }) + + if(!matched) { + other[k] = v + } + }) + + results.push(other) + return results +} + + +export function splitInTwo (obj, keyGroup) { + return split(obj, keyGroup) +}