tailvars 1.1.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "docs",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "@boxicons/js": "^1.0.1",
13
+ "@fontsource/ibm-plex-sans": "^5.2.8",
14
+ "@fontsource/ibm-plex-serif": "^5.2.7",
15
+ "lit": "^3.3.1",
16
+ "tailvars": "file:..",
17
+ "universal-router": "^10.0.3"
18
+ },
19
+ "devDependencies": {
20
+ "@fontsource/vt323": "^5.2.7",
21
+ "postcss-custom-media": "^12.0.1",
22
+ "vite": "^8.0.0-beta.13"
23
+ },
24
+ "overrides": {
25
+ "vite": "^8.0.0-beta.13"
26
+ }
27
+ }
@@ -0,0 +1,5 @@
1
+ import postcssCustomMedia from "postcss-custom-media";
2
+
3
+ export default {
4
+ plugins: [postcssCustomMedia()],
5
+ };
Binary file
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="25.6" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 320"><path fill="#00E8FF" d="m64 192l25.926-44.727l38.233-19.114l63.974 63.974l10.833 61.754L192 320l-64-64l-38.074-25.615z"></path><path fill="#283198" d="M128 256V128l64-64v128l-64 64ZM0 256l64 64l9.202-60.602L64 192l-37.542 23.71L0 256Z"></path><path fill="#324FFF" d="M64 192V64l64-64v128l-64 64Zm128 128V192l64-64v128l-64 64ZM0 256V128l64 64l-64 64Z"></path><path fill="#0FF" d="M64 320V192l64 64z"></path></svg>
@@ -0,0 +1,74 @@
1
+ import { css, html, LitElement } from "lit";
2
+ import { GlobalCSSMixin } from "../global-css-mixin";
3
+ /**
4
+ * An example element.
5
+ *
6
+ * @slot - This element has a slot
7
+ * @csspart button - The button
8
+ */
9
+ export class AboutSection extends GlobalCSSMixin(LitElement) {
10
+ static properties = {
11
+ title: {},
12
+ };
13
+
14
+ render() {
15
+ return html`
16
+ <section>
17
+ <h2>Why another CSS library?</h2>
18
+ <p>
19
+ Most think that CSS has been solved at this point. We don't need any more Bootstraps or Bulmas, and if we do, well, you can just install Bootstrap or Bulma - problem solved! I was also like that, until I actually <i>tried</i> to write CSS without a library. Turns out, it's easier said than done to design an application when given <i>all</i> the tools. I was a kid in a candy shop. What colors should I use? How wide should a button be? It quickly became impossible to maintain any level of consistency.
20
+ </p>
21
+ <p>
22
+ <a href="https://tailwindcss.com" target="_blank">TailwindCSS</a> solves this issue elegantly, something that isn't surprising considering <a href="https://refactoringui.com" target="_blank">Refactoring UI</a>, also written by Stever Schroger, regularly speaks about this issue. But TailwindCSS is not just a system of style values, it's also a <a href="https://v1.tailwindcss.com/#what-is-tailwind" target="_blank"><i>philosophy</i></a>&mdash;A philosophy I quickly found myself disagreeing with. Scrapping CSS for a system of only utility classes comes with a few uncomfortable realities:
23
+ <ul>
24
+ <li>The HTML code quickly gets hard to read and filled with class names.</li>
25
+ <li>Toggling or applying groups of classes depending on state is burdensome, requiring workarounds like dictionaries or <code>@apply</code>.</li>
26
+ <li>Many frontend frameworks don't consider tailwind classes well enough in their implementations.</li>
27
+ </ul>
28
+ </p>
29
+ <p>
30
+ CSS now supports nesting natively and scoped CSS is available in most framework or through shadow DOMs. With that in mind, the goal of tailvars is to maintain the spirit of the TailwindCSS style system (which I quite like) but in the form of CSS custom properties, rather than utility classes. Custom properties have their own strengths and weaknesses, of course, but I found them more practical to work with.
31
+ </p>
32
+ </section>
33
+ `;
34
+ }
35
+
36
+ static styles = css`
37
+ :host {
38
+ display: block;
39
+ }
40
+
41
+ section {
42
+ padding-top: min(8vw, var(--size-12));
43
+ padding-bottom: min(8vw, var(--size-8));
44
+ }
45
+
46
+ h2 {
47
+ font: var(--font-heading-md);
48
+ margin-bottom: var(--size-1);
49
+ }
50
+
51
+ a {
52
+ text-decoration: underline;
53
+ }
54
+
55
+ p {
56
+ margin-bottom: var(--size-2);
57
+ }
58
+
59
+ h3 {
60
+ font: var(--font-heading-sm);
61
+ }
62
+
63
+ ul {
64
+ list-style: disc outside;
65
+ margin-left: var(--size-4);
66
+
67
+ & li {
68
+ margin-bottom: var(--size-2);
69
+ }
70
+ }
71
+ `;
72
+ }
73
+
74
+ window.customElements.define("about-section", AboutSection);
@@ -0,0 +1,31 @@
1
+ import { createElement } from "@boxicons/js";
2
+ import { css, LitElement } from "lit";
3
+ import { GlobalCSSMixin } from "../global-css-mixin";
4
+
5
+ /**
6
+ * An example element.
7
+ *
8
+ * @slot - This element has a slot
9
+ * @csspart button - The button
10
+ */
11
+ export class BoxIcon extends GlobalCSSMixin(LitElement) {
12
+ static properties = {
13
+ icon: {},
14
+ size: {},
15
+ pack: {}
16
+ };
17
+
18
+ render() {
19
+ return "";
20
+ }
21
+
22
+ connectedCallback() {
23
+ const el = createElement(this.icon, { size: this.size || "base", pack: this.pack || "basic" });
24
+ this.appendChild(el);
25
+ }
26
+
27
+ static styles = css`
28
+ `;
29
+ }
30
+
31
+ window.customElements.define("box-icon", BoxIcon);
@@ -0,0 +1,32 @@
1
+ import { css, html, LitElement } from "lit";
2
+ import { GlobalCSSMixin } from "../global-css-mixin";
3
+
4
+ /**
5
+ * An example element.
6
+ *
7
+ * @slot - This element has a slot
8
+ * @csspart button - The button
9
+ */
10
+ export class ColorBox extends GlobalCSSMixin(LitElement) {
11
+ static properties = {};
12
+
13
+ render() {
14
+ return html`
15
+ <div class="color-box">
16
+ <slot></slot>
17
+ </div>
18
+ `;
19
+ }
20
+
21
+ static styles = css`
22
+ .color-box {
23
+ padding: var(--size-2) var(--size-4);
24
+ border-radius: var(--rounded-md);
25
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3E%3Cpath fill='"var(--color-slate-100)"' fill-opacity='1' d='M1 3h1v1H1V3zm2-2h1v1H3V1z'%3E%3C/path%3E%3C/svg%3E");
26
+ background-size: 6px;
27
+ border: 1px solid var(--color-slate-300);
28
+ }
29
+ `;
30
+ }
31
+
32
+ window.customElements.define("color-box", ColorBox);
@@ -0,0 +1,89 @@
1
+ import { css, html, LitElement } from "lit";
2
+ import { map } from "lit/directives/map.js";
3
+ import { GlobalCSSMixin } from "../global-css-mixin";
4
+
5
+ const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
6
+
7
+ /**
8
+ * An example element.
9
+ *
10
+ * @slot - This element has a slot
11
+ * @csspart button - The button
12
+ */
13
+ export class ColorShades extends GlobalCSSMixin(LitElement) {
14
+ static properties = {
15
+ color: {},
16
+ };
17
+
18
+ copyShade(shade) {
19
+ navigator.clipboard.writeText(`--color-${this.color}-${shade}`);
20
+ alert(`Copied "--color-${this.color}-${shade}"`);
21
+ }
22
+
23
+ render() {
24
+ return html`
25
+ <h3>--color-${this.color}</h3>
26
+ <article class="color">
27
+ <div class="spacer"></div>
28
+ ${map(shades, (shade) => {
29
+ return html`
30
+ <button @click=${() => this.copyShade(shade)} class="shade" style="
31
+ background: var(--color-${this.color}-${shade});
32
+ color: ${shade > 400 ? "white" : "black"}
33
+ ">
34
+ ${shade}
35
+ </button>
36
+ `;
37
+ })}
38
+ </article>
39
+ `;
40
+ }
41
+
42
+ static styles = css`
43
+ h3 {
44
+ font: var(--font-heading-md);
45
+ text-transform: lowercase;
46
+ }
47
+
48
+ .color {
49
+ display: grid;
50
+ flex-wrap: wrap;
51
+ gap: var(--size-2);
52
+ border: 1px solid var(--color-slate-300);
53
+ border-radius: var(--rounded-sm);
54
+ padding: var(--size-3);
55
+ background: var(--color-white);
56
+ margin-bottom: var(--size-3);
57
+ grid-template-columns: repeat(4, 1fr);
58
+
59
+ @media (min-width: 640px) {
60
+ grid-template-columns: repeat(6, 1fr);
61
+ }
62
+
63
+ @media (min-width: 1024px) {
64
+ grid-template-columns: repeat(11, 1fr);
65
+ & .spacer {
66
+ display: none;
67
+ }
68
+ }
69
+ }
70
+
71
+ .shade {
72
+ aspect-ratio: 3/2;
73
+ cursor: pointer;
74
+ display: flex;
75
+ align-items: center;
76
+ justify-content: center;
77
+ font: var(--font-heading-sm);
78
+ padding: var(--size-3);
79
+ box-shadow: inset 0 0 0 1px var(--color-slate-400);
80
+ transition: var(--transition-quick);
81
+
82
+ &:hover {
83
+ opacity: 60%;
84
+ }
85
+ }
86
+ `;
87
+ }
88
+
89
+ window.customElements.define("color-shades", ColorShades);
@@ -0,0 +1,54 @@
1
+ import { css, html, LitElement } from "lit";
2
+ import { unsafeHTML } from "lit/directives/unsafe-html.js";
3
+ import { GlobalCSSMixin } from "../global-css-mixin";
4
+
5
+ /**
6
+ * An example element.
7
+ *
8
+ * @slot - This element has a slot
9
+ * @csspart button - The button
10
+ */
11
+ export class FeatureListItem extends GlobalCSSMixin(LitElement) {
12
+ static properties = {
13
+ title: {},
14
+ description: {},
15
+ icon: {},
16
+ };
17
+
18
+ render() {
19
+ return html`
20
+ <article role="listitem">
21
+ <box-icon .icon=${this.icon} class="icon"></box-icon>
22
+ <h3>${this.title}</h3>
23
+ <p><slot></p>
24
+ </article>
25
+ `;
26
+ }
27
+
28
+ static styles = css`
29
+ article {
30
+ padding: var(--size-4);
31
+ padding-bottom: var(--size-6);
32
+ background-color: var(--color-slate-200);
33
+ border-radius: var(--rounded-md);
34
+ height: 100%;
35
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3E%3Cpath fill='%23ffffff' fill-opacity='0.8' d='M1 3h1v1H1V3zm2-2h1v1H3V1z'%3E%3C/path%3E%3C/svg%3E");
36
+ background-size: 6px;
37
+ }
38
+
39
+ .icon {
40
+ display: inline-flex;
41
+ padding: var(--size-3);
42
+ border-radius: var(--rounded-md);
43
+ background-color: var(--color-slate-50);
44
+ margin-bottom: var(--size-3);
45
+ color: var(--color-slate-800);
46
+ }
47
+
48
+ h3 {
49
+ font: var(--font-heading-md);
50
+ }
51
+ `;
52
+ }
53
+
54
+ window.customElements.define("feature-list-item", FeatureListItem);
@@ -0,0 +1,57 @@
1
+ import { css, html, LitElement } from "lit";
2
+ import { GlobalCSSMixin } from "../global-css-mixin";
3
+
4
+ /**
5
+ * An example element.
6
+ *
7
+ * @slot - This element has a slot
8
+ * @csspart button - The button
9
+ */
10
+ export class FeatureList extends GlobalCSSMixin(LitElement) {
11
+ static properties = {
12
+ features: {},
13
+ };
14
+
15
+ render() {
16
+ return html`
17
+ <section>
18
+ <ul>
19
+ <slot>
20
+ </ul>
21
+ </section>
22
+ `;
23
+ }
24
+
25
+ static styles = css`
26
+ section {
27
+ margin: var(--size-4) 0;
28
+ }
29
+
30
+ ul {
31
+ display: grid;
32
+ grid-template-columns: repeat(2, 1fr);
33
+ flex-wrap: wrap;
34
+ gap: var(--size-6);
35
+
36
+ & > ::slotted(*) {
37
+ grid-column: auto / span 2;
38
+ }
39
+
40
+ @media (min-width: 640px) and (max-width: 1023px) {
41
+ grid-template-columns: repeat(4, 1fr);
42
+ gap: var(--size-3);
43
+
44
+ & > ::slotted(*:nth-child(odd):last-of-type) {
45
+ grid-column: 2 / span 2 !important;
46
+ }
47
+ }
48
+
49
+ @media (min-width: 1024px) {
50
+ grid-template-columns: repeat(6, 1fr);
51
+ gap: var(--size-6);
52
+ }
53
+ }
54
+ `;
55
+ }
56
+
57
+ window.customElements.define("feature-list", FeatureList);
@@ -0,0 +1,78 @@
1
+ import { css, html, LitElement } from "lit";
2
+ import { GlobalCSSMixin } from "../global-css-mixin.js";
3
+ /**
4
+ * An example element.
5
+ *
6
+ * @slot - This element has a slot
7
+ * @csspart button - The button
8
+ */
9
+
10
+ export class HeroSection extends GlobalCSSMixin(LitElement) {
11
+ static properties = {
12
+ title: {},
13
+ subtitle: {},
14
+ };
15
+
16
+ render() {
17
+ return html`
18
+ <section class="container">
19
+ <h1>--tailvars</h1>
20
+ <p>Putting the CSS back into TailwindCSS</p>
21
+ </section>
22
+ `;
23
+ }
24
+
25
+ static styles = css`
26
+ :host {
27
+ display: block;
28
+ }
29
+
30
+ section {
31
+ padding-top: 12vw;
32
+ padding-bottom: 12vw;
33
+ background-color: var(--color-slate-200);
34
+ color: var(--color-indigo-950);
35
+ background-image: url("/hero-texture.webp");
36
+ background-size: 100%;
37
+ background-fit: cover;
38
+ background-blend-mode: overlay;
39
+
40
+ & h1 {
41
+ font: var(--font-heading-fluid-2xl);
42
+
43
+ &::after {
44
+ content: "";
45
+ display: inline-block;
46
+ position: relative;
47
+ margin-left: -1ch;
48
+ margin-bottom: -0.1em;
49
+ pointer-events: none;
50
+ mix-blend-mode: hard-light;
51
+ width: 1ch;
52
+ aspect-ratio: 1/2;
53
+ background-color: var(--color-indigo-600);
54
+ animation: 2s steps(1) infinite blink;
55
+ }
56
+ }
57
+
58
+ & p {
59
+ --color-opacity: 90%;
60
+ font: var(--font-heading-fluid-md);
61
+ }
62
+ }
63
+
64
+ @keyframes blink {
65
+ from {
66
+ opacity: 100%;
67
+ }
68
+ 50% {
69
+ opacity: 0%;
70
+ }
71
+ to {
72
+ opacity: 100%;
73
+ }
74
+ }
75
+ `;
76
+ }
77
+
78
+ window.customElements.define("hero-section", HeroSection);
@@ -0,0 +1,50 @@
1
+ import { css, html, LitElement } from "lit";
2
+ import { GlobalCSSMixin } from "../global-css-mixin";
3
+
4
+ /**
5
+ * An example element.
6
+ *
7
+ * @slot - This element has a slot
8
+ * @csspart button - The button
9
+ */
10
+ export class SidebarLayout extends GlobalCSSMixin(LitElement) {
11
+ static properties = {
12
+ title: {},
13
+ };
14
+
15
+ render() {
16
+ return html`
17
+ <div class="wrapper">
18
+ <aside>
19
+ <div class="sidebar-content">
20
+ <slot name="sidebar"></slot>
21
+ </div>
22
+ </aside>
23
+ <div>
24
+ <slot></slot>
25
+ </div>
26
+ <div></div>
27
+ </div>
28
+ `;
29
+ }
30
+
31
+ static styles = css`
32
+ .wrapper {
33
+ display: grid;
34
+ grid-template-columns: 200px 1fr 200px;
35
+ gap: var(--size-12);
36
+ }
37
+
38
+ .sidebar-content {
39
+ display: flex;
40
+ flex-direction: column;
41
+ gap: var(--size-3);
42
+ position: sticky;
43
+ top: 0;
44
+ left: 0;
45
+ padding: min(8vw, var(--size-12)) 0;
46
+ }
47
+ `;
48
+ }
49
+
50
+ window.customElements.define("sidebar-layout", SidebarLayout);
@@ -0,0 +1,89 @@
1
+ import { css, html, LitElement } from "lit";
2
+ import { GlobalCSSMixin } from "../global-css-mixin";
3
+ import { map } from "lit/directives/map.js";
4
+ import { when } from "lit/directives/when.js";
5
+ import { query } from "lit/decorators.js";
6
+
7
+ /**
8
+ * An example element.
9
+ *
10
+ * @slot - This element has a slot
11
+ * @csspart button - The button
12
+ */
13
+ export class TableOfContentsItem extends GlobalCSSMixin(LitElement) {
14
+ static properties = {
15
+ target: {},
16
+ label: {},
17
+ icon: {},
18
+ active: {},
19
+ };
20
+
21
+ /** @type {HTMLElement} */
22
+ targetElement;
23
+
24
+ handleClick(e) {
25
+ e.preventDefault();
26
+ this.targetElement?.scrollIntoView({ behavior: "smooth" });
27
+ }
28
+
29
+ // Use arrow function to preserve value of `this`
30
+ handleWindowScroll = () => {
31
+ if (this.targetElement == null) return;
32
+ const rect = this.targetElement.getBoundingClientRect();
33
+ const origin = window.innerHeight / 3;
34
+ this.active = origin > rect.top && origin < rect.bottom;
35
+ };
36
+
37
+ connectedCallback() {
38
+ super.connectedCallback();
39
+ const page = this.ownerDocument.body.firstChild;
40
+ this.targetElement = page?.shadowRoot?.getElementById(this.target);
41
+
42
+ window.addEventListener("scroll", this.handleWindowScroll);
43
+ }
44
+
45
+ disconnectedCallback() {
46
+ super.disconnectedCallback();
47
+ window.removeEventListener("scroll", this.handleWindowScroll);
48
+ }
49
+
50
+ render() {
51
+ return html`
52
+ <li>
53
+ <a @click=${this.handleClick} href="#${this.target}" class=${this.active ? "active" : ""}>
54
+ <span>
55
+ ${when(
56
+ this.icon,
57
+ () =>
58
+ html`<box-icon size="xs" pack="filled" .icon=${this.icon}></box-icon>`,
59
+ )}
60
+ </span>
61
+
62
+ <span><slot></slot></span>
63
+ </a>
64
+ </li>
65
+ `;
66
+ }
67
+
68
+ static styles = css`
69
+ li {
70
+ list-style: none;
71
+ }
72
+
73
+ a {
74
+ display: flex;
75
+ align-items: center;
76
+ gap: var(--size-2);
77
+ padding: var(--size-1) var(--size-3);
78
+ color: var(--color-slate-900) !important;
79
+ font: var(--font-heading-sm) !important;
80
+ border-radius: var(--rounded-xs);
81
+
82
+ &.active {
83
+ background: var(--color-slate-200);
84
+ }
85
+ }
86
+ `;
87
+ }
88
+
89
+ window.customElements.define("table-of-contents-item", TableOfContentsItem);
@@ -0,0 +1,35 @@
1
+ import { css, html, LitElement } from "lit";
2
+ import { map } from "lit/directives/map.js";
3
+ import { when } from "lit/directives/when.js";
4
+ import { GlobalCSSMixin } from "../global-css-mixin";
5
+
6
+ /**
7
+ * An example element.
8
+ *
9
+ * @slot - This element has a slot
10
+ * @csspart button - The button
11
+ */
12
+ export class TableOfContents extends GlobalCSSMixin(LitElement) {
13
+ static properties = {};
14
+
15
+ render() {
16
+ return html`
17
+ <ul>
18
+ <slot></slot>
19
+ </ul>
20
+ `;
21
+ }
22
+
23
+ static styles = css`
24
+ ul {
25
+ border-radius: var(--rounded-md);
26
+ border: 1px solid var(--color-slate-300);
27
+ padding: var(--size-1);
28
+ gap: calc(-1 * var(--size-1));
29
+ display: flex;
30
+ flex-direction: column;
31
+ }
32
+ `;
33
+ }
34
+
35
+ window.customElements.define("table-of-contents", TableOfContents);
@@ -0,0 +1,14 @@
1
+ import css from "./global.css?inline";
2
+
3
+ const sheet = new CSSStyleSheet();
4
+ sheet.replaceSync(css);
5
+
6
+ export const globalCSS = sheet;
7
+
8
+ export const GlobalCSSMixin = (superClass) =>
9
+ class extends superClass {
10
+ connectedCallback() {
11
+ super.connectedCallback();
12
+ this.shadowRoot.adoptedStyleSheets.push(globalCSS);
13
+ }
14
+ };