Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bd83d28400 |
@@ -1,75 +0,0 @@
|
|||||||
# Tailwind Dashed Border SVG
|
|
||||||
|
|
||||||
A plugin for addressing issue with customizable and animatable dashed border via tailwind interface (mostly).
|
|
||||||
|
|
||||||
It uses real svg under the hood what provide the maximum flexibility.
|
|
||||||
|
|
||||||
# Setup
|
|
||||||
|
|
||||||
for the tailwind v4 use this in your css file:
|
|
||||||
```css
|
|
||||||
@import "tailwindcss";
|
|
||||||
@plugin "tailwind-dashed-border";
|
|
||||||
```
|
|
||||||
|
|
||||||
then prepare a component for svg in case you don't want repeat yourself everywhere. I will show it as it would look in React, but similar approach should work in any framework.
|
|
||||||
|
|
||||||
```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>
|
|
||||||
```
|
|
||||||
|
|
||||||
# Usage Example
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
<div
|
|
||||||
className="
|
|
||||||
px-14 py-10 bg-purple-900 rounded-2xl text-2xl
|
|
||||||
|
|
||||||
dashed-border dashed-border-width-5 dashed-border-color-orange-600
|
|
||||||
dashed-border-align-edge
|
|
||||||
duration-300 ease-in-out transition-all
|
|
||||||
dashed-border-gap-0 dashed-border-length-9
|
|
||||||
hover:dashed-border-gap-3 hover:dashed-border-length-6
|
|
||||||
dashed-border-offset-2.5 hover:dashed-border-offset-1
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<DashedBorderSvg />
|
|
||||||
<div>Content</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||

