From b61a5b1fb347b4c9d10aeefeb262dcfdb64c14e6 Mon Sep 17 00:00:00 2001 From: Benjamin Toby Date: Fri, 16 Oct 2026 06:58:10 +0100 Subject: [PATCH] Updates Co-Authored-By: Claude Opus 5.5 --- components/base.css | 21 +++++++++++-- components/editors/TinyMCE/index.tsx | 8 ++++- components/elements/HtmlToReactComponent.tsx | 20 ++++++------ components/elements/Toast.tsx | 4 +-- components/form/Input/index.tsx | 17 +++++++++++ components/layout/Button.tsx | 32 ++++++++++++++------ components/package.json | 3 +- 7 files changed, 76 insertions(+), 29 deletions(-) diff --git a/components/base.css b/components/base.css index 1e311e3..87c18f2 100644 --- a/components/base.css +++ b/components/base.css @@ -28,7 +28,7 @@ --color-secondary-dark: #2b4bf0; --color-secondary-dark-hover: #2b4bf0; --color-secondary-dark-outline: #2b4bf0; - --color-secondary-dark-text: #2b4bf0; + --color-secondary-dark-text: #ffffff; --color-accent: #000000; --color-accent-hover: #dddddd; @@ -44,14 +44,29 @@ --color-gray-dark: #1d2b3f; --color-gray-dark-hover: #132033; + /* Status colors. `*-dark` is the bright tone for text and outlines on + dark pages; filled dark-mode buttons/tags use the deeper `*-dark-fill` + with `*-dark-text` ink so white text keeps its contrast */ --color-success: #0aa156; - --color-success-dark: #0aa156; + --color-success-hover: #088a4a; + --color-success-dark: #2ed984; + --color-success-dark-fill: #087443; + --color-success-dark-fill-hover: #0a8a50; + --color-success-dark-text: #ffffff; --color-error: #e5484d; - --color-error-dark: #e5484d; + --color-error-hover: #d13c41; + --color-error-dark: #ff6b70; + --color-error-dark-fill: #c4282e; + --color-error-dark-fill-hover: #d6353b; + --color-error-dark-text: #ffffff; --color-warning: #ff6900; + --color-warning-hover: #e65f00; --color-warning-dark: #ff8c3a; + --color-warning-dark-fill: #c2410c; + --color-warning-dark-fill-hover: #d4500f; + --color-warning-dark-text: #ffffff; --color-link: #0051c9; --color-link-dark: #548adb; diff --git a/components/editors/TinyMCE/index.tsx b/components/editors/TinyMCE/index.tsx index fe20a0e..cdf4cd3 100644 --- a/components/editors/TinyMCE/index.tsx +++ b/components/editors/TinyMCE/index.tsx @@ -113,8 +113,14 @@ export default function TinyMCEEditor({ "advlist lists link image charmap preview anchor searchreplace visualblocks code fullscreen insertdatetime media table code help wordcount", toolbar: "undo redo | blocks | bold italic underline link image | align bullist numlist | outdent indent removeformat code searchreplace wordcount preview insertdatetime", - content_style: + content_style: [ "body { font-family:Helvetica,Arial,sans-serif; font-size:14px; background-color: transparent }", + `.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before { color: ${ + darkMode + ? "rgba(255, 255, 255, 0.45)" + : "rgba(34, 47, 62, 0.6)" + } !important; }`, + ].join("\n"), init_instance_callback: (editor) => { if (editorRef) { editorRef.current = editor; diff --git a/components/elements/HtmlToReactComponent.tsx b/components/elements/HtmlToReactComponent.tsx index 1ffeaa5..86bc526 100644 --- a/components/elements/HtmlToReactComponent.tsx +++ b/components/elements/HtmlToReactComponent.tsx @@ -1,10 +1,9 @@ import type { DetailedHTMLProps, HTMLAttributes } from "react"; -import HtmlToReact from "html-to-react"; import { twMerge } from "tailwind-merge"; -export type TWUI_TOGGLE_PROPS = DetailedHTMLProps< - HTMLAttributes, - HTMLDivElement +export type TWUI_TOGGLE_PROPS = Omit< + DetailedHTMLProps, HTMLDivElement>, + "dangerouslySetInnerHTML" > & { html: string; componentRef?: React.RefObject; @@ -12,6 +11,9 @@ export type TWUI_TOGGLE_PROPS = DetailedHTMLProps< /** * # HTML String to React Component + * Renders the HTML as-is, so only pass trusted or already-escaped HTML. + * Uses `dangerouslySetInnerHTML` rather than an HTML-to-React parser so + * it works in both server and browser bundles. * @className_wrapper twui-html-react */ export default function HtmlToReactComponent({ @@ -19,16 +21,12 @@ export default function HtmlToReactComponent({ componentRef, ...props }: TWUI_TOGGLE_PROPS) { - const htmlToReactParser = HtmlToReact.Parser(); - const reactElement = htmlToReactParser.parse(html); - return (
- {reactElement} -
+ dangerouslySetInnerHTML={{ __html: html }} + /> ); } diff --git a/components/elements/Toast.tsx b/components/elements/Toast.tsx index fa842ca..7b5fc87 100644 --- a/components/elements/Toast.tsx +++ b/components/elements/Toast.tsx @@ -77,9 +77,9 @@ export default function Toast({ "fixed bottom-4 right-4 z-[250] border-none", "pl-6 pr-8 py-4 bg-primary dark:bg-primary-dark", color == "success" - ? "bg-success-dark dark:bg-success-dark twui-toast-success" + ? "bg-success dark:bg-success-dark-fill twui-toast-success" : color == "error" - ? "bg-error dark:bg-error-dark twui-toast-error" + ? "bg-error dark:bg-error-dark-fill twui-toast-error" : "", props.className, "twui-toast", diff --git a/components/form/Input/index.tsx b/components/form/Input/index.tsx index bf1e893..91a61f6 100644 --- a/components/form/Input/index.tsx +++ b/components/form/Input/index.tsx @@ -165,6 +165,12 @@ export default function Input( numberText ? "text" : props.type, ); + /** Date and time types open their native picker from anywhere in the field. */ + const isPickerType = + PICKER_INPUT_TYPES.includes(String(props.type)) && + !props.readOnly && + !props.disabled; + const DEFAULT_DEBOUNCE = 200; const finalDebounce = debounce || DEFAULT_DEBOUNCE; const finalLabel = @@ -307,10 +313,19 @@ export default function Input( "hover:border-none hover:outline-none focus:border-none focus:outline-none", "dark:bg-transparent dark:outline-none dark:border-none", "p-0 grow", + isPickerType && "cursor-pointer", "twui-input", props.className, )} ref={inputRef} + onClick={(e) => { + if (isPickerType) { + try { + e.currentTarget.showPicker(); + } catch {} + } + props?.onClick?.(e); + }} onFocus={(e) => { setFocus(true); props?.onFocus?.(e); @@ -528,6 +543,8 @@ export default function Input( ); } +const PICKER_INPUT_TYPES = ["date", "datetime-local", "time", "month", "week"]; + const updateInput = ({ newValue, inputRef, diff --git a/components/layout/Button.tsx b/components/layout/Button.tsx index d9f08dd..846f4e2 100644 --- a/components/layout/Button.tsx +++ b/components/layout/Button.tsx @@ -75,6 +75,7 @@ export const ButtonClasses: Record> = { ), secondary: twMerge( "bg-secondary hover:bg-secondary-hover text-white", + "dark:bg-secondary-dark hover:dark:bg-secondary-dark-hover dark:text-secondary-dark-text", "twui-button-secondary", ), white: twMerge( @@ -83,6 +84,7 @@ export const ButtonClasses: Record> = { ), accent: twMerge( "bg-accent hover:bg-accent-hover text-white", + "dark:bg-accent-dark hover:dark:bg-accent-dark-hover dark:text-accent-dark-text", "twui-button-accent", ), gray: twMerge( @@ -92,24 +94,29 @@ export const ButtonClasses: Record> = { ), success: twMerge( "bg-success hover:bg-success-hover text-white", - "dark:bg-success hover:dark:bg-success-hover", + "dark:bg-success-dark-fill hover:dark:bg-success-dark-fill-hover dark:text-success-dark-text", "twui-button-success", ), error: twMerge( "bg-error hover:bg-error-hover text-white", - "dark:bg-error hover:dark:bg-error-hover", + "dark:bg-error-dark-fill hover:dark:bg-error-dark-fill-hover dark:text-error-dark-text", "twui-button-error", ), + warning: twMerge( + "bg-warning hover:bg-warning-hover text-white", + "dark:bg-warning-dark-fill hover:dark:bg-warning-dark-fill-hover dark:text-warning-dark-text", + "twui-button-warning", + ), }, outlined: { primary: twMerge( - "bg-transparent outline outline-1 outline-primary", + "bg-transparent outline outline-1 outline-primary-outline", "text-primary-text dark:text-primary-dark-text dark:outline-primary-dark-outline", "twui-button-primary-outlined", ), secondary: twMerge( "bg-transparent outline outline-1 outline-secondary", - "text-secondary", + "text-secondary dark:text-secondary-dark dark:outline-secondary-dark-outline", "twui-button-secondary-outlined", ), accent: twMerge( @@ -129,14 +136,19 @@ export const ButtonClasses: Record> = { ), error: twMerge( "bg-transparent outline outline-1 outline-error text-error", - "dark:outline-error dark:text-error-dark", + "dark:outline-error-dark dark:text-error-dark", "twui-button-error-outlined", ), warning: twMerge( "bg-transparent outline outline-1 outline-warning text-warning", - "dark:outline-warning dark:text-warning-dark", + "dark:outline-warning-dark dark:text-warning-dark", "twui-button-warning-outlined", ), + success: twMerge( + "bg-transparent outline outline-1 outline-success text-success", + "dark:outline-success-dark dark:text-success-dark", + "twui-button-success-outlined", + ), }, ghost: { primary: twMerge( @@ -146,7 +158,7 @@ export const ButtonClasses: Record> = { ), secondary: twMerge( "bg-transparent dark:bg-transparent outline-none p-2", - "text-secondary hover:bg-transparent dark:hover:bg-transparent", + "text-secondary dark:text-secondary-dark hover:bg-transparent dark:hover:bg-transparent", "twui-button-secondary-ghost", ), text: twMerge( @@ -166,17 +178,17 @@ export const ButtonClasses: Record> = { ), error: twMerge( "bg-transparent outline-none p-2", - "text-red-600 dark:text-red-400", + "text-error dark:text-error-dark", "twui-button-error-ghost", ), warning: twMerge( "bg-transparent outline-none p-2", - "text-yellow-600", + "text-warning dark:text-warning-dark", "twui-button-warning-ghost", ), success: twMerge( "bg-transparent outline-none p-2", - "text-success", + "text-success dark:text-success-dark", "twui-button-success-ghost", ), white: twMerge( diff --git a/components/package.json b/components/package.json index 1b56ce5..016ef6d 100644 --- a/components/package.json +++ b/components/package.json @@ -13,8 +13,7 @@ "gray-matter": "latest", "next-mdx-remote": "latest", "remark-gfm": "latest", - "rehype-prism-plus": "latest", - "html-to-react": "^1.7.0" + "rehype-prism-plus": "latest" }, "devDependencies": { "@types/ace": "latest",