@stacksjs/defaults 0.75.26 → 0.75.28
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/ai/skills/stacks-dashboard/SKILL.md +2 -2
- package/ai/skills/stacks-mail/SKILL.md +36 -0
- package/ide/vscode/package.json +1 -1
- package/package.json +2 -2
- package/resources/components/Email/EmailButton.stx +2 -2
- package/resources/components/Email/EmailCode.stx +28 -0
- package/resources/components/Email/EmailDivider.stx +2 -2
- package/resources/components/Email/EmailLayout.stx +77 -8
- package/resources/components/Email/EmailText.stx +6 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: stacks-dashboard
|
|
3
|
-
description: Use when building or customizing the Stacks admin dashboard, including dashboard pages, model management views, analytics widgets, commerce dashboards, content management, settings panels, deployment monitoring, job/queue management, or the
|
|
3
|
+
description: Use when building or customizing the Stacks admin dashboard, including dashboard pages, model management views, analytics widgets, commerce dashboards, content management, settings panels, deployment monitoring, job/queue management, or the 407 built-in dashboard components. Covers the dashboard system at storage/framework/defaults/.
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Bun >= 1.3.0, TypeScript
|
|
6
6
|
allowed-tools: Read Edit Write Bash Grep Glob
|
|
@@ -8,7 +8,7 @@ allowed-tools: Read Edit Write Bash Grep Glob
|
|
|
8
8
|
|
|
9
9
|
# Stacks Dashboard
|
|
10
10
|
|
|
11
|
-
The Stacks admin dashboard provides a full-featured admin panel with 100+ route views,
|
|
11
|
+
The Stacks admin dashboard provides a full-featured admin panel with 100+ route views, 407 components, and a multi-section layout.
|
|
12
12
|
|
|
13
13
|
## Key Paths
|
|
14
14
|
- Dashboard components: `storage/framework/defaults/resources/components/Dashboard/`
|
|
@@ -106,6 +106,42 @@ export default {
|
|
|
106
106
|
}
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
+
## The base design: email components
|
|
110
|
+
|
|
111
|
+
STX templates build on a bundled component library (`defaults/resources/components/Email`),
|
|
112
|
+
which every bundled template uses and which gives an app a finished look with a few props:
|
|
113
|
+
|
|
114
|
+
```html
|
|
115
|
+
<EmailLayout
|
|
116
|
+
title="Your license key"
|
|
117
|
+
preheader="Shown after the subject in the inbox"
|
|
118
|
+
brand="Uplink" brandUrl="https://uplink.example" accent="#f59e0b"
|
|
119
|
+
footer="You are getting this because you bought Uplink."
|
|
120
|
+
>
|
|
121
|
+
<EmailText size="heading">You have Uplink Monthly.</EmailText>
|
|
122
|
+
<EmailText>Here is your license key.</EmailText>
|
|
123
|
+
<EmailCode>UPLK-7RKR-Z2BJ-V6QV-4PUH</EmailCode>
|
|
124
|
+
<EmailButton href="https://uplink.example/activate" bg="#f59e0b" color="#18181b">Activate</EmailButton>
|
|
125
|
+
<EmailDivider />
|
|
126
|
+
<EmailText size="sm">Small print.</EmailText>
|
|
127
|
+
</EmailLayout>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
- `<EmailLayout>`: a muted page with one bordered card (a border, not a shadow: Gmail strips
|
|
131
|
+
shadows), an optional brand header and footer, a hidden `preheader`, and `@media` rules for
|
|
132
|
+
dark mode and phones on the class hooks the other components carry. Clients that ignore
|
|
133
|
+
`@media` keep the inline light design.
|
|
134
|
+
- `<EmailText size="heading|lg|md|sm">`, `<EmailButton>`, `<EmailDivider>`, `<EmailSection>`,
|
|
135
|
+
`<EmailImage>`, `<EmailLink>`.
|
|
136
|
+
- `<EmailCode>`: a key or one-time code, large and monospace, selectable in one tap, wrapping at
|
|
137
|
+
hyphens on a phone.
|
|
138
|
+
|
|
139
|
+
A colour passed as a prop is the template's own choice: that element gets no dark-mode hook.
|
|
140
|
+
|
|
141
|
+
Two stx traps when writing a component or template: never put the literal text of a style
|
|
142
|
+
tag inside a `<script server>` comment (the parser takes it for one), and write head CSS as a
|
|
143
|
+
raw string (`{!! css !!}`) rather than a style element, which stx lifts out as component CSS.
|
|
144
|
+
|
|
109
145
|
## Template Locations
|
|
110
146
|
- STX templates: `.stx` files processed by STX engine
|
|
111
147
|
- HTML templates: `.html` files with `{{ }}` variable interpolation
|
package/ide/vscode/package.json
CHANGED
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/defaults",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.28",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/stacksjs/stacks.git",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@iconify-json/f7": "^1.2.2",
|
|
57
57
|
"@iconify-json/hugeicons": "^1.2.27",
|
|
58
|
-
"@stacksjs/mobile": "^0.75.
|
|
58
|
+
"@stacksjs/mobile": "^0.75.28",
|
|
59
59
|
"@stacksjs/sanitizer": "^0.2.113",
|
|
60
60
|
"ts-qr-codes": "^0.1.8"
|
|
61
61
|
}
|
|
@@ -23,8 +23,8 @@ const padY = props.padY || '14px'
|
|
|
23
23
|
</script>
|
|
24
24
|
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:24px 0;border-collapse:separate;">
|
|
25
25
|
<tr>
|
|
26
|
-
<td align="center" style="border-radius:
|
|
27
|
-
<a href="{{ href }}" style="display:inline-block;padding:{{ padY }} {{ padX }};color:{{ color }};text-decoration:none;border-radius:
|
|
26
|
+
<td align="center" style="border-radius:8px;background-color:{{ bg }};mso-padding-alt:0;">
|
|
27
|
+
<a href="{{ href }}" style="display:inline-block;padding:{{ padY }} {{ padX }};color:{{ color }};text-decoration:none;border-radius:8px;font-weight:600;font-size:16px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;text-align:center;mso-padding-alt:{{ padY }} {{ padX }};">
|
|
28
28
|
<slot />
|
|
29
29
|
</a>
|
|
30
30
|
</td>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
// A code the reader copies: a license key, a one-time passcode, a recovery
|
|
3
|
+
// code. Large monospace on a tinted block, spaced so O and 0, l and 1 are
|
|
4
|
+
// told apart, and selectable as one unit where the client allows it
|
|
5
|
+
// (`user-select: all`), so a tap or double-click takes the whole code. On a
|
|
6
|
+
// narrow screen it wraps at a hyphen rather than mid-group, and only breaks
|
|
7
|
+
// inside a group when a code has no hyphens to wrap at.
|
|
8
|
+
//
|
|
9
|
+
// <EmailCode>UPLK-7RKR-Z2BJ-V6QV-4PUH</EmailCode>
|
|
10
|
+
// <EmailCode size="32px" align="center">482 913</EmailCode>
|
|
11
|
+
interface EmailCodeProps {
|
|
12
|
+
size?: string
|
|
13
|
+
align?: 'left' | 'center' | 'right'
|
|
14
|
+
spacing?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const props = defineProps<EmailCodeProps>()
|
|
18
|
+
const size = props.size || '22px'
|
|
19
|
+
const align = props.align || 'center'
|
|
20
|
+
const spacing = props.spacing || '24px'
|
|
21
|
+
</script>
|
|
22
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="width:100%;border-collapse:separate;margin:0 0 {{ spacing }} 0;">
|
|
23
|
+
<tr>
|
|
24
|
+
<td class="email-code" align="{{ align }}" style="padding:18px 20px;background-color:#f4f4f5;border:1px solid #e4e4e7;border-radius:10px;color:#18181b;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,'Liberation Mono',monospace;font-size:{{ size }};font-weight:600;letter-spacing:0.08em;line-height:1.4;text-align:{{ align }};-webkit-user-select:all;user-select:all;overflow-wrap:anywhere;">
|
|
25
|
+
<slot />
|
|
26
|
+
</td>
|
|
27
|
+
</tr>
|
|
28
|
+
</table>
|
|
@@ -10,11 +10,11 @@ interface EmailDividerProps {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
const props = defineProps<EmailDividerProps>()
|
|
13
|
-
const color = props.color || '#
|
|
13
|
+
const color = props.color || '#e4e4e7'
|
|
14
14
|
const spacing = props.spacing || '24px'
|
|
15
15
|
</script>
|
|
16
16
|
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="width:100%;border-collapse:collapse;margin:{{ spacing }} 0;">
|
|
17
17
|
<tr>
|
|
18
|
-
<td style="height:1px;font-size:1px;line-height:1px;background-color:{{ color }};"> </td>
|
|
18
|
+
<td class="email-divider" style="height:1px;font-size:1px;line-height:1px;background-color:{{ color }};"> </td>
|
|
19
19
|
</tr>
|
|
20
20
|
</table>
|
|
@@ -3,10 +3,25 @@
|
|
|
3
3
|
//
|
|
4
4
|
// Wraps content in the table-based body that Outlook + Gmail (Android)
|
|
5
5
|
// require, plus the standard meta block (charset / viewport /
|
|
6
|
-
// X-UA-Compatible / colour-scheme).
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
6
|
+
// X-UA-Compatible / colour-scheme). Named slots: head / header / footer.
|
|
7
|
+
//
|
|
8
|
+
// The base design, which every bundled template inherits:
|
|
9
|
+
// - a muted page with one white card, a hairline border and a 12px radius.
|
|
10
|
+
// A border rather than a shadow: Gmail strips box-shadow, so a shadow-only
|
|
11
|
+
// card lost its edge in the most-used client.
|
|
12
|
+
// - `preheader`: the line an inbox shows after the subject. Without it the
|
|
13
|
+
// inbox shows whatever text comes first - usually the brand name again.
|
|
14
|
+
// - `brand` / `accent` / `brandUrl`: a small brand header (an accent dot and
|
|
15
|
+
// the name) when the template does not fill the `header` slot itself.
|
|
16
|
+
// - `footer`: a muted, centred line under the card when the template does
|
|
17
|
+
// not fill the `footer` slot.
|
|
18
|
+
// - dark mode and a tighter phone layout, as `@media` rules on the class
|
|
19
|
+
// hooks below. The CSS inliner leaves `@media` rules in the head
|
|
20
|
+
// (stacksjs/stacks#1902), so clients that honour them (Apple Mail, iOS,
|
|
21
|
+
// Outlook for Mac) get them and the rest keep the inline light design.
|
|
22
|
+
// - those rules are written out raw from here, not as a style element in
|
|
23
|
+
// the template: stx treats a component's own style element as component
|
|
24
|
+
// CSS and lifts it out of the head, which lost them.
|
|
10
25
|
//
|
|
11
26
|
// Notes on `defineProps()`: stx fills missing props with an empty
|
|
12
27
|
// string (not `undefined`), so destructure defaults silently no-op
|
|
@@ -17,6 +32,11 @@ interface EmailLayoutProps {
|
|
|
17
32
|
width?: number | string
|
|
18
33
|
bodyBg?: string
|
|
19
34
|
contentBg?: string
|
|
35
|
+
preheader?: string
|
|
36
|
+
brand?: string
|
|
37
|
+
brandUrl?: string
|
|
38
|
+
accent?: string
|
|
39
|
+
footer?: string
|
|
20
40
|
}
|
|
21
41
|
|
|
22
42
|
const props = defineProps<EmailLayoutProps>()
|
|
@@ -24,6 +44,32 @@ const title = props.title || ''
|
|
|
24
44
|
const width = props.width || 600
|
|
25
45
|
const bodyBg = props.bodyBg || '#f4f4f5'
|
|
26
46
|
const contentBg = props.contentBg || '#ffffff'
|
|
47
|
+
const preheader = props.preheader || ''
|
|
48
|
+
const brand = props.brand || ''
|
|
49
|
+
const brandUrl = props.brandUrl || ''
|
|
50
|
+
const accent = props.accent || '#3451b2'
|
|
51
|
+
const footer = props.footer || ''
|
|
52
|
+
const fontStack = '-apple-system,BlinkMacSystemFont,\'Segoe UI\',Roboto,\'Helvetica Neue\',Arial,sans-serif'
|
|
53
|
+
|
|
54
|
+
const responsiveCss = [
|
|
55
|
+
'<style>',
|
|
56
|
+
'@media only screen and (max-width: 620px) {',
|
|
57
|
+
' .email-outer { padding: 16px 8px !important; }',
|
|
58
|
+
' .email-pad { padding: 28px 24px !important; }',
|
|
59
|
+
' .email-brand { padding: 24px 24px 0 !important; }',
|
|
60
|
+
' .email-code { font-size: 18px !important; letter-spacing: 0.04em !important; }',
|
|
61
|
+
'}',
|
|
62
|
+
'@media (prefers-color-scheme: dark) {',
|
|
63
|
+
' .email-body, .email-outer { background-color: #0c0c0e !important; }',
|
|
64
|
+
' .email-card { background-color: #141417 !important; border-color: #27272a !important; }',
|
|
65
|
+
' .email-heading, .email-brand-name { color: #f4f4f5 !important; }',
|
|
66
|
+
' .email-text { color: #d4d4d8 !important; }',
|
|
67
|
+
' .email-muted { color: #a1a1aa !important; }',
|
|
68
|
+
' .email-code { background-color: #1c1c20 !important; border-color: #27272a !important; color: #f4f4f5 !important; }',
|
|
69
|
+
' .email-divider { background-color: #27272a !important; }',
|
|
70
|
+
'}',
|
|
71
|
+
'</style>',
|
|
72
|
+
].join('\n')
|
|
27
73
|
</script>
|
|
28
74
|
|
|
29
75
|
<!DOCTYPE html>
|
|
@@ -35,19 +81,42 @@ const contentBg = props.contentBg || '#ffffff'
|
|
|
35
81
|
<meta name="color-scheme" content="light dark">
|
|
36
82
|
<meta name="supported-color-schemes" content="light dark">
|
|
37
83
|
<title>{{ title }}</title>
|
|
84
|
+
{!! responsiveCss !!}
|
|
38
85
|
<slot name="head" />
|
|
39
86
|
</head>
|
|
40
|
-
<body style="margin:0;padding:0;background-color:{{ bodyBg }};font-family
|
|
87
|
+
<body class="email-body" style="margin:0;padding:0;background-color:{{ bodyBg }};font-family:{{ fontStack }};line-height:1.6;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
|
88
|
+
@if(preheader)
|
|
89
|
+
<div style="display:none;max-height:0;max-width:0;overflow:hidden;opacity:0;mso-hide:all;font-size:1px;line-height:1px;color:{{ bodyBg }};">{{ preheader }} ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏</div>
|
|
90
|
+
@endif
|
|
41
91
|
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="width:100%;border-collapse:collapse;background-color:{{ bodyBg }};">
|
|
42
92
|
<tr>
|
|
43
|
-
<td align="center" style="padding:40px 20px;">
|
|
44
|
-
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="max-width:{{ width }}px;width:100%;background-color:{{ contentBg }};border-radius:
|
|
93
|
+
<td class="email-outer" align="center" style="padding:40px 20px;background-color:{{ bodyBg }};">
|
|
94
|
+
<table class="email-card" role="presentation" cellpadding="0" cellspacing="0" border="0" style="max-width:{{ width }}px;width:100%;background-color:{{ contentBg }};border:1px solid #e4e4e7;border-radius:12px;overflow:hidden;border-collapse:separate;">
|
|
95
|
+
@if(brand)
|
|
96
|
+
<tr>
|
|
97
|
+
<td class="email-brand" style="padding:32px 40px 0;">
|
|
98
|
+
<span style="display:inline-block;width:10px;height:10px;border-radius:5px;background-color:{{ accent }};vertical-align:middle;margin-right:10px;"></span>
|
|
99
|
+
@if(brandUrl)
|
|
100
|
+
<a class="email-brand-name" href="{{ brandUrl }}" style="color:#18181b;text-decoration:none;font-size:15px;font-weight:600;letter-spacing:-0.01em;vertical-align:middle;font-family:{{ fontStack }};">{{ brand }}</a>
|
|
101
|
+
@else
|
|
102
|
+
<span class="email-brand-name" style="color:#18181b;font-size:15px;font-weight:600;letter-spacing:-0.01em;vertical-align:middle;font-family:{{ fontStack }};">{{ brand }}</span>
|
|
103
|
+
@endif
|
|
104
|
+
</td>
|
|
105
|
+
</tr>
|
|
106
|
+
@endif
|
|
45
107
|
<slot name="header" />
|
|
46
|
-
<tr><td style="padding:40px;">
|
|
108
|
+
<tr><td class="email-pad" style="padding:32px 40px 40px;">
|
|
47
109
|
<slot />
|
|
48
110
|
</td></tr>
|
|
49
111
|
<slot name="footer" />
|
|
50
112
|
</table>
|
|
113
|
+
@if(footer)
|
|
114
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="max-width:{{ width }}px;width:100%;border-collapse:collapse;">
|
|
115
|
+
<tr>
|
|
116
|
+
<td class="email-muted" style="padding:20px 24px 0;text-align:center;color:#71717a;font-size:12px;line-height:1.6;font-family:{{ fontStack }};">{{ footer }}</td>
|
|
117
|
+
</tr>
|
|
118
|
+
</table>
|
|
119
|
+
@endif
|
|
51
120
|
</td>
|
|
52
121
|
</tr>
|
|
53
122
|
</table>
|
|
@@ -14,17 +14,20 @@ interface EmailTextProps {
|
|
|
14
14
|
const props = defineProps<EmailTextProps>()
|
|
15
15
|
const align = props.align || 'left'
|
|
16
16
|
const size = props.size || 'md'
|
|
17
|
-
const color = props.color || (size === 'heading' ? '#
|
|
17
|
+
const color = props.color || (size === 'heading' ? '#18181b' : size === 'sm' ? '#71717a' : '#3f3f46')
|
|
18
|
+
// Hooks for the dark-mode rules in <EmailLayout>. A colour passed as a prop is
|
|
19
|
+
// the template's own choice, so it gets no hook and dark mode leaves it alone.
|
|
20
|
+
const role = props.color ? '' : size === 'heading' ? 'email-heading' : size === 'sm' ? 'email-muted' : 'email-text'
|
|
18
21
|
const spacing = props.spacing || '16px'
|
|
19
22
|
|
|
20
23
|
const sizeStyles: Record<string, string> = {
|
|
21
24
|
sm: 'font-size:14px;line-height:1.5;',
|
|
22
25
|
md: 'font-size:16px;line-height:1.6;',
|
|
23
26
|
lg: 'font-size:18px;line-height:1.6;',
|
|
24
|
-
heading: 'font-size:
|
|
27
|
+
heading: 'font-size:22px;line-height:1.3;font-weight:600;letter-spacing:-0.01em;',
|
|
25
28
|
}
|
|
26
29
|
const sizeStyle = sizeStyles[size] || sizeStyles.md
|
|
27
30
|
</script>
|
|
28
|
-
<p style="margin:0 0 {{ spacing }} 0;text-align:{{ align }};color:{{ color }};{{ sizeStyle }}">
|
|
31
|
+
<p class="{{ role }}" style="margin:0 0 {{ spacing }} 0;text-align:{{ align }};color:{{ color }};{{ sizeStyle }}">
|
|
29
32
|
<slot />
|
|
30
33
|
</p>
|