Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ Simply override/remap
> Make sure `:root, .dark { ... }` comes AFTER `.root { ... } .dark { ... }` to
> take precedence.

## Userscript for shadcn sites

A userscript is available that automatically extracts the primary color from any shadcn website and generates MCU colors. This allows you to use MCU color variables on any shadcn site without modifying the site's code.

See [userscripts/README.md](./userscripts/README.md) for installation and usage instructions.

# Dev

## INSTALL
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
},
"scripts": {
"build": "tsup",
"build:userscript": "tsup --config userscripts/tsup.config.ts",
"lgtm": "pnpm run build && pnpm run check-format && pnpm run check-exports && pnpm run typecheck && pnpm run test",
"typecheck": "tsc",
"test": "vitest run",
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { Mcu } from "./Mcu";
export { Mcu, generateCss, type McuConfig } from "./Mcu";
export { useMcu } from "./Mcu.context";
117 changes: 117 additions & 0 deletions userscripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Shadcn MCU Colors Userscript

This userscript automatically extracts the primary color from any shadcn/ui website and generates a complete Material Design 3 (MCU) color scheme based on it.

## What it does

1. Detects if a page uses shadcn/ui by looking for the `--primary` CSS variable
2. Extracts the primary color value (supports HSL format used by shadcn)
3. Generates a full MCU color palette using the existing `generateCss` function from react-mcu
4. Injects `--mcu-*` CSS variables into the page

## Installation

### Prerequisites

You need a userscript manager extension installed in your browser:

- **Chrome/Edge**: [Tampermonkey](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo) or [Violentmonkey](https://chrome.google.com/webstore/detail/violentmonkey/jinjaccalgkegednnccohejagnlnfdag)
- **Firefox**: [Tampermonkey](https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/) or [Greasemonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/)
- **Safari**: [Userscripts](https://apps.apple.com/us/app/userscripts/id1463298887)

### Install the userscript

1. Click on the userscript file: [shadcn-mcu.user.js](./shadcn-mcu.user.js)
2. Your userscript manager should detect it and prompt you to install
3. Click "Install" or "Confirm"

Alternatively, you can manually copy the contents of `shadcn-mcu.user.js` and create a new userscript in your manager.

## Usage

Once installed, the userscript will automatically run on every page you visit. It will:

1. Check for the presence of the `--primary` CSS variable (indicating a shadcn site)
2. If found, generate and inject MCU color variables
3. Log to the console whether colors were injected or not

You can check the browser console to see if the script detected and processed the page:

- "No shadcn primary color found on this page" - The page doesn't use shadcn
- "Found shadcn primary color: #..." - Primary color detected
- "MCU colors injected successfully" - MCU variables are now available

## Available CSS Variables

After the script runs, you'll have access to all MCU color variables:

### Color Roles

- `--mcu-primary`, `--mcu-on-primary`
- `--mcu-secondary`, `--mcu-on-secondary`
- `--mcu-tertiary`, `--mcu-on-tertiary`
- `--mcu-error`, `--mcu-on-error`
- `--mcu-background`, `--mcu-on-background`
- `--mcu-surface`, `--mcu-on-surface`
- And many more...

### Tonal Palettes

For each core color (primary, secondary, tertiary, neutral, neutral-variant, error), you get all tones:

- `--mcu-primary-0` through `--mcu-primary-100`
- `--mcu-secondary-0` through `--mcu-secondary-100`
- etc.

## Example

Visit any shadcn site (e.g., https://ui.shadcn.com) and you can now use MCU colors in your browser's developer tools:

```javascript
// In the browser console
document.body.style.backgroundColor = "var(--mcu-surface)";
document.body.style.color = "var(--mcu-on-surface)";
```

Or inject custom CSS:

```javascript
const style = document.createElement("style");
style.textContent = `
.my-custom-element {
background-color: var(--mcu-primary-container);
color: var(--mcu-on-primary-container);
}
`;
document.head.appendChild(style);
```

## Debugging

Open your browser's console to see debug messages:

- Press `F12` or `Cmd+Option+I` (Mac) / `Ctrl+Shift+I` (Windows/Linux)
- Go to the "Console" tab
- Look for messages starting with `[Shadcn MCU]`

## Technical Details

- Reuses the `generateCss` function from react-mcu core library (DRY principle)
- Bundles all dependencies into a single userscript file (~238KB)
- Default scheme: Tonal Spot (following Material Design 3 guidelines)
- Supports both light and dark color schemes
- Automatically converts shadcn's HSL format to hex for processing

## Development

To rebuild the userscript after making changes:

```bash
pnpm run build:userscript
```

This bundles `userscripts/shadcn-mcu.ts` into `userscripts/shadcn-mcu.user.js` using the existing codebase.

## License

MIT
118 changes: 118 additions & 0 deletions userscripts/shadcn-mcu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { generateCss } from "../src/Mcu";

// Wait for the page to load
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}

function init() {
// Check if this is a shadcn site by looking for --primary CSS variable
const primaryColor = getShadcnPrimaryColor();

if (!primaryColor) {
console.log("[Shadcn MCU] No shadcn primary color found on this page");
return;
}

console.log("[Shadcn MCU] Found shadcn primary color:", primaryColor);

// Generate and inject MCU colors using existing generateCss function
const { css } = generateCss({ source: primaryColor });

// Inject the CSS into the page
const styleId = "shadcn-mcu-colors";
let styleEl = document.getElementById(styleId);

if (!styleEl) {
styleEl = document.createElement("style");
styleEl.id = styleId;
document.head.appendChild(styleEl);
}

styleEl.textContent = css;

console.log("[Shadcn MCU] MCU colors injected successfully");
}

/**
* Get the shadcn --primary color from the page
* @returns {string|null} The primary color in hex format, or null if not found
*/
function getShadcnPrimaryColor() {
// Get the computed style of the root element
const rootStyles = getComputedStyle(document.documentElement);

// Get the --primary variable value
const primaryValue = rootStyles.getPropertyValue("--primary").trim();

if (!primaryValue) {
return null;
}

// shadcn uses HSL values like "222.2 47.4% 11.2%"
// We need to convert this to hex
if (primaryValue.includes("%")) {
return hslToHex(primaryValue);
}

// If it's already a hex color
if (primaryValue.startsWith("#")) {
return primaryValue;
}

return null;
}

/**
* Convert HSL string to hex color
* @param {string} hslString - HSL string like "222.2 47.4% 11.2%"
* @returns {string} Hex color like "#0e1216"
*/
function hslToHex(hslString) {
const parts = hslString.split(/\s+/);
const h = parseFloat(parts[0]);
const s = parseFloat(parts[1]) / 100;
const l = parseFloat(parts[2]) / 100;

const c = (1 - Math.abs(2 * l - 1)) * s;
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
const m = l - c / 2;

let r = 0,
g = 0,
b = 0;

if (h >= 0 && h < 60) {
r = c;
g = x;
b = 0;
} else if (h >= 60 && h < 120) {
r = x;
g = c;
b = 0;
} else if (h >= 120 && h < 180) {
r = 0;
g = c;
b = x;
} else if (h >= 180 && h < 240) {
r = 0;
g = x;
b = c;
} else if (h >= 240 && h < 300) {
r = x;
g = 0;
b = c;
} else if (h >= 300 && h < 360) {
r = c;
g = 0;
b = x;
}

r = Math.round((r + m) * 255);
g = Math.round((g + m) * 255);
b = Math.round((b + m) * 255);

return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
Loading