Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-16 06:58:10 +01:00
co-authored by Claude Opus 5.5
parent 44088c5a9c
commit b61a5b1fb3
7 changed files with 76 additions and 29 deletions
+18 -3
View File
@@ -28,7 +28,7 @@
--color-secondary-dark: #2b4bf0; --color-secondary-dark: #2b4bf0;
--color-secondary-dark-hover: #2b4bf0; --color-secondary-dark-hover: #2b4bf0;
--color-secondary-dark-outline: #2b4bf0; --color-secondary-dark-outline: #2b4bf0;
--color-secondary-dark-text: #2b4bf0; --color-secondary-dark-text: #ffffff;
--color-accent: #000000; --color-accent: #000000;
--color-accent-hover: #dddddd; --color-accent-hover: #dddddd;
@@ -44,14 +44,29 @@
--color-gray-dark: #1d2b3f; --color-gray-dark: #1d2b3f;
--color-gray-dark-hover: #132033; --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: #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: #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: #ff6900;
--color-warning-hover: #e65f00;
--color-warning-dark: #ff8c3a; --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: #0051c9;
--color-link-dark: #548adb; --color-link-dark: #548adb;
+7 -1
View File
@@ -113,8 +113,14 @@ export default function TinyMCEEditor<KeyType extends string>({
"advlist lists link image charmap preview anchor searchreplace visualblocks code fullscreen insertdatetime media table code help wordcount", "advlist lists link image charmap preview anchor searchreplace visualblocks code fullscreen insertdatetime media table code help wordcount",
toolbar: toolbar:
"undo redo | blocks | bold italic underline link image | align bullist numlist | outdent indent removeformat code searchreplace wordcount preview insertdatetime", "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 }", "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) => { init_instance_callback: (editor) => {
if (editorRef) { if (editorRef) {
editorRef.current = editor; editorRef.current = editor;
+9 -11
View File
@@ -1,10 +1,9 @@
import type { DetailedHTMLProps, HTMLAttributes } from "react"; import type { DetailedHTMLProps, HTMLAttributes } from "react";
import HtmlToReact from "html-to-react";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
export type TWUI_TOGGLE_PROPS = DetailedHTMLProps< export type TWUI_TOGGLE_PROPS = Omit<
HTMLAttributes<HTMLDivElement>, DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>,
HTMLDivElement "dangerouslySetInnerHTML"
> & { > & {
html: string; html: string;
componentRef?: React.RefObject<any>; componentRef?: React.RefObject<any>;
@@ -12,6 +11,9 @@ export type TWUI_TOGGLE_PROPS = DetailedHTMLProps<
/** /**
* # HTML String to React Component * # 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 * @className_wrapper twui-html-react
*/ */
export default function HtmlToReactComponent({ export default function HtmlToReactComponent({
@@ -19,16 +21,12 @@ export default function HtmlToReactComponent({
componentRef, componentRef,
...props ...props
}: TWUI_TOGGLE_PROPS) { }: TWUI_TOGGLE_PROPS) {
const htmlToReactParser = HtmlToReact.Parser();
const reactElement = htmlToReactParser.parse(html);
return ( return (
<div <div
{...props} {...props}
className={twMerge("", props.className)} className={twMerge("twui-html-react", props.className)}
ref={componentRef} ref={componentRef}
> dangerouslySetInnerHTML={{ __html: html }}
{reactElement} />
</div>
); );
} }
+2 -2
View File
@@ -77,9 +77,9 @@ export default function Toast({
"fixed bottom-4 right-4 z-[250] border-none", "fixed bottom-4 right-4 z-[250] border-none",
"pl-6 pr-8 py-4 bg-primary dark:bg-primary-dark", "pl-6 pr-8 py-4 bg-primary dark:bg-primary-dark",
color == "success" color == "success"
? "bg-success-dark dark:bg-success-dark twui-toast-success" ? "bg-success dark:bg-success-dark-fill twui-toast-success"
: color == "error" : color == "error"
? "bg-error dark:bg-error-dark twui-toast-error" ? "bg-error dark:bg-error-dark-fill twui-toast-error"
: "", : "",
props.className, props.className,
"twui-toast", "twui-toast",
+17
View File
@@ -165,6 +165,12 @@ export default function Input<KeyType extends string>(
numberText ? "text" : props.type, 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 DEFAULT_DEBOUNCE = 200;
const finalDebounce = debounce || DEFAULT_DEBOUNCE; const finalDebounce = debounce || DEFAULT_DEBOUNCE;
const finalLabel = const finalLabel =
@@ -307,10 +313,19 @@ export default function Input<KeyType extends string>(
"hover:border-none hover:outline-none focus:border-none focus:outline-none", "hover:border-none hover:outline-none focus:border-none focus:outline-none",
"dark:bg-transparent dark:outline-none dark:border-none", "dark:bg-transparent dark:outline-none dark:border-none",
"p-0 grow", "p-0 grow",
isPickerType && "cursor-pointer",
"twui-input", "twui-input",
props.className, props.className,
)} )}
ref={inputRef} ref={inputRef}
onClick={(e) => {
if (isPickerType) {
try {
e.currentTarget.showPicker();
} catch {}
}
props?.onClick?.(e);
}}
onFocus={(e) => { onFocus={(e) => {
setFocus(true); setFocus(true);
props?.onFocus?.(e); props?.onFocus?.(e);
@@ -528,6 +543,8 @@ export default function Input<KeyType extends string>(
); );
} }
const PICKER_INPUT_TYPES = ["date", "datetime-local", "time", "month", "week"];
const updateInput = ({ const updateInput = ({
newValue, newValue,
inputRef, inputRef,
+22 -10
View File
@@ -75,6 +75,7 @@ export const ButtonClasses: Record<string, Record<string, string>> = {
), ),
secondary: twMerge( secondary: twMerge(
"bg-secondary hover:bg-secondary-hover text-white", "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", "twui-button-secondary",
), ),
white: twMerge( white: twMerge(
@@ -83,6 +84,7 @@ export const ButtonClasses: Record<string, Record<string, string>> = {
), ),
accent: twMerge( accent: twMerge(
"bg-accent hover:bg-accent-hover text-white", "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", "twui-button-accent",
), ),
gray: twMerge( gray: twMerge(
@@ -92,24 +94,29 @@ export const ButtonClasses: Record<string, Record<string, string>> = {
), ),
success: twMerge( success: twMerge(
"bg-success hover:bg-success-hover text-white", "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", "twui-button-success",
), ),
error: twMerge( error: twMerge(
"bg-error hover:bg-error-hover text-white", "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", "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: { outlined: {
primary: twMerge( 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", "text-primary-text dark:text-primary-dark-text dark:outline-primary-dark-outline",
"twui-button-primary-outlined", "twui-button-primary-outlined",
), ),
secondary: twMerge( secondary: twMerge(
"bg-transparent outline outline-1 outline-secondary", "bg-transparent outline outline-1 outline-secondary",
"text-secondary", "text-secondary dark:text-secondary-dark dark:outline-secondary-dark-outline",
"twui-button-secondary-outlined", "twui-button-secondary-outlined",
), ),
accent: twMerge( accent: twMerge(
@@ -129,14 +136,19 @@ export const ButtonClasses: Record<string, Record<string, string>> = {
), ),
error: twMerge( error: twMerge(
"bg-transparent outline outline-1 outline-error text-error", "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", "twui-button-error-outlined",
), ),
warning: twMerge( warning: twMerge(
"bg-transparent outline outline-1 outline-warning text-warning", "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", "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: { ghost: {
primary: twMerge( primary: twMerge(
@@ -146,7 +158,7 @@ export const ButtonClasses: Record<string, Record<string, string>> = {
), ),
secondary: twMerge( secondary: twMerge(
"bg-transparent dark:bg-transparent outline-none p-2", "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", "twui-button-secondary-ghost",
), ),
text: twMerge( text: twMerge(
@@ -166,17 +178,17 @@ export const ButtonClasses: Record<string, Record<string, string>> = {
), ),
error: twMerge( error: twMerge(
"bg-transparent outline-none p-2", "bg-transparent outline-none p-2",
"text-red-600 dark:text-red-400", "text-error dark:text-error-dark",
"twui-button-error-ghost", "twui-button-error-ghost",
), ),
warning: twMerge( warning: twMerge(
"bg-transparent outline-none p-2", "bg-transparent outline-none p-2",
"text-yellow-600", "text-warning dark:text-warning-dark",
"twui-button-warning-ghost", "twui-button-warning-ghost",
), ),
success: twMerge( success: twMerge(
"bg-transparent outline-none p-2", "bg-transparent outline-none p-2",
"text-success", "text-success dark:text-success-dark",
"twui-button-success-ghost", "twui-button-success-ghost",
), ),
white: twMerge( white: twMerge(
+1 -2
View File
@@ -13,8 +13,7 @@
"gray-matter": "latest", "gray-matter": "latest",
"next-mdx-remote": "latest", "next-mdx-remote": "latest",
"remark-gfm": "latest", "remark-gfm": "latest",
"rehype-prism-plus": "latest", "rehype-prism-plus": "latest"
"html-to-react": "^1.7.0"
}, },
"devDependencies": { "devDependencies": {
"@types/ace": "latest", "@types/ace": "latest",