@magentofullstackdev/hyperion-shared 0.0.0-stage → 0.9.0-beta.2
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 +21 -0
- package/README.md +78 -2
- package/package.json +66 -3
- package/src/html/index.ts +12 -0
- package/src/html/sanitize.ts +156 -0
- package/src/index.ts +14 -0
- package/src/lit/HyperionLightElement.ts +30 -0
- package/src/lit/index.ts +13 -0
- package/src/tailwind/preset.ts +121 -0
- package/src/tokens/colors.css +105 -0
- package/src/tokens/index.css +13 -0
- package/src/tokens/spacing.css +14 -0
- package/src/tokens/typography.css +20 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sergiu Ro -- magentofullstackdev
|
|
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
CHANGED
|
@@ -1,3 +1,79 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @magentofullstackdev/hyperion-shared
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Shared design tokens, Tailwind preset, and base Lit element / mixins for the
|
|
4
|
+
[Hyperion](https://github.com/magentofullstackdev/hyperion) Magento 2 theme
|
|
5
|
+
suite.
|
|
6
|
+
|
|
7
|
+
This package is published as TypeScript / CSS source (a beta, `0.9.0-beta.x`,
|
|
8
|
+
versioned with the suite). It has no build step: sibling packages
|
|
9
|
+
(`@magentofullstackdev/hyperion-theme`, `@magentofullstackdev/hyperion-checkout`)
|
|
10
|
+
consume it directly through their own bundler (Vite). Installing it does not
|
|
11
|
+
install or activate anything in Magento; the storefront comes from the
|
|
12
|
+
Composer packages, see
|
|
13
|
+
[`docs/install.md`](https://github.com/magentofullstackdev/hyperion/blob/main/docs/install.md).
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @magentofullstackdev/hyperion-shared
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
Tailwind preset:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
// tailwind.config.ts
|
|
27
|
+
import hyperionPreset from '@magentofullstackdev/hyperion-shared/tailwind/preset';
|
|
28
|
+
|
|
29
|
+
export default {
|
|
30
|
+
presets: [hyperionPreset],
|
|
31
|
+
content: ['./src/**/*.{ts,tsx,js,html}'],
|
|
32
|
+
};
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Design tokens (CSS):
|
|
36
|
+
|
|
37
|
+
```css
|
|
38
|
+
/* your-theme.css */
|
|
39
|
+
@import "@magentofullstackdev/hyperion-shared/tokens/index.css";
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Base Lit element:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { HyperionLightElement } from '@magentofullstackdev/hyperion-shared/lit';
|
|
46
|
+
|
|
47
|
+
class MyComponent extends HyperionLightElement {
|
|
48
|
+
/* ... */
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Exports
|
|
53
|
+
|
|
54
|
+
| Subpath | Purpose |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `.` | Barrel: `tailwindPreset`, base Lit element, mixins |
|
|
57
|
+
| `./tailwind/preset` | Tailwind 3.x preset (semantic colours, spacing, typography) |
|
|
58
|
+
| `./tokens/*` | Raw CSS custom properties (`colors.css`, `spacing.css`, `typography.css`, `index.css`) |
|
|
59
|
+
| `./lit` / `./lit/*` | Base Lit element + mixins |
|
|
60
|
+
| `./html` | HTML sanitizer (allowlist) used for cookie and response HTML |
|
|
61
|
+
|
|
62
|
+
## Requirements
|
|
63
|
+
|
|
64
|
+
A TypeScript-aware bundler (Vite, esbuild, webpack with `ts-loader`, etc.).
|
|
65
|
+
This package ships `.ts` source, not pre-compiled JavaScript, so it cannot be
|
|
66
|
+
loaded directly by Node.
|
|
67
|
+
|
|
68
|
+
## Links
|
|
69
|
+
|
|
70
|
+
- Website: <https://hyperionfront.dev>
|
|
71
|
+
- Live demo: <https://demo.hyperionfront.dev>
|
|
72
|
+
- Repository: <https://github.com/magentofullstackdev/hyperion>
|
|
73
|
+
- Changelog: [`CHANGELOG.md`](https://github.com/magentofullstackdev/hyperion/blob/main/CHANGELOG.md) in the main repo
|
|
74
|
+
- Issues: <https://github.com/magentofullstackdev/hyperion/issues>
|
|
75
|
+
- Documentation: `docs/child-themes.md` in the main repo (design tokens, base element)
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
MIT, see [LICENSE](./LICENSE).
|
package/package.json
CHANGED
|
@@ -1,6 +1,69 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magentofullstackdev/hyperion-shared",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.9.0-beta.2",
|
|
4
|
+
"description": "Shared design tokens, Tailwind preset, and base Lit element/mixins for the Hyperion Magento 2 theme suite. Consumed by @magentofullstackdev/hyperion-theme and @magentofullstackdev/hyperion-checkout.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Sergiu Ro <sergiu.ro@magentofullstack.dev>",
|
|
7
|
+
"homepage": "https://hyperionfront.dev",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/magentofullstackdev/hyperion.git",
|
|
11
|
+
"directory": "frontend/packages/shared"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/magentofullstackdev/hyperion/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"magento",
|
|
18
|
+
"magento2",
|
|
19
|
+
"hyperion",
|
|
20
|
+
"tailwind",
|
|
21
|
+
"tailwind-preset",
|
|
22
|
+
"lit",
|
|
23
|
+
"design-tokens",
|
|
24
|
+
"frontend"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"main": "./src/index.ts",
|
|
28
|
+
"types": "./src/index.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": "./src/index.ts",
|
|
31
|
+
"./tokens/*": "./src/tokens/*",
|
|
32
|
+
"./tailwind/preset": "./src/tailwind/preset.ts",
|
|
33
|
+
"./lit": "./src/lit/index.ts",
|
|
34
|
+
"./lit/*": "./src/lit/*",
|
|
35
|
+
"./html": "./src/html/index.ts"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"src",
|
|
39
|
+
"README.md",
|
|
40
|
+
"LICENSE",
|
|
41
|
+
"!src/**/__tests__",
|
|
42
|
+
"!src/**/*.test.*",
|
|
43
|
+
"!src/**/*.spec.*"
|
|
44
|
+
],
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@tailwindcss/typography": "^0.5.19"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"lit": "^3.1.2",
|
|
53
|
+
"tailwindcss": "^3.4.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
|
57
|
+
"@typescript-eslint/parser": "^6.15.0",
|
|
58
|
+
"eslint": "^8.56.0",
|
|
59
|
+
"typescript": "^5.3.0"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=22.0.0"
|
|
63
|
+
},
|
|
64
|
+
"scripts": {
|
|
65
|
+
"build": "echo \"@magentofullstackdev/hyperion-shared has no build step: consumed as TypeScript source by sibling workspaces\"",
|
|
66
|
+
"typecheck": "tsc --noEmit",
|
|
67
|
+
"lint": "eslint 'src/**/*.ts'"
|
|
68
|
+
}
|
|
6
69
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2026 Sergiu Ro -- magentofullstackdev
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
sanitizeHtml,
|
|
6
|
+
sanitizeMessageHtml,
|
|
7
|
+
htmlToText,
|
|
8
|
+
RICH_TEXT_TAGS,
|
|
9
|
+
MESSAGE_TAGS,
|
|
10
|
+
MESSAGE_OPTIONS,
|
|
11
|
+
} from './sanitize.js';
|
|
12
|
+
export type { SanitizeOptions } from './sanitize.js';
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2026 Sergiu Ro -- magentofullstackdev
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Allowlist HTML sanitizer for the few places where Magento hands the
|
|
6
|
+
* storefront display HTML: session messages (the `mage-messages` cookie)
|
|
7
|
+
* and admin-authored rich text in the checkout.
|
|
8
|
+
*
|
|
9
|
+
* Not a general-purpose sanitizer. The input is parsed into an inert
|
|
10
|
+
* document (DOMParser: no script runs, no resource loads, no event
|
|
11
|
+
* handler fires) and the output is rebuilt node by node from the
|
|
12
|
+
* allowlist, so nothing that is not named here can reach the page.
|
|
13
|
+
* Attributes are dropped, except the listed ones on links.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export interface SanitizeOptions {
|
|
17
|
+
/** Element names kept in the output. Anything else is unwrapped to its children. */
|
|
18
|
+
tags?: readonly string[];
|
|
19
|
+
/** Attributes kept on `<a>`. */
|
|
20
|
+
linkAttributes?: readonly string[];
|
|
21
|
+
/** URL schemes (with the colon) a link may point to; relative links resolve to the page's own. */
|
|
22
|
+
linkProtocols?: readonly string[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Inline formatting and lists, as Magento's rich-text editor emits them. */
|
|
26
|
+
export const RICH_TEXT_TAGS: readonly string[] = [
|
|
27
|
+
'p', 'br', 'strong', 'em', 'b', 'i', 'u',
|
|
28
|
+
'ul', 'ol', 'li',
|
|
29
|
+
'a', 'span', 'div',
|
|
30
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
/** What core's message component allows (Magento_Theme/js/view/messages.js). */
|
|
34
|
+
export const MESSAGE_TAGS: readonly string[] = ['div', 'span', 'b', 'strong', 'i', 'em', 'u', 'a'];
|
|
35
|
+
|
|
36
|
+
export const MESSAGE_OPTIONS: SanitizeOptions = {
|
|
37
|
+
tags: MESSAGE_TAGS,
|
|
38
|
+
linkAttributes: ['href'],
|
|
39
|
+
linkProtocols: ['http:', 'https:'],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const DEFAULT_LINK_ATTRIBUTES: readonly string[] = ['href', 'title', 'rel', 'target'];
|
|
43
|
+
const DEFAULT_LINK_PROTOCOLS: readonly string[] = ['http:', 'https:', 'mailto:', 'tel:'];
|
|
44
|
+
|
|
45
|
+
/** Elements whose content is code or foreign markup, never text to show. */
|
|
46
|
+
const DISCARDED_TAGS = new Set([
|
|
47
|
+
'script', 'style', 'template', 'noscript', 'iframe', 'object', 'embed',
|
|
48
|
+
'svg', 'math', 'textarea', 'title', 'head',
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
interface Rules {
|
|
52
|
+
tags: Set<string>;
|
|
53
|
+
linkAttributes: Set<string>;
|
|
54
|
+
linkProtocols: Set<string>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function parse(input: string): { doc: Document; root: Element | null } {
|
|
58
|
+
const doc = new DOMParser().parseFromString(`<div>${input}</div>`, 'text/html');
|
|
59
|
+
return { doc, root: doc.body.firstElementChild };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The scheme is decided by the URL parser, not by a pattern: it is what
|
|
64
|
+
* the browser will use and it is immune to case and whitespace tricks.
|
|
65
|
+
*/
|
|
66
|
+
function isSafeHref(href: string, protocols: Set<string>): boolean {
|
|
67
|
+
try {
|
|
68
|
+
return protocols.has(new URL(href, window.location.origin).protocol);
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function cleanLink(source: Element, clean: Element, rules: Rules): void {
|
|
75
|
+
for (const attr of Array.from(source.attributes)) {
|
|
76
|
+
const name = attr.name.toLowerCase();
|
|
77
|
+
if (rules.linkAttributes.has(name)) {
|
|
78
|
+
clean.setAttribute(name, attr.value);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const href = clean.getAttribute('href') ?? '';
|
|
82
|
+
if (href && !isSafeHref(href.trim(), rules.linkProtocols)) {
|
|
83
|
+
clean.removeAttribute('href');
|
|
84
|
+
}
|
|
85
|
+
const target = clean.getAttribute('target');
|
|
86
|
+
if (target !== null && target !== '_blank') {
|
|
87
|
+
clean.removeAttribute('target');
|
|
88
|
+
}
|
|
89
|
+
const safeHref = clean.getAttribute('href') ?? '';
|
|
90
|
+
if (/^https?:/i.test(safeHref) || clean.getAttribute('target') === '_blank') {
|
|
91
|
+
clean.setAttribute('rel', 'noopener noreferrer');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function appendClean(doc: Document, source: Node, target: Node, rules: Rules): void {
|
|
96
|
+
source.childNodes.forEach((child) => {
|
|
97
|
+
if (child.nodeType === 3) {
|
|
98
|
+
target.appendChild(doc.createTextNode(child.textContent ?? ''));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (child.nodeType !== 1) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const element = child as Element;
|
|
105
|
+
const tag = element.tagName.toLowerCase();
|
|
106
|
+
if (DISCARDED_TAGS.has(tag)) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (!rules.tags.has(tag)) {
|
|
110
|
+
appendClean(doc, element, target, rules);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const clean = doc.createElement(tag);
|
|
114
|
+
if (tag === 'a') {
|
|
115
|
+
cleanLink(element, clean, rules);
|
|
116
|
+
}
|
|
117
|
+
appendClean(doc, element, clean, rules);
|
|
118
|
+
target.appendChild(clean);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* HTML that holds only the allowed elements; safe to assign to `innerHTML`.
|
|
124
|
+
*/
|
|
125
|
+
export function sanitizeHtml(input: string, options: SanitizeOptions = {}): string {
|
|
126
|
+
const { doc, root } = parse(String(input ?? ''));
|
|
127
|
+
if (!root) return '';
|
|
128
|
+
const out = doc.createElement('div');
|
|
129
|
+
appendClean(doc, root, out, {
|
|
130
|
+
tags: new Set(options.tags ?? RICH_TEXT_TAGS),
|
|
131
|
+
linkAttributes: new Set(options.linkAttributes ?? DEFAULT_LINK_ATTRIBUTES),
|
|
132
|
+
linkProtocols: new Set(options.linkProtocols ?? DEFAULT_LINK_PROTOCOLS),
|
|
133
|
+
});
|
|
134
|
+
return out.innerHTML;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Session message from Magento as HTML: core's message allowlist, links
|
|
139
|
+
* to http(s) and relative targets only.
|
|
140
|
+
*/
|
|
141
|
+
export function sanitizeMessageHtml(input: string): string {
|
|
142
|
+
return sanitizeHtml(input, MESSAGE_OPTIONS);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Plain text of a value Magento hands out as display HTML (messages, cart
|
|
147
|
+
* item option values: escaped text, list entries joined with `<br />`,
|
|
148
|
+
* bundle selections with the price in a `<span>`). Line breaks become `, `.
|
|
149
|
+
*/
|
|
150
|
+
export function htmlToText(input: string): string {
|
|
151
|
+
const { doc, root } = parse(String(input ?? ''));
|
|
152
|
+
if (!root) return '';
|
|
153
|
+
root.querySelectorAll(Array.from(DISCARDED_TAGS).join(',')).forEach((element) => element.remove());
|
|
154
|
+
root.querySelectorAll('br').forEach((br) => br.replaceWith(doc.createTextNode(', ')));
|
|
155
|
+
return (root.textContent ?? '').replace(/\s+/g, ' ').replace(/ ,/g, ',').trim();
|
|
156
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2026 Sergiu Ro -- magentofullstackdev
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @magentofullstackdev/hyperion-shared barrel export.
|
|
6
|
+
*
|
|
7
|
+
* Re-exports the public API surface of the shared workspace package:
|
|
8
|
+
* Tailwind preset, base Lit element, mixins, HTML sanitizer. Design tokens (CSS) are
|
|
9
|
+
* consumed via direct CSS imports -- see `./tokens/*` exports in
|
|
10
|
+
* package.json.
|
|
11
|
+
*/
|
|
12
|
+
export { default as tailwindPreset } from './tailwind/preset.js';
|
|
13
|
+
export * from './lit/index.js';
|
|
14
|
+
export * from './html/index.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2026 Sergiu Ro -- magentofullstackdev
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Base Lit element for Hyperion components that need light-DOM rendering.
|
|
6
|
+
*
|
|
7
|
+
* Several Hyperion components -- every form-bearing component in the
|
|
8
|
+
* checkout, the account-nav enhancer, the coupon form,
|
|
9
|
+
* etc. -- must render to light DOM so:
|
|
10
|
+
*
|
|
11
|
+
* * Tailwind utility classes from the page's stylesheet cascade in,
|
|
12
|
+
* * Browser autofill and password managers (1Password, Bitwarden,
|
|
13
|
+
* browser built-ins) can introspect the inputs,
|
|
14
|
+
* * `form.checkValidity()` and the Constraint Validation API cross
|
|
15
|
+
* the element boundary reliably.
|
|
16
|
+
*
|
|
17
|
+
* This base class collapses the boilerplate `createRenderRoot() { return this; }`
|
|
18
|
+
* that every such component would otherwise duplicate. Components that
|
|
19
|
+
* need shadow DOM (sliders, dropdowns, the mini-cart drawer) should
|
|
20
|
+
* extend `LitElement` directly -- they don't need this class.
|
|
21
|
+
*/
|
|
22
|
+
import { LitElement } from 'lit';
|
|
23
|
+
|
|
24
|
+
export class HyperionLightElement extends LitElement {
|
|
25
|
+
public override createRenderRoot(): HTMLElement {
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type HyperionLightElementBase = typeof HyperionLightElement;
|
package/src/lit/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2026 Sergiu Ro -- magentofullstackdev
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @magentofullstackdev/hyperion-shared/lit barrel.
|
|
6
|
+
*
|
|
7
|
+
* Re-exports the Lit primitives shared across @magentofullstackdev/hyperion-theme
|
|
8
|
+
* and @magentofullstackdev/hyperion-checkout: a base element with the suite-wide
|
|
9
|
+
* light-DOM render-root convention, and mixins for SignalWatcher
|
|
10
|
+
* integration.
|
|
11
|
+
*/
|
|
12
|
+
export { HyperionLightElement } from './HyperionLightElement.js';
|
|
13
|
+
export type { HyperionLightElementBase } from './HyperionLightElement.js';
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2026 Sergiu Ro -- magentofullstackdev
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @magentofullstackdev/hyperion-shared/tailwind/preset
|
|
6
|
+
*
|
|
7
|
+
* Single source of truth for the Hyperion suite's Tailwind palette
|
|
8
|
+
* mapping. Both @magentofullstackdev/hyperion-theme and @magentofullstackdev/hyperion-checkout
|
|
9
|
+
* extend this preset (`presets: [hyperionPreset]`) so a `bg-primary` /
|
|
10
|
+
* `text-on-primary` / `border-subtle` class resolves identically in
|
|
11
|
+
* theme markup and checkout markup.
|
|
12
|
+
*
|
|
13
|
+
* Every semantic color token is mapped to a CSS Custom Property
|
|
14
|
+
* declared in `@magentofullstackdev/hyperion-shared/tokens/colors.css` using the
|
|
15
|
+
* `rgb(var(--color-*) / <alpha-value>)` syntax that lets Tailwind's
|
|
16
|
+
* opacity-modifier classes (e.g. `bg-primary/50`) work.
|
|
17
|
+
*
|
|
18
|
+
* `primary` and `primary-hover` are independent tokens so that
|
|
19
|
+
* `ring-primary` (focus-visibility) and `bg-primary-hover` (hover)
|
|
20
|
+
* resolve to separate CSS variables -- satisfies WCAG 2.1 AA
|
|
21
|
+
* focus-visibility overridability.
|
|
22
|
+
*
|
|
23
|
+
* The `content` array is intentionally EMPTY here -- each consuming
|
|
24
|
+
* package declares its own `content` glob in its `tailwind.config.ts`
|
|
25
|
+
* because the paths are package-specific.
|
|
26
|
+
*/
|
|
27
|
+
import typography from '@tailwindcss/typography';
|
|
28
|
+
import type { Config } from 'tailwindcss';
|
|
29
|
+
|
|
30
|
+
const preset: Partial<Config> = {
|
|
31
|
+
theme: {
|
|
32
|
+
extend: {
|
|
33
|
+
colors: {
|
|
34
|
+
primary: 'rgb(var(--color-primary) / <alpha-value>)',
|
|
35
|
+
'primary-hover': 'rgb(var(--color-primary-hover) / <alpha-value>)',
|
|
36
|
+
secondary: 'rgb(var(--color-secondary) / <alpha-value>)',
|
|
37
|
+
accent: 'rgb(var(--color-accent) / <alpha-value>)',
|
|
38
|
+
surface: 'rgb(var(--color-surface) / <alpha-value>)',
|
|
39
|
+
'surface-raised': 'rgb(var(--color-surface-raised) / <alpha-value>)',
|
|
40
|
+
'on-primary': 'rgb(var(--color-on-primary) / <alpha-value>)',
|
|
41
|
+
'on-secondary': 'rgb(var(--color-on-secondary) / <alpha-value>)',
|
|
42
|
+
muted: 'rgb(var(--color-muted) / <alpha-value>)',
|
|
43
|
+
subtle: 'rgb(var(--color-subtle) / <alpha-value>)',
|
|
44
|
+
error: 'rgb(var(--color-error) / <alpha-value>)',
|
|
45
|
+
'on-error': 'rgb(var(--color-on-error) / <alpha-value>)',
|
|
46
|
+
'error-strong': 'rgb(var(--color-error-strong) / <alpha-value>)',
|
|
47
|
+
success: 'rgb(var(--color-success) / <alpha-value>)',
|
|
48
|
+
'on-success': 'rgb(var(--color-on-success) / <alpha-value>)',
|
|
49
|
+
'success-strong': 'rgb(var(--color-success-strong) / <alpha-value>)',
|
|
50
|
+
warning: 'rgb(var(--color-warning) / <alpha-value>)',
|
|
51
|
+
'warning-strong': 'rgb(var(--color-warning-strong) / <alpha-value>)',
|
|
52
|
+
'on-warning': 'rgb(var(--color-on-warning) / <alpha-value>)',
|
|
53
|
+
info: 'rgb(var(--color-info) / <alpha-value>)',
|
|
54
|
+
'on-info': 'rgb(var(--color-on-info) / <alpha-value>)',
|
|
55
|
+
inverted: 'rgb(var(--color-surface-inverted) / <alpha-value>)',
|
|
56
|
+
'on-inverted': 'rgb(var(--color-on-inverted) / <alpha-value>)',
|
|
57
|
+
'on-inverted-muted': 'rgb(var(--color-on-inverted-muted) / <alpha-value>)',
|
|
58
|
+
'subtle-inverted': 'rgb(var(--color-subtle-inverted) / <alpha-value>)',
|
|
59
|
+
'price-sale': 'rgb(var(--color-price-sale) / <alpha-value>)',
|
|
60
|
+
// Extended tokens: semantic replacements for the banned palette classes.
|
|
61
|
+
'surface-sunken': 'rgb(var(--color-surface-sunken) / <alpha-value>)',
|
|
62
|
+
'surface-overlay': 'rgb(var(--color-surface-overlay) / <alpha-value>)',
|
|
63
|
+
'text-default': 'rgb(var(--color-text-default) / <alpha-value>)',
|
|
64
|
+
'text-secondary': 'rgb(var(--color-text-secondary) / <alpha-value>)',
|
|
65
|
+
'text-tertiary': 'rgb(var(--color-text-tertiary) / <alpha-value>)',
|
|
66
|
+
'border-default': 'rgb(var(--color-border-default) / <alpha-value>)',
|
|
67
|
+
divider: 'rgb(var(--color-divider) / <alpha-value>)',
|
|
68
|
+
'error-hover': 'rgb(var(--color-error-hover) / <alpha-value>)',
|
|
69
|
+
'error-active': 'rgb(var(--color-error-active) / <alpha-value>)',
|
|
70
|
+
'success-subtle': 'rgb(var(--color-success-subtle) / <alpha-value>)',
|
|
71
|
+
'warning-light': 'rgb(var(--color-warning-light) / <alpha-value>)',
|
|
72
|
+
'focus-ring': 'rgb(var(--color-focus-ring) / <alpha-value>)',
|
|
73
|
+
// Checkout aliases -- kept in the preset so existing
|
|
74
|
+
// checkout component class names (`bg-brand`, `text-on-brand`)
|
|
75
|
+
// keep resolving without a per-component rewrite.
|
|
76
|
+
brand: 'rgb(var(--checkout-color-brand) / <alpha-value>)',
|
|
77
|
+
'on-brand': 'rgb(var(--checkout-color-on-brand) / <alpha-value>)',
|
|
78
|
+
danger: 'rgb(var(--checkout-color-danger) / <alpha-value>)',
|
|
79
|
+
'on-danger': 'rgb(var(--checkout-color-on-danger) / <alpha-value>)',
|
|
80
|
+
},
|
|
81
|
+
typography: () => ({
|
|
82
|
+
DEFAULT: {
|
|
83
|
+
css: {
|
|
84
|
+
'--tw-prose-body': 'rgb(var(--color-secondary))',
|
|
85
|
+
'--tw-prose-headings': 'rgb(var(--color-secondary))',
|
|
86
|
+
'--tw-prose-lead': 'rgb(var(--color-muted))',
|
|
87
|
+
'--tw-prose-links': 'rgb(var(--color-primary))',
|
|
88
|
+
'--tw-prose-bold': 'rgb(var(--color-secondary))',
|
|
89
|
+
'--tw-prose-counters': 'rgb(var(--color-muted))',
|
|
90
|
+
'--tw-prose-bullets': 'rgb(var(--color-subtle))',
|
|
91
|
+
'--tw-prose-hr': 'rgb(var(--color-subtle))',
|
|
92
|
+
'--tw-prose-quotes': 'rgb(var(--color-secondary))',
|
|
93
|
+
'--tw-prose-quote-borders': 'rgb(var(--color-primary))',
|
|
94
|
+
'--tw-prose-captions': 'rgb(var(--color-muted))',
|
|
95
|
+
'--tw-prose-code': 'rgb(var(--color-secondary))',
|
|
96
|
+
'--tw-prose-pre-code': 'rgb(var(--color-on-inverted))',
|
|
97
|
+
'--tw-prose-pre-bg': 'rgb(var(--color-surface-inverted))',
|
|
98
|
+
'--tw-prose-th-borders': 'rgb(var(--color-subtle))',
|
|
99
|
+
'--tw-prose-td-borders': 'rgb(var(--color-subtle))',
|
|
100
|
+
maxWidth: 'none',
|
|
101
|
+
a: {
|
|
102
|
+
fontWeight: '500',
|
|
103
|
+
textDecoration: 'underline',
|
|
104
|
+
textUnderlineOffset: '2px',
|
|
105
|
+
'&:hover': {
|
|
106
|
+
color: 'rgb(var(--color-primary-hover))',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
'h1, h2, h3, h4': {
|
|
110
|
+
fontWeight: '700',
|
|
111
|
+
letterSpacing: '-0.01em',
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
}),
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
plugins: [typography],
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export default preset;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MIT */
|
|
2
|
+
/* Copyright (c) 2026 Sergiu Ro -- magentofullstackdev */
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* @magentofullstackdev/hyperion-shared -- semantic color tokens.
|
|
6
|
+
*
|
|
7
|
+
* Single source of truth for the Hyperion suite's palette. Both
|
|
8
|
+
* @magentofullstackdev/hyperion-theme and @magentofullstackdev/hyperion-checkout import this
|
|
9
|
+
* file (via `@magentofullstackdev/hyperion-shared/tokens/colors.css`) so a single rebrand
|
|
10
|
+
* propagates everywhere.
|
|
11
|
+
*
|
|
12
|
+
* Values MUST be raw RGB channel triplets as space-separated integers
|
|
13
|
+
* (e.g. `37 99 235`) -- never hex, never `rgb()` wrappers. This is the
|
|
14
|
+
* only format that lets Tailwind's `rgb(var(--color-*) / <alpha-value>)`
|
|
15
|
+
* mapping produce valid CSS for opacity-modifier classes such as
|
|
16
|
+
* `bg-primary/50`.
|
|
17
|
+
*
|
|
18
|
+
* Child themes override these declarations in their own `_theme.css`
|
|
19
|
+
* (loaded after this file in the cascade) -- see THEMING_GUIDE.md.
|
|
20
|
+
*/
|
|
21
|
+
:root {
|
|
22
|
+
/* --- Primary brand color -- buttons, links, active states, focus rings --- */
|
|
23
|
+
--color-primary: 37 99 235; /* #2563eb */
|
|
24
|
+
--color-primary-hover: 29 78 216; /* #1d4ed8 */
|
|
25
|
+
|
|
26
|
+
/* --- Secondary / supporting color --- */
|
|
27
|
+
--color-secondary: 100 116 139; /* #64748b */
|
|
28
|
+
|
|
29
|
+
/* --- Accent / highlight color --- */
|
|
30
|
+
--color-accent: 245 158 11; /* #f59e0b */
|
|
31
|
+
|
|
32
|
+
/* --- Surface backgrounds --- */
|
|
33
|
+
--color-surface: 255 255 255; /* #ffffff */
|
|
34
|
+
--color-surface-raised: 249 250 251; /* #f9fafb */
|
|
35
|
+
|
|
36
|
+
/* --- Text on colored backgrounds --- */
|
|
37
|
+
--color-on-primary: 255 255 255; /* #ffffff */
|
|
38
|
+
--color-on-secondary: 255 255 255; /* #ffffff */
|
|
39
|
+
|
|
40
|
+
/* --- Body text variants --- */
|
|
41
|
+
--color-muted: 107 114 128; /* #6b7280 */
|
|
42
|
+
--color-subtle: 229 231 235; /* #e5e7eb -- borders and dividers */
|
|
43
|
+
|
|
44
|
+
/* --- Semantic state colors --- */
|
|
45
|
+
--color-error: 220 38 38; /* #dc2626 */
|
|
46
|
+
--color-on-error: 255 255 255; /* #ffffff */
|
|
47
|
+
--color-error-strong: 185 28 28; /* #b91c1c, red-700 -- error TEXT on the bg-error/10 tint: 6.47:1 on white, 5.54:1 on the tint (the error red itself measured 4.13:1 there) */
|
|
48
|
+
--color-success: 21 128 61; /* #15803d, green-700: 5.02:1 on white, 4.57:1 on the success-subtle toast (axe flagged green-600 at 3.3:1) */
|
|
49
|
+
--color-on-success: 255 255 255; /* #ffffff */
|
|
50
|
+
--color-success-strong: 22 101 52; /* #166534, green-800 -- success TEXT on the bg-success/10 tint: 7.13:1 on white, 6.23:1 on the tint (green-700 measured 4.38:1 there) */
|
|
51
|
+
--color-warning: 217 119 6; /* #d97706 -- amber for validation / soft-fail: borders, icons, badges */
|
|
52
|
+
--color-warning-strong: 180 83 9; /* #b45309, amber-700 -- warning TEXT: 5.02:1 on white, 4.50:1 on the bg-warning/10 tint (amber-600 measured 2.86:1 on the "Only N left" pill, axe serious) */
|
|
53
|
+
--color-on-warning: 255 255 255; /* #ffffff */
|
|
54
|
+
--color-info: 37 99 235; /* #2563eb -- same as primary by default */
|
|
55
|
+
--color-on-info: 255 255 255; /* #ffffff */
|
|
56
|
+
|
|
57
|
+
/* --- Pricing tokens --- */
|
|
58
|
+
/* Color of the active "special" / final price on discounted items.
|
|
59
|
+
Defaults to the error red so the discount reads immediately; a
|
|
60
|
+
store can remap to `--color-primary` or a bespoke sale-hot-pink
|
|
61
|
+
without touching any .phtml. */
|
|
62
|
+
--color-price-sale: var(--color-error);
|
|
63
|
+
|
|
64
|
+
/* --- Extended tokens: secondary surface + text scale + helpers.
|
|
65
|
+
Each replaces an absolute palette class (banned in the theme) so every
|
|
66
|
+
color decision still resolves through a custom property. */
|
|
67
|
+
--color-surface-sunken: 248 250 252; /* #f8fafc -- slate-50, sub-page background blocks */
|
|
68
|
+
--color-surface-overlay: 241 245 249; /* #f1f5f9 -- modal/scrim backdrop */
|
|
69
|
+
--color-text-default: 17 24 39; /* #111827 -- primary body copy */
|
|
70
|
+
--color-text-secondary: 55 65 81; /* #374151 -- supporting copy */
|
|
71
|
+
--color-text-tertiary: 107 114 128; /* #6b7280 -- captions, muted helpers */
|
|
72
|
+
--color-border-default: 229 231 235; /* #e5e7eb */
|
|
73
|
+
--color-divider: 243 244 246; /* #f3f4f6 -- table row separators */
|
|
74
|
+
--color-error-hover: 220 38 38; /* #dc2626 -- destructive hover */
|
|
75
|
+
--color-error-active: 185 28 28; /* #b91c1c -- destructive pressed */
|
|
76
|
+
--color-success-subtle: 220 252 231; /* #dcfce7 -- success toast bg */
|
|
77
|
+
--color-warning-light: 253 224 71; /* #fde047 -- group-hover star icon */
|
|
78
|
+
--color-focus-ring: 99 102 241; /* #6366f1 -- focus ring */
|
|
79
|
+
|
|
80
|
+
/* --- Inverted surface (dark footer, cookie banner) --- */
|
|
81
|
+
/* An inverted surface is a dark band (typically the footer) with
|
|
82
|
+
light text and muted-light secondary text. A child theme can
|
|
83
|
+
redefine the pair without touching markup. */
|
|
84
|
+
--color-surface-inverted: 17 24 39; /* #111827 */
|
|
85
|
+
--color-on-inverted: 229 231 235; /* #e5e7eb */
|
|
86
|
+
--color-on-inverted-muted: 156 163 175; /* #9ca3af */
|
|
87
|
+
--color-subtle-inverted: 55 65 81; /* #374151 -- dividers inside dark surfaces */
|
|
88
|
+
|
|
89
|
+
/* --- Checkout aliases --- */
|
|
90
|
+
/* The checkout package reads --checkout-color-* tokens (its
|
|
91
|
+
Tailwind config in frontend/packages/module-checkout). Alias them
|
|
92
|
+
to the semantic tokens above so the checkout bundle inherits the store
|
|
93
|
+
look without a rebuild. Override in a child theme to give the
|
|
94
|
+
checkout a distinct visual identity from the rest of the store. */
|
|
95
|
+
--checkout-color-brand: var(--color-secondary);
|
|
96
|
+
--checkout-color-accent: var(--color-primary);
|
|
97
|
+
--checkout-color-danger: var(--color-error);
|
|
98
|
+
--checkout-color-muted: var(--color-muted);
|
|
99
|
+
--checkout-color-surface: var(--color-surface);
|
|
100
|
+
--checkout-color-surface-raised: var(--color-surface-raised);
|
|
101
|
+
--checkout-color-on-brand: var(--color-on-primary);
|
|
102
|
+
--checkout-color-on-accent: var(--color-on-primary);
|
|
103
|
+
--checkout-color-on-danger: var(--color-on-error);
|
|
104
|
+
--checkout-color-subtle: var(--color-subtle);
|
|
105
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MIT */
|
|
2
|
+
/* Copyright (c) 2026 Sergiu Ro -- magentofullstackdev */
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* @magentofullstackdev/hyperion-shared -- design-token barrel.
|
|
6
|
+
*
|
|
7
|
+
* Imported by both @magentofullstackdev/hyperion-theme (via web/css/source/_theme.css)
|
|
8
|
+
* and @magentofullstackdev/hyperion-checkout (via src/styles/vars.css). All Hyperion
|
|
9
|
+
* Composer packages render against the same semantic palette.
|
|
10
|
+
*/
|
|
11
|
+
@import "./colors.css";
|
|
12
|
+
@import "./typography.css";
|
|
13
|
+
@import "./spacing.css";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MIT */
|
|
2
|
+
/* Copyright (c) 2026 Sergiu Ro -- magentofullstackdev */
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* @magentofullstackdev/hyperion-shared -- spacing tokens.
|
|
6
|
+
*
|
|
7
|
+
* Section-level rhythm for theme chrome and content surfaces. Component-
|
|
8
|
+
* specific spacing (slider gap, swatch size, panel width, etc.) lives
|
|
9
|
+
* in the consuming package because it is component-specific, not part
|
|
10
|
+
* of the global design contract.
|
|
11
|
+
*/
|
|
12
|
+
:root {
|
|
13
|
+
--spacing-section: 2rem;
|
|
14
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MIT */
|
|
2
|
+
/* Copyright (c) 2026 Sergiu Ro -- magentofullstackdev */
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* @magentofullstackdev/hyperion-shared -- typography tokens.
|
|
6
|
+
*
|
|
7
|
+
* Single source of truth for type-scale and font stacks across the
|
|
8
|
+
* Hyperion suite. Override in a child theme's `_theme.css` to swap the
|
|
9
|
+
* font stack without touching markup.
|
|
10
|
+
*/
|
|
11
|
+
:root {
|
|
12
|
+
--font-family-base: ui-sans-serif, system-ui, sans-serif;
|
|
13
|
+
--font-size-base: 1rem;
|
|
14
|
+
|
|
15
|
+
/* --- Checkout font alias --- */
|
|
16
|
+
/* The checkout package reads --checkout-font-sans (its Tailwind
|
|
17
|
+
config in frontend/packages/module-checkout). Alias to the suite
|
|
18
|
+
font so the checkout inherits the store typography by default. */
|
|
19
|
+
--checkout-font-sans: var(--font-family-base);
|
|
20
|
+
}
|