@reykjavik/webtools 0.2.4 → 0.2.6

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/CHANGELOG.md CHANGED
@@ -4,6 +4,20 @@
4
4
 
5
5
  - ... <!-- Add new lines here. -->
6
6
 
7
+ ## 0.2.6
8
+
9
+ _2025-06-20_
10
+
11
+ - `@reykjavik/webtools/next/vanillaExtract`:
12
+ - feat: Add `vanillaVars` helper to generate privately scoped CSS variables
13
+
14
+ ## 0.2.5
15
+
16
+ _2025-05-21_
17
+
18
+ - `@reykjavik/webtools/async`:
19
+ - feat: Support passing `AbortSignal` to `sleep` and `addLag` helpers
20
+
7
21
  ## 0.2.4
8
22
 
9
23
  _2025-05-09_
@@ -39,7 +53,7 @@ _2024-12-17_
39
53
  _2024-12-12_
40
54
 
41
55
  - feat: Add `@reykjavik/webtools/react-router/*` (previously `/remix/*`)
42
- - **BREAKING** feat: Remove all `@reykjavik/webtools/react-router/*` modules
56
+ - **BREAKING** feat: Remove all `@reykjavik/webtools/remix/*` modules
43
57
  - **BREAKING** feat: Bump `pkg.engines.node` version to >=20
44
58
  - **BREAKING** feat: Remove `@reykjavik/webtools/next/SiteImprove` (deprecated
45
59
  module)
