This commit is contained in:
Mira
2026-08-04 21:57:46 +03:00
parent 47d03a7c2c
commit e7937bd51b
6 changed files with 0 additions and 6969 deletions
-48
View File
@@ -1,48 +0,0 @@
# 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>
);
}
```
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
-217
View File
@@ -1,217 +0,0 @@
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
View File
@@ -1,210 +0,0 @@
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
View File
@@ -1,33 +0,0 @@
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') }
},
})