velloo 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -7
- package/canvas/assets/index-CnyI3aig.js +29 -0
- package/canvas/assets/index-GNLd0pCG.css +2 -0
- package/canvas/assets/lucide-all-CH8LmVh0.js +1 -0
- package/canvas/index.html +2 -2
- package/{chunk-91pajfnm.js → chunk-08mjtg1e.js} +1 -1
- package/{chunk-m1wp8cpt.js → chunk-1gkeepkm.js} +1 -1
- package/{chunk-2dm4x17z.js → chunk-1td6z6ey.js} +1 -1
- package/{chunk-ykdx12na.js → chunk-2ba6edh8.js} +1 -1
- package/chunk-2q561dec.js +2 -0
- package/chunk-2web93qz.js +2 -0
- package/{chunk-py6sjyzk.js → chunk-3trkmgfr.js} +1 -1
- package/chunk-52m26hh4.js +3 -0
- package/{chunk-7xffbj5h.js → chunk-56vfy0db.js} +1 -1
- package/{chunk-8t5phg0s.js → chunk-56z5xd5b.js} +1 -1
- package/{chunk-xbhxzdat.js → chunk-63y5gdzw.js} +1 -1
- package/{chunk-t9ygewjx.js → chunk-6813v926.js} +257 -3
- package/{chunk-cejmmx3r.js → chunk-69qb8p8s.js} +2 -2
- package/chunk-6k05kqnc.js +3 -0
- package/chunk-74yxy75r.js +3 -0
- package/{chunk-7a45tn73.js → chunk-8cj4swpr.js} +5 -5
- package/chunk-8grzf8q1.js +2 -0
- package/chunk-8vam2893.js +3 -0
- package/{chunk-dj1pp4e3.js → chunk-8vgpffn7.js} +1 -1
- package/{chunk-j5axqyan.js → chunk-96qhpth5.js} +1 -1
- package/chunk-ab8fga32.js +22 -0
- package/chunk-azeqkms7.js +6 -0
- package/{chunk-2y9nnzpr.js → chunk-bc7pkjcn.js} +1 -1
- package/{chunk-7gtvstne.js → chunk-c4spp880.js} +1 -1
- package/{chunk-a9mgvspm.js → chunk-chtv0yj6.js} +211 -110
- package/{chunk-r9ekd92e.js → chunk-dar6wbf2.js} +1 -1
- package/{chunk-aa3str7g.js → chunk-e2btp1wd.js} +1 -1
- package/{chunk-hsrg23gd.js → chunk-eqjtq673.js} +1 -1
- package/chunk-esb7n55g.js +244 -0
- package/chunk-ey65err5.js +5 -0
- package/{chunk-r03aywsb.js → chunk-ff0jfn0v.js} +1 -1
- package/chunk-ff2vt7cz.js +12 -0
- package/chunk-g185gx8a.js +2 -0
- package/{chunk-km3z2czq.js → chunk-h85hv05d.js} +1 -1
- package/{chunk-db7mf114.js → chunk-hf4adxe0.js} +9 -7
- package/{chunk-6zr596r2.js → chunk-hjc5eg5c.js} +1 -1
- package/{chunk-b4jy4hcd.js → chunk-hsfpbjpc.js} +1 -1
- package/{chunk-ph94nrc8.js → chunk-jsvkqae2.js} +1 -1
- package/{chunk-rfrbnw8r.js → chunk-k423dkjz.js} +1 -1
- package/chunk-mfch0b0t.js +3 -0
- package/chunk-nbbnb5e9.js +3 -0
- package/{chunk-tr49kraa.js → chunk-nx09zqgs.js} +1 -1
- package/chunk-p93x4pn2.js +3 -0
- package/{chunk-k3dpwjx4.js → chunk-pxaxgkaq.js} +1 -1
- package/chunk-qwccndcd.js +5 -0
- package/{chunk-vjrn10xr.js → chunk-rjjc9vam.js} +2 -2
- package/{chunk-npsg7btt.js → chunk-rk238m15.js} +1 -1
- package/chunk-t2x3mztc.js +2 -0
- package/chunk-t6qr2ay9.js +3 -0
- package/chunk-ta84ehgg.js +2 -0
- package/chunk-tac2wvc7.js +2 -0
- package/chunk-tbd6wn3q.js +6 -0
- package/chunk-v6c3h9ft.js +3 -0
- package/chunk-w2naqxqt.js +2 -0
- package/chunk-wfhv9jg4.js +2 -0
- package/chunk-wnj432fw.js +3 -0
- package/chunk-x02z2ccm.js +2 -0
- package/{chunk-b58pnj20.js → chunk-xtwx2p9g.js} +1 -1
- package/chunk-yfvc8d2g.js +716 -0
- package/chunk-z73q4dq9.js +2 -0
- package/{chunk-yfttm20w.js → chunk-ztsckexy.js} +2 -2
- package/cli.js +1 -1
- package/package.json +3 -1
- package/pkgs/helpers/src/descriptors.ts +1 -0
- package/pkgs/helpers/src/index.ts +7 -4
- package/pkgs/helpers/src/lowering.ts +0 -24
- package/pkgs/helpers/src/primitive-classes.tsx +43 -0
- package/pkgs/provider-antd/src/intro.ts +3 -3
- package/pkgs/provider-antd/src/manifest.ts +478 -57
- package/pkgs/provider-antd/src/registry.ts +8 -5
- package/pkgs/provider-antd/src/render-pass.ts +10 -4
- package/pkgs/provider-antd/src/reset-css.d.ts +5 -0
- package/pkgs/provider-antd/src/theme.ts +54 -11
- package/pkgs/provider-chakra/src/manifest.ts +5 -1
- package/pkgs/provider-html/src/index.ts +68 -0
- package/pkgs/provider-html/src/registry.ts +121 -0
- package/pkgs/provider-html/src/version.ts +1 -0
- package/pkgs/provider-mui/src/index.ts +4 -2
- package/pkgs/provider-mui/src/manifest.ts +10 -1
- package/pkgs/provider-mui/src/render-pass.ts +15 -1
- package/pkgs/provider-mui/src/theme.ts +10 -4
- package/pkgs/provider-none/src/components-inline.tsx +12 -3
- package/pkgs/provider-none/src/components.tsx +26 -53
- package/pkgs/provider-none/src/manifest.ts +2 -0
- package/pkgs/schema/src/annotation.ts +6 -3
- package/pkgs/schema/src/comment.ts +10 -1
- package/pkgs/schema/src/config.ts +33 -1
- package/pkgs/schema/src/css-declarations.ts +41 -0
- package/pkgs/schema/src/index.ts +16 -1
- package/pkgs/schema/src/node-identity.ts +92 -0
- package/pkgs/schema/src/node.ts +13 -4
- package/pkgs/schema/src/theme.ts +46 -1
- package/pkgs/schema/src/typeset.ts +24 -17
- package/pkgs/shadcn-snapshot/dist/manifest.json +1 -0
- package/pkgs/shadcn-snapshot/src/components/ui/alert.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/aspect-ratio.tsx +2 -3
- package/pkgs/shadcn-snapshot/src/components/ui/attachment.tsx +3 -4
- package/pkgs/shadcn-snapshot/src/components/ui/avatar.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/badge.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/breadcrumb.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/bubble.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/button-group.tsx +3 -4
- package/pkgs/shadcn-snapshot/src/components/ui/button.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/card.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/carousel.tsx +3 -4
- package/pkgs/shadcn-snapshot/src/components/ui/chart.tsx +1 -1
- package/pkgs/shadcn-snapshot/src/components/ui/direction.tsx +2 -3
- package/pkgs/shadcn-snapshot/src/components/ui/empty.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/field.tsx +3 -4
- package/pkgs/shadcn-snapshot/src/components/ui/input-group.tsx +3 -4
- package/pkgs/shadcn-snapshot/src/components/ui/item.tsx +3 -4
- package/pkgs/shadcn-snapshot/src/components/ui/kbd.tsx +2 -3
- package/pkgs/shadcn-snapshot/src/components/ui/label.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/marker.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/message.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/native-select.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/navigation-menu.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/pagination.tsx +3 -4
- package/pkgs/shadcn-snapshot/src/components/ui/progress.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/radio-group.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/scroll-area.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/separator.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/skeleton.tsx +2 -3
- package/pkgs/shadcn-snapshot/src/components/ui/spinner.tsx +2 -3
- package/pkgs/shadcn-snapshot/src/components/ui/table.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/tabs.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/toggle.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/shadcn-tailwind.css +1 -1
- package/pkgs/shadcn-snapshot/src/tailwind-entry.css +1 -1
- package/plugins/claude/velloo/agents/velloo-design-reviewer.md +56 -13
- package/plugins/claude/velloo/agents/velloo-designer.md +4 -1
- package/skills/velloo-setup/SKILL.md +19 -55
- package/canvas/assets/index-B1OD0Yz5.css +0 -2
- package/canvas/assets/index-BWYFx2Rr.js +0 -29
- package/canvas/assets/lucide-all-Bcm80WFX.js +0 -1
- package/chunk-00t0bz7x.js +0 -244
- package/chunk-0446t32z.js +0 -2
- package/chunk-0zen7bzb.js +0 -3
- package/chunk-2h24va0j.js +0 -2
- package/chunk-2jbhv7vh.js +0 -3
- package/chunk-5ywkk7d6.js +0 -2
- package/chunk-b7famda5.js +0 -3
- package/chunk-cvn689d5.js +0 -3
- package/chunk-e6nhc3ay.js +0 -6
- package/chunk-f5g45xed.js +0 -2
- package/chunk-fjbzk4sw.js +0 -3
- package/chunk-hv52a6hf.js +0 -2
- package/chunk-jep31ffe.js +0 -2
- package/chunk-jwzhhhzn.js +0 -3
- package/chunk-mcymwefw.js +0 -5
- package/chunk-n0kx20s2.js +0 -2
- package/chunk-nwf2kn41.js +0 -26
- package/chunk-phz993p3.js +0 -2
- package/chunk-qan5dnjt.js +0 -688
- package/chunk-r5d0jsnd.js +0 -5
- package/chunk-shac6bat.js +0 -6
- package/chunk-t4zvcece.js +0 -10
- package/chunk-tarws372.js +0 -2
- package/chunk-yghypth2.js +0 -2
- package/chunk-zqtz23xk.js +0 -3
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
import { Drawer, Dropdown, Modal, Popover, Tooltip } from "./overlays.ts";
|
|
37
37
|
|
|
38
38
|
const REUSED_HELPER_IDS = [
|
|
39
|
+
"Box",
|
|
39
40
|
"Icon",
|
|
40
41
|
"Image",
|
|
41
42
|
"Placeholder",
|
|
@@ -49,8 +50,9 @@ const REUSED_HELPER_IDS = [
|
|
|
49
50
|
* The runtime registry for antd-native folders: design `$ref` ids → real antd
|
|
50
51
|
* components, SSR'd in-process via the adapter's cssinjs render pass. Dotted
|
|
51
52
|
* antd subcomponents get manifest-friendly flat ids (`Typography.Title` ⇒
|
|
52
|
-
* `TypographyTitle`, `List.Item` ⇒ `ListItem`)
|
|
53
|
-
*
|
|
53
|
+
* `TypographyTitle`, `List.Item` ⇒ `ListItem`) because neither a manifest id nor
|
|
54
|
+
* a `$ref` can carry a dot; the manifest records the dotted export as
|
|
55
|
+
* `nativeExport`, so emitted code names it directly. The overlay surface
|
|
54
56
|
* (Modal/Drawer/Popover/Tooltip/Dropdown) is canvas-safe-wrapped in
|
|
55
57
|
* `overlays.ts` — antd's own overlays portal and render NOTHING in SSR, so the
|
|
56
58
|
* shims render them open + inline instead.
|
|
@@ -97,8 +99,9 @@ export const registry: ComponentRegistry = {
|
|
|
97
99
|
TypographyText: Typography.Text,
|
|
98
100
|
TypographyTitle: Typography.Title,
|
|
99
101
|
// Framework-neutral velloo helpers antd has no equivalent for — chiefly
|
|
100
|
-
// `Icon` (lucide; @ant-design/icons isn't surfaced)
|
|
101
|
-
//
|
|
102
|
-
//
|
|
102
|
+
// `Icon` (lucide; @ant-design/icons isn't surfaced), `Box` (the plain element
|
|
103
|
+
// compose lowers `<div>` and stray text to) + imagery/composition helpers.
|
|
104
|
+
// Reused verbatim from `@velloo/helpers`, like provider-mui. They size via
|
|
105
|
+
// props (`Icon size`), not Tailwind — the JIT is off on an antd folder.
|
|
103
106
|
...helpersRegistry(REUSED_HELPER_IDS),
|
|
104
107
|
};
|
|
@@ -2,8 +2,9 @@ import { createCache, extractStyle, StyleProvider } from "@ant-design/cssinjs";
|
|
|
2
2
|
import type { RenderPass } from "@velloo/provider";
|
|
3
3
|
import type { Theme as VellooTheme } from "@velloo/schema";
|
|
4
4
|
import { ConfigProvider } from "antd";
|
|
5
|
+
import antdResetCss from "antd/dist/reset.css" with { type: "text" };
|
|
5
6
|
import { createElement } from "react";
|
|
6
|
-
import { antdThemeConfig } from "./theme.ts";
|
|
7
|
+
import { antdDocumentCss, antdThemeConfig } from "./theme.ts";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* A fresh cssinjs + antd render pass for one SSR. `wrap` nests the tree under
|
|
@@ -11,8 +12,12 @@ import { antdThemeConfig } from "./theme.ts";
|
|
|
11
12
|
* projected velloo tokens (with `cssVar: true`, so `--ant-*` variables reach
|
|
12
13
|
* inline styles); `css()` extracts the style rules the render registered in
|
|
13
14
|
* the cache. The renderer injects that CSS into a `data-velloo-adapter`
|
|
14
|
-
* <style
|
|
15
|
-
*
|
|
15
|
+
* <style>, led by antd's own `reset.css` — the global sheet every antd app
|
|
16
|
+
* imports, which makes boxes border-box and zeroes body and heading margins,
|
|
17
|
+
* so a sized container measures what it does in the app — and the document
|
|
18
|
+
* baseline (`antdDocumentCss`) that gives plain elements the theme's type
|
|
19
|
+
* and color. Unlike emotion, cssinjs extraction
|
|
20
|
+
* reads the cache directly — the rendered HTML isn't needed.
|
|
16
21
|
*
|
|
17
22
|
* SSR works in-process because antd is a velloo dependency, so it links the
|
|
18
23
|
* shared monorepo React — no dual-React hazard.
|
|
@@ -29,7 +34,8 @@ export function makeRenderPass(theme: VellooTheme, dark = false): RenderPass {
|
|
|
29
34
|
),
|
|
30
35
|
// `plain: true` returns bare CSS text; strip <style> wrappers defensively
|
|
31
36
|
// anyway — the RenderPass contract returns CSS, not markup.
|
|
32
|
-
css: () =>
|
|
37
|
+
css: () =>
|
|
38
|
+
`${antdResetCss}\n${antdDocumentCss(theme, dark)}\n${stripStyleTags(extractStyle(cache, { plain: true }))}`,
|
|
33
39
|
};
|
|
34
40
|
}
|
|
35
41
|
|
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
type ColorPair,
|
|
4
4
|
DEFAULT_TYPESET_NAME,
|
|
5
5
|
resolveColors,
|
|
6
|
+
sanitizeCssTokenValue,
|
|
7
|
+
TYPESET_DEFAULT,
|
|
6
8
|
typesetScale,
|
|
7
9
|
type Theme as VellooTheme,
|
|
8
10
|
} from "@velloo/schema";
|
|
@@ -18,7 +20,9 @@ import { formatRgb, parse } from "culori";
|
|
|
18
20
|
* theme stays hex (and round-trips identically in codegen).
|
|
19
21
|
*/
|
|
20
22
|
function antdColor(css: string): string {
|
|
21
|
-
|
|
23
|
+
// Theme strings are free text from the design JSON and end up inside
|
|
24
|
+
// cssinjs rules, so nothing that could close a declaration gets through.
|
|
25
|
+
const s = sanitizeCssTokenValue(css);
|
|
22
26
|
if (/^(#|rgb|hsl)/i.test(s)) return s;
|
|
23
27
|
return formatRgb(parse(s)) ?? s;
|
|
24
28
|
}
|
|
@@ -74,17 +78,27 @@ function antdTokens(theme: VellooTheme, dark: boolean): NonNullable<ThemeConfig[
|
|
|
74
78
|
* antd has only five heading tokens (h1–h5), so the velloo `h6` rung has no
|
|
75
79
|
* antd equivalent and is intentionally dropped here; `Prose` regions still get
|
|
76
80
|
* it from the typeset CSS.
|
|
81
|
+
*
|
|
82
|
+
* Line heights are left to antd unless the typeset chose its own leading.
|
|
83
|
+
* antd derives each one from its font size (14px reads 1.5714), and an antd
|
|
84
|
+
* app that sets only sizes gets those; velloo's default leading is a
|
|
85
|
+
* long-form reading rhythm, not something the app asked for, so projecting
|
|
86
|
+
* it would put every component on a rhythm the app does not have. A leading
|
|
87
|
+
* the user changed is a decision, and it reaches every antd line height.
|
|
77
88
|
*/
|
|
78
89
|
function antdTypography(theme: VellooTheme): NonNullable<ThemeConfig["token"]> {
|
|
79
90
|
const typography = theme.typography;
|
|
80
|
-
const
|
|
91
|
+
const typeset = typography.typesets?.[DEFAULT_TYPESET_NAME];
|
|
92
|
+
const scale = typesetScale(typeset, {
|
|
81
93
|
...(typography.fontFamily ? { fontFamily: typography.fontFamily } : {}),
|
|
82
94
|
});
|
|
95
|
+
const ownLeading = typeset?.leading !== undefined && typeset.leading !== TYPESET_DEFAULT.leading;
|
|
83
96
|
return {
|
|
84
|
-
fontFamily:
|
|
97
|
+
fontFamily: sanitizeCssTokenValue(
|
|
85
98
|
scale.body.fontFamily ??
|
|
86
|
-
|
|
87
|
-
|
|
99
|
+
typography.fontFamily?.sans ??
|
|
100
|
+
"system-ui, -apple-system, sans-serif",
|
|
101
|
+
),
|
|
88
102
|
fontSize: scale.body.fontSize,
|
|
89
103
|
fontSizeSM: scale.caption.fontSize,
|
|
90
104
|
fontSizeLG: scale.lead.fontSize,
|
|
@@ -93,12 +107,16 @@ function antdTypography(theme: VellooTheme): NonNullable<ThemeConfig["token"]> {
|
|
|
93
107
|
fontSizeHeading3: scale.h3.fontSize,
|
|
94
108
|
fontSizeHeading4: scale.h4.fontSize,
|
|
95
109
|
fontSizeHeading5: scale.h5.fontSize,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
110
|
+
...(ownLeading
|
|
111
|
+
? {
|
|
112
|
+
lineHeight: scale.body.lineHeight,
|
|
113
|
+
lineHeightHeading1: scale.h1.lineHeight,
|
|
114
|
+
lineHeightHeading2: scale.h2.lineHeight,
|
|
115
|
+
lineHeightHeading3: scale.h3.lineHeight,
|
|
116
|
+
lineHeightHeading4: scale.h4.lineHeight,
|
|
117
|
+
lineHeightHeading5: scale.h5.lineHeight,
|
|
118
|
+
}
|
|
119
|
+
: {}),
|
|
102
120
|
};
|
|
103
121
|
}
|
|
104
122
|
|
|
@@ -116,6 +134,31 @@ export function antdThemeConfig(theme: VellooTheme, dark = false): ThemeConfig {
|
|
|
116
134
|
};
|
|
117
135
|
}
|
|
118
136
|
|
|
137
|
+
/**
|
|
138
|
+
* The document rules an antd frame renders on, after antd's own `reset.css`
|
|
139
|
+
* (which a real antd app imports, and which sets box-sizing and margins but
|
|
140
|
+
* no type or color). antd v5 styles only its own components, so a plain
|
|
141
|
+
* element — a `Box`, a `Flex` container's own text — inherits from `<body>`,
|
|
142
|
+
* and the `style` channel skips the JIT that gives other providers a themed
|
|
143
|
+
* body. So `<body>` takes antd's resolved tokens for this theme, the values
|
|
144
|
+
* its components read, and the text between antd components matches them.
|
|
145
|
+
* The seed tokens are already sanitized, so they interpolate as-is.
|
|
146
|
+
*/
|
|
147
|
+
export function antdDocumentCss(theme: VellooTheme, dark = false): string {
|
|
148
|
+
const token = antdTheme.getDesignToken(antdThemeConfig(theme, dark));
|
|
149
|
+
return (
|
|
150
|
+
"html,body{" +
|
|
151
|
+
[
|
|
152
|
+
`background-color:${token.colorBgBase}`,
|
|
153
|
+
`color:${token.colorTextBase}`,
|
|
154
|
+
`font-family:${token.fontFamily}`,
|
|
155
|
+
`font-size:${token.fontSize}px`,
|
|
156
|
+
`line-height:${token.lineHeight}`,
|
|
157
|
+
].join(";") +
|
|
158
|
+
"}"
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
119
162
|
/**
|
|
120
163
|
* The codegen projection (`themeToNative`): the same token mapping, but the
|
|
121
164
|
* algorithm emits as a bare identifier (`antdTheme.darkAlgorithm`) that the
|
|
@@ -96,7 +96,11 @@ function helper(
|
|
|
96
96
|
|
|
97
97
|
export const CHAKRA_MANIFEST: Manifest = [
|
|
98
98
|
// --- layout ---
|
|
99
|
-
ui(
|
|
99
|
+
ui(
|
|
100
|
+
"Box",
|
|
101
|
+
[children, str("as")],
|
|
102
|
+
'Generic layout primitive; style via sx. `as` renders another HTML tag ("span", "section").',
|
|
103
|
+
),
|
|
100
104
|
ui(
|
|
101
105
|
"Flex",
|
|
102
106
|
[children, json("gap", "number | string"), str("align"), str("justify"), str("direction")],
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ComponentDescriptor,
|
|
3
|
+
type FrameworkAdapter,
|
|
4
|
+
type Manifest,
|
|
5
|
+
STYLE_PROP,
|
|
6
|
+
} from "@velloo/provider";
|
|
7
|
+
import { componentsDir, entryCssPath, NONE_MANIFEST } from "@velloo/provider-none";
|
|
8
|
+
import { registry } from "./registry.ts";
|
|
9
|
+
import { HTML_VERSION } from "./version.ts";
|
|
10
|
+
|
|
11
|
+
export { Html, isSafeTag, safeAttributeValue } from "./registry.ts";
|
|
12
|
+
|
|
13
|
+
const htmlDescriptors: ComponentDescriptor[] = [
|
|
14
|
+
{
|
|
15
|
+
id: "Html",
|
|
16
|
+
category: "ui",
|
|
17
|
+
source: "velloo",
|
|
18
|
+
group: "layout",
|
|
19
|
+
family: "Html",
|
|
20
|
+
allowUnknownProps: true,
|
|
21
|
+
designModeNotes:
|
|
22
|
+
"Use for a native HTML element. Set as to a semantic tag and pass ordinary HTML or hx-* attributes as props.",
|
|
23
|
+
props: [
|
|
24
|
+
{ name: "as", type: "string", optional: true, control: "string", defaultValue: "div" },
|
|
25
|
+
{ name: "className", type: "string", optional: true, control: "string" },
|
|
26
|
+
{ name: "style", type: "CSSProperties", optional: true, control: "string" },
|
|
27
|
+
{ name: "children", type: "ReactNode", optional: true, control: "string" },
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const HTML_MANIFEST: Manifest = [...NONE_MANIFEST, ...htmlDescriptors];
|
|
33
|
+
|
|
34
|
+
const HTML_INTRO = [
|
|
35
|
+
"**This is a server-rendered HTML/htmx app.** Compose semantic HTML with `Html` nodes (`as=\"form\"`, …), styled by the app's own CSS classes or inline `style` — there is no Tailwind; new classes need CSS authored in the app. Give forms and controls the real `hx-*` attributes: the canvas never fires them, and `emit_code` keeps them in the native HTML it returns for the app's templates.",
|
|
36
|
+
"",
|
|
37
|
+
"A design never contacts the running app: it is styled by copies of the app's stylesheets, which `store_host_files` refreshes from source or from a capture (including built CSS a `screenshot` reports missing). Reference the app's files by their real paths (`src=\"/static/logo.png\"`), not as Velloo assets. Theme tokens are CSS variables (`var(--color-primary)`); `emit_theme` writes the stylesheet that defines them.",
|
|
38
|
+
"",
|
|
39
|
+
"To reproduce a page, capture it from the running app with `start_capture_session` (the user signs in if needed), rebuild it from `get_capture` with the content written into the nodes, and verify with `compare_to_url { captureId }`.",
|
|
40
|
+
"",
|
|
41
|
+
"On a form, a custom `hx-trigger` replaces htmx's default submit trigger — include `submit` when a submit button must swap a response instead of navigating.",
|
|
42
|
+
"",
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The HTML/htmx adapter: the no-library primitives and helpers on the inline
|
|
47
|
+
* `style` channel, plus `Html`, styled by the app's own stylesheets. No browser
|
|
48
|
+
* component bundle — the server render is the page — and emission is native
|
|
49
|
+
* HTML, not JSX.
|
|
50
|
+
*/
|
|
51
|
+
export function createProvider(): FrameworkAdapter {
|
|
52
|
+
return {
|
|
53
|
+
id: "html",
|
|
54
|
+
version: HTML_VERSION,
|
|
55
|
+
label: "HTML + htmx",
|
|
56
|
+
componentsDir,
|
|
57
|
+
styleEntryPath: entryCssPath,
|
|
58
|
+
registry,
|
|
59
|
+
loadManifest: async () => HTML_MANIFEST,
|
|
60
|
+
styleChannel: STYLE_PROP,
|
|
61
|
+
styleChannels: ["style"],
|
|
62
|
+
registryForChannel: () => registry,
|
|
63
|
+
mcpIntro: () => HTML_INTRO,
|
|
64
|
+
hostStylesheets: true,
|
|
65
|
+
codegenFormat: "html",
|
|
66
|
+
elementComponent: "Html",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HTML/htmx components and their sanitizer, free of Node APIs: the canvas
|
|
3
|
+
* renders them server-side, and the cloud share viewer imports this module in
|
|
4
|
+
* the browser to render a published HTML design. A published tree is
|
|
5
|
+
* untrusted input there, so every guarantee below holds at render time, not
|
|
6
|
+
* only when a design is written.
|
|
7
|
+
*/
|
|
8
|
+
import type { ComponentRegistry } from "@velloo/provider";
|
|
9
|
+
import { inlineRegistry } from "@velloo/provider-none/registry-inline";
|
|
10
|
+
import { styleObjectFromCss } from "@velloo/schema";
|
|
11
|
+
import { createElement, type HTMLAttributes, type ReactNode } from "react";
|
|
12
|
+
|
|
13
|
+
// Keep executable/embedded tags out of editable designs while accepting the
|
|
14
|
+
// ordinary semantic elements and static SVG shapes found in native templates.
|
|
15
|
+
const SAFE_TAG =
|
|
16
|
+
/^(?:a|abbr|address|article|aside|b|bdi|bdo|blockquote|br|button|caption|circle|cite|clipPath|code|col|colgroup|data|dd|defs|del|details|dfn|div|dl|dt|ellipse|em|fieldset|figcaption|figure|footer|form|g|h[1-6]|header|hr|i|img|input|ins|kbd|label|legend|li|line|linearGradient|main|mark|mask|meter|nav|ol|optgroup|option|output|p|path|picture|polygon|polyline|pre|progress|q|radialGradient|rect|s|samp|section|select|small|source|span|stop|strong|sub|summary|sup|svg|table|tbody|td|text|textarea|tfoot|th|thead|time|tr|tspan|u|ul|var|wbr)$/;
|
|
17
|
+
|
|
18
|
+
/** Attributes whose value the browser loads or navigates to. */
|
|
19
|
+
const URL_ATTRIBUTES = new Set([
|
|
20
|
+
"href",
|
|
21
|
+
"src",
|
|
22
|
+
"action",
|
|
23
|
+
"formaction",
|
|
24
|
+
"poster",
|
|
25
|
+
"cite",
|
|
26
|
+
"xlinkhref",
|
|
27
|
+
"xlink:href",
|
|
28
|
+
]);
|
|
29
|
+
// Browsers ignore ASCII whitespace and control characters inside a scheme, so
|
|
30
|
+
// `java\tscript:` must be caught too.
|
|
31
|
+
const SCRIPT_URL = /^(?:javascript|vbscript):|^data:text\/html/i;
|
|
32
|
+
|
|
33
|
+
const withoutControls = (value: string): string =>
|
|
34
|
+
[...value].filter((char) => char.charCodeAt(0) > 0x20).join("");
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* HTML attribute names an agent writing markup reaches for, as the React props
|
|
38
|
+
* that render them — React renders `autocomplete` too, but warns on every
|
|
39
|
+
* render and drops `class`'s meaning for its own `className` handling.
|
|
40
|
+
*/
|
|
41
|
+
const REACT_PROP: Record<string, string> = {
|
|
42
|
+
class: "className",
|
|
43
|
+
for: "htmlFor",
|
|
44
|
+
autocomplete: "autoComplete",
|
|
45
|
+
autofocus: "autoFocus",
|
|
46
|
+
tabindex: "tabIndex",
|
|
47
|
+
readonly: "readOnly",
|
|
48
|
+
maxlength: "maxLength",
|
|
49
|
+
minlength: "minLength",
|
|
50
|
+
colspan: "colSpan",
|
|
51
|
+
rowspan: "rowSpan",
|
|
52
|
+
enctype: "encType",
|
|
53
|
+
novalidate: "noValidate",
|
|
54
|
+
datetime: "dateTime",
|
|
55
|
+
srcset: "srcSet",
|
|
56
|
+
crossorigin: "crossOrigin",
|
|
57
|
+
accesskey: "accessKey",
|
|
58
|
+
contenteditable: "contentEditable",
|
|
59
|
+
spellcheck: "spellCheck",
|
|
60
|
+
formaction: "formAction",
|
|
61
|
+
formmethod: "formMethod",
|
|
62
|
+
inputmode: "inputMode",
|
|
63
|
+
referrerpolicy: "referrerPolicy",
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
type HtmlProps = HTMLAttributes<HTMLElement> & {
|
|
67
|
+
as?: string;
|
|
68
|
+
children?: ReactNode;
|
|
69
|
+
[key: string]: unknown;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** Whether `Html` renders this tag: semantic HTML and static SVG, nothing that runs or embeds. */
|
|
73
|
+
export function isSafeTag(tag: string): boolean {
|
|
74
|
+
return SAFE_TAG.test(tag);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Whether an attribute may carry this value: never an event handler or raw
|
|
79
|
+
* inner HTML, and never a URL that runs script.
|
|
80
|
+
*/
|
|
81
|
+
export function safeAttributeValue(name: string, value: unknown): boolean {
|
|
82
|
+
const key = name.toLowerCase();
|
|
83
|
+
if (key.startsWith("on") || key === "dangerouslysetinnerhtml") return false;
|
|
84
|
+
if (URL_ATTRIBUTES.has(key) && typeof value === "string") {
|
|
85
|
+
return !SCRIPT_URL.test(withoutControls(value));
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function safeTag(component: string, as: string): string {
|
|
91
|
+
if (!SAFE_TAG.test(as))
|
|
92
|
+
throw new Error(`${component}: unsupported HTML tag ${JSON.stringify(as)}`);
|
|
93
|
+
return as;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Props a design may put on a native element: never event handlers, raw inner
|
|
98
|
+
* HTML, or a URL that runs script — the canvas executes the document, so an
|
|
99
|
+
* attribute is as dangerous as a `<script>` tag.
|
|
100
|
+
*/
|
|
101
|
+
function nativeAttributes(props: Record<string, unknown>): Record<string, unknown> {
|
|
102
|
+
return Object.fromEntries(
|
|
103
|
+
Object.entries(props)
|
|
104
|
+
.map(([name, value]) => [REACT_PROP[name] ?? name, value] as const)
|
|
105
|
+
.filter(([name, value]) => safeAttributeValue(name, value))
|
|
106
|
+
.flatMap(([name, value]) => {
|
|
107
|
+
// HTML's own `style="…"` (a snapshot of server output, or a design
|
|
108
|
+
// written as markup) is text; React only takes an object.
|
|
109
|
+
if (name !== "style" || typeof value !== "string") return [[name, value] as const];
|
|
110
|
+
const style = styleObjectFromCss(value);
|
|
111
|
+
return style ? [[name, style] as const] : [];
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function Html({ as = "div", children, ...props }: HtmlProps) {
|
|
117
|
+
return createElement(safeTag("Html", as), nativeAttributes(props), children);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** The inline-style primitives and helpers, plus the native HTML components. */
|
|
121
|
+
export const registry: ComponentRegistry = { ...inlineRegistry, Html };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const HTML_VERSION = "1";
|
|
@@ -82,8 +82,10 @@ export function createProvider(): FrameworkAdapter {
|
|
|
82
82
|
{
|
|
83
83
|
importPath: path,
|
|
84
84
|
exportName: id,
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
// A helper is Velloo's own component, not a stand-in
|
|
86
|
+
// for an MUI one: this file is its real render.
|
|
87
|
+
fidelity: "exact" as const,
|
|
88
|
+
note: "Velloo helper, mounted beside the host framework's components.",
|
|
87
89
|
},
|
|
88
90
|
],
|
|
89
91
|
},
|
|
@@ -70,7 +70,16 @@ function helper(
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
export const MUI_MANIFEST: Manifest = [
|
|
73
|
-
ui(
|
|
73
|
+
ui(
|
|
74
|
+
"Box",
|
|
75
|
+
[
|
|
76
|
+
children,
|
|
77
|
+
// Compose writes `<span>` as `as="span"` on every library's element; MUI
|
|
78
|
+
// types and documents the override as `component`, so it emits as that.
|
|
79
|
+
{ name: "as", type: "string", optional: true, control: "string", nativeName: "component" },
|
|
80
|
+
],
|
|
81
|
+
'Generic layout primitive; style via sx. `as` renders another HTML tag ("span", "section") and emits as MUI\'s `component`.',
|
|
82
|
+
),
|
|
74
83
|
ui(
|
|
75
84
|
"Stack",
|
|
76
85
|
[children, enumProp("direction", ["row", "column"], "column")],
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import createCache from "@emotion/cache";
|
|
2
2
|
import { CacheProvider } from "@emotion/react";
|
|
3
3
|
import createEmotionServer from "@emotion/server/create-instance";
|
|
4
|
+
import CssBaseline from "@mui/material/CssBaseline";
|
|
4
5
|
import { ThemeProvider } from "@mui/material/styles";
|
|
5
6
|
import type { RenderPass } from "@velloo/provider";
|
|
6
7
|
import type { Theme as VellooTheme } from "@velloo/schema";
|
|
@@ -13,6 +14,14 @@ import { muiThemeFrom } from "./theme.ts";
|
|
|
13
14
|
* tokens; `css(html)` extracts exactly the emotion rules the rendered markup
|
|
14
15
|
* uses. The renderer injects that CSS into a `data-velloo-adapter` <style>.
|
|
15
16
|
*
|
|
17
|
+
* The tree renders beside a `CssBaseline`, as a real MUI app's root does: MUI
|
|
18
|
+
* styles only its own components, and the style channel skips the Tailwind
|
|
19
|
+
* JIT, so without it a plain element inherits a bare `<body>` (Times, 16px,
|
|
20
|
+
* black, an 8px margin). The baseline puts the theme's body1 type, text color
|
|
21
|
+
* and background on the body, from the same theme the components get, and
|
|
22
|
+
* `enableColorScheme` gives a dark frame dark native controls. Its rules are
|
|
23
|
+
* emotion globals, which `extractCriticalToChunks` always keeps.
|
|
24
|
+
*
|
|
16
25
|
* SSR works in-process because @mui/material is a velloo dependency, so it links
|
|
17
26
|
* the shared monorepo React — no dual-React hazard.
|
|
18
27
|
*/
|
|
@@ -25,7 +34,12 @@ export function makeRenderPass(theme: VellooTheme, dark = false): RenderPass {
|
|
|
25
34
|
createElement(
|
|
26
35
|
CacheProvider,
|
|
27
36
|
{ value: cache },
|
|
28
|
-
createElement(
|
|
37
|
+
createElement(
|
|
38
|
+
ThemeProvider,
|
|
39
|
+
{ theme: muiTheme },
|
|
40
|
+
createElement(CssBaseline, { enableColorScheme: true }),
|
|
41
|
+
element,
|
|
42
|
+
),
|
|
29
43
|
),
|
|
30
44
|
css: (html) =>
|
|
31
45
|
server
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
type ColorPair,
|
|
4
4
|
DEFAULT_TYPESET_NAME,
|
|
5
5
|
resolveColors,
|
|
6
|
+
sanitizeCssTokenValue,
|
|
6
7
|
typesetScale,
|
|
7
8
|
type Theme as VellooTheme,
|
|
8
9
|
} from "@velloo/schema";
|
|
@@ -16,7 +17,9 @@ import { formatRgb, parse } from "culori";
|
|
|
16
17
|
* theme stays hex (and round-trips identically in codegen).
|
|
17
18
|
*/
|
|
18
19
|
function muiColor(css: string): string {
|
|
19
|
-
|
|
20
|
+
// Theme strings are free text from the design JSON and end up inside
|
|
21
|
+
// emotion rules, so nothing that could close a declaration gets through.
|
|
22
|
+
const s = sanitizeCssTokenValue(css);
|
|
20
23
|
if (/^(#|rgb|hsl)/i.test(s)) return s;
|
|
21
24
|
return formatRgb(parse(s)) ?? s;
|
|
22
25
|
}
|
|
@@ -90,15 +93,18 @@ function muiTypography(theme: VellooTheme): NonNullable<ThemeOptions["typography
|
|
|
90
93
|
...(typography.fontFamily ? { fontFamily: typography.fontFamily } : {}),
|
|
91
94
|
});
|
|
92
95
|
// The body face, falling back to the `sans` role then the system stack.
|
|
93
|
-
const bodyFamily =
|
|
94
|
-
scale.body.fontFamily ?? typography.fontFamily?.sans ?? "system-ui, -apple-system, sans-serif"
|
|
96
|
+
const bodyFamily = sanitizeCssTokenValue(
|
|
97
|
+
scale.body.fontFamily ?? typography.fontFamily?.sans ?? "system-ui, -apple-system, sans-serif",
|
|
98
|
+
);
|
|
95
99
|
|
|
96
100
|
const variant = (role: keyof typeof scale) => {
|
|
97
101
|
const r = scale[role];
|
|
98
102
|
return {
|
|
99
103
|
// Only override the family when the typeset names a *different* face for
|
|
100
104
|
// this role; otherwise inherit the theme's own fontFamily.
|
|
101
|
-
...(r.fontFamily && r.fontFamily !== bodyFamily
|
|
105
|
+
...(r.fontFamily && sanitizeCssTokenValue(r.fontFamily) !== bodyFamily
|
|
106
|
+
? { fontFamily: sanitizeCssTokenValue(r.fontFamily) }
|
|
107
|
+
: {}),
|
|
102
108
|
fontSize: `${r.fontSize}px`,
|
|
103
109
|
lineHeight: r.lineHeight,
|
|
104
110
|
letterSpacing: r.letterSpacing,
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { headingInlineStyle, textInlineStyle } from "@velloo/schema/typeset";
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import
|
|
3
|
+
import {
|
|
4
|
+
BOX_TAG,
|
|
5
|
+
type ButtonProps,
|
|
6
|
+
type ContainerProps,
|
|
7
|
+
type InputProps,
|
|
8
|
+
type StackProps,
|
|
9
|
+
} from "./components.tsx";
|
|
4
10
|
|
|
5
11
|
/**
|
|
6
12
|
* Inline-styled variants of the no-library primitives, for a `none`-CSS folder
|
|
@@ -28,8 +34,11 @@ function merge(
|
|
|
28
34
|
/** Tailwind's spacing scale is 0.25rem per step (gap-4 ⇒ 1rem). */
|
|
29
35
|
const space = (n: number) => `${n * 0.25}rem`;
|
|
30
36
|
|
|
31
|
-
export const Box = React.forwardRef<
|
|
32
|
-
|
|
37
|
+
export const Box = React.forwardRef<
|
|
38
|
+
HTMLDivElement,
|
|
39
|
+
React.HTMLAttributes<HTMLDivElement> & { as?: string }
|
|
40
|
+
>(({ as, ...rest }, ref) =>
|
|
41
|
+
React.createElement(typeof as === "string" && BOX_TAG.test(as) ? as : "div", { ref, ...rest }),
|
|
33
42
|
);
|
|
34
43
|
Box.displayName = "Box";
|
|
35
44
|
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BUTTON_BASE_CLASS,
|
|
3
|
+
BUTTON_VARIANT_CLASS,
|
|
4
|
+
CARD_CLASS,
|
|
5
|
+
CONTAINER_WIDTH_CLASS,
|
|
6
|
+
INPUT_CLASS,
|
|
7
|
+
STACK_ALIGN_CLASS,
|
|
8
|
+
STACK_JUSTIFY_CLASS,
|
|
9
|
+
} from "@velloo/helpers";
|
|
1
10
|
import { clsx } from "clsx";
|
|
2
11
|
import * as React from "react";
|
|
3
12
|
|
|
@@ -14,9 +23,17 @@ import * as React from "react";
|
|
|
14
23
|
|
|
15
24
|
interface DivProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
16
25
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
26
|
+
/** `as` takes a lowercase HTML tag only — anything else renders the div. */
|
|
27
|
+
export const BOX_TAG = /^[a-z][a-z0-9]*$/;
|
|
28
|
+
|
|
29
|
+
export const Box = React.forwardRef<HTMLDivElement, DivProps & { as?: string }>(
|
|
30
|
+
({ as, className, ...rest }, ref) =>
|
|
31
|
+
React.createElement(typeof as === "string" && BOX_TAG.test(as) ? as : "div", {
|
|
32
|
+
ref,
|
|
33
|
+
className: clsx(className),
|
|
34
|
+
...rest,
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
20
37
|
Box.displayName = "Box";
|
|
21
38
|
|
|
22
39
|
export interface StackProps extends DivProps {
|
|
@@ -30,20 +47,6 @@ export interface StackProps extends DivProps {
|
|
|
30
47
|
justify?: "start" | "center" | "end" | "between" | "around";
|
|
31
48
|
}
|
|
32
49
|
|
|
33
|
-
const alignClass: Record<NonNullable<StackProps["align"]>, string> = {
|
|
34
|
-
start: "items-start",
|
|
35
|
-
center: "items-center",
|
|
36
|
-
end: "items-end",
|
|
37
|
-
stretch: "items-stretch",
|
|
38
|
-
};
|
|
39
|
-
const justifyClass: Record<NonNullable<StackProps["justify"]>, string> = {
|
|
40
|
-
start: "justify-start",
|
|
41
|
-
center: "justify-center",
|
|
42
|
-
end: "justify-end",
|
|
43
|
-
between: "justify-between",
|
|
44
|
-
around: "justify-around",
|
|
45
|
-
};
|
|
46
|
-
|
|
47
50
|
export const Stack = React.forwardRef<HTMLDivElement, StackProps>(
|
|
48
51
|
({ direction = "col", gap = 4, align, justify, className, ...rest }, ref) => (
|
|
49
52
|
<div
|
|
@@ -52,8 +55,8 @@ export const Stack = React.forwardRef<HTMLDivElement, StackProps>(
|
|
|
52
55
|
"flex",
|
|
53
56
|
direction === "row" ? "flex-row" : "flex-col",
|
|
54
57
|
`gap-${gap}`,
|
|
55
|
-
align ?
|
|
56
|
-
justify ?
|
|
58
|
+
align ? STACK_ALIGN_CLASS[align] : undefined,
|
|
59
|
+
justify ? STACK_JUSTIFY_CLASS[justify] : undefined,
|
|
57
60
|
className,
|
|
58
61
|
)}
|
|
59
62
|
{...rest}
|
|
@@ -67,19 +70,11 @@ export interface ContainerProps extends DivProps {
|
|
|
67
70
|
size?: "sm" | "md" | "lg" | "xl" | "full";
|
|
68
71
|
}
|
|
69
72
|
|
|
70
|
-
const containerWidth: Record<NonNullable<ContainerProps["size"]>, string> = {
|
|
71
|
-
sm: "max-w-screen-sm",
|
|
72
|
-
md: "max-w-screen-md",
|
|
73
|
-
lg: "max-w-screen-lg",
|
|
74
|
-
xl: "max-w-screen-xl",
|
|
75
|
-
full: "max-w-full",
|
|
76
|
-
};
|
|
77
|
-
|
|
78
73
|
export const Container = React.forwardRef<HTMLDivElement, ContainerProps>(
|
|
79
74
|
({ size = "md", className, ...rest }, ref) => (
|
|
80
75
|
<div
|
|
81
76
|
ref={ref}
|
|
82
|
-
className={clsx("mx-auto w-full px-4",
|
|
77
|
+
className={clsx("mx-auto w-full px-4", CONTAINER_WIDTH_CLASS[size], className)}
|
|
83
78
|
{...rest}
|
|
84
79
|
/>
|
|
85
80
|
),
|
|
@@ -90,23 +85,12 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
|
|
|
90
85
|
variant?: "default" | "ghost" | "outline";
|
|
91
86
|
}
|
|
92
87
|
|
|
93
|
-
const buttonVariant: Record<NonNullable<ButtonProps["variant"]>, string> = {
|
|
94
|
-
default: "bg-foreground text-background hover:bg-foreground/90",
|
|
95
|
-
ghost: "bg-transparent text-foreground hover:bg-muted",
|
|
96
|
-
outline: "bg-transparent text-foreground border border-border hover:bg-muted",
|
|
97
|
-
};
|
|
98
|
-
|
|
99
88
|
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
100
89
|
({ variant = "default", className, type, ...rest }, ref) => (
|
|
101
90
|
<button
|
|
102
91
|
ref={ref}
|
|
103
92
|
type={type ?? "button"}
|
|
104
|
-
className={clsx(
|
|
105
|
-
"inline-flex items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-medium",
|
|
106
|
-
"focus-visible:outline-2 focus-visible:outline-offset-2",
|
|
107
|
-
buttonVariant[variant],
|
|
108
|
-
className,
|
|
109
|
-
)}
|
|
93
|
+
className={clsx(BUTTON_BASE_CLASS, BUTTON_VARIANT_CLASS[variant], className)}
|
|
110
94
|
{...rest}
|
|
111
95
|
/>
|
|
112
96
|
),
|
|
@@ -128,11 +112,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
128
112
|
value={value}
|
|
129
113
|
onChange={onChange}
|
|
130
114
|
readOnly={isStatic || rest.readOnly}
|
|
131
|
-
className={clsx(
|
|
132
|
-
"block w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
|
|
133
|
-
"placeholder:text-muted-foreground focus-visible:outline-2 focus-visible:outline-offset-2",
|
|
134
|
-
className,
|
|
135
|
-
)}
|
|
115
|
+
className={clsx(INPUT_CLASS, className)}
|
|
136
116
|
{...rest}
|
|
137
117
|
/>
|
|
138
118
|
);
|
|
@@ -143,13 +123,6 @@ Input.displayName = "Input";
|
|
|
143
123
|
export interface CardProps extends DivProps {}
|
|
144
124
|
|
|
145
125
|
export const Card = React.forwardRef<HTMLDivElement, CardProps>(({ className, ...rest }, ref) => (
|
|
146
|
-
<div
|
|
147
|
-
ref={ref}
|
|
148
|
-
className={clsx(
|
|
149
|
-
"rounded-lg border border-border bg-card p-6 text-card-foreground shadow-sm",
|
|
150
|
-
className,
|
|
151
|
-
)}
|
|
152
|
-
{...rest}
|
|
153
|
-
/>
|
|
126
|
+
<div ref={ref} className={clsx(CARD_CLASS, className)} {...rest} />
|
|
154
127
|
));
|
|
155
128
|
Card.displayName = "Card";
|