|
|
||||||
|
|
||||||
# Plugin 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, 1 - outside, etc.)
|
|
||||||
|
|
||||||
## Vanilla extended
|
|
||||||
- `duration-[time]`
|
|
||||||
- `[timing function]` (`easing-linear`, `ease-in-out`, etc.)
|
|
||||||
- `transition-all` - animation may not work without it.
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB |
+6509
File diff suppressed because it is too large
Load Diff
+1
-11
@@ -2,8 +2,6 @@
|
|||||||
"name": "tailwind-dashed-border",
|
"name": "tailwind-dashed-border",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "Configurable dashed border utility for Tailwind CSS v4",
|
"description": "Configurable dashed border utility for Tailwind CSS v4",
|
||||||
"author": "Mira Gluhih",
|
|
||||||
"contributors": [],
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
@@ -21,10 +19,6 @@
|
|||||||
"build": "tsc -p tsconfig.json",
|
"build": "tsc -p tsconfig.json",
|
||||||
"check": "tsc -p tsconfig.json --noEmit"
|
"check": "tsc -p tsconfig.json --noEmit"
|
||||||
},
|
},
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/ArsenijSerjogin/tailwind-dashed-border.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"tailwindcss",
|
"tailwindcss",
|
||||||
"tailwind",
|
"tailwind",
|
||||||
@@ -42,9 +36,5 @@
|
|||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/ArsenijSerjogin/tailwind-dashed-border/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/ArsenijSerjogin/tailwind-dashed-border"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
prepare a component for svg
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
export function BorderSVG() {
|
||||||
|
return (
|
||||||
|
<svg className="dashed-border-svg" aria-hidden="true">
|
||||||
|
<rect className="dashed-border-rect" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BorderBottomLineSVG() {
|
||||||
|
return (
|
||||||
|
<svg className="dashed-border-svg" aria-hidden="true">
|
||||||
|
<line
|
||||||
|
className="dashed-border-bottom-line"
|
||||||
|
x1="0"
|
||||||
|
x2="100%"
|
||||||
|
y1="100%"
|
||||||
|
y2="100%"
|
||||||
|
/>
|
||||||
|
</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
|
||||||
|
- dashed-border-bottom-line
|
||||||
|
|
||||||
|
## 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
|
||||||
@@ -163,9 +163,15 @@ function toCssVariable(
|
|||||||
|
|
||||||
const dashedBorderPlugin: ReturnType<typeof plugin> = plugin(
|
const dashedBorderPlugin: ReturnType<typeof plugin> = plugin(
|
||||||
({ addBase, addUtilities, theme, ...pluginApi }) => {
|
({ addBase, addUtilities, theme, ...pluginApi }) => {
|
||||||
|
// const matchUtilities = pluginApi.matchUtilities;
|
||||||
const matchUtilities = betterMatchUtility(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_COLOR = "currentColor";
|
||||||
|
const DEFAULT_RADIUS = "0px";
|
||||||
|
|
||||||
addBase({
|
addBase({
|
||||||
"@property --tw-dashed-border-gap": {
|
"@property --tw-dashed-border-gap": {
|
||||||
@@ -210,9 +216,14 @@ const dashedBorderPlugin: ReturnType<typeof plugin> = plugin(
|
|||||||
},
|
},
|
||||||
".dashed-border": {
|
".dashed-border": {
|
||||||
position: "relative",
|
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-color": DEFAULT_COLOR,
|
||||||
"--tw-dashed-border-ease": "var(--tw-ease)",
|
"--tw-dashed-border-ease": "var(--tw-ease)",
|
||||||
"--tw-dashed-border-duration": "var(--tw-duration)",
|
"--tw-dashed-border-duration": "var(--tw-duration)",
|
||||||
|
// "--tw-dashed-border-radius": DEFAULT_RADIUS,
|
||||||
},
|
},
|
||||||
".dashed-border-svg": {
|
".dashed-border-svg": {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
@@ -245,6 +256,17 @@ const dashedBorderPlugin: ReturnType<typeof plugin> = plugin(
|
|||||||
height: INNER_SIZE,
|
height: INNER_SIZE,
|
||||||
rx: ADJUSTED_RADIUS,
|
rx: ADJUSTED_RADIUS,
|
||||||
},
|
},
|
||||||
|
".dashed-border-bottom-line": {
|
||||||
|
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)",
|
||||||
|
// "stroke-linecap": "butt",
|
||||||
|
"vector-effect": "non-scaling-stroke",
|
||||||
|
transform: `translateY(${INSET})`,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const spacingRawValues = theme("spacing") as
|
const spacingRawValues = theme("spacing") as
|
||||||
@@ -328,17 +350,27 @@ const dashedBorderPlugin: ReturnType<typeof plugin> = plugin(
|
|||||||
}
|
}
|
||||||
const ALL_TRANSITION_PROPERTIES =
|
const ALL_TRANSITION_PROPERTIES =
|
||||||
"stroke-dasharray, stroke-dashoffset, stroke-width, stroke, rx, x, y, width, height";
|
"stroke-dasharray, stroke-dashoffset, stroke-width, stroke, rx, x, y, width, height";
|
||||||
|
const ALL_LINE_TRANSITION_PROPERTIES =
|
||||||
|
"stroke-dasharray, stroke-dashoffset, stroke-width, stroke, transform";
|
||||||
addUtilities({
|
addUtilities({
|
||||||
".dashed-border.transition .dashed-border-rect": createTransitionStyles(
|
".dashed-border.transition .dashed-border-rect": createTransitionStyles(
|
||||||
ALL_TRANSITION_PROPERTIES,
|
ALL_TRANSITION_PROPERTIES,
|
||||||
),
|
),
|
||||||
|
".dashed-border.transition .dashed-border-bottom-line":
|
||||||
|
createTransitionStyles(ALL_LINE_TRANSITION_PROPERTIES),
|
||||||
".dashed-border.transition-all .dashed-border-rect":
|
".dashed-border.transition-all .dashed-border-rect":
|
||||||
createTransitionStyles(ALL_TRANSITION_PROPERTIES),
|
createTransitionStyles(ALL_TRANSITION_PROPERTIES),
|
||||||
|
".dashed-border.transition-all .dashed-border-bottom-line":
|
||||||
|
createTransitionStyles(ALL_LINE_TRANSITION_PROPERTIES),
|
||||||
".dashed-border.transition-colors .dashed-border-rect":
|
".dashed-border.transition-colors .dashed-border-rect":
|
||||||
createTransitionStyles("stroke"),
|
createTransitionStyles("stroke"),
|
||||||
|
".dashed-border.transition-colors .dashed-border-bottom-line":
|
||||||
|
createTransitionStyles("stroke"),
|
||||||
|
|
||||||
".dashed-border.transition-dashed-border .dashed-border-rect":
|
".dashed-border.transition-dashed-border .dashed-border-rect":
|
||||||
createTransitionStyles(ALL_TRANSITION_PROPERTIES),
|
createTransitionStyles(ALL_TRANSITION_PROPERTIES),
|
||||||
|
".dashed-border.transition-dashed-border .dashed-border-bottom-line":
|
||||||
|
createTransitionStyles(ALL_LINE_TRANSITION_PROPERTIES),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
+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') }
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user