package/README.md CHANGED
@@ -51,6 +51,7 @@ bun add @reykjavik/webtools
51
51
  - [`vanillaClass`](#vanillaclass)
52
52
  - [`vanillaGlobal`](#vanillaglobal)
53
53
  - [`vanillaProps`](#vanillaprops)
54
+ - [`vanillaVars`](#vanillavars)
54
55
  - [Framework Specific Tools](#framework-specific-tools)
55
56
  - [React-Router Tools](#react-router-tools)
56
57
  - [Next.js Tools](#nextjs-tools)
@@ -821,14 +822,26 @@ To opt out of the `&&` replacement, use the callback function signature.
821
822
  // someFile.css.ts
822
823
  import { vanillaClass } from '@reykjavik/webtools/vanillaExtract';
823
824
 
824
- // Simple class selector block
825
+ // 1) Simple class selector block — no sub-selectors — auto-wrapped
826
+ // in a class-name selector block.
825
827
  export const myClass = vanillaClass(`
826
828
  background-color: #ccc;
827
829
  padding: .5em 1em;
828
830
  `);
831
+ // Generated CSS:
832
+ /*
833
+ .x1y2z3 {
834
+ background-color: #ccc;
835
+ padding: .5em 1em;
836
+ }
837
+ */
838
+
839
+ // ---------------------------------------------------------------------------
829
840
 
830
- // With && tokens that get replaced with the generated class-name
831
- export const myClasWithAmp = vanillaClass(`
841
+ // 2) More advanced usage with `&&` tokens that get replaced with the
842
+ // generated class-name selector (prefixed with a dot).
843
+ export const myClasWithAmp = vanillaClass(
844
+ `
832
845
  && {
833
846
  background-color: #ccc;
834
847
  padding: .5em 1em;
@@ -836,10 +849,38 @@ export const myClasWithAmp = vanillaClass(`
836
849
  && > strong {
837
850
  color: #c00;
838
851
  }
839
- `);
852
+ @media (min-width: 800px) {
853
+ && {
854
+ background-color: #eee;
855
+ }
856
+ }
857
+ /* NOTE: Root-level CSS rules are NOT auto-wrapped when ` &&
858
+ ` tokens are otherwise present */
859
+ color: blue;
860
+ `
861
+ );
862
+ // Generated CSS:
863
+ /*
864
+ .y2X1z3 {
865
+ background-color: #ccc;
866
+ padding: .5em 1em;
867
+ }
868
+ .y2X1z3 > strong {
869
+ color: #c00;
870
+ }
871
+ @media (min-width: 800px) {
872
+ .y2X1z3 {
873
+ background-color: #eee;
874
+ }
875
+ }
876
+ /* NOTE: Root-level CSS rules are NOT auto-wrapped when `&&` tokens are otherwise present *​/
877
+ color: blue;
878
+ */
840
879
 
841
- // Passing a function to get the generated class-name for
842
- // more complex styles.
880
+ // ---------------------------------------------------------------------------
881
+
882
+ // 3) Advanced use: Pass a function to get the raw generated class-name,
883
+ // plus a more convenient dot-prefixed selector for the class-name.
843
884
  export const myOtherClass = vanillaClass(
844
885
  (classNameRaw, classNameSelector) => `
845
886
  ${classNameSelector} {
@@ -855,22 +896,48 @@ export const myOtherClass = vanillaClass(
855
896
  }
856
897
  }
857
898
  /* NOTE: '&&' tokens returned from a callback function are NOT replaced */
858
- && { will-not-be: interpolated; }
899
+ && { this-is-not: interpolated; }
859
900
  `
860
901
  );
902
+ // Generated CSS:
903
+ /*
904
+ .y3z1X2 {
905
+ background-color: #ccc;
906
+ padding: .5em 1em;
907
+ }
908
+ [class="y3z1X2"] > strong {
909
+ color: #c00;
910
+ }
911
+ @media (min-width: 800px) {
912
+ .y3z1X2 {
913
+ background-color: #eee;
914
+ }
915
+ }
916
+ /* NOTE: '&&' tokens returned from a callback function are NOT replaced *​​/
917
+ && { this-is-not: interpolated; }
918
+ */
919
+
920
+ // ---------------------------------------------------------------------------
861
921
 
862
- // With a human readable debugId
922
+ // 4) ...with a human readable debugId
863
923
  export const humanReadableClass = vanillaClass(
864
- 'HumanReadable',
924
+ 'HumanReadable__classNamePrefix',
865
925
  `
866
926
  border: 1px dashed hotpink;
867
927
  cursor: pointer;
868
928
  `
869
929
  );
930
+ // Generated CSS:
931
+ /*
932
+ .HumanReadable__classNamePrefix_x2y1z3 {
933
+ border: 1px dashed hotpink;
934
+ cursor: pointer;
935
+ }
936
+ */
870
937
  ```
871
938
 
872
- (NOTE: The dot-prefixed `&&` pattern is chosen to not conflict with the bare
873
- `&` token in modern nested CSS.)
939
+ (NOTE: The dot-prefixed `&&` pattern was chosen as to not conflict with the
940
+ bare `&` token in modern nested CSS.)
874
941
 
875
942
  ### `vanillaGlobal`
876
943
 
@@ -914,6 +981,59 @@ const myStyle = style({
914
981
  });
915
982
  ```
916
983
 
984
+ ### `vanillaVars`
985
+
986
+ **Syntax:**
987
+ `` vanillaVars(...varNames: Array<T>): Record <`var${Capitalize<T>}`, string> ``
988
+
989
+ Returns an object with privately scoped CSS variables props. Pass them around
990
+ and use them in your CSS.
991
+
992
+ ```ts
993
+ // MyComponent.css.ts
994
+ import {
995
+ vanillaVars,
996
+ vanillaGlobal,
997
+ } from '@reykjavik/webtools/vanillaExtract';
998
+
999
+ export const { varPrimaryColor, varSecondaryColor } = vanillaVars(
1000
+ 'primaryColor',
1001
+ 'secondaryColor'
1002
+ );
1003
+
1004
+ vanillaGlobal(`
1005
+ :root {
1006
+ ${varPrimaryColor}: #ff0000;
1007
+ ${varSecondaryColor}: #00ff00;
1008
+ }
1009
+ body {
1010
+ background-color: var(${varPrimaryColor});
1011
+ color: var(${varSecondaryColor});
1012
+ }
1013
+ `);
1014
+ ```
1015
+
1016
+ …and then in your component:
1017
+
1018
+ ```ts
1019
+ // MyComponent.tsx
1020
+ import React from 'react';
1021
+ import * as cl from './someFile.css.ts';
1022
+
1023
+ export function MyComponent() {
1024
+ return (
1025
+ <div
1026
+ style={{
1027
+ [cl.varPrimaryColor]: 'yellow',
1028
+ [cl.varSecondaryColor]: 'blue',
1029
+ }}
1030
+ >
1031
+ ...children...
1032
+ </div>
1033
+ );
1034
+ }
1035
+ ```
1036
+
917
1037
  ---
918
1038
 
919
1039
  ## Framework Specific Tools
@@ -921,7 +1041,7 @@ const myStyle = style({
921
1041
  ### React-Router Tools
922
1042
 
923
1043
  See [README-rr.md](./README-rr.md) for helpers and components specifically
924
- designed for use in Remix.run projects.
1044
+ designed for use in React-router projects.
925
1045
 
926
1046
  (NOTE: If you're still using [Remix.run](https://remix.run) you can install
927
1047
  version `"^0.1.22"` of this package.)
package/async.d.ts CHANGED
@@ -4,12 +4,16 @@ type PlainObj = Record<string, unknown>;
4
4
  * Simple sleep function. Returns a promise that resolves after `length`
5
5
  * milliseconds.
6
6
  */
7
- export declare const sleep: (length: number) => Promise<void>;
7
+ export declare const sleep: (length: number, opts?: {
8
+ signal?: AbortSignal;
9
+ }) => Promise<void>;
8
10
  /**
9
11
  * Returns a function that adds lag/delay to a promise chain,
10
12
  * passing the promise payload through.
11
13
  */
12
- export declare const addLag: (length: number) => <T>(res: T) => Promise<T>;
14
+ export declare const addLag: (length: number, opts?: {
15
+ signal?: AbortSignal;
16
+ }) => <T>(res: T) => Promise<T>;
13
17
  /**
14
18
  * Resolves as soon as all of the passed `promises` have resolved/settled,
15
19
  * or after `timeout` milliseconds — whichever comes first.
package/async.js CHANGED
@@ -7,14 +7,33 @@ exports.maxWait = maxWait;
7
7
  * milliseconds.
8
8
  */
9
9
  /*#__NO_SIDE_EFFECTS__*/
10
- const sleep = (length) => new Promise((resolve) => setTimeout(resolve, length));
10
+ const sleep = (length, opts) => new Promise((resolve, reject) => {
11
+ const signal = opts && opts.signal;
12
+ if (!signal) {
13
+ return setTimeout(resolve, length);
14
+ }
15
+ if (signal.aborted) {
16
+ return reject(signal.reason);
17
+ }
18
+ const onAbort = () => {
19
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
20
+ clearTimeout(timer);
21
+ signal.removeEventListener('abort', onAbort);
22
+ reject(signal.reason);
23
+ };
24
+ signal.addEventListener('abort', onAbort);
25
+ const timer = setTimeout(() => {
26
+ signal.removeEventListener('abort', onAbort);
27
+ resolve();
28
+ }, length);
29
+ });
11
30
  exports.sleep = sleep;
12
31
  /**
13
32
  * Returns a function that adds lag/delay to a promise chain,
14
33
  * passing the promise payload through.
15
34
  */
16
35
  /*#__NO_SIDE_EFFECTS__*/
17
- const addLag = (length) => (res) => (0, exports.sleep)(length).then(() => res);
36
+ const addLag = (length, opts) => (res) => (0, exports.sleep)(length, opts).then(() => res);
18
37
  exports.addLag = addLag;
19
38
  /*#__NO_SIDE_EFFECTS__*/
20
39
  function maxWait(timeout, promises) {
package/esm/async.d.ts CHANGED
@@ -4,12 +4,16 @@ type PlainObj = Record<string, unknown>;
4
4
  * Simple sleep function. Returns a promise that resolves after `length`
5
5
  * milliseconds.
6
6
  */
7
- export declare const sleep: (length: number) => Promise<void>;
7
+ export declare const sleep: (length: number, opts?: {
8
+ signal?: AbortSignal;
9
+ }) => Promise<void>;
8
10
  /**
9
11
  * Returns a function that adds lag/delay to a promise chain,
10
12
  * passing the promise payload through.
11
13
  */
12
- export declare const addLag: (length: number) => <T>(res: T) => Promise<T>;
14
+ export declare const addLag: (length: number, opts?: {
15
+ signal?: AbortSignal;
16
+ }) => <T>(res: T) => Promise<T>;
13
17
  /**
14
18
  * Resolves as soon as all of the passed `promises` have resolved/settled,
15
19
  * or after `timeout` milliseconds — whichever comes first.
package/esm/async.js CHANGED
@@ -3,13 +3,32 @@
3
3
  * milliseconds.
4
4
  */
5
5
  /*#__NO_SIDE_EFFECTS__*/
6
- export const sleep = (length) => new Promise((resolve) => setTimeout(resolve, length));
6
+ export const sleep = (length, opts) => new Promise((resolve, reject) => {
7
+ const signal = opts && opts.signal;
8
+ if (!signal) {
9
+ return setTimeout(resolve, length);
10
+ }
11
+ if (signal.aborted) {
12
+ return reject(signal.reason);
13
+ }
14
+ const onAbort = () => {
15
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
16
+ clearTimeout(timer);
17
+ signal.removeEventListener('abort', onAbort);
18
+ reject(signal.reason);
19
+ };
20
+ signal.addEventListener('abort', onAbort);
21
+ const timer = setTimeout(() => {
22
+ signal.removeEventListener('abort', onAbort);
23
+ resolve();
24
+ }, length);
25
+ });
7
26
  /**
8
27
  * Returns a function that adds lag/delay to a promise chain,
9
28
  * passing the promise payload through.
10
29
  */
11
30
  /*#__NO_SIDE_EFFECTS__*/
12
- export const addLag = (length) => (res) => sleep(length).then(() => res);
31
+ export const addLag = (length, opts) => (res) => sleep(length, opts).then(() => res);
13
32
  /*#__NO_SIDE_EFFECTS__*/
14
33
  export function maxWait(timeout, promises) {
15
34
  if (Array.isArray(promises)) {
@@ -27,7 +27,7 @@ type WaitFallbacks = {
27
27
  };
28
28
  export type WaitProps<T> = WaitPropsBase<T> & WaitFallbacks;
29
29
  /**
30
- * A function component that wraps `@reykjavik/webtools/remix/Wait` to provide
30
+ * A function component that wraps `@reykjavik/webtools/react-router/Wait` to provide
31
31
  * custom properties for `meanwhile` and `error` fallbacks, and/or other
32
32
  * behaviors.
33
33
  *
@@ -37,7 +37,7 @@ export type WaitComponent<CustomProps extends Record<string, unknown> = Record<n
37
37
  displayName?: string;
38
38
  };
39
39
  /**
40
- * A thin wrapper around [Remix's `Await`](https://remix.run/docs/en/2/components/await)
40
+ * A thin wrapper around [React-router's `Await`](https://reactrouter.com/api/components/Await)
41
41
  * component, to provide a more ergonomic API.
42
42
  *
43
43
  * If the awaited promise (`props.for`) resolves to an object with a truthy
@@ -1,7 +1,7 @@
1
1
  import React, { Suspense } from 'react';
2
2
  import { Await } from 'react-router';
3
3
  /**
4
- * A thin wrapper around [Remix's `Await`](https://remix.run/docs/en/2/components/await)
4
+ * A thin wrapper around [React-router's `Await`](https://reactrouter.com/api/components/Await)
5
5
  * component, to provide a more ergonomic API.
6
6
  *
7
7
  * If the awaited promise (`props.for`) resolves to an object with a truthy
@@ -31,4 +31,11 @@ classNameSelector: string) => string;
31
31
  */
32
32
  export declare function vanillaClass(css: string | ClassNameCallback): string;
33
33
  export declare function vanillaClass(debugId: string, css: string | ClassNameCallback): string;
34
+ /**
35
+ * Returns an object with privately scoped CSS variables props.
36
+ * Pass them around and use them in your CSS.
37
+ *
38
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#vanillacvars
39
+ */
40
+ export declare const vanillaVars: <T extends string>(...varNames: Array<T>) => Record<`var${Capitalize<T>}`, string>;
34
41
  export {};
@@ -1,3 +1,4 @@
1
+ import { capitalize } from '@reykjavik/hanna-utils';
1
2
  import { globalStyle, style } from '@vanilla-extract/css';
2
3
  /**
3
4
  * Adds free-form CSS as a globalStyle
@@ -25,3 +26,18 @@ export function vanillaClass(cssOrDebugId, css) {
25
26
  : css.replace(/&&/g, `.${className}`));
26
27
  return className;
27
28
  }
29
+ // ---------------------------------------------------------------------------
30
+ /**
31
+ * Returns an object with privately scoped CSS variables props.
32
+ * Pass them around and use them in your CSS.
33
+ *
34
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#vanillacvars
35
+ */
36
+ export const vanillaVars = (...varNames) => {
37
+ const id = vanillaClass(``);
38
+ const vars = {};
39
+ for (const name of varNames) {
40
+ vars[`var${capitalize(name)}`] = `--${id}--${name}`;
41
+ }
42
+ return vars;
43
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reykjavik/webtools",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Misc. JS/TS helpers used by Reykjavík City's web dev teams.",
5
5
  "main": "index.js",
6
6
  "repository": "ssh://git@github.com:reykjavikcity/webtools.git",
@@ -27,7 +27,7 @@ type WaitFallbacks = {
27
27
  };
28
28
  export type WaitProps<T> = WaitPropsBase<T> & WaitFallbacks;
29
29
  /**
30
- * A function component that wraps `@reykjavik/webtools/remix/Wait` to provide
30
+ * A function component that wraps `@reykjavik/webtools/react-router/Wait` to provide
31
31
  * custom properties for `meanwhile` and `error` fallbacks, and/or other
32
32
  * behaviors.
33
33
  *
@@ -37,7 +37,7 @@ export type WaitComponent<CustomProps extends Record<string, unknown> = Record<n
37
37
  displayName?: string;
38
38
  };
39
39
  /**
40
- * A thin wrapper around [Remix's `Await`](https://remix.run/docs/en/2/components/await)
40
+ * A thin wrapper around [React-router's `Await`](https://reactrouter.com/api/components/Await)
41
41
  * component, to provide a more ergonomic API.
42
42
  *
43
43
  * If the awaited promise (`props.for`) resolves to an object with a truthy
@@ -37,7 +37,7 @@ exports.Wait = void 0;
37
37
  const react_1 = __importStar(require("react"));
38
38
  const react_router_1 = require("react-router");
39
39
  /**
40
- * A thin wrapper around [Remix's `Await`](https://remix.run/docs/en/2/components/await)
40
+ * A thin wrapper around [React-router's `Await`](https://reactrouter.com/api/components/Await)
41
41
  * component, to provide a more ergonomic API.
42
42
  *
43
43
  * If the awaited promise (`props.for`) resolves to an object with a truthy
@@ -31,4 +31,11 @@ classNameSelector: string) => string;
31
31
  */
32
32
  export declare function vanillaClass(css: string | ClassNameCallback): string;
33
33
  export declare function vanillaClass(debugId: string, css: string | ClassNameCallback): string;
34
+ /**
35
+ * Returns an object with privately scoped CSS variables props.
36
+ * Pass them around and use them in your CSS.
37
+ *
38
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#vanillacvars
39
+ */
40
+ export declare const vanillaVars: <T extends string>(...varNames: Array<T>) => Record<`var${Capitalize<T>}`, string>;
34
41
  export {};
package/vanillaExtract.js CHANGED
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.vanillaProps = exports.vanillaGlobal = void 0;
3
+ exports.vanillaVars = exports.vanillaProps = exports.vanillaGlobal = void 0;
4
4
  exports.vanillaClass = vanillaClass;
5
+ const hanna_utils_1 = require("@reykjavik/hanna-utils");
5
6
  const css_1 = require("@vanilla-extract/css");
6
7
  /**
7
8
  * Adds free-form CSS as a globalStyle
@@ -31,3 +32,19 @@ function vanillaClass(cssOrDebugId, css) {
31
32
  : css.replace(/&&/g, `.${className}`));
32
33
  return className;
33
34
  }
35
+ // ---------------------------------------------------------------------------
36
+ /**
37
+ * Returns an object with privately scoped CSS variables props.
38
+ * Pass them around and use them in your CSS.
39
+ *
40
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#vanillacvars
41
+ */
42
+ const vanillaVars = (...varNames) => {
43
+ const id = vanillaClass(``);
44
+ const vars = {};
45
+ for (const name of varNames) {
46
+ vars[`var${(0, hanna_utils_1.capitalize)(name)}`] = `--${id}--${name}`;
47
+ }
48
+ return vars;
49
+ };
50
+ exports.vanillaVars = vanillaVars;