@slyxup/ui 0.2.0 → 0.2.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/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/dist/components/BillingPortal/BillingPortal.d.ts +22 -0
- package/dist/components/BillingPortal/BillingPortal.d.ts.map +1 -0
- package/dist/components/BillingPortal/BillingPortal.js +61 -0
- package/dist/components/BillingPortal/BillingPortal.js.map +1 -0
- package/dist/components/EmailVerification/EmailVerification.d.ts.map +1 -1
- package/dist/components/EmailVerification/EmailVerification.js +2 -0
- package/dist/components/EmailVerification/EmailVerification.js.map +1 -1
- package/dist/components/ForgotPassword/ForgotPassword.d.ts.map +1 -1
- package/dist/components/ForgotPassword/ForgotPassword.js +2 -0
- package/dist/components/ForgotPassword/ForgotPassword.js.map +1 -1
- package/dist/components/PricingTable/PricingTable.d.ts +19 -0
- package/dist/components/PricingTable/PricingTable.d.ts.map +1 -0
- package/dist/components/PricingTable/PricingTable.js +54 -0
- package/dist/components/PricingTable/PricingTable.js.map +1 -0
- package/dist/components/ResetPassword/ResetPassword.d.ts.map +1 -1
- package/dist/components/ResetPassword/ResetPassword.js +2 -0
- package/dist/components/ResetPassword/ResetPassword.js.map +1 -1
- package/dist/components/SignIn/SignIn.d.ts.map +1 -1
- package/dist/components/SignIn/SignIn.js +16 -3
- package/dist/components/SignIn/SignIn.js.map +1 -1
- package/dist/components/SignUp/SignUp.d.ts.map +1 -1
- package/dist/components/SignUp/SignUp.js +16 -3
- package/dist/components/SignUp/SignUp.js.map +1 -1
- package/dist/components/SocialButtons/SocialButtons.d.ts +1 -1
- package/dist/components/SocialButtons/SocialButtons.d.ts.map +1 -1
- package/dist/components/SocialButtons/SocialButtons.js +7 -2
- package/dist/components/SocialButtons/SocialButtons.js.map +1 -1
- package/dist/components/UserButton/UserButton.d.ts.map +1 -1
- package/dist/components/UserButton/UserButton.js +2 -0
- package/dist/components/UserButton/UserButton.js.map +1 -1
- package/dist/components/UserProfile/UserProfile.d.ts.map +1 -1
- package/dist/components/UserProfile/UserProfile.js +2 -0
- package/dist/components/UserProfile/UserProfile.js.map +1 -1
- package/dist/styles.d.ts +3 -2
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +37 -16
- package/dist/styles.js.map +1 -1
- package/package.json +13 -9
- package/src/components/BillingPortal/BillingPortal.tsx +184 -0
- package/src/components/EmailVerification/EmailVerification.tsx +2 -0
- package/src/components/ForgotPassword/ForgotPassword.tsx +2 -0
- package/src/components/PricingTable/PricingTable.tsx +157 -0
- package/src/components/ResetPassword/ResetPassword.tsx +2 -0
- package/src/components/SignIn/SignIn.tsx +29 -2
- package/src/components/SignUp/SignUp.tsx +29 -2
- package/src/components/SocialButtons/SocialButtons.tsx +10 -3
- package/src/components/UserButton/UserButton.tsx +2 -0
- package/src/components/UserProfile/UserProfile.tsx +2 -0
- package/src/styles.ts +38 -16
- package/test/components.test.tsx +135 -0
- package/test/icons.test.tsx +32 -0
- package/test/styles.test.ts +47 -0
- package/vitest.config.ts +2 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useAuth, useUser } from '@slyxup/react';
|
|
3
3
|
import { useEffect, useState } from 'react';
|
|
4
|
+
import { injectStyles } from '../../styles';
|
|
4
5
|
/** Edit first/last name + avatar URL. */
|
|
5
6
|
export function UserProfile() {
|
|
7
|
+
injectStyles();
|
|
6
8
|
const { isLoaded, user, reload } = useUser();
|
|
7
9
|
const { client } = useAuth();
|
|
8
10
|
const [firstName, setFirstName] = useState('');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserProfile.js","sourceRoot":"","sources":["../../../src/components/UserProfile/UserProfile.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAkB,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"UserProfile.js","sourceRoot":"","sources":["../../../src/components/UserProfile/UserProfile.tsx"],"names":[],"mappings":"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"}
|
package/dist/styles.d.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* SlyxUp UI — design tokens + stylesheet.
|
|
3
3
|
* Injected once via <SlyxUpStyles />. Theme with CSS variables on :root or .slyxup-scope.
|
|
4
4
|
*/
|
|
5
|
-
export declare const
|
|
6
|
-
|
|
5
|
+
export declare const FONT_LINK = "<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\"><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin><link href=\"https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,300;9..40,400;9..40,500;9..40,600;9..40,700;9..40,800&family=Space+Grotesk:wght@400;500;600;700&display=swap\" rel=\"stylesheet\">";
|
|
6
|
+
export declare const CSS = "\n@import url('https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,300;9..40,400;9..40,500;9..40,600;9..40,700;9..40,800&family=Space+Grotesk:wght@400;500;600;700&display=swap');\n\n.slyxup-root {\n --slx-accent: #5b5bd6;\n --slx-accent-hover: #4c4cc4;\n --slx-accent-soft: rgba(91, 91, 214, 0.14);\n --slx-bg: #ffffff;\n --slx-bg-page: #e9eaf6;\n --slx-ink: #16161d;\n --slx-muted: #6f6f7b;\n --slx-border: #d8d8e8;\n --slx-danger: #d64550;\n --slx-radius: 12px;\n --slx-font: \"DM Sans\", -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n --slx-display: \"Space Grotesk\", \"DM Sans\", sans-serif;\n --slx-mono: ui-monospace, SFMono-Regular, Menlo, monospace;\n\n font-family: var(--slx-font);\n color: var(--slx-ink);\n -webkit-font-smoothing: antialiased;\n}\n@media (prefers-color-scheme: dark) {\n .slyxup-root:not(.slyxup-light) {\n --slx-accent: #8484f2;\n --slx-accent-hover: #9696f5;\n --slx-accent-soft: rgba(132, 132, 242, 0.14);\n --slx-bg: #191920;\n --slx-bg-page: #121218;\n --slx-ink: #f2f2f5;\n --slx-muted: #9a9aa6;\n --slx-border: #2c2c36;\n --slx-danger: #f0737d;\n }\n}\n\n@keyframes slx-shake {\n 10%, 90% { transform: translateX(-1px); }\n 20%, 80% { transform: translateX(2px); }\n 30%, 50%, 70% { transform: translateX(-4px); }\n 40%, 60% { transform: translateX(4px); }\n}\n@keyframes slx-spin { to { transform: rotate(360deg); } }\n@media (prefers-reduced-motion: reduce) {\n .slyxup-root * { animation: none !important; transition: none !important; }\n}\n\n/* \u2500\u2500 Card \u2500\u2500 */\n.slx-card {\n width: 100%;\n max-width: 380px;\n background: var(--slx-bg);\n border: 1px solid var(--slx-border);\n border-radius: calc(var(--slx-radius) + 4px);\n padding: 32px;\n box-shadow: 0 4px 12px rgba(16,16,29,.08), 0 16px 40px rgba(16,16,29,.12), 0 0 0 1px rgba(16,16,29,.04);\n box-sizing: border-box;\n}\n.slyxup-root .slx-card { background: #ffffff; }\n.slx-card-error { animation: slx-shake .45s cubic-bezier(.36,.07,.19,.97) both; }\n\n/* \u2500\u2500 Header / keyhole mark \u2500\u2500 */\n.slx-mark {\n width: 44px; height: 44px; border-radius: 12px;\n display: flex; align-items: center; justify-content: center;\n background: linear-gradient(135deg, var(--slx-accent), #9a6cf0);\n margin-bottom: 18px;\n}\n.slx-mark svg { display: block; }\n.slx-title { font-family: var(--slx-display); font-size: 20px; font-weight: 650; letter-spacing: -0.02em; margin: 0 0 6px; }\n.slx-subtitle { font-size: 13.5px; color: var(--slx-muted); margin: 0 0 22px; line-height: 1.5; }\n\n/* \u2500\u2500 Fields \u2500\u2500 */\n.slx-field { margin-bottom: 14px; }\n.slx-label { display: block; font-size: 12.5px; font-weight: 550; margin-bottom: 6px; letter-spacing: 0.01em; }\n.slx-input {\n width: 100%; box-sizing: border-box;\n font: inherit; font-size: 14px; color: var(--slx-ink);\n background: transparent;\n border: 1px solid var(--slx-border);\n border-radius: var(--slx-radius);\n padding: 10px 12px;\n outline: none;\n transition: border-color .15s, box-shadow .15s;\n}\n.slx-input::placeholder { color: var(--slx-muted); opacity: .75; }\n.slx-input:focus-visible {\n border-color: var(--slx-accent);\n box-shadow: 0 0 0 3px var(--slx-accent-soft);\n}\n.slx-hint { font-size: 12px; color: var(--slx-muted); margin-top: 5px; }\n.slx-error-text {\n font-size: 13px; color: var(--slx-danger);\n background: color-mix(in srgb, var(--slx-danger) 9%, transparent);\n border-radius: 8px; padding: 9px 11px; margin: 0 0 14px; line-height: 1.4;\n}\n\n/* \u2500\u2500 Button \u2500\u2500 */\n.slx-btn {\n width: 100%; box-sizing: border-box;\n font-family: var(--slx-font); font-size: 14px; font-weight: 600; letter-spacing: 0.01em;\n color: #fff; background: #0a0a0f;\n border: 1px solid #0a0a0f; border-radius: var(--slx-radius);\n padding: 11px 14px; cursor: pointer;\n display: inline-flex; align-items: center; justify-content: center; gap: 8px;\n transition: background .15s, transform .06s, border-color .15s;\n}\n.slx-btn:hover { background: #1a1a23; border-color: #1a1a23; }\n.slx-btn:active { transform: scale(.985); background: #000; }\n.slx-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(10,10,15,.14), 0 0 0 1px #0a0a0f; }\n.slx-btn[disabled] { opacity: .6; cursor: not-allowed; }\n@media (prefers-color-scheme: dark) {\n .slyxup-root:not(.slyxup-light) .slx-btn { background: #f2f2f5; color: #0a0a0f; border-color: #f2f2f5; }\n .slyxup-root:not(.slyxup-light) .slx-btn:hover { background: #e6e6eb; border-color: #e6e6eb; }\n}\n.slx-spinner {\n width: 15px; height: 15px; flex: none;\n border: 2px solid rgba(255,255,255,.35); border-top-color: #fff;\n border-radius: 50%; animation: slx-spin .7s linear infinite;\n}\n\n/* \u2500\u2500 Social \u2500\u2500 */\n.slx-social { display: grid; gap: 10px; margin-bottom: 16px; }\n.slx-social-btn {\n width: 100%; box-sizing: border-box;\n font: inherit; font-size: 13.5px; font-weight: 550;\n color: var(--slx-ink); background: var(--slx-bg);\n border: 1px solid var(--slx-border); border-radius: var(--slx-radius);\n padding: 10px 14px; cursor: pointer;\n display: inline-flex; align-items: center; justify-content: center; gap: 10px;\n transition: background .15s, border-color .15s;\n}\n.slx-social-btn:hover { background: color-mix(in srgb, var(--slx-ink) 4%, var(--slx-bg)); }\n.slx-social-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--slx-accent-soft); }\n\n/* \u2500\u2500 Divider & footer \u2500\u2500 */\n.slx-divider {\n display: flex; align-items: center; gap: 12px;\n color: var(--slx-muted); font-size: 12px;\n margin: 18px 0;\n}\n.slx-divider::before, .slx-divider::after {\n content: \"\"; height: 1px; flex: 1; background: var(--slx-border);\n}\n.slx-footer { font-size: 13px; color: var(--slx-muted); margin-top: 20px; text-align: center; }\n.slx-link {\n color: var(--slx-accent); font-weight: 600; text-decoration: none; cursor: pointer;\n background: none; border: none; font: inherit; font-size: inherit;\n padding: 0; margin: 0;\n}\n.slx-link:hover { text-decoration: underline; color: var(--slx-accent-hover); }\n.slx-link:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--slx-accent-soft); border-radius: 4px; }\n\n/* \u2500\u2500 Success state \u2500\u2500 */\n.slx-success-icon {\n width: 46px; height: 46px; border-radius: 50%;\n display: flex; align-items: center; justify-content: center;\n background: color-mix(in srgb, #34a853 12%, transparent);\n color: #34a853; margin: 4px auto 18px;\n}\n\n/* \u2500\u2500 User button \u2500\u2500 */\n.slx-userbtn-wrap { position: relative; display: inline-block; }\n.slx-userbtn-avatar {\n width: 36px; height: 36px; border-radius: 50%;\n border: 1px solid var(--slx-border); cursor: pointer;\n display: flex; align-items: center; justify-content: center;\n font-size: 14px; font-weight: 650; color: #fff;\n background: linear-gradient(135deg, var(--slx-accent), #9a6cf0);\n padding: 0; overflow: hidden;\n}\n.slx-userbtn-avatar img { width: 100%; height: 100%; object-fit: cover; }\n.slx-userbtn-avatar:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--slx-accent-soft); }\n.slx-menu {\n position: absolute; right: 0; top: calc(100% + 8px);\n min-width: 230px;\n background: var(--slx-bg);\n border: 1px solid var(--slx-border);\n border-radius: var(--slx-radius);\n box-shadow: 0 8px 30px rgba(10,10,20,.14);\n overflow: hidden; z-index: 1000;\n}\n.slx-menu-header { padding: 14px 16px; border-bottom: 1px solid var(--slx-border); }\n.slx-menu-name { font-size: 14px; font-weight: 600; margin: 0 0 2px; }\n.slx-menu-email { font-size: 12.5px; color: var(--slx-muted); margin: 0; word-break: break-all; }\n.slx-menu-item {\n display: block; width: 100%; text-align: left; box-sizing: border-box;\n font: inherit; font-size: 13.5px; color: var(--slx-ink);\n background: none; border: none; padding: 10px 16px; cursor: pointer;\n}\n.slx-menu-item:hover { background: color-mix(in srgb, var(--slx-ink) 5%, transparent); }\n.slx-menu-item:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--slx-accent-soft); }\n.slx-menu-item-danger { color: var(--slx-danger); }\n";
|
|
7
|
+
/** Inject the SlyxUp stylesheet + DM Sans font once per document. */
|
|
7
8
|
export declare function injectStyles(): void;
|
|
8
9
|
//# sourceMappingURL=styles.d.ts.map
|
package/dist/styles.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,eAAO,MAAM,GAAG,
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,eAAO,MAAM,SAAS,4VAAgV,CAAC;AAEvW,eAAO,MAAM,GAAG,4iQAiMf,CAAC;AAIF,qEAAqE;AACrE,wBAAgB,YAAY,IAAI,IAAI,CAenC"}
|
package/dist/styles.js
CHANGED
|
@@ -2,19 +2,23 @@
|
|
|
2
2
|
* SlyxUp UI — design tokens + stylesheet.
|
|
3
3
|
* Injected once via <SlyxUpStyles />. Theme with CSS variables on :root or .slyxup-scope.
|
|
4
4
|
*/
|
|
5
|
+
export const FONT_LINK = `<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,300;9..40,400;9..40,500;9..40,600;9..40,700;9..40,800&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">`;
|
|
5
6
|
export const CSS = `
|
|
7
|
+
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,300;9..40,400;9..40,500;9..40,600;9..40,700;9..40,800&family=Space+Grotesk:wght@400;500;600;700&display=swap');
|
|
8
|
+
|
|
6
9
|
.slyxup-root {
|
|
7
10
|
--slx-accent: #5b5bd6;
|
|
8
11
|
--slx-accent-hover: #4c4cc4;
|
|
9
|
-
--slx-accent-soft: rgba(91, 91, 214, 0.
|
|
12
|
+
--slx-accent-soft: rgba(91, 91, 214, 0.14);
|
|
10
13
|
--slx-bg: #ffffff;
|
|
11
|
-
--slx-bg-page: #
|
|
14
|
+
--slx-bg-page: #e9eaf6;
|
|
12
15
|
--slx-ink: #16161d;
|
|
13
16
|
--slx-muted: #6f6f7b;
|
|
14
|
-
--slx-border: #
|
|
17
|
+
--slx-border: #d8d8e8;
|
|
15
18
|
--slx-danger: #d64550;
|
|
16
19
|
--slx-radius: 12px;
|
|
17
|
-
--slx-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
20
|
+
--slx-font: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
21
|
+
--slx-display: "Space Grotesk", "DM Sans", sans-serif;
|
|
18
22
|
--slx-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
19
23
|
|
|
20
24
|
font-family: var(--slx-font);
|
|
@@ -54,9 +58,10 @@ export const CSS = `
|
|
|
54
58
|
border: 1px solid var(--slx-border);
|
|
55
59
|
border-radius: calc(var(--slx-radius) + 4px);
|
|
56
60
|
padding: 32px;
|
|
57
|
-
box-shadow: 0
|
|
61
|
+
box-shadow: 0 4px 12px rgba(16,16,29,.08), 0 16px 40px rgba(16,16,29,.12), 0 0 0 1px rgba(16,16,29,.04);
|
|
58
62
|
box-sizing: border-box;
|
|
59
63
|
}
|
|
64
|
+
.slyxup-root .slx-card { background: #ffffff; }
|
|
60
65
|
.slx-card-error { animation: slx-shake .45s cubic-bezier(.36,.07,.19,.97) both; }
|
|
61
66
|
|
|
62
67
|
/* ── Header / keyhole mark ── */
|
|
@@ -67,7 +72,7 @@ export const CSS = `
|
|
|
67
72
|
margin-bottom: 18px;
|
|
68
73
|
}
|
|
69
74
|
.slx-mark svg { display: block; }
|
|
70
|
-
.slx-title { font-size:
|
|
75
|
+
.slx-title { font-family: var(--slx-display); font-size: 20px; font-weight: 650; letter-spacing: -0.02em; margin: 0 0 6px; }
|
|
71
76
|
.slx-subtitle { font-size: 13.5px; color: var(--slx-muted); margin: 0 0 22px; line-height: 1.5; }
|
|
72
77
|
|
|
73
78
|
/* ── Fields ── */
|
|
@@ -98,17 +103,21 @@ export const CSS = `
|
|
|
98
103
|
/* ── Button ── */
|
|
99
104
|
.slx-btn {
|
|
100
105
|
width: 100%; box-sizing: border-box;
|
|
101
|
-
font:
|
|
102
|
-
color: #fff; background:
|
|
103
|
-
border:
|
|
106
|
+
font-family: var(--slx-font); font-size: 14px; font-weight: 600; letter-spacing: 0.01em;
|
|
107
|
+
color: #fff; background: #0a0a0f;
|
|
108
|
+
border: 1px solid #0a0a0f; border-radius: var(--slx-radius);
|
|
104
109
|
padding: 11px 14px; cursor: pointer;
|
|
105
110
|
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
|
|
106
|
-
transition: background .15s, transform .06s;
|
|
111
|
+
transition: background .15s, transform .06s, border-color .15s;
|
|
107
112
|
}
|
|
108
|
-
.slx-btn:hover { background:
|
|
109
|
-
.slx-btn:active { transform: scale(.985); }
|
|
110
|
-
.slx-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px
|
|
113
|
+
.slx-btn:hover { background: #1a1a23; border-color: #1a1a23; }
|
|
114
|
+
.slx-btn:active { transform: scale(.985); background: #000; }
|
|
115
|
+
.slx-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(10,10,15,.14), 0 0 0 1px #0a0a0f; }
|
|
111
116
|
.slx-btn[disabled] { opacity: .6; cursor: not-allowed; }
|
|
117
|
+
@media (prefers-color-scheme: dark) {
|
|
118
|
+
.slyxup-root:not(.slyxup-light) .slx-btn { background: #f2f2f5; color: #0a0a0f; border-color: #f2f2f5; }
|
|
119
|
+
.slyxup-root:not(.slyxup-light) .slx-btn:hover { background: #e6e6eb; border-color: #e6e6eb; }
|
|
120
|
+
}
|
|
112
121
|
.slx-spinner {
|
|
113
122
|
width: 15px; height: 15px; flex: none;
|
|
114
123
|
border: 2px solid rgba(255,255,255,.35); border-top-color: #fff;
|
|
@@ -139,8 +148,12 @@ export const CSS = `
|
|
|
139
148
|
content: ""; height: 1px; flex: 1; background: var(--slx-border);
|
|
140
149
|
}
|
|
141
150
|
.slx-footer { font-size: 13px; color: var(--slx-muted); margin-top: 20px; text-align: center; }
|
|
142
|
-
.slx-link {
|
|
143
|
-
|
|
151
|
+
.slx-link {
|
|
152
|
+
color: var(--slx-accent); font-weight: 600; text-decoration: none; cursor: pointer;
|
|
153
|
+
background: none; border: none; font: inherit; font-size: inherit;
|
|
154
|
+
padding: 0; margin: 0;
|
|
155
|
+
}
|
|
156
|
+
.slx-link:hover { text-decoration: underline; color: var(--slx-accent-hover); }
|
|
144
157
|
.slx-link:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--slx-accent-soft); border-radius: 4px; }
|
|
145
158
|
|
|
146
159
|
/* ── Success state ── */
|
|
@@ -185,10 +198,18 @@ export const CSS = `
|
|
|
185
198
|
.slx-menu-item-danger { color: var(--slx-danger); }
|
|
186
199
|
`;
|
|
187
200
|
let injected = false;
|
|
188
|
-
/** Inject the SlyxUp stylesheet once per document. */
|
|
201
|
+
/** Inject the SlyxUp stylesheet + DM Sans font once per document. */
|
|
189
202
|
export function injectStyles() {
|
|
190
203
|
if (injected || typeof document === 'undefined')
|
|
191
204
|
return;
|
|
205
|
+
// DM Sans font link
|
|
206
|
+
if (!document.querySelector('link[href*="DM+Sans"]')) {
|
|
207
|
+
const fontLink = document.createElement('link');
|
|
208
|
+
fontLink.rel = 'stylesheet';
|
|
209
|
+
fontLink.href =
|
|
210
|
+
'https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,300;9..40,400;9..40,500;9..40,600;9..40,700;9..40,800&family=Space+Grotesk:wght@400;500;600;700&display=swap';
|
|
211
|
+
document.head.appendChild(fontLink);
|
|
212
|
+
}
|
|
192
213
|
const style = document.createElement('style');
|
|
193
214
|
style.setAttribute('data-slyxup', 'styles');
|
|
194
215
|
style.textContent = CSS;
|
package/dist/styles.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"styles.js","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,CAAC,MAAM,GAAG,GAAG
|
|
1
|
+
{"version":3,"file":"styles.js","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,CAAC,MAAM,SAAS,GAAG,6UAA6U,CAAC;AAEvW,MAAM,CAAC,MAAM,GAAG,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiMlB,CAAC;AAEF,IAAI,QAAQ,GAAG,KAAK,CAAC;AAErB,qEAAqE;AACrE,MAAM,UAAU,YAAY;IAC1B,IAAI,QAAQ,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO;IACxD,oBAAoB;IACpB,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,uBAAuB,CAAC,EAAE,CAAC;QACrD,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAChD,QAAQ,CAAC,GAAG,GAAG,YAAY,CAAC;QAC5B,QAAQ,CAAC,IAAI;YACX,+KAA+K,CAAC;QAClL,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IACtC,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC9C,KAAK,CAAC,YAAY,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;IAC5C,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACjC,QAAQ,GAAG,IAAI,CAAC;AAClB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slyxup/ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "@slyxup/ui — SlyxUp SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
-
"scripts": {
|
|
9
|
-
"build": "tsc -p tsconfig.json",
|
|
10
|
-
"typecheck": "tsc --noEmit"
|
|
11
|
-
},
|
|
12
8
|
"repository": {
|
|
13
9
|
"type": "git",
|
|
14
10
|
"url": "git+https://github.com/slyxup/stack.git",
|
|
@@ -27,11 +23,19 @@
|
|
|
27
23
|
"react": "^18 || ^19"
|
|
28
24
|
},
|
|
29
25
|
"dependencies": {
|
|
30
|
-
"@slyxup/core": "
|
|
31
|
-
"@slyxup/react": "
|
|
26
|
+
"@slyxup/core": "0.2.1",
|
|
27
|
+
"@slyxup/react": "0.2.1"
|
|
32
28
|
},
|
|
33
29
|
"devDependencies": {
|
|
34
30
|
"@types/node": "^26.2.0",
|
|
35
|
-
"@types/react": "^18"
|
|
31
|
+
"@types/react": "^18",
|
|
32
|
+
"react": "^18.3.1",
|
|
33
|
+
"react-dom": "^18.3.1",
|
|
34
|
+
"vitest": "^4.1.11"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc -p tsconfig.json",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"test": "vitest run --passWithNoTests"
|
|
36
40
|
}
|
|
37
|
-
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { injectStyles } from '../../styles';
|
|
5
|
+
|
|
6
|
+
interface Subscription {
|
|
7
|
+
id: string;
|
|
8
|
+
status: string;
|
|
9
|
+
currentPeriodEnd: string | null;
|
|
10
|
+
cancelAtPeriodEnd: boolean;
|
|
11
|
+
}
|
|
12
|
+
interface Invoice {
|
|
13
|
+
id: string;
|
|
14
|
+
amount: number;
|
|
15
|
+
currency: string;
|
|
16
|
+
status: 'paid' | 'pending' | 'overdue' | 'refunded';
|
|
17
|
+
billedAt: string | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface BillingPortalProps {
|
|
21
|
+
subscription: Subscription | null;
|
|
22
|
+
invoices: Invoice[];
|
|
23
|
+
onCancel?: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Current plan + invoices table */
|
|
27
|
+
export function BillingPortal({
|
|
28
|
+
subscription,
|
|
29
|
+
invoices,
|
|
30
|
+
onCancel,
|
|
31
|
+
}: BillingPortalProps) {
|
|
32
|
+
injectStyles();
|
|
33
|
+
if (!subscription) {
|
|
34
|
+
return (
|
|
35
|
+
<div className="slx-card">
|
|
36
|
+
<h3 style={{ fontSize: 16, fontWeight: 600 }}>
|
|
37
|
+
No active subscription
|
|
38
|
+
</h3>
|
|
39
|
+
<p style={{ fontSize: 13.5, color: 'var(--slx-muted)', marginTop: 6 }}>
|
|
40
|
+
You don't have a subscription yet. Choose a plan to get started.
|
|
41
|
+
</p>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
|
|
48
|
+
{/* Plan card */}
|
|
49
|
+
<div className="slx-card">
|
|
50
|
+
<div
|
|
51
|
+
style={{
|
|
52
|
+
display: 'flex',
|
|
53
|
+
justifyContent: 'space-between',
|
|
54
|
+
alignItems: 'center',
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
<div>
|
|
58
|
+
<h3 style={{ fontSize: 15, fontWeight: 600 }}>Current plan</h3>
|
|
59
|
+
<p
|
|
60
|
+
style={{ fontSize: 13, color: 'var(--slx-muted)', marginTop: 4 }}
|
|
61
|
+
>
|
|
62
|
+
Status:{' '}
|
|
63
|
+
<span
|
|
64
|
+
style={{
|
|
65
|
+
fontWeight: 650,
|
|
66
|
+
color:
|
|
67
|
+
subscription.status === 'active' ||
|
|
68
|
+
subscription.status === 'trialing'
|
|
69
|
+
? '#34d399'
|
|
70
|
+
: subscription.status === 'past_due'
|
|
71
|
+
? 'var(--slx-danger)'
|
|
72
|
+
: 'var(--slx-muted)',
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
{subscription.status}
|
|
76
|
+
</span>
|
|
77
|
+
</p>
|
|
78
|
+
{subscription.currentPeriodEnd && (
|
|
79
|
+
<p
|
|
80
|
+
style={{
|
|
81
|
+
fontSize: 12.5,
|
|
82
|
+
color: 'var(--slx-muted)',
|
|
83
|
+
marginTop: 4,
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
Renews{' '}
|
|
87
|
+
{new Date(subscription.currentPeriodEnd).toLocaleDateString()}
|
|
88
|
+
{subscription.cancelAtPeriodEnd
|
|
89
|
+
? ' (cancels at period end)'
|
|
90
|
+
: ''}
|
|
91
|
+
</p>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
{subscription.status !== 'canceled' &&
|
|
95
|
+
!subscription.cancelAtPeriodEnd &&
|
|
96
|
+
onCancel && (
|
|
97
|
+
<button
|
|
98
|
+
type="button"
|
|
99
|
+
onClick={onCancel}
|
|
100
|
+
style={{
|
|
101
|
+
fontSize: 13,
|
|
102
|
+
fontWeight: 550,
|
|
103
|
+
background: 'transparent',
|
|
104
|
+
border: '1px solid var(--slx-border)',
|
|
105
|
+
borderRadius: 'var(--slx-radius)',
|
|
106
|
+
padding: '8px 14px',
|
|
107
|
+
cursor: 'pointer',
|
|
108
|
+
color: 'var(--slx-danger)',
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
Cancel
|
|
112
|
+
</button>
|
|
113
|
+
)}
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
{/* Invoices */}
|
|
118
|
+
{invoices.length > 0 && (
|
|
119
|
+
<div className="slx-card">
|
|
120
|
+
<h3 style={{ fontSize: 15, fontWeight: 600, marginBottom: 12 }}>
|
|
121
|
+
Invoices
|
|
122
|
+
</h3>
|
|
123
|
+
<table
|
|
124
|
+
style={{ width: '100%', fontSize: 13, borderCollapse: 'collapse' }}
|
|
125
|
+
>
|
|
126
|
+
<thead>
|
|
127
|
+
<tr
|
|
128
|
+
style={{
|
|
129
|
+
borderBottom: '1px solid var(--slx-border)',
|
|
130
|
+
textAlign: 'left',
|
|
131
|
+
}}
|
|
132
|
+
>
|
|
133
|
+
<th style={{ padding: '6px 0', fontWeight: 550 }}>Date</th>
|
|
134
|
+
<th style={{ padding: '6px 0', fontWeight: 550 }}>Amount</th>
|
|
135
|
+
<th style={{ padding: '6px 0', fontWeight: 550 }}>Status</th>
|
|
136
|
+
</tr>
|
|
137
|
+
</thead>
|
|
138
|
+
<tbody>
|
|
139
|
+
{invoices.map((inv) => (
|
|
140
|
+
<tr
|
|
141
|
+
key={inv.id}
|
|
142
|
+
style={{ borderBottom: '1px solid var(--slx-border)' }}
|
|
143
|
+
>
|
|
144
|
+
<td style={{ padding: '8px 0' }}>
|
|
145
|
+
{inv.billedAt
|
|
146
|
+
? new Date(inv.billedAt).toLocaleDateString()
|
|
147
|
+
: '—'}
|
|
148
|
+
</td>
|
|
149
|
+
<td style={{ padding: '8px 0' }}>
|
|
150
|
+
${(inv.amount / 100).toFixed(2)} {inv.currency}
|
|
151
|
+
</td>
|
|
152
|
+
<td style={{ padding: '8px 0' }}>
|
|
153
|
+
<span
|
|
154
|
+
style={{
|
|
155
|
+
fontSize: 11.5,
|
|
156
|
+
fontWeight: 600,
|
|
157
|
+
padding: '2px 8px',
|
|
158
|
+
borderRadius: 999,
|
|
159
|
+
background:
|
|
160
|
+
inv.status === 'paid'
|
|
161
|
+
? 'rgba(52,211,153,.1)'
|
|
162
|
+
: inv.status === 'overdue'
|
|
163
|
+
? 'rgba(214,69,80,.1)'
|
|
164
|
+
: 'rgba(108,108,232,.08)',
|
|
165
|
+
color:
|
|
166
|
+
inv.status === 'paid'
|
|
167
|
+
? '#34d399'
|
|
168
|
+
: inv.status === 'overdue'
|
|
169
|
+
? 'var(--slx-danger)'
|
|
170
|
+
: 'var(--slx-accent)',
|
|
171
|
+
}}
|
|
172
|
+
>
|
|
173
|
+
{inv.status}
|
|
174
|
+
</span>
|
|
175
|
+
</td>
|
|
176
|
+
</tr>
|
|
177
|
+
))}
|
|
178
|
+
</tbody>
|
|
179
|
+
</table>
|
|
180
|
+
</div>
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type FormEvent, useEffect, useState } from 'react';
|
|
2
2
|
import { CheckIcon, KeyholeMark } from '../../icons';
|
|
3
|
+
import { injectStyles } from '../../styles';
|
|
3
4
|
|
|
4
5
|
export interface EmailVerificationProps {
|
|
5
6
|
/** Verification token (from email link ?token=...). If absent, shows resend form. */
|
|
@@ -14,6 +15,7 @@ export function EmailVerification({
|
|
|
14
15
|
apiUrl,
|
|
15
16
|
onSuccess,
|
|
16
17
|
}: EmailVerificationProps) {
|
|
18
|
+
injectStyles();
|
|
17
19
|
const [status, setStatus] = useState<
|
|
18
20
|
'verifying' | 'success' | 'error' | 'resend'
|
|
19
21
|
>(token ? 'verifying' : 'resend');
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SlyxupClient } from '@slyxup/core';
|
|
2
2
|
import { type FormEvent, useEffect, useState } from 'react';
|
|
3
3
|
import { CheckIcon, KeyholeMark } from '../../icons';
|
|
4
|
+
import { injectStyles } from '../../styles';
|
|
4
5
|
|
|
5
6
|
export interface ForgotPasswordProps {
|
|
6
7
|
apiUrl?: string;
|
|
@@ -14,6 +15,7 @@ export function ForgotPassword({
|
|
|
14
15
|
onSuccess,
|
|
15
16
|
onBackToSignIn,
|
|
16
17
|
}: ForgotPasswordProps) {
|
|
18
|
+
injectStyles();
|
|
17
19
|
const [email, setEmail] = useState('');
|
|
18
20
|
const [sent, setSent] = useState(false);
|
|
19
21
|
const [busy, setBusy] = useState(false);
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { injectStyles } from '../../styles';
|
|
5
|
+
|
|
6
|
+
interface Plan {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
amount: number;
|
|
10
|
+
currency: string;
|
|
11
|
+
interval: 'month' | 'year';
|
|
12
|
+
trialDays: number | null;
|
|
13
|
+
features: string[] | null;
|
|
14
|
+
isPopular: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PricingTableProps {
|
|
18
|
+
plans: Plan[];
|
|
19
|
+
onSelect?: (plan: Plan) => void;
|
|
20
|
+
loading?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Clean pricing grid with popular badge */
|
|
24
|
+
export function PricingTable({ plans, onSelect, loading }: PricingTableProps) {
|
|
25
|
+
injectStyles();
|
|
26
|
+
if (loading) {
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
className="slx-pricing"
|
|
30
|
+
style={{
|
|
31
|
+
display: 'grid',
|
|
32
|
+
gridTemplateColumns: `repeat(${Math.min(plans.length, 3)}, 1fr)`,
|
|
33
|
+
gap: 16,
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
{[1, 2, 3].map((i) => (
|
|
37
|
+
<div
|
|
38
|
+
key={i}
|
|
39
|
+
className="slx-card"
|
|
40
|
+
style={{ minHeight: 320 }}
|
|
41
|
+
aria-busy="true"
|
|
42
|
+
/>
|
|
43
|
+
))}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
style={{
|
|
51
|
+
display: 'grid',
|
|
52
|
+
gridTemplateColumns: `repeat(${Math.min(plans.length, 3)}, 1fr)`,
|
|
53
|
+
gap: 16,
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
{plans.map((plan) => (
|
|
57
|
+
<div
|
|
58
|
+
key={plan.id}
|
|
59
|
+
className={`slx-card${plan.isPopular ? ' slx-card-popular' : ''}`}
|
|
60
|
+
style={{
|
|
61
|
+
position: plan.isPopular ? 'relative' : undefined,
|
|
62
|
+
borderColor: plan.isPopular ? 'var(--slx-accent)' : undefined,
|
|
63
|
+
display: 'flex',
|
|
64
|
+
flexDirection: 'column',
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
{plan.isPopular && (
|
|
68
|
+
<span
|
|
69
|
+
style={{
|
|
70
|
+
position: 'absolute',
|
|
71
|
+
top: -11,
|
|
72
|
+
right: 20,
|
|
73
|
+
fontSize: 10.5,
|
|
74
|
+
fontFamily: 'var(--slx-mono)',
|
|
75
|
+
background:
|
|
76
|
+
'linear-gradient(135deg, var(--slx-accent), #8b5cf6)',
|
|
77
|
+
color: '#fff',
|
|
78
|
+
padding: '3px 10px',
|
|
79
|
+
borderRadius: 999,
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
POPULAR
|
|
83
|
+
</span>
|
|
84
|
+
)}
|
|
85
|
+
<h3
|
|
86
|
+
style={{ fontSize: 15, fontWeight: 550, color: 'var(--slx-muted)' }}
|
|
87
|
+
>
|
|
88
|
+
{plan.name}
|
|
89
|
+
</h3>
|
|
90
|
+
<div style={{ margin: '8px 0 2px' }}>
|
|
91
|
+
<span
|
|
92
|
+
style={{
|
|
93
|
+
fontSize: 38,
|
|
94
|
+
fontWeight: 750,
|
|
95
|
+
letterSpacing: '-0.03em',
|
|
96
|
+
fontFamily: '"Space Grotesk",sans-serif',
|
|
97
|
+
}}
|
|
98
|
+
>
|
|
99
|
+
${(plan.amount / 100).toFixed(0)}
|
|
100
|
+
</span>
|
|
101
|
+
<span style={{ fontSize: 14, color: 'var(--slx-muted)' }}>
|
|
102
|
+
/{plan.interval}
|
|
103
|
+
</span>
|
|
104
|
+
</div>
|
|
105
|
+
{plan.trialDays ? (
|
|
106
|
+
<p
|
|
107
|
+
style={{
|
|
108
|
+
fontSize: 12,
|
|
109
|
+
color: 'var(--slx-accent)',
|
|
110
|
+
marginBottom: 4,
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
{plan.trialDays} day free trial
|
|
114
|
+
</p>
|
|
115
|
+
) : (
|
|
116
|
+
<p style={{ fontSize: 12, color: 'transparent', marginBottom: 4 }}>
|
|
117
|
+
|
|
118
|
+
</p>
|
|
119
|
+
)}
|
|
120
|
+
<ul style={{ listStyle: 'none', margin: '14px 0 22px', flex: 1 }}>
|
|
121
|
+
{((plan.features ?? []) as string[]).map((f: string) => (
|
|
122
|
+
<li
|
|
123
|
+
key={f}
|
|
124
|
+
style={{
|
|
125
|
+
fontSize: 13.5,
|
|
126
|
+
color: 'var(--slx-ink)',
|
|
127
|
+
padding: '5px 0 5px 24px',
|
|
128
|
+
position: 'relative',
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
<span
|
|
132
|
+
style={{
|
|
133
|
+
position: 'absolute',
|
|
134
|
+
left: 0,
|
|
135
|
+
color: '#34d399',
|
|
136
|
+
fontWeight: 700,
|
|
137
|
+
}}
|
|
138
|
+
>
|
|
139
|
+
✓
|
|
140
|
+
</span>{' '}
|
|
141
|
+
{f}
|
|
142
|
+
</li>
|
|
143
|
+
))}
|
|
144
|
+
</ul>
|
|
145
|
+
<button
|
|
146
|
+
type="button"
|
|
147
|
+
className="slx-btn"
|
|
148
|
+
onClick={() => onSelect?.(plan)}
|
|
149
|
+
disabled={loading}
|
|
150
|
+
>
|
|
151
|
+
Get started
|
|
152
|
+
</button>
|
|
153
|
+
</div>
|
|
154
|
+
))}
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type FormEvent, useEffect, useState } from 'react';
|
|
2
2
|
import { CheckIcon, KeyholeMark } from '../../icons';
|
|
3
|
+
import { injectStyles } from '../../styles';
|
|
3
4
|
|
|
4
5
|
export interface ResetPasswordProps {
|
|
5
6
|
/** Reset token (from email link ?token=...) */
|
|
@@ -14,6 +15,7 @@ export function ResetPassword({
|
|
|
14
15
|
apiUrl,
|
|
15
16
|
onSuccess,
|
|
16
17
|
}: ResetPasswordProps) {
|
|
18
|
+
injectStyles();
|
|
17
19
|
const [password, setPassword] = useState('');
|
|
18
20
|
const [done, setDone] = useState(false);
|
|
19
21
|
const [busy, setBusy] = useState(false);
|