Compare commits
10 Commits
cfe2102f51
...
e524c5046a
| Author | SHA1 | Date | |
|---|---|---|---|
| e524c5046a | |||
| 0b807644b1 | |||
| 2ce7a39c1a | |||
| 0a25911ba9 | |||
| 4c36674c60 | |||
| 1acf0ce9cd | |||
| fe93824063 | |||
| c64db01bb4 | |||
| e4c91c4a93 | |||
| dd4ab29df2 |
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
dist
|
||||
pnpm-lock.yaml
|
||||
.direnv
|
||||
@@ -0,0 +1,306 @@
|
||||
# Tailwind Dashed Border Plugin: Technical Description
|
||||
|
||||
This plugin provides a small set of Tailwind CSS utilities for drawing configurable dashed borders with an SVG `<rect>` overlay instead of using the native CSS `border-style: dashed`. The SVG approach makes dash length, gap, offset, stroke width, color, and rounded corners independently controllable.
|
||||
|
||||
## What the plugin does
|
||||
|
||||
The plugin defines:
|
||||
|
||||
- Base CSS custom properties for dashed-border configuration.
|
||||
- Structural utilities for the container, SVG overlay, and SVG rectangle.
|
||||
- Generated utility families for gap, dash length, offset, stroke width, stroke color, and border radius.
|
||||
- Transition helpers so dashed-border visuals can animate consistently with Tailwind transition utilities.
|
||||
|
||||
The plugin expects the dashed border to be rendered by an element structure like this:
|
||||
|
||||
```html
|
||||
<div class="dashed-border dashed-border-gap-2 dashed-border-length-4 dashed-border-width-2">
|
||||
<svg class="dashed-border-svg" aria-hidden="true">
|
||||
<rect class="dashed-border-rect" />
|
||||
</svg>
|
||||
|
||||
<!-- content -->
|
||||
</div>
|
||||
```
|
||||
|
||||
## Core structural utilities
|
||||
|
||||
### `.dashed-border`
|
||||
|
||||
This is the root utility for any element using the plugin. It:
|
||||
|
||||
- Sets default CSS variables.
|
||||
- Applies `position: relative` so the SVG overlay can be absolutely positioned.
|
||||
|
||||
Default values:
|
||||
|
||||
- `--tw-dashed-border-gap`: `calc(var(--spacing, 0.25rem) * 2)`
|
||||
- `--tw-dashed-border-length`: `calc(var(--spacing, 0.25rem) * 3)`
|
||||
- `--tw-dashed-border-offset`: `0px`
|
||||
- `--tw-dashed-border-width`: `1px`
|
||||
- `--tw-dashed-border-color`: `currentColor`
|
||||
- `--tw-dashed-border-radius`: `0px`
|
||||
|
||||
### `.dashed-border-svg`
|
||||
|
||||
This utility is intended for the SVG overlay element. It:
|
||||
|
||||
- Makes the SVG fill the container with `position: absolute` and `inset: 0`.
|
||||
- Sets `width: 100%` and `height: 100%`.
|
||||
- Uses `overflow: hidden`.
|
||||
- Uses `pointer-events: none` so the overlay does not block interaction.
|
||||
|
||||
### `.dashed-border-rect`
|
||||
|
||||
This utility is intended for the SVG `<rect>` that draws the actual border. It:
|
||||
|
||||
- Removes fill with `fill: none`.
|
||||
- Uses `stroke` for the border color.
|
||||
- Uses `stroke-dasharray` for dash length and gap.
|
||||
- Uses `stroke-dashoffset` for dash phase shifting.
|
||||
- Uses `vector-effect: non-scaling-stroke` to keep stroke width stable.
|
||||
- Positions the rectangle inset by half the stroke width, so the stroke remains visually inside the element bounds.
|
||||
- Uses `rx` to simulate rounded corners.
|
||||
|
||||
Computed shape rules:
|
||||
|
||||
- `x` / `y`: `calc(var(--tw-dashed-border-width) / 2)`
|
||||
- `width` / `height`: `calc(100% - var(--tw-dashed-border-width))`
|
||||
- `rx`: `max(0px, calc(var(--tw-dashed-border-radius) - (var(--tw-dashed-border-width) / 2)))`
|
||||
|
||||
Dash pattern:
|
||||
|
||||
```css
|
||||
stroke-dasharray: <length> <gap>;
|
||||
```
|
||||
|
||||
## Registered custom properties
|
||||
|
||||
The plugin registers these `@property` definitions:
|
||||
|
||||
- `--tw-dashed-border-gap`
|
||||
- `--tw-dashed-border-length`
|
||||
- `--tw-dashed-border-offset`
|
||||
- `--tw-dashed-border-width`
|
||||
- `--tw-dashed-border-radius`
|
||||
|
||||
These registrations provide typed CSS custom properties so transitions and interpolation behave more predictably in supporting browsers.
|
||||
|
||||
## Generated utility families
|
||||
|
||||
### Gap utilities
|
||||
|
||||
Class shape:
|
||||
|
||||
```text
|
||||
dashed-border-gap-*
|
||||
```
|
||||
|
||||
Source values:
|
||||
|
||||
- Tailwind `theme.spacing`
|
||||
|
||||
Effect:
|
||||
|
||||
- Sets `--tw-dashed-border-gap`
|
||||
- Recomputes `stroke-dasharray`
|
||||
|
||||
Example:
|
||||
|
||||
```html
|
||||
<div class="dashed-border dashed-border-gap-4">
|
||||
```
|
||||
|
||||
### Dash length utilities
|
||||
|
||||
Class shape:
|
||||
|
||||
```text
|
||||
dashed-border-length-*
|
||||
```
|
||||
|
||||
Source values:
|
||||
|
||||
- Tailwind `theme.spacing`
|
||||
|
||||
Effect:
|
||||
|
||||
- Sets `--tw-dashed-border-length`
|
||||
- Recomputes `stroke-dasharray`
|
||||
|
||||
Example:
|
||||
|
||||
```html
|
||||
<div class="dashed-border dashed-border-length-6">
|
||||
```
|
||||
|
||||
### Dash offset utilities
|
||||
|
||||
Class shape:
|
||||
|
||||
```text
|
||||
dashed-border-offset-*
|
||||
```
|
||||
|
||||
Source values:
|
||||
|
||||
- Tailwind `theme.spacing`
|
||||
|
||||
Effect:
|
||||
|
||||
- Sets `--tw-dashed-border-offset`
|
||||
- Updates `stroke-dashoffset`
|
||||
|
||||
Example:
|
||||
|
||||
```html
|
||||
<div class="dashed-border dashed-border-offset-2">
|
||||
```
|
||||
|
||||
### Stroke width utilities
|
||||
|
||||
Class shape:
|
||||
|
||||
```text
|
||||
dashed-border-width-*
|
||||
```
|
||||
|
||||
Source values:
|
||||
|
||||
- Tailwind `theme.borderWidth`
|
||||
|
||||
Effect:
|
||||
|
||||
- Sets `--tw-dashed-border-width`
|
||||
- Updates `stroke-width`
|
||||
- Recomputes `x`, `y`, `width`, `height`, and `rx`
|
||||
|
||||
Example:
|
||||
|
||||
```html
|
||||
<div class="dashed-border dashed-border-width-2">
|
||||
```
|
||||
|
||||
### Stroke color utilities
|
||||
|
||||
Class shape:
|
||||
|
||||
```text
|
||||
dashed-border-color-*
|
||||
```
|
||||
|
||||
Source values:
|
||||
|
||||
- Flattened Tailwind `theme.colors`
|
||||
- Plus explicit aliases:
|
||||
- `dashed-border-color-current`
|
||||
- `dashed-border-color-inherit`
|
||||
- `dashed-border-color-transparent`
|
||||
|
||||
Effect:
|
||||
|
||||
- Sets `--tw-dashed-border-color`
|
||||
- Updates SVG `stroke`
|
||||
|
||||
Example:
|
||||
|
||||
```html
|
||||
<div class="dashed-border dashed-border-color-slate-500">
|
||||
```
|
||||
|
||||
Nested Tailwind colors are flattened with dash-separated keys, so a theme color like `slate.500` becomes:
|
||||
|
||||
```text
|
||||
dashed-border-color-slate-500
|
||||
```
|
||||
|
||||
### Radius utilities
|
||||
|
||||
Class shape:
|
||||
|
||||
```text
|
||||
dashed-border.rounded-*
|
||||
```
|
||||
|
||||
These are not generated as `dashed-border-radius-*`. Instead, the plugin creates compound selectors that combine `.dashed-border` with Tailwind rounded classes.
|
||||
|
||||
Examples of generated selectors:
|
||||
|
||||
- `.dashed-border.rounded`
|
||||
- `.dashed-border.rounded-sm`
|
||||
- `.dashed-border.rounded-lg`
|
||||
- `.dashed-border.rounded-full`
|
||||
|
||||
Source values:
|
||||
|
||||
- Tailwind `theme.borderRadius`
|
||||
|
||||
Effect:
|
||||
|
||||
- Sets `--tw-dashed-border-radius`
|
||||
- Lets the SVG rectangle inherit matching rounded-corner behavior through its computed `rx`
|
||||
|
||||
Example:
|
||||
|
||||
```html
|
||||
<div class="dashed-border rounded-lg">
|
||||
```
|
||||
|
||||
This means the expected way to apply radius is:
|
||||
|
||||
```html
|
||||
<div class="dashed-border rounded-xl">
|
||||
```
|
||||
|
||||
not:
|
||||
|
||||
```html
|
||||
<div class="dashed-border-radius-xl">
|
||||
```
|
||||
|
||||
## Transition behavior
|
||||
|
||||
The plugin adds transition helpers for dashed-border visuals.
|
||||
|
||||
Supported patterns:
|
||||
|
||||
- `.dashed-border-transition`
|
||||
- `.dashed-border.transition`
|
||||
- `.dashed-border.transition-all`
|
||||
- `.dashed-border.transition-colors`
|
||||
|
||||
Behavior:
|
||||
|
||||
- Inherits Tailwind-style transition duration and timing defaults.
|
||||
- Applies transitions to the inner `.dashed-border-rect`.
|
||||
- `transition` / `transition-all` animate:
|
||||
- `stroke-dasharray`
|
||||
- `stroke-dashoffset`
|
||||
- `stroke-width`
|
||||
- `stroke`
|
||||
- `rx`
|
||||
- `transition-colors` animates only `stroke`
|
||||
|
||||
Example:
|
||||
|
||||
```html
|
||||
<div class="dashed-border rounded-lg transition-all dashed-border-color-slate-400 hover:dashed-border-color-slate-900">
|
||||
```
|
||||
|
||||
## Expected visual model
|
||||
|
||||
When used correctly, the utilities should produce:
|
||||
|
||||
- A container element that establishes the positioning context.
|
||||
- A full-size SVG overlay sitting on top of the container.
|
||||
- A `<rect>` inset by half the stroke width so the line is not clipped.
|
||||
- A dashed stroke whose dash length and gap are independently tunable.
|
||||
- Rounded corners that visually track Tailwind `rounded-*` utilities.
|
||||
- Hover, or others transition triggers should be embedded from main tailwind flow. Use their duration and animation function.
|
||||
|
||||
## Implementation notes
|
||||
|
||||
- The plugin flattens nested color objects from `theme.colors` into dash-separated utility suffixes.
|
||||
- `DEFAULT` keys are handled specially so default theme values map cleanly to `rounded` and other default-style selectors.
|
||||
- Radius is adjusted by subtracting half the stroke width, which keeps the visible curve aligned with the outer element radius.
|
||||
- The plugin is built around Tailwind CSS v4 plugin APIs: `addBase`, `addUtilities`, and `matchUtilities`.
|
||||
Generated
+60
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1776105745,
|
||||
"narHash": "sha256-3TdZByz6NafPdfpFS+pevgO75kSS9qFOR2pI9cZp7XE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d17aeaf88dea0fe37c73d33629d71cb51deafdb0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
+6509
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "tailwind-dashed-border",
|
||||
"version": "0.1.0",
|
||||
"description": "Configurable dashed border utility for Tailwind CSS v4",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"check": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"keywords": [
|
||||
"tailwindcss",
|
||||
"tailwind",
|
||||
"plugin",
|
||||
"dashed-border",
|
||||
"border"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"tailwindcss": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tailwindcss": "^4.2.1",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
diff --git a/dist/plugin.d.mts b/dist/plugin.d.mts
|
||||
index 5c32fa73db5d471048e566b1c676a4225805d39a..77b8cbfe7d79bce396eb77537baa657be31af32d 100644
|
||||
--- a/dist/plugin.d.mts
|
||||
+++ b/dist/plugin.d.mts
|
||||
@@ -1,6 +1,6 @@
|
||||
-export { P as PluginUtils } from './resolve-config-QUZ9b-Gn.mjs';
|
||||
+export type { P as PluginUtils, N as NamedUtilityValue } from './resolve-config-QUZ9b-Gn.mjs';
|
||||
import { a as PluginFn, C as Config, b as PluginWithConfig, c as PluginWithOptions } from './types-CJYAW1ql.mjs';
|
||||
-export { d as PluginAPI, P as PluginsConfig, T as ThemeConfig } from './types-CJYAW1ql.mjs';
|
||||
+export type { d as PluginAPI, P as PluginsConfig, T as ThemeConfig, MatchOptions, BareValueResolver, MatchFn } from './types-CJYAW1ql.mjs';
|
||||
import './colors.mjs';
|
||||
|
||||
declare function createPlugin(handler: PluginFn, config?: Partial<Config>): PluginWithConfig;
|
||||
@@ -8,4 +8,4 @@ declare namespace createPlugin {
|
||||
var withOptions: <T>(pluginFunction: (options?: T) => PluginFn, configFunction?: (options?: T) => Partial<Config>) => PluginWithOptions<T>;
|
||||
}
|
||||
|
||||
-export { Config, PluginFn as PluginCreator, createPlugin as default };
|
||||
+export { type Config, type PluginFn, createPlugin as default };
|
||||
diff --git a/dist/types-CJYAW1ql.d.mts b/dist/types-CJYAW1ql.d.mts
|
||||
index 6d5ca4a855759cbd60007c69d072bdd7b8710e77..2505e2accb9e9b01de53c9dbb0b27c14abb252e1 100644
|
||||
--- a/dist/types-CJYAW1ql.d.mts
|
||||
+++ b/dist/types-CJYAW1ql.d.mts
|
||||
@@ -56,31 +56,26 @@ type PluginAPI = {
|
||||
}): number;
|
||||
}): void;
|
||||
addUtilities(utilities: Record<string, CssInJs | CssInJs[]> | Record<string, CssInJs | CssInJs[]>[], options?: {}): void;
|
||||
- matchUtilities(utilities: Record<string, (value: string, extra: {
|
||||
- modifier: string | null;
|
||||
- }) => CssInJs | CssInJs[]>, options?: Partial<{
|
||||
- type: string | string[];
|
||||
- supportsNegativeValues: boolean;
|
||||
- values: Record<string, string> & {
|
||||
- __BARE_VALUE__?: (value: NamedUtilityValue) => string | undefined;
|
||||
- };
|
||||
- modifiers: 'any' | Record<string, string>;
|
||||
- }>): void;
|
||||
+ matchUtilities(utilities: Record<string, MatchFn>, options?: MatchOptions): void;
|
||||
addComponents(utilities: Record<string, CssInJs> | Record<string, CssInJs>[], options?: {}): void;
|
||||
- matchComponents(utilities: Record<string, (value: string, extra: {
|
||||
- modifier: string | null;
|
||||
- }) => CssInJs>, options?: Partial<{
|
||||
- type: string | string[];
|
||||
- supportsNegativeValues: boolean;
|
||||
- values: Record<string, string> & {
|
||||
- __BARE_VALUE__?: (value: NamedUtilityValue) => string | undefined;
|
||||
- };
|
||||
- modifiers: 'any' | Record<string, string>;
|
||||
- }>): void;
|
||||
+ matchComponents(utilities: Record<string, MatchFn>, options?: MatchOptions): void;
|
||||
theme(path: string, defaultValue?: any): any;
|
||||
config(path?: string, defaultValue?: any): any;
|
||||
prefix(className: string): string;
|
||||
};
|
||||
+
|
||||
+type MatchFn = (value: string, extra: { modifier: string | null; }) => CssInJs | CssInJs[]
|
||||
+
|
||||
+type MatchOptions = Partial<{
|
||||
+ type: string | string[];
|
||||
+ supportsNegativeValues: boolean;
|
||||
+ values: Record<string, string> & {
|
||||
+ __BARE_VALUE__?: BareValueResolver;
|
||||
+ };
|
||||
+ modifiers: 'any' | Record<string, string>;
|
||||
+}>
|
||||
+type BareValueResolver = (value: NamedUtilityValue) => string | undefined
|
||||
+
|
||||
type CssInJs = {
|
||||
[key: string]: string | string[] | CssInJs | CssInJs[];
|
||||
};
|
||||
@@ -125,4 +120,4 @@ interface UserConfig {
|
||||
experimental?: 'all' | Record<string, boolean>;
|
||||
}
|
||||
|
||||
-export type { Config as C, Plugin as P, SourceLocation as S, ThemeConfig as T, UserConfig as U, PluginFn as a, PluginWithConfig as b, PluginWithOptions as c, PluginAPI as d };
|
||||
+export type { Config as C, Plugin as P, SourceLocation as S, ThemeConfig as T, UserConfig as U, PluginFn as a, PluginWithConfig as b, PluginWithOptions as c, PluginAPI as d, MatchOptions, BareValueResolver, MatchFn };
|
||||
@@ -0,0 +1,2 @@
|
||||
patchedDependencies:
|
||||
tailwindcss: patches/tailwindcss.patch
|
||||
@@ -0,0 +1,52 @@
|
||||
# Tailwind Dashed Border SVG
|
||||
|
||||
A plugin for adressing issue with custamable and animatable dashed border via tailwind interface (mostly).
|
||||
|
||||
It uses real svg under the hood what provide the maximum flexibility. Non animated border coming soon.
|
||||
|
||||
# Setup
|
||||
|
||||
```
|
||||
pnpm i tailwind-dashed-border
|
||||
```
|
||||
|
||||
prepare a component for svg
|
||||
|
||||
```tsx
|
||||
export function BorderSVG() {
|
||||
return (
|
||||
<svg className="dashed-border-svg" aria-hidden="true">
|
||||
<rect className="dashed-border-rect" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
and now you are ready to make your borders
|
||||
|
||||
```tsx
|
||||
<div className="dashed-border">
|
||||
<BorderSVG/>
|
||||
</div>
|
||||
```
|
||||
|
||||
# Properties
|
||||
|
||||
## Setup
|
||||
- dashed-border
|
||||
- dashed-border-svg
|
||||
- dashed-border-rect
|
||||
|
||||
## Configuration
|
||||
- dashed-border-color-[color]
|
||||
- dashed-border-width-[spacing]
|
||||
- dashed-border-length-[spacing]
|
||||
- dashed-border-gap-[spacing]
|
||||
- dashed-border-offset-[spacing]
|
||||
- dashed-border-align-["inside"/"edge"/"outside"]
|
||||
- dashed-border-align-[spacing] (0 - inside, toward the outside)
|
||||
|
||||
## Vanilla extended
|
||||
- duration-[time]
|
||||
- [timing function] (easing-linear, ease-in-out, etc.)
|
||||
- transition-all
|
||||
+357
@@ -0,0 +1,357 @@
|
||||
import plugin from "tailwindcss/plugin";
|
||||
import type {
|
||||
BareValueResolver,
|
||||
NamedUtilityValue as BareValue,
|
||||
MatchOptions,
|
||||
PluginAPI,
|
||||
MatchFn,
|
||||
} from "tailwindcss/plugin";
|
||||
import flattenColorPalette from "tailwindcss/lib/util/flattenColorPalette";
|
||||
|
||||
type FlatThemeLeaf = Record<string, string>;
|
||||
|
||||
const POSITIVE_NUMBER_PATTERN = /^(?:\d+|\d*\.\d+)$/; //accept numbers like "12", ".5", "12.5"
|
||||
|
||||
function omitDefault(values: FlatThemeLeaf): FlatThemeLeaf {
|
||||
const { DEFAULT: _default, ...rest } = values;
|
||||
return rest;
|
||||
}
|
||||
|
||||
function spacingBareValueResolver(candidate: BareValue): string | undefined {
|
||||
if (
|
||||
candidate.fraction !== null ||
|
||||
!POSITIVE_NUMBER_PATTERN.test(candidate.value)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return `calc(var(--spacing) * ${candidate.value})`;
|
||||
}
|
||||
|
||||
function borderWidthBareValueResolver(
|
||||
candidate: BareValue,
|
||||
): string | undefined {
|
||||
if (
|
||||
candidate.fraction !== null ||
|
||||
!POSITIVE_NUMBER_PATTERN.test(candidate.value)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return `${candidate.value}px`;
|
||||
}
|
||||
|
||||
// Please not use T as an object; that not how things used to be in tw anyway, just don't.
|
||||
type ConfigValuesTree<T = string> = {
|
||||
[key: string]: ConfigValuesTree<T> | T;
|
||||
};
|
||||
|
||||
function isConfigValuesTree<T>(
|
||||
x: ConfigValuesTree<T> | T,
|
||||
): x is ConfigValuesTree<T> {
|
||||
const isObject = typeof x === "object" && x !== null && !Array.isArray(x);
|
||||
return isObject;
|
||||
}
|
||||
|
||||
type BetterMatchUtility = {
|
||||
utilityName: string;
|
||||
arbitraryValueResolver: MatchFn;
|
||||
bareValueResolver?: BareValueResolver;
|
||||
type?: MatchOptions["type"];
|
||||
defaultValues?: ConfigValuesTree<unknown> | undefined;
|
||||
modifiers?: MatchOptions["modifiers"];
|
||||
supportsNegativeValues?: MatchOptions["supportsNegativeValues"];
|
||||
};
|
||||
function betterMatchUtility(
|
||||
matchUtilities: PluginAPI["matchUtilities"],
|
||||
): (utility: BetterMatchUtility | BetterMatchUtility[]) => void {
|
||||
function handleUtility(utility: BetterMatchUtility): void {
|
||||
// define utility base
|
||||
const arbitraryValueResolver: Record<string, MatchFn> = {
|
||||
[utility.utilityName]: utility.arbitraryValueResolver,
|
||||
};
|
||||
// define options
|
||||
const defaultValues = utility.defaultValues
|
||||
? { ...utility.defaultValues }
|
||||
: {};
|
||||
defaultValues["__CSS_VALUES__"] = undefined;
|
||||
const flatDefaultValues = flat(defaultValues);
|
||||
const stringValues = toStringOrSkip(flatDefaultValues);
|
||||
const values = utility.bareValueResolver
|
||||
? withBareValueResolver(stringValues, utility.bareValueResolver)
|
||||
: stringValues;
|
||||
const options: MatchOptions = {
|
||||
type: utility.type,
|
||||
supportsNegativeValues: utility.supportsNegativeValues,
|
||||
values,
|
||||
modifiers: utility.modifiers,
|
||||
};
|
||||
// Register this utility
|
||||
matchUtilities(arbitraryValueResolver, options);
|
||||
}
|
||||
|
||||
return function (utilities) {
|
||||
if (Array.isArray(utilities)) {
|
||||
utilities.forEach(handleUtility);
|
||||
} else {
|
||||
handleUtility(utilities);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
type MatchValues = Required<MatchOptions>["values"];
|
||||
function withBareValueResolver(
|
||||
values: FlatThemeLeaf,
|
||||
resolver: BareValueResolver,
|
||||
): MatchValues {
|
||||
const valuesWithResolver: MatchValues = {};
|
||||
Object.assign(valuesWithResolver, values);
|
||||
valuesWithResolver.__BARE_VALUE__ = resolver;
|
||||
return valuesWithResolver;
|
||||
}
|
||||
|
||||
function flat<T>(
|
||||
configPart: ConfigValuesTree<T>,
|
||||
path: string = "",
|
||||
): Record<string, T> {
|
||||
function addKeyToPath(path: string, key: string): string {
|
||||
if (key === "DEFAULT") return path;
|
||||
if (path === "") return key;
|
||||
return `${path}-${key}`;
|
||||
}
|
||||
|
||||
const flatConf: Record<string, T> = {};
|
||||
for (const [key, value] of Object.entries(configPart)) {
|
||||
const currentPath = addKeyToPath(path, key);
|
||||
if (isConfigValuesTree(value)) {
|
||||
Object.assign(flatConf, flat(value, currentPath));
|
||||
} else {
|
||||
flatConf[currentPath] = value;
|
||||
}
|
||||
}
|
||||
return flatConf;
|
||||
}
|
||||
|
||||
const _typeOf = typeof "any";
|
||||
type BaseTypes = typeof _typeOf;
|
||||
|
||||
function toStringOrSkip(
|
||||
flatConfigPart: Record<string, unknown>,
|
||||
): Record<string, string> {
|
||||
const allowedTypes: BaseTypes[] = ["bigint", "boolean", "number", "string"];
|
||||
const entries = Object.entries(flatConfigPart);
|
||||
const stringifiedEntries: [string, string][] = [];
|
||||
entries.forEach(([key, value]) => {
|
||||
const type = typeof value;
|
||||
if (allowedTypes.includes(type))
|
||||
stringifiedEntries.push([key, String(value)]);
|
||||
});
|
||||
const result = Object.fromEntries(stringifiedEntries);
|
||||
return result;
|
||||
}
|
||||
|
||||
function toCssVariable(
|
||||
cssVariable: string,
|
||||
): (value: string) => ReturnType<MatchFn> {
|
||||
return (value) => {
|
||||
const cssInJs = {
|
||||
[cssVariable]: value,
|
||||
};
|
||||
return cssInJs;
|
||||
};
|
||||
}
|
||||
|
||||
const dashedBorderPlugin: ReturnType<typeof plugin> = plugin(
|
||||
({ addBase, addUtilities, theme, ...pluginApi }) => {
|
||||
// const matchUtilities = pluginApi.matchUtilities;
|
||||
const matchUtilities = betterMatchUtility(pluginApi.matchUtilities);
|
||||
|
||||
const DEFAULT_GAP = "0.5rem";
|
||||
const DEFAULT_LENGTH = "0.75rem";
|
||||
const DEFAULT_OFFSET = "0px";
|
||||
const DEFAULT_WIDTH = "1px";
|
||||
const DEFAULT_COLOR = "currentColor";
|
||||
const DEFAULT_RADIUS = "0px";
|
||||
|
||||
addBase({
|
||||
"@property --tw-dashed-border-gap": {
|
||||
syntax: '"<length>"',
|
||||
inherits: "true",
|
||||
"initial-value": "8px",
|
||||
},
|
||||
"@property --tw-dashed-border-length": {
|
||||
syntax: '"<length>"',
|
||||
inherits: "true",
|
||||
"initial-value": "10px",
|
||||
},
|
||||
"@property --tw-dashed-border-offset": {
|
||||
syntax: '"<length>"',
|
||||
inherits: "true",
|
||||
"initial-value": "0px",
|
||||
},
|
||||
"@property --tw-dashed-border-width": {
|
||||
syntax: '"<length>"',
|
||||
inherits: "true",
|
||||
"initial-value": "1px",
|
||||
},
|
||||
"@property --tw-dashed-border-radius": {
|
||||
syntax: '"<length>"',
|
||||
inherits: "true",
|
||||
"initial-value": "currentColor",
|
||||
},
|
||||
"@property --tw-dashed-border-alignment": {
|
||||
syntax: '"<number>"',
|
||||
inherits: "true",
|
||||
"initial-value": "0",
|
||||
},
|
||||
"@property --tw-dashed-border-duration": {
|
||||
syntax: '"<time>"',
|
||||
inherits: "true",
|
||||
"initial-value": "150ms",
|
||||
},
|
||||
"@property --tw-dashed-border-ease": {
|
||||
syntax: '"*"',
|
||||
inherits: "true",
|
||||
"initial-value": "initial",
|
||||
},
|
||||
".dashed-border": {
|
||||
position: "relative",
|
||||
// "--tw-dashed-border-gap": DEFAULT_GAP,
|
||||
// "--tw-dashed-border-length": DEFAULT_LENGTH,
|
||||
// "--tw-dashed-border-offset": DEFAULT_OFFSET,
|
||||
// "--tw-dashed-border-width": DEFAULT_WIDTH,
|
||||
"--tw-dashed-border-color": DEFAULT_COLOR,
|
||||
"--tw-dashed-border-ease": "var(--tw-ease)",
|
||||
"--tw-dashed-border-duration": "var(--tw-duration)",
|
||||
// "--tw-dashed-border-radius": DEFAULT_RADIUS,
|
||||
},
|
||||
".dashed-border-svg": {
|
||||
position: "absolute",
|
||||
inset: "0",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
"pointer-events": "none",
|
||||
},
|
||||
});
|
||||
|
||||
const INSET =
|
||||
"calc((0.5 - var(--tw-dashed-border-alignment)) * var(--tw-dashed-border-width))";
|
||||
const INNER_SIZE =
|
||||
"calc(100% + var(--tw-dashed-border-width) * (2 * var(--tw-dashed-border-alignment) - 1))";
|
||||
const ADJUSTED_RADIUS =
|
||||
"max(0px, calc(var(--tw-dashed-border-radius) - (0.5 - var(--tw-dashed-border-alignment)) * var(--tw-dashed-border-width)))";
|
||||
|
||||
addBase({
|
||||
".dashed-border-rect": {
|
||||
fill: "none",
|
||||
stroke: "var(--tw-dashed-border-color)",
|
||||
"stroke-width": "var(--tw-dashed-border-width)",
|
||||
"stroke-dasharray":
|
||||
"var(--tw-dashed-border-length) var(--tw-dashed-border-gap)",
|
||||
"stroke-dashoffset": "var(--tw-dashed-border-offset)",
|
||||
"vector-effect": "non-scaling-stroke",
|
||||
x: INSET,
|
||||
y: INSET,
|
||||
width: INNER_SIZE,
|
||||
height: INNER_SIZE,
|
||||
rx: ADJUSTED_RADIUS,
|
||||
},
|
||||
});
|
||||
|
||||
const spacingRawValues = theme("spacing") as
|
||||
| ConfigValuesTree<string>
|
||||
| undefined;
|
||||
const spacingValues = flat(spacingRawValues ?? {});
|
||||
|
||||
const spacingUtil = (opts: {
|
||||
utilityName: string;
|
||||
supportsNegativeValues?: boolean;
|
||||
}): BetterMatchUtility => {
|
||||
return {
|
||||
utilityName: opts.utilityName,
|
||||
arbitraryValueResolver: toCssVariable(`--tw-${opts.utilityName}`),
|
||||
bareValueResolver: spacingBareValueResolver,
|
||||
defaultValues: spacingValues,
|
||||
supportsNegativeValues: opts.supportsNegativeValues,
|
||||
type: "length",
|
||||
};
|
||||
};
|
||||
|
||||
matchUtilities([
|
||||
spacingUtil({ utilityName: "dashed-border-gap" }),
|
||||
spacingUtil({ utilityName: "dashed-border-length" }),
|
||||
spacingUtil({
|
||||
utilityName: "dashed-border-offset",
|
||||
supportsNegativeValues: true,
|
||||
}),
|
||||
{
|
||||
utilityName: "dashed-border-align",
|
||||
arbitraryValueResolver: toCssVariable("--tw-dashed-border-alignment"),
|
||||
defaultValues: {
|
||||
inside: "0",
|
||||
edge: "0.5",
|
||||
outside: "1",
|
||||
},
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
utilityName: "dashed-border-width",
|
||||
arbitraryValueResolver: toCssVariable("--tw-dashed-border-width"),
|
||||
bareValueResolver: borderWidthBareValueResolver,
|
||||
defaultValues: omitDefault(theme("borderWidth")),
|
||||
type: "length",
|
||||
},
|
||||
]);
|
||||
|
||||
const colorValues = {
|
||||
DEFAULT: flattenColorPalette(theme("colors")),
|
||||
current: "currentColor",
|
||||
inherit: "inherit",
|
||||
transparent: "transparent",
|
||||
};
|
||||
|
||||
matchUtilities({
|
||||
utilityName: "dashed-border-color",
|
||||
arbitraryValueResolver: toCssVariable("--tw-dashed-border-color"),
|
||||
defaultValues: colorValues,
|
||||
type: "color",
|
||||
});
|
||||
|
||||
matchUtilities({
|
||||
utilityName: "rounded",
|
||||
arbitraryValueResolver: toCssVariable("--tw-dashed-border-radius"),
|
||||
defaultValues: omitDefault(theme("borderRadius")),
|
||||
});
|
||||
addUtilities({
|
||||
".rounded": {
|
||||
"--tw-dashed-border-radius": theme("borderRadius.DEFAULT"),
|
||||
},
|
||||
});
|
||||
|
||||
function createTransitionStyles(properties: string) {
|
||||
return {
|
||||
"transition-property": properties,
|
||||
"transition-duration":
|
||||
"var(--tw-dashed-border-duration, var(--default-transition-duration))",
|
||||
"transition-timing-function":
|
||||
"var(--tw-dashed-border-ease, var(--default-transition-timing-function))",
|
||||
};
|
||||
}
|
||||
const ALL_TRANSITION_PROPERTIES =
|
||||
"stroke-dasharray, stroke-dashoffset, stroke-width, stroke, rx, x, y, width, height";
|
||||
addUtilities({
|
||||
".dashed-border.transition .dashed-border-rect": createTransitionStyles(
|
||||
ALL_TRANSITION_PROPERTIES,
|
||||
),
|
||||
".dashed-border.transition-all .dashed-border-rect":
|
||||
createTransitionStyles(ALL_TRANSITION_PROPERTIES),
|
||||
".dashed-border.transition-colors .dashed-border-rect":
|
||||
createTransitionStyles("stroke"),
|
||||
|
||||
".dashed-border.transition-dashed-border .dashed-border-rect":
|
||||
createTransitionStyles(ALL_TRANSITION_PROPERTIES),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export default dashedBorderPlugin;
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
import plugin from "tailwindcss/plugin";
|
||||
|
||||
interface ThemeValueMap {
|
||||
[key: string]: string | ThemeValueMap;
|
||||
}
|
||||
|
||||
const DEFAULT_SPACING_VALUE = "0.25rem";
|
||||
const DEFAULT_GAP = `calc(var(--spacing, ${DEFAULT_SPACING_VALUE}) * 2)`;
|
||||
const DEFAULT_LENGTH = `calc(var(--spacing, ${DEFAULT_SPACING_VALUE}) * 3)`;
|
||||
const DEFAULT_BORDER_WIDTH = "1px";
|
||||
const DEFAULT_OFFSET = "0px";
|
||||
const DEFAULT_COLOR = "currentColor";
|
||||
const DEFAULT_RADIUS = "0px";
|
||||
const DEFAULT_ALIGN = "calc(var(--tw-dashed-border-width) / 2)";
|
||||
const dashedBorderAlign = "var(--tw-dashed-border-align)";
|
||||
const svgRadius = `max(0px, calc(var(--tw-dashed-border-radius) - (${dashedBorderAlign})))`;
|
||||
const dashedBorderTransitionProperties =
|
||||
"stroke-dasharray, stroke-dashoffset, stroke-width, stroke, rx, x, y, width, height";
|
||||
|
||||
const flattenThemeMap = (
|
||||
input: ThemeValueMap,
|
||||
path: string[] = [],
|
||||
): Record<string, string> => {
|
||||
const output: Record<string, string> = {};
|
||||
|
||||
for (const [key, value] of Object.entries(input)) {
|
||||
const nextPath = key === "DEFAULT" ? path : [...path, key];
|
||||
|
||||
if (typeof value === "string") {
|
||||
output[nextPath.join("-")] = value;
|
||||
continue;
|
||||
}
|
||||
|
||||
Object.assign(output, flattenThemeMap(value, nextPath));
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
const roundedSelector = (name: string): string =>
|
||||
name === "DEFAULT" ? ".rounded" : `.rounded-${name}`;
|
||||
|
||||
const resolveAlignValue = (value: string): string =>
|
||||
/^-?(?:\d+|\d*\.\d+)$/.test(value)
|
||||
? `calc((0.5 - ${value}) * var(--tw-dashed-border-width))`
|
||||
: `calc((var(--tw-dashed-border-width) / 2) - (${value}))`;
|
||||
|
||||
const dashedBorderPlugin: ReturnType<typeof plugin> = plugin(
|
||||
({ addBase, addUtilities, matchUtilities, theme }) => {
|
||||
addBase({
|
||||
"@property --tw-dashed-border-gap": {
|
||||
syntax: "<length-percentage>",
|
||||
inherits: "true",
|
||||
"initial-value": "0.5rem",
|
||||
},
|
||||
"@property --tw-dashed-border-length": {
|
||||
syntax: "<length-percentage>",
|
||||
inherits: "true",
|
||||
"initial-value": "0.75rem",
|
||||
},
|
||||
"@property --tw-dashed-border-offset": {
|
||||
syntax: "<length-percentage>",
|
||||
inherits: "true",
|
||||
"initial-value": DEFAULT_OFFSET,
|
||||
},
|
||||
"@property --tw-dashed-border-width": {
|
||||
syntax: "<length>",
|
||||
inherits: "true",
|
||||
"initial-value": DEFAULT_BORDER_WIDTH,
|
||||
},
|
||||
"@property --tw-dashed-border-align": {
|
||||
syntax: "<length-percentage>",
|
||||
inherits: "true",
|
||||
"initial-value": DEFAULT_ALIGN,
|
||||
},
|
||||
"@property --tw-dashed-border-radius": {
|
||||
syntax: "<length-percentage>",
|
||||
inherits: "true",
|
||||
"initial-value": DEFAULT_RADIUS,
|
||||
},
|
||||
});
|
||||
|
||||
addUtilities({
|
||||
".dashed-border": {
|
||||
"--tw-dashed-border-gap": DEFAULT_GAP,
|
||||
"--tw-dashed-border-length": DEFAULT_LENGTH,
|
||||
"--tw-dashed-border-offset": DEFAULT_OFFSET,
|
||||
"--tw-dashed-border-width": DEFAULT_BORDER_WIDTH,
|
||||
"--tw-dashed-border-color": DEFAULT_COLOR,
|
||||
"--tw-dashed-border-align": DEFAULT_ALIGN,
|
||||
"--tw-dashed-border-radius": DEFAULT_RADIUS,
|
||||
position: "relative",
|
||||
},
|
||||
".dashed-border-svg": {
|
||||
display: "block",
|
||||
position: "absolute",
|
||||
inset: "0",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
overflow: "hidden",
|
||||
pointerEvents: "none",
|
||||
},
|
||||
".dashed-border-rect": {
|
||||
fill: "none",
|
||||
stroke: "var(--tw-dashed-border-color)",
|
||||
strokeWidth: "var(--tw-dashed-border-width)",
|
||||
strokeDasharray:
|
||||
"var(--tw-dashed-border-length) var(--tw-dashed-border-gap)",
|
||||
strokeDashoffset: "var(--tw-dashed-border-offset)",
|
||||
shapeRendering: "geometricPrecision",
|
||||
vectorEffect: "non-scaling-stroke",
|
||||
x: dashedBorderAlign,
|
||||
y: dashedBorderAlign,
|
||||
width: `calc(100% - (${dashedBorderAlign} * 2))`,
|
||||
height: `calc(100% - (${dashedBorderAlign} * 2))`,
|
||||
rx: svgRadius,
|
||||
},
|
||||
".dashed-border-transition, .dashed-border.transition, .dashed-border.transition-all":
|
||||
{
|
||||
transitionTimingFunction:
|
||||
"var(--default-transition-timing-function, cubic-bezier(0.4, 0, 0.2, 1))",
|
||||
transitionDuration: "var(--default-transition-duration, 150ms)",
|
||||
},
|
||||
".dashed-border.transition-colors": {
|
||||
transitionTimingFunction:
|
||||
"var(--default-transition-timing-function, cubic-bezier(0.4, 0, 0.2, 1))",
|
||||
transitionDuration: "var(--default-transition-duration, 150ms)",
|
||||
},
|
||||
".dashed-border-transition .dashed-border-rect, .dashed-border.transition .dashed-border-rect, .dashed-border.transition-all .dashed-border-rect":
|
||||
{
|
||||
transitionProperty: dashedBorderTransitionProperties,
|
||||
transitionTimingFunction: "inherit",
|
||||
transitionDuration: "inherit",
|
||||
},
|
||||
".dashed-border.transition-colors .dashed-border-rect": {
|
||||
transitionProperty: "stroke",
|
||||
transitionTimingFunction: "inherit",
|
||||
transitionDuration: "inherit",
|
||||
},
|
||||
});
|
||||
|
||||
const borderWidths = theme("borderWidth", {
|
||||
DEFAULT: DEFAULT_BORDER_WIDTH,
|
||||
}) as Record<string, string>;
|
||||
const colors = flattenThemeMap(theme("colors", {}) as ThemeValueMap);
|
||||
const radii = theme("borderRadius", {
|
||||
DEFAULT: DEFAULT_RADIUS,
|
||||
}) as Record<string, string>;
|
||||
|
||||
matchUtilities(
|
||||
{
|
||||
"dashed-border-gap": (value: string) => ({
|
||||
"--tw-dashed-border-gap": value,
|
||||
}),
|
||||
"dashed-border-length": (value: string) => ({
|
||||
"--tw-dashed-border-length": value,
|
||||
}),
|
||||
"dashed-border-offset": (value: string) => ({
|
||||
"--tw-dashed-border-offset": value,
|
||||
}),
|
||||
"dashed-border-align": (value: string) => ({
|
||||
"--tw-dashed-border-align": resolveAlignValue(value),
|
||||
}),
|
||||
"dashed-border-radius": (value: string) => ({
|
||||
"--tw-dashed-border-radius": value,
|
||||
}),
|
||||
},
|
||||
{
|
||||
values: {
|
||||
inside: DEFAULT_ALIGN,
|
||||
edge: "0px",
|
||||
outside: "calc(var(--tw-dashed-border-width) / -2)",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
matchUtilities(
|
||||
{
|
||||
"dashed-border-width": (value: string) => ({
|
||||
"--tw-dashed-border-width": value,
|
||||
}),
|
||||
},
|
||||
{
|
||||
values: borderWidths,
|
||||
},
|
||||
);
|
||||
|
||||
matchUtilities(
|
||||
{
|
||||
"dashed-border-color": (value: string) => ({
|
||||
"--tw-dashed-border-color": value,
|
||||
}),
|
||||
},
|
||||
{
|
||||
values: {
|
||||
...colors,
|
||||
current: "currentColor",
|
||||
inherit: "inherit",
|
||||
transparent: "transparent",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
addUtilities(
|
||||
Object.fromEntries(
|
||||
Object.entries(radii).map(([name, value]) => [
|
||||
`.dashed-border${roundedSelector(name)}`,
|
||||
{
|
||||
"--tw-dashed-border-radius": value,
|
||||
},
|
||||
]),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default dashedBorderPlugin;
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
import plugin from "tailwindcss/plugin";
|
||||
|
||||
interface ThemeValueMap {
|
||||
[key: string]: string | ThemeValueMap;
|
||||
}
|
||||
|
||||
const DEFAULT_SPACING_VALUE = "0.25rem";
|
||||
const DEFAULT_GAP = `calc(var(--spacing, ${DEFAULT_SPACING_VALUE}) * 2)`;
|
||||
const DEFAULT_LENGTH = `calc(var(--spacing, ${DEFAULT_SPACING_VALUE}) * 3)`;
|
||||
const DEFAULT_BORDER_WIDTH = "1px";
|
||||
const DEFAULT_OFFSET = "0px";
|
||||
const DEFAULT_COLOR = "currentColor";
|
||||
const DEFAULT_RADIUS = "0px";
|
||||
const DEFAULT_ALIGNMENT = "0%";
|
||||
const dashedBorderInset =
|
||||
"calc((0.5 - (var(--tw-dashed-border-alignment) / 100%)) * var(--tw-dashed-border-width))";
|
||||
const svgRadius = `max(0px, calc(var(--tw-dashed-border-radius) - (${dashedBorderInset})))`;
|
||||
const dashedBorderTransitionProperties =
|
||||
"stroke-dasharray, stroke-dashoffset, stroke-width, stroke, rx, x, y, width, height";
|
||||
|
||||
const flattenThemeMap = (
|
||||
input: ThemeValueMap,
|
||||
path: string[] = [],
|
||||
): Record<string, string> => {
|
||||
const output: Record<string, string> = {};
|
||||
|
||||
for (const [key, value] of Object.entries(input)) {
|
||||
const nextPath = key === "DEFAULT" ? path : [...path, key];
|
||||
|
||||
if (typeof value === "string") {
|
||||
output[nextPath.join("-")] = value;
|
||||
continue;
|
||||
}
|
||||
|
||||
Object.assign(output, flattenThemeMap(value, nextPath));
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
const roundedSelector = (name: string): string =>
|
||||
name === "DEFAULT" ? ".rounded" : `.rounded-${name}`;
|
||||
|
||||
const dashedBorderPlugin: ReturnType<typeof plugin> = plugin(
|
||||
({ addBase, addUtilities, matchUtilities, theme }) => {
|
||||
addBase({
|
||||
"@property --tw-dashed-border-gap": {
|
||||
syntax: "<length-percentage>",
|
||||
inherits: "true",
|
||||
"initial-value": "0.5rem",
|
||||
},
|
||||
"@property --tw-dashed-border-length": {
|
||||
syntax: "<length-percentage>",
|
||||
inherits: "true",
|
||||
"initial-value": "0.75rem",
|
||||
},
|
||||
"@property --tw-dashed-border-offset": {
|
||||
syntax: "<length-percentage>",
|
||||
inherits: "true",
|
||||
"initial-value": DEFAULT_OFFSET,
|
||||
},
|
||||
"@property --tw-dashed-border-width": {
|
||||
syntax: "<length>",
|
||||
inherits: "true",
|
||||
"initial-value": DEFAULT_BORDER_WIDTH,
|
||||
},
|
||||
"@property --tw-dashed-border-alignment": {
|
||||
syntax: "<percentage>",
|
||||
inherits: "true",
|
||||
"initial-value": DEFAULT_ALIGNMENT,
|
||||
},
|
||||
"@property --tw-dashed-border-radius": {
|
||||
syntax: "<length-percentage>",
|
||||
inherits: "true",
|
||||
"initial-value": DEFAULT_RADIUS,
|
||||
},
|
||||
});
|
||||
|
||||
addUtilities({
|
||||
".dashed-border": {
|
||||
"--tw-dashed-border-gap": DEFAULT_GAP,
|
||||
"--tw-dashed-border-length": DEFAULT_LENGTH,
|
||||
"--tw-dashed-border-offset": DEFAULT_OFFSET,
|
||||
"--tw-dashed-border-width": DEFAULT_BORDER_WIDTH,
|
||||
"--tw-dashed-border-color": DEFAULT_COLOR,
|
||||
"--tw-dashed-border-alignment": DEFAULT_ALIGNMENT,
|
||||
"--tw-dashed-border-radius": DEFAULT_RADIUS,
|
||||
position: "relative",
|
||||
},
|
||||
".dashed-border-svg": {
|
||||
display: "block",
|
||||
position: "absolute",
|
||||
inset: "0",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
overflow: "hidden",
|
||||
pointerEvents: "none",
|
||||
},
|
||||
".dashed-border-rect": {
|
||||
fill: "none",
|
||||
stroke: "var(--tw-dashed-border-color)",
|
||||
strokeWidth: "var(--tw-dashed-border-width)",
|
||||
strokeDasharray:
|
||||
"var(--tw-dashed-border-length) var(--tw-dashed-border-gap)",
|
||||
strokeDashoffset: "var(--tw-dashed-border-offset)",
|
||||
shapeRendering: "geometricPrecision",
|
||||
vectorEffect: "non-scaling-stroke",
|
||||
x: dashedBorderInset,
|
||||
y: dashedBorderInset,
|
||||
width: `calc(100% - (${dashedBorderInset} * 2))`,
|
||||
height: `calc(100% - (${dashedBorderInset} * 2))`,
|
||||
rx: svgRadius,
|
||||
},
|
||||
".dashed-border-transition, .dashed-border.transition, .dashed-border.transition-all":
|
||||
{
|
||||
transitionTimingFunction:
|
||||
"var(--default-transition-timing-function, cubic-bezier(0.4, 0, 0.2, 1))",
|
||||
transitionDuration: "var(--default-transition-duration, 150ms)",
|
||||
},
|
||||
".dashed-border.transition-colors": {
|
||||
transitionTimingFunction:
|
||||
"var(--default-transition-timing-function, cubic-bezier(0.4, 0, 0.2, 1))",
|
||||
transitionDuration: "var(--default-transition-duration, 150ms)",
|
||||
},
|
||||
".dashed-border-transition .dashed-border-rect, .dashed-border.transition .dashed-border-rect, .dashed-border.transition-all .dashed-border-rect":
|
||||
{
|
||||
transitionProperty: dashedBorderTransitionProperties,
|
||||
transitionTimingFunction: "inherit",
|
||||
transitionDuration: "inherit",
|
||||
},
|
||||
".dashed-border.transition-colors .dashed-border-rect": {
|
||||
transitionProperty: "stroke",
|
||||
transitionTimingFunction: "inherit",
|
||||
transitionDuration: "inherit",
|
||||
},
|
||||
});
|
||||
|
||||
const spacing = theme("spacing", {}) as Record<string, string>;
|
||||
const borderWidths = theme("borderWidth", {
|
||||
DEFAULT: DEFAULT_BORDER_WIDTH,
|
||||
}) as Record<string, string>;
|
||||
const colors = flattenThemeMap(theme("colors", {}) as ThemeValueMap);
|
||||
const radii = theme("borderRadius", {
|
||||
DEFAULT: DEFAULT_RADIUS,
|
||||
}) as Record<string, string>;
|
||||
|
||||
matchUtilities(
|
||||
{
|
||||
"dashed-border-gap": (value: string) => ({
|
||||
"--tw-dashed-border-gap": value,
|
||||
}),
|
||||
"dashed-border-length": (value: string) => ({
|
||||
"--tw-dashed-border-length": value,
|
||||
}),
|
||||
"dashed-border-offset": (value: string) => ({
|
||||
"--tw-dashed-border-offset": value,
|
||||
}),
|
||||
"dashed-border-align": (value: string) => ({
|
||||
"--tw-dashed-border-alignment": value,
|
||||
}),
|
||||
"dashed-border-radius": (value: string) => ({
|
||||
"--tw-dashed-border-radius": value,
|
||||
}),
|
||||
},
|
||||
{
|
||||
values: spacing,
|
||||
},
|
||||
);
|
||||
|
||||
matchUtilities(
|
||||
{
|
||||
"dashed-border-width": (value: string) => ({
|
||||
"--tw-dashed-border-width": value,
|
||||
}),
|
||||
},
|
||||
{
|
||||
values: borderWidths,
|
||||
},
|
||||
);
|
||||
|
||||
matchUtilities(
|
||||
{
|
||||
"dashed-border-color": (value: string) => ({
|
||||
"--tw-dashed-border-color": value,
|
||||
}),
|
||||
},
|
||||
{
|
||||
values: {
|
||||
...colors,
|
||||
current: "currentColor",
|
||||
inherit: "inherit",
|
||||
transparent: "transparent",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
addUtilities(
|
||||
Object.fromEntries(
|
||||
Object.entries(radii).map(([name, value]) => [
|
||||
`.dashed-border${roundedSelector(name)}`,
|
||||
{
|
||||
"--tw-dashed-border-radius": value,
|
||||
},
|
||||
]),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default dashedBorderPlugin;
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { createRequire } from 'node:module'
|
||||
import plugin from 'tailwindcss/plugin'
|
||||
import { compile } from 'tailwindcss'
|
||||
import flattenColorPalette from 'tailwindcss/lib/util/flattenColorPalette'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const pluginTest = plugin(({ theme, config }) => {
|
||||
const raw = theme('colors')
|
||||
const flat = flattenColorPalette(raw ?? {})
|
||||
const random_fn = theme("backgroundOpacity.__BARE_VALUE__")
|
||||
const random_fn = theme("backgroundOpacity.__BARE_VALUE__")
|
||||
console.log('default bare function at 80', random_fn, random_fn({value: 80, fraction: null}));
|
||||
})
|
||||
|
||||
await compile('@import "tailwindcss"; @plugin "inspect-plugin";', {
|
||||
base: process.cwd(),
|
||||
async loadModule(id, base) {
|
||||
if (id === 'inspect-plugin') {
|
||||
return { path: 'inspect-plugin', base, module: pluginTest }
|
||||
}
|
||||
},
|
||||
async loadStylesheet(id, base) {
|
||||
if (id === 'tailwindcss') {
|
||||
const pkg = path.dirname(require.resolve('tailwindcss/package.json'))
|
||||
const file = path.join(pkg, 'index.css')
|
||||
return { path: file, base: path.dirname(file), content: await fs.readFile(file, 'utf8') }
|
||||
}
|
||||
const resolved = path.resolve(base, id)
|
||||
return { path: resolved, base: path.dirname(resolved), content: await fs.readFile(resolved, 'utf8') }
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user