Everything broken...

This commit is contained in:
Mira
2026-04-15 14:16:05 +03:00
parent e4c91c4a93
commit c64db01bb4
2 changed files with 352 additions and 29 deletions
+306
View File
@@ -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`.
+46 -29
View File
@@ -11,11 +11,14 @@ 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 svgRadius =
"max(0px, calc(var(--tw-dashed-border-radius) - (var(--tw-dashed-border-width) / 2)))";
const dashedBorderTransitionProperties =
"stroke-dasharray, stroke-dashoffset, stroke-width, stroke, rx, x, y, width, height";
"stroke-dasharray, stroke-dashoffset, stroke-width, stroke, rx";
const strokeDasharrayValue = (
length: string,
gap: string,
): string => `${length} max(${gap}, 0.001px)`;
const flattenThemeMap = (
input: ThemeValueMap,
@@ -40,11 +43,6 @@ const flattenThemeMap = (
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({
@@ -68,11 +66,6 @@ const dashedBorderPlugin: ReturnType<typeof plugin> = plugin(
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",
@@ -87,7 +80,6 @@ const dashedBorderPlugin: ReturnType<typeof plugin> = plugin(
"--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",
},
@@ -104,15 +96,17 @@ const dashedBorderPlugin: ReturnType<typeof plugin> = plugin(
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)",
strokeDasharray: strokeDasharrayValue(
"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))`,
x: "calc(var(--tw-dashed-border-width) / 2)",
y: "calc(var(--tw-dashed-border-width) / 2)",
width: "calc(100% - var(--tw-dashed-border-width))",
height: "calc(100% - var(--tw-dashed-border-width))",
rx: svgRadius,
},
".dashed-border-transition, .dashed-border.transition, .dashed-border.transition-all":
@@ -139,6 +133,7 @@ const dashedBorderPlugin: ReturnType<typeof plugin> = plugin(
},
});
const spacing = theme("spacing", {}) as Record<string, string>;
const borderWidths = theme("borderWidth", {
DEFAULT: DEFAULT_BORDER_WIDTH,
}) as Record<string, string>;
@@ -151,26 +146,37 @@ const dashedBorderPlugin: ReturnType<typeof plugin> = plugin(
{
"dashed-border-gap": (value: string) => ({
"--tw-dashed-border-gap": value,
"& .dashed-border-rect": {
strokeDasharray: strokeDasharrayValue(
"var(--tw-dashed-border-length)",
value,
),
},
}),
"dashed-border-length": (value: string) => ({
"--tw-dashed-border-length": value,
"& .dashed-border-rect": {
strokeDasharray: strokeDasharrayValue(
value,
"var(--tw-dashed-border-gap)",
),
},
}),
"dashed-border-offset": (value: string) => ({
"--tw-dashed-border-offset": value,
}),
"dashed-border-align": (value: string) => ({
"--tw-dashed-border-align": resolveAlignValue(value),
"& .dashed-border-rect": {
strokeDashoffset: value,
},
}),
"dashed-border-radius": (value: string) => ({
"--tw-dashed-border-radius": value,
"& .dashed-border-rect": {
rx: `max(0px, calc(${value} - (var(--tw-dashed-border-width) / 2)))`,
},
}),
},
{
values: {
inside: DEFAULT_ALIGN,
edge: "0px",
outside: "calc(var(--tw-dashed-border-width) / -2)",
},
values: spacing,
},
);
@@ -178,6 +184,14 @@ const dashedBorderPlugin: ReturnType<typeof plugin> = plugin(
{
"dashed-border-width": (value: string) => ({
"--tw-dashed-border-width": value,
"& .dashed-border-rect": {
strokeWidth: value,
x: `calc(${value} / 2)`,
y: `calc(${value} / 2)`,
width: `calc(100% - ${value})`,
height: `calc(100% - ${value})`,
rx: `max(0px, calc(var(--tw-dashed-border-radius) - (${value} / 2)))`,
},
}),
},
{
@@ -189,6 +203,9 @@ const dashedBorderPlugin: ReturnType<typeof plugin> = plugin(
{
"dashed-border-color": (value: string) => ({
"--tw-dashed-border-color": value,
"& .dashed-border-rect": {
stroke: value,
},
}),
},
{