@fluixi-ui/skin-material 0.2.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fluixi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # @fluixi-ui/skin-material
2
+
3
+ A Material Design skin for Fluixi UI. Restyles the components to a Material 3
4
+ flavour — pill buttons with state layers and elevation, M3 switch geometry,
5
+ filled text fields, emphasized motion, and Roboto.
6
+
7
+ ## Usage
8
+
9
+ Import the stylesheet after the tokens + preset, then activate with
10
+ `data-ui="material"` on a root (or any subtree):
11
+
12
+ ```ts
13
+ import '@fluixi-ui/tokens/preset.css';
14
+ import '@fluixi-ui/skin-material/styles.css';
15
+
16
+ document.documentElement.dataset.ui = 'material';
17
+ ```
18
+
19
+ Or drive it reactively with core's `createTheme`, lazy-loading the CSS only when
20
+ the skin is active:
21
+
22
+ ```ts
23
+ import { createTheme } from '@fluixi/core';
24
+
25
+ const skin = createTheme({
26
+ attribute: 'data-ui',
27
+ themes: ['fluixi', 'material'],
28
+ default: 'fluixi',
29
+ load: (s) => s === 'material' && import('@fluixi-ui/skin-material/styles.css'),
30
+ });
31
+ skin.init();
32
+ ```
33
+
34
+ ## How it works
35
+
36
+ All rules live in the `flx.skin` cascade layer, which sits **above**
37
+ `flx.components` (the default fluixi look), so they win wherever they match —
38
+ while consumer CSS stays unlayered and wins over both. Everything is scoped to
39
+ `[data-ui="material"]`, so the skin only applies where you opt in, and it
40
+ composes with the `data-theme` (light/dark) and `data-accent` axes.
41
+
42
+ > True click-origin ripple needs JavaScript; this CSS-only skin uses a
43
+ > currentColor **state layer** (hover / focus / press) as a close stand-in.
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Material click-origin ripple — the one piece the CSS state layer can't do
3
+ * (it needs the pointer position). Call `installRipple()` once; it delegates a
4
+ * single `pointerdown` listener that spawns an expanding circle inside any
5
+ * Material control. The ripple's styles + keyframes ship in the skin stylesheet.
6
+ */
7
+ /**
8
+ * Attach the ripple delegate to a root (default `document`). Returns a disposer
9
+ * that removes the listener. Safe to no-op on the server.
10
+ */
11
+ export declare function installRipple(root?: Document | HTMLElement): () => void;
12
+ //# sourceMappingURL=ripple.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ripple.d.ts","sourceRoot":"","sources":["../src/ripple.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,GAAE,QAAQ,GAAG,WAAiC,GAAG,MAAM,IAAI,CAe5F"}
package/dist/ripple.js ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Material click-origin ripple — the one piece the CSS state layer can't do
3
+ * (it needs the pointer position). Call `installRipple()` once; it delegates a
4
+ * single `pointerdown` listener that spawns an expanding circle inside any
5
+ * Material control. The ripple's styles + keyframes ship in the skin stylesheet.
6
+ */
7
+ const RIPPLE_SELECTOR = '[data-variant], [role="menuitem"], [role="option"], [role="tab"]';
8
+ /**
9
+ * Attach the ripple delegate to a root (default `document`). Returns a disposer
10
+ * that removes the listener. Safe to no-op on the server.
11
+ */
12
+ export function installRipple(root = globalThis.document) {
13
+ if (!root || typeof root.addEventListener !== 'function')
14
+ return () => { };
15
+ const onPointerDown = (event) => {
16
+ const e = event;
17
+ if (e.button !== 0)
18
+ return; // primary button / touch only
19
+ const origin = e.target;
20
+ const el = origin?.closest(RIPPLE_SELECTOR);
21
+ if (!el || !el.closest('[data-ui="material"]'))
22
+ return;
23
+ if (el.hasAttribute('disabled') || el.getAttribute('aria-disabled') === 'true')
24
+ return;
25
+ spawnRipple(el, e);
26
+ };
27
+ root.addEventListener('pointerdown', onPointerDown, { passive: true });
28
+ return () => root.removeEventListener('pointerdown', onPointerDown);
29
+ }
30
+ function spawnRipple(el, e) {
31
+ const rect = el.getBoundingClientRect();
32
+ // Cover the element from the click point: diameter = 2× the farthest corner.
33
+ const dx = Math.max(e.clientX - rect.left, rect.right - e.clientX);
34
+ const dy = Math.max(e.clientY - rect.top, rect.bottom - e.clientY);
35
+ const size = 2 * Math.hypot(dx, dy);
36
+ const ripple = document.createElement('span');
37
+ ripple.className = 'flx-mat-ripple';
38
+ ripple.style.width = ripple.style.height = `${size}px`;
39
+ ripple.style.left = `${e.clientX - rect.left - size / 2}px`;
40
+ ripple.style.top = `${e.clientY - rect.top - size / 2}px`;
41
+ ripple.addEventListener('animationend', () => ripple.remove(), { once: true });
42
+ el.appendChild(ripple);
43
+ }
@@ -0,0 +1 @@
1
+ {"fileNames":["../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es5.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2015.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2016.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2017.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2018.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2019.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2020.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2021.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2022.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.dom.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.dom.iterable.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2015.core.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2015.collection.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2015.generator.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2015.promise.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2016.intl.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2017.date.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2017.object.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2017.string.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2017.intl.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2018.intl.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2018.promise.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2019.array.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2019.object.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2019.string.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2019.intl.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2020.date.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2020.promise.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2020.string.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2020.intl.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2020.number.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2021.promise.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2021.string.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2021.intl.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2022.array.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2022.error.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2022.intl.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2022.object.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2022.string.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.decorators.d.ts","../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../../node_modules/.pnpm/tslib@2.8.1/node_modules/tslib/tslib.d.ts","../../../node_modules/.pnpm/tslib@2.8.1/node_modules/tslib/modules/index.d.ts","../src/ripple.ts"],"fileIdsList":[[60],[61]],"fileInfos":[{"version":"69684132aeb9b5642cbcd9e22dff7818ff0ee1aa831728af0ecf97d3364d5546","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"092c2bfe125ce69dbb1223c85d68d4d2397d7d8411867b5cc03cec902c233763","affectsGlobalScope":true,"impliedFormat":1},{"version":"07f073f19d67f74d732b1adea08e1dc66b1b58d77cb5b43931dee3d798a2fd53","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"936e80ad36a2ee83fc3caf008e7c4c5afe45b3cf3d5c24408f039c1d47bdc1df","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"fef8cfad2e2dc5f5b3d97a6f4f2e92848eb1b88e897bb7318cef0e2820bceaab","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"b5ce7a470bc3628408429040c4e3a53a27755022a32fd05e2cb694e7015386c7","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"a6a5253138c5432c68a1510c70fe78a644fe2e632111ba778e1978010d6edfec","impliedFormat":1},{"version":"b8f34dd1757f68e03262b1ca3ddfa668a855b872f8bdd5224d6f993a7b37dc2c","impliedFormat":99},{"version":"0d9eba127f45527253640d90a7d4a726b704f73691a93b973d0d33f48dda7ae6","signature":"7c757d2e0b0b1a3d2ddb39a712eb3977f7254339a09517a89855feb72a69c854","impliedFormat":99}],"root":[62],"options":{"composite":true,"declaration":true,"declarationMap":true,"emitDeclarationOnly":false,"importHelpers":true,"module":199,"noEmitOnError":true,"noFallthroughCasesInSwitch":true,"noImplicitOverride":true,"noImplicitReturns":true,"noUnusedLocals":true,"outDir":"./","rootDir":"../src","skipLibCheck":true,"strict":true,"target":9,"tsBuildInfoFile":"./tsconfig.lib.tsbuildinfo","verbatimModuleSyntax":true},"referencedMap":[[61,1],[62,2]],"latestChangedDtsFile":"./ripple.d.ts","version":"5.8.3"}
package/material.css ADDED
@@ -0,0 +1,213 @@
1
+ /**
2
+ * @fluixi-ui/skin-material — a Material Design skin for Fluixi UI.
3
+ *
4
+ * Activate by setting `data-ui="material"` on a root (or any subtree). All rules
5
+ * live in the `flx.skin` cascade layer, which sits above `flx.components`, so
6
+ * they override the default fluixi look wherever they match; consumer CSS stays
7
+ * unlayered and still wins. Import after the tokens + preset.
8
+ *
9
+ * Approximates Material 3: pill buttons with state layers + elevation, M3 switch
10
+ * geometry, filled text fields, emphasized motion, Roboto. (True click-origin
11
+ * ripple needs JS; the state layer stands in for it here.)
12
+ */
13
+ @layer flx.tokens, flx.components, flx.skin;
14
+
15
+ /* Self-hosted Roboto (variable, latin subset) — no CDN / privacy dependency. */
16
+ @font-face {
17
+ font-family: 'Roboto';
18
+ font-style: normal;
19
+ font-weight: 100 900;
20
+ font-display: swap;
21
+ src: url('./fonts/roboto-latin-variable.woff2') format('woff2');
22
+ }
23
+
24
+ @layer flx.skin {
25
+ /* ---- Global Material tokens ---------------------------------------------- */
26
+ [data-ui='material'] {
27
+ --flx-ui-font-family-sans: 'Roboto', 'Helvetica Neue', Arial, sans-serif;
28
+ --flx-ui-radius-sm: 4px;
29
+ --flx-ui-radius-md: 12px;
30
+ --flx-ui-radius-lg: 16px;
31
+ --flx-ui-radius-xl: 28px;
32
+ --flx-ui-duration-fast: 120ms;
33
+ --flx-ui-duration-normal: 200ms;
34
+ --flx-ui-ease-standard: cubic-bezier(0.2, 0, 0, 1);
35
+ --flx-ui-ease-emphasized: cubic-bezier(0.2, 0, 0, 1);
36
+ --flx-ui-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3), 0 1px 3px 1px rgba(0, 0, 0, 0.15);
37
+ --flx-ui-shadow-md: 0 1px 2px rgba(0, 0, 0, 0.3), 0 2px 6px 2px rgba(0, 0, 0, 0.15);
38
+ --flx-ui-shadow-lg: 0 4px 8px 3px rgba(0, 0, 0, 0.15), 0 1px 3px rgba(0, 0, 0, 0.3);
39
+ font-family: var(--flx-ui-font-family-sans);
40
+ }
41
+
42
+ /* ---- Button: pill shape, state layer, filled/tonal/outlined/text ---------- */
43
+ [data-ui='material'] :where([data-variant]) {
44
+ position: relative;
45
+ overflow: hidden;
46
+ border-radius: 9999px;
47
+ padding: 0 24px;
48
+ height: 40px;
49
+ font-weight: 500;
50
+ letter-spacing: 0.01em;
51
+ box-shadow: none;
52
+ transition:
53
+ box-shadow var(--flx-ui-duration-normal) var(--flx-ui-ease-standard),
54
+ background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
55
+ }
56
+ /* The Material "state layer": a currentColor overlay that fades in on interaction.
57
+ On a filled button currentColor is the on-primary ink (white) → a subtle
58
+ lightening; on outlined/text/tonal it's the primary colour. */
59
+ [data-ui='material'] :where([data-variant])::before {
60
+ content: '';
61
+ position: absolute;
62
+ inset: 0;
63
+ background: currentColor;
64
+ opacity: 0;
65
+ pointer-events: none;
66
+ transition: opacity var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
67
+ }
68
+ [data-ui='material'] :where([data-variant]:hover)::before { opacity: 0.08; }
69
+ [data-ui='material'] :where([data-variant]:focus-visible)::before { opacity: 0.12; }
70
+ [data-ui='material'] :where([data-variant]:active)::before { opacity: 0.16; }
71
+ [data-ui='material'] :where([data-variant]:focus-visible) { box-shadow: none; outline: none; }
72
+ [data-ui='material'] :where([data-variant][disabled], [data-variant]:disabled) {
73
+ box-shadow: none;
74
+ opacity: 0.38;
75
+ }
76
+
77
+ /* Filled (solid) = the classic contained button: rests on elevation, lifts
78
+ further on hover. Elevation is per-variant — outlined/text/tonal stay flat. */
79
+ [data-ui='material'] :where([data-variant='solid']) { box-shadow: var(--flx-ui-shadow-sm); }
80
+ [data-ui='material'] :where([data-variant='solid']:hover) { box-shadow: var(--flx-ui-shadow-md); }
81
+ [data-ui='material'] :where([data-variant='solid']:active) { box-shadow: var(--flx-ui-shadow-sm); }
82
+ /* Tonal (soft): secondary-container feel. */
83
+ [data-ui='material'] :where([data-variant='soft']) { background: var(--flx-ui-color-primary-subtle); color: var(--flx-ui-color-primary-active); }
84
+ /* Outlined: 1px hairline, no fill. */
85
+ [data-ui='material'] :where([data-variant='outline']) { border: 1px solid var(--flx-ui-color-border-strong); }
86
+ /* Text: tighter padding, no border. */
87
+ [data-ui='material'] :where([data-variant='ghost']) { padding: 0 12px; }
88
+
89
+ /* Badges also carry [data-variant] — undo the button treatment (they're chips,
90
+ not buttons): restore size, keep the pill, drop the state layer. */
91
+ [data-ui='material'] :where(.flx-ui-badge[data-variant]) {
92
+ height: auto;
93
+ padding: 2px 10px;
94
+ box-shadow: none;
95
+ }
96
+ [data-ui='material'] :where(.flx-ui-badge[data-variant])::before { content: none; }
97
+
98
+ /* ---- Switch: M3 geometry (52×32 track, growing thumb) --------------------- */
99
+ [data-ui='material'] :where([role='switch']) {
100
+ width: 52px;
101
+ height: 32px;
102
+ padding: 0;
103
+ background: var(--flx-ui-color-bg-muted);
104
+ border: 2px solid var(--flx-ui-color-border-strong);
105
+ }
106
+ [data-ui='material'] :where([role='switch'])::after {
107
+ width: 16px;
108
+ height: 16px;
109
+ margin: 6px;
110
+ background: var(--flx-ui-color-border-strong);
111
+ box-shadow: none;
112
+ }
113
+ [data-ui='material'] :where([role='switch'][data-state='checked']) {
114
+ background: var(--flx-ui-color-primary);
115
+ border-color: var(--flx-ui-color-primary);
116
+ }
117
+ [data-ui='material'] :where([role='switch'][data-state='checked'])::after {
118
+ width: 24px;
119
+ height: 24px;
120
+ margin: 2px;
121
+ background: #fff;
122
+ transform: translateX(20px);
123
+ }
124
+
125
+ /* ---- Checkbox: M3 rounded square ----------------------------------------- */
126
+ [data-ui='material'] :where([role='checkbox']) {
127
+ width: 18px;
128
+ height: 18px;
129
+ border-width: 2px;
130
+ border-radius: 2px;
131
+ }
132
+
133
+ /* ---- Slider: M3 filled thumb -------------------------------------------- */
134
+ [data-ui='material'] :where([role='slider']) {
135
+ width: 20px;
136
+ height: 20px;
137
+ background: var(--flx-ui-color-primary);
138
+ border: none;
139
+ }
140
+ [data-ui='material'] :where([data-slider-track]) { height: 6px; }
141
+
142
+ /* ---- Tabs: M3 primary indicator (thicker, rounded) ----------------------- */
143
+ [data-ui='material'] :where([role='tab'][data-state='active']) {
144
+ border-bottom-width: 3px;
145
+ border-top-left-radius: 3px;
146
+ border-top-right-radius: 3px;
147
+ }
148
+
149
+ /* ---- Filled text field + combobox control -------------------------------- */
150
+ [data-ui='material'] :where([data-input], [data-combobox-control]) {
151
+ background: var(--flx-ui-color-bg-muted);
152
+ border: none;
153
+ border-bottom: 1px solid var(--flx-ui-color-border-strong);
154
+ border-radius: 4px 4px 0 0;
155
+ }
156
+ [data-ui='material'] :where([data-input]:focus, [data-input]:focus-visible, [data-combobox-control]:focus-within) {
157
+ border-bottom: 2px solid var(--flx-ui-color-primary);
158
+ box-shadow: none;
159
+ outline: none;
160
+ }
161
+
162
+ /* ---- Menus & lists: full-bleed M3 rows, roomier padding ------------------- */
163
+ [data-ui='material'] :where([role='menuitem'], [role='option']) {
164
+ border-radius: 0;
165
+ padding: 10px var(--flx-ui-space-3);
166
+ }
167
+ [data-ui='material'] :where([role='menu'], [role='listbox']:not([data-state])) {
168
+ padding: 8px 0;
169
+ }
170
+
171
+ /* ---- Surfaces: M3 corner radii (elevation comes from the shadow tokens) --- */
172
+ /* Dialog = "extra large" 28px; hover-card = "medium" 12px; tooltip = 4px. */
173
+ [data-ui='material'] :where([role='dialog']:not([data-placement]), [role='alertdialog']) {
174
+ border-radius: 28px;
175
+ }
176
+ [data-ui='material'] :where([data-hovercard-content]) {
177
+ border-radius: 12px;
178
+ }
179
+ [data-ui='material'] :where([role='tooltip']) {
180
+ border-radius: 4px;
181
+ }
182
+
183
+ /* ---- Click-origin ripple (installed by the ripple primitive) ------------- */
184
+ [data-ui='material'] :where([data-variant], [role='menuitem'], [role='option'], [role='tab']) {
185
+ position: relative;
186
+ overflow: hidden;
187
+ }
188
+ [data-ui='material'] .flx-mat-ripple {
189
+ position: absolute;
190
+ border-radius: 50%;
191
+ background: currentColor;
192
+ opacity: 0;
193
+ transform: scale(0);
194
+ pointer-events: none;
195
+ /* Decelerating expand; opacity holds while it grows, then fades out at the
196
+ end, so the ripple reads as fluid rather than a flash. */
197
+ animation: flx-mat-ripple 650ms cubic-bezier(0.2, 0.6, 0.3, 1) forwards;
198
+ }
199
+ @keyframes flx-mat-ripple {
200
+ 0% {
201
+ transform: scale(0);
202
+ opacity: 0.15;
203
+ }
204
+ 60% {
205
+ transform: scale(0.9);
206
+ opacity: 0.15;
207
+ }
208
+ 100% {
209
+ transform: scale(1);
210
+ opacity: 0;
211
+ }
212
+ }
213
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@fluixi-ui/skin-material",
3
+ "version": "0.2.0",
4
+ "description": "Material Design skin for Fluixi UI — activate with data-ui=\"material\"",
5
+ "license": "MIT",
6
+ "private": false,
7
+ "type": "module",
8
+ "publishConfig": {
9
+ "registry": "https://registry.npmjs.org/",
10
+ "access": "public"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/fluixi/ui.git",
15
+ "directory": "packages/skin-material"
16
+ },
17
+ "sideEffects": [
18
+ "*.css"
19
+ ],
20
+ "files": [
21
+ "dist",
22
+ "fonts",
23
+ "material.css",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "exports": {
28
+ "./package.json": "./package.json",
29
+ "./styles.css": "./material.css",
30
+ "./ripple": {
31
+ "types": "./dist/ripple.d.ts",
32
+ "import": "./dist/ripple.js",
33
+ "default": "./dist/ripple.js"
34
+ }
35
+ },
36
+ "scripts": {
37
+ "build": "tsc -p tsconfig.lib.json"
38
+ }
39
+ }