@hubspot/cms-component-library 0.1.0-alpha.1
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 +3 -0
- package/cli/commands/customize.ts +145 -0
- package/cli/commands/help.ts +56 -0
- package/cli/commands/version.ts +12 -0
- package/cli/index.ts +42 -0
- package/cli/tests/commands.test.ts +128 -0
- package/cli/tests/get-file.test.ts +82 -0
- package/cli/tests/version-integration.test.ts +39 -0
- package/cli/utils/cli-metadata.ts +9 -0
- package/cli/utils/component-naming.ts +76 -0
- package/cli/utils/components.ts +74 -0
- package/cli/utils/file-operations.ts +158 -0
- package/cli/utils/logging.ts +13 -0
- package/cli/utils/prompts.ts +80 -0
- package/cli/utils/version.ts +33 -0
- package/components/componentLibrary/Button/index.module.scss +9 -0
- package/components/componentLibrary/Button/index.tsx +83 -0
- package/components/componentLibrary/Button/scaffolds/fields.tsx.template +70 -0
- package/components/componentLibrary/Button/scaffolds/index.ts.template +95 -0
- package/components/componentLibrary/Heading/index.module.scss +9 -0
- package/components/componentLibrary/Heading/index.tsx +34 -0
- package/components/componentLibrary/Heading/scaffolds/fields.tsx.template +62 -0
- package/components/componentLibrary/Heading/scaffolds/index.ts.template +46 -0
- package/components/componentLibrary/index.ts +1 -0
- package/components/componentLibrary/styles/_component-base.scss +246 -0
- package/components/componentLibrary/types/index.ts +308 -0
- package/components/componentLibrary/utils/chainApi/choiceFieldGenerator.tsx +64 -0
- package/components/componentLibrary/utils/chainApi/index.ts +115 -0
- package/components/componentLibrary/utils/chainApi/labelGenerator.ts +76 -0
- package/components/componentLibrary/utils/chainApi/stateManager.ts +178 -0
- package/components/componentLibrary/utils/classname.ts +40 -0
- package/components/componentLibrary/utils/createConditionalClasses.ts +44 -0
- package/components/componentLibrary/utils/createHsclComponent.tsx +167 -0
- package/components/componentLibrary/utils/propResolution/createCssVariables.ts +58 -0
- package/components/componentLibrary/utils/propResolution/propResolutionUtils.ts +113 -0
- package/components/componentLibrary/utils/storybook/standardArgs.ts +607 -0
- package/package.json +62 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createComponentInstance } from './utils/createHsclComponent.js';
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
// Auto-generated conditional styles
|
|
2
|
+
// DO NOT EDIT MANUALLY - Generated from STYLE_COMPONENT_PROPS
|
|
3
|
+
// Run: npm run generate-conditional-styles to regenerate
|
|
4
|
+
|
|
5
|
+
@mixin hscl-component-conditional($component-name) {
|
|
6
|
+
&:global(.hsclStyle-color) {
|
|
7
|
+
color: var(--hscl-#{$component-name}-color, revert-layer);
|
|
8
|
+
}
|
|
9
|
+
&:global(.hsclStyle-background) {
|
|
10
|
+
background: var(--hscl-#{$component-name}-background, revert-layer);
|
|
11
|
+
}
|
|
12
|
+
&:global(.hsclStyle-background-color) {
|
|
13
|
+
background-color: var(--hscl-#{$component-name}-background-color, revert-layer);
|
|
14
|
+
}
|
|
15
|
+
&:global(.hsclStyle-font-size) {
|
|
16
|
+
font-size: var(--hscl-#{$component-name}-font-size, revert-layer);
|
|
17
|
+
}
|
|
18
|
+
&:global(.hsclStyle-font-style) {
|
|
19
|
+
font-style: var(--hscl-#{$component-name}-font-style, revert-layer);
|
|
20
|
+
}
|
|
21
|
+
&:global(.hsclStyle-font-weight) {
|
|
22
|
+
font-weight: var(--hscl-#{$component-name}-font-weight, revert-layer);
|
|
23
|
+
}
|
|
24
|
+
&:global(.hsclStyle-font-family) {
|
|
25
|
+
font-family: var(--hscl-#{$component-name}-font-family, revert-layer);
|
|
26
|
+
}
|
|
27
|
+
&:global(.hsclStyle-line-height) {
|
|
28
|
+
line-height: var(--hscl-#{$component-name}-line-height, revert-layer);
|
|
29
|
+
}
|
|
30
|
+
&:global(.hsclStyle-letter-spacing) {
|
|
31
|
+
letter-spacing: var(--hscl-#{$component-name}-letter-spacing, revert-layer);
|
|
32
|
+
}
|
|
33
|
+
&:global(.hsclStyle-text-align) {
|
|
34
|
+
text-align: var(--hscl-#{$component-name}-text-align, revert-layer);
|
|
35
|
+
}
|
|
36
|
+
&:global(.hsclStyle-text-transform) {
|
|
37
|
+
text-transform: var(--hscl-#{$component-name}-text-transform, revert-layer);
|
|
38
|
+
}
|
|
39
|
+
&:global(.hsclStyle-text-decoration) {
|
|
40
|
+
text-decoration: var(--hscl-#{$component-name}-text-decoration, revert-layer);
|
|
41
|
+
}
|
|
42
|
+
&:global(.hsclStyle-cursor) {
|
|
43
|
+
cursor: var(--hscl-#{$component-name}-cursor, revert-layer);
|
|
44
|
+
}
|
|
45
|
+
&:global(.hsclStyle-opacity) {
|
|
46
|
+
opacity: var(--hscl-#{$component-name}-opacity, revert-layer);
|
|
47
|
+
}
|
|
48
|
+
&:global(.hsclStyle-box-shadow) {
|
|
49
|
+
box-shadow: var(--hscl-#{$component-name}-box-shadow, revert-layer);
|
|
50
|
+
}
|
|
51
|
+
&:global(.hsclStyle-transition) {
|
|
52
|
+
transition: var(--hscl-#{$component-name}-transition, revert-layer);
|
|
53
|
+
}
|
|
54
|
+
&:global(.hsclStyle-transition-property) {
|
|
55
|
+
transition-property: var(--hscl-#{$component-name}-transition-property, revert-layer);
|
|
56
|
+
}
|
|
57
|
+
&:global(.hsclStyle-transition-duration) {
|
|
58
|
+
transition-duration: var(--hscl-#{$component-name}-transition-duration, revert-layer);
|
|
59
|
+
}
|
|
60
|
+
&:global(.hsclStyle-transition-timing-function) {
|
|
61
|
+
transition-timing-function: var(--hscl-#{$component-name}-transition-timing-function, revert-layer);
|
|
62
|
+
}
|
|
63
|
+
&:global(.hsclStyle-transition-delay) {
|
|
64
|
+
transition-delay: var(--hscl-#{$component-name}-transition-delay, revert-layer);
|
|
65
|
+
}
|
|
66
|
+
&:global(.hsclStyle-transform) {
|
|
67
|
+
transform: var(--hscl-#{$component-name}-transform, revert-layer);
|
|
68
|
+
}
|
|
69
|
+
&:global(.hsclStyle-transform-origin) {
|
|
70
|
+
transform-origin: var(--hscl-#{$component-name}-transform-origin, revert-layer);
|
|
71
|
+
}
|
|
72
|
+
&:global(.hsclStyle-border-block-start) {
|
|
73
|
+
border-block-start: var(--hscl-#{$component-name}-border-block-start, revert-layer);
|
|
74
|
+
}
|
|
75
|
+
&:global(.hsclStyle-border-block-end) {
|
|
76
|
+
border-block-end: var(--hscl-#{$component-name}-border-block-end, revert-layer);
|
|
77
|
+
}
|
|
78
|
+
&:global(.hsclStyle-border-inline-start) {
|
|
79
|
+
border-inline-start: var(--hscl-#{$component-name}-border-inline-start, revert-layer);
|
|
80
|
+
}
|
|
81
|
+
&:global(.hsclStyle-border-inline-end) {
|
|
82
|
+
border-inline-end: var(--hscl-#{$component-name}-border-inline-end, revert-layer);
|
|
83
|
+
}
|
|
84
|
+
&:global(.hsclStyle-border) {
|
|
85
|
+
border: var(--hscl-#{$component-name}-border, revert-layer);
|
|
86
|
+
}
|
|
87
|
+
&:global(.hsclStyle-border-radius) {
|
|
88
|
+
border-radius: var(--hscl-#{$component-name}-border-radius, revert-layer);
|
|
89
|
+
}
|
|
90
|
+
&:global(.hsclStyle-outline) {
|
|
91
|
+
outline: var(--hscl-#{$component-name}-outline, revert-layer);
|
|
92
|
+
}
|
|
93
|
+
&:global(.hsclStyle-margin-inline) {
|
|
94
|
+
margin-inline: var(--hscl-#{$component-name}-margin-inline, revert-layer);
|
|
95
|
+
}
|
|
96
|
+
&:global(.hsclStyle-margin-block) {
|
|
97
|
+
margin-block: var(--hscl-#{$component-name}-margin-block, revert-layer);
|
|
98
|
+
}
|
|
99
|
+
&:global(.hsclStyle-margin) {
|
|
100
|
+
margin: var(--hscl-#{$component-name}-margin, revert-layer);
|
|
101
|
+
}
|
|
102
|
+
&:global(.hsclStyle-padding-inline) {
|
|
103
|
+
padding-inline: var(--hscl-#{$component-name}-padding-inline, revert-layer);
|
|
104
|
+
}
|
|
105
|
+
&:global(.hsclStyle-padding-block) {
|
|
106
|
+
padding-block: var(--hscl-#{$component-name}-padding-block, revert-layer);
|
|
107
|
+
}
|
|
108
|
+
&:global(.hsclStyle-padding) {
|
|
109
|
+
padding: var(--hscl-#{$component-name}-padding, revert-layer);
|
|
110
|
+
}
|
|
111
|
+
&:global(.hsclStyle-position) {
|
|
112
|
+
position: var(--hscl-#{$component-name}-position, revert-layer);
|
|
113
|
+
}
|
|
114
|
+
&:global(.hsclStyle-top) {
|
|
115
|
+
top: var(--hscl-#{$component-name}-top, revert-layer);
|
|
116
|
+
}
|
|
117
|
+
&:global(.hsclStyle-right) {
|
|
118
|
+
right: var(--hscl-#{$component-name}-right, revert-layer);
|
|
119
|
+
}
|
|
120
|
+
&:global(.hsclStyle-bottom) {
|
|
121
|
+
bottom: var(--hscl-#{$component-name}-bottom, revert-layer);
|
|
122
|
+
}
|
|
123
|
+
&:global(.hsclStyle-left) {
|
|
124
|
+
left: var(--hscl-#{$component-name}-left, revert-layer);
|
|
125
|
+
}
|
|
126
|
+
&:global(.hsclStyle-z-index) {
|
|
127
|
+
z-index: var(--hscl-#{$component-name}-z-index, revert-layer);
|
|
128
|
+
}
|
|
129
|
+
&:global(.hsclStyle-width) {
|
|
130
|
+
width: var(--hscl-#{$component-name}-width, revert-layer);
|
|
131
|
+
}
|
|
132
|
+
&:global(.hsclStyle-height) {
|
|
133
|
+
height: var(--hscl-#{$component-name}-height, revert-layer);
|
|
134
|
+
}
|
|
135
|
+
&:global(.hsclStyle-min-width) {
|
|
136
|
+
min-width: var(--hscl-#{$component-name}-min-width, revert-layer);
|
|
137
|
+
}
|
|
138
|
+
&:global(.hsclStyle-min-height) {
|
|
139
|
+
min-height: var(--hscl-#{$component-name}-min-height, revert-layer);
|
|
140
|
+
}
|
|
141
|
+
&:global(.hsclStyle-max-width) {
|
|
142
|
+
max-width: var(--hscl-#{$component-name}-max-width, revert-layer);
|
|
143
|
+
}
|
|
144
|
+
&:global(.hsclStyle-max-height) {
|
|
145
|
+
max-height: var(--hscl-#{$component-name}-max-height, revert-layer);
|
|
146
|
+
}
|
|
147
|
+
&:global(.hsclStyle-display) {
|
|
148
|
+
display: var(--hscl-#{$component-name}-display, revert-layer);
|
|
149
|
+
}
|
|
150
|
+
&:global(.hsclStyle-overflow) {
|
|
151
|
+
overflow: var(--hscl-#{$component-name}-overflow, revert-layer);
|
|
152
|
+
}
|
|
153
|
+
&:global(.hsclStyle-visibility) {
|
|
154
|
+
visibility: var(--hscl-#{$component-name}-visibility, revert-layer);
|
|
155
|
+
}
|
|
156
|
+
&:global(.hsclStyle-justify-content) {
|
|
157
|
+
justify-content: var(--hscl-#{$component-name}-justify-content, revert-layer);
|
|
158
|
+
}
|
|
159
|
+
&:global(.hsclStyle-align-items) {
|
|
160
|
+
align-items: var(--hscl-#{$component-name}-align-items, revert-layer);
|
|
161
|
+
}
|
|
162
|
+
&:global(.hsclStyle-flex-direction) {
|
|
163
|
+
flex-direction: var(--hscl-#{$component-name}-flex-direction, revert-layer);
|
|
164
|
+
}
|
|
165
|
+
&:global(.hsclStyle-gap) {
|
|
166
|
+
gap: var(--hscl-#{$component-name}-gap, revert-layer);
|
|
167
|
+
}
|
|
168
|
+
&:global(.hsclStyle-align-self) {
|
|
169
|
+
align-self: var(--hscl-#{$component-name}-align-self, revert-layer);
|
|
170
|
+
}
|
|
171
|
+
&:global(.hsclStyle-flex) {
|
|
172
|
+
flex: var(--hscl-#{$component-name}-flex, revert-layer);
|
|
173
|
+
}
|
|
174
|
+
&:global(.hsclStyle-flex-grow) {
|
|
175
|
+
flex-grow: var(--hscl-#{$component-name}-flex-grow, revert-layer);
|
|
176
|
+
}
|
|
177
|
+
&:global(.hsclStyle-flex-shrink) {
|
|
178
|
+
flex-shrink: var(--hscl-#{$component-name}-flex-shrink, revert-layer);
|
|
179
|
+
}
|
|
180
|
+
&:global(.hsclStyle-flex-basis) {
|
|
181
|
+
flex-basis: var(--hscl-#{$component-name}-flex-basis, revert-layer);
|
|
182
|
+
}
|
|
183
|
+
&:global(.hsclStyle-order) {
|
|
184
|
+
order: var(--hscl-#{$component-name}-order, revert-layer);
|
|
185
|
+
}
|
|
186
|
+
&:global(.hsclStyle-white-space) {
|
|
187
|
+
white-space: var(--hscl-#{$component-name}-white-space, revert-layer);
|
|
188
|
+
}
|
|
189
|
+
&:global(.hsclStyle-word-break) {
|
|
190
|
+
word-break: var(--hscl-#{$component-name}-word-break, revert-layer);
|
|
191
|
+
}
|
|
192
|
+
&:global(.hsclStyle-text-wrap) {
|
|
193
|
+
text-wrap: var(--hscl-#{$component-name}-text-wrap, revert-layer);
|
|
194
|
+
}
|
|
195
|
+
&:global(.hsclStyle-overflow-wrap) {
|
|
196
|
+
overflow-wrap: var(--hscl-#{$component-name}-overflow-wrap, revert-layer);
|
|
197
|
+
}
|
|
198
|
+
&:global(.hsclStyle-text-overflow) {
|
|
199
|
+
text-overflow: var(--hscl-#{$component-name}-text-overflow, revert-layer);
|
|
200
|
+
}
|
|
201
|
+
&:global(.hsclStyle-word-wrap) {
|
|
202
|
+
word-wrap: var(--hscl-#{$component-name}-word-wrap, revert-layer);
|
|
203
|
+
}
|
|
204
|
+
&:global(.hsclStyle-pointer-events) {
|
|
205
|
+
pointer-events: var(--hscl-#{$component-name}-pointer-events, revert-layer);
|
|
206
|
+
}
|
|
207
|
+
&:global(.hsclStyle-object-fit) {
|
|
208
|
+
object-fit: var(--hscl-#{$component-name}-object-fit, revert-layer);
|
|
209
|
+
}
|
|
210
|
+
&:global(.hsclStyle-aspect-ratio) {
|
|
211
|
+
aspect-ratio: var(--hscl-#{$component-name}-aspect-ratio, revert-layer);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@mixin hscl-component-hover-conditional($component-name) {
|
|
216
|
+
&:global(.hsclStyle-hover-color):hover {
|
|
217
|
+
color: var(--hscl-#{$component-name}-hover-color, revert-layer);
|
|
218
|
+
}
|
|
219
|
+
&:global(.hsclStyle-hover-background):hover {
|
|
220
|
+
background: var(--hscl-#{$component-name}-hover-background, revert-layer);
|
|
221
|
+
}
|
|
222
|
+
&:global(.hsclStyle-hover-background-color):hover {
|
|
223
|
+
background-color: var(--hscl-#{$component-name}-hover-background-color, revert-layer);
|
|
224
|
+
}
|
|
225
|
+
&:global(.hsclStyle-hover-border-block-start):hover {
|
|
226
|
+
border-block-start: var(--hscl-#{$component-name}-hover-border-block-start, revert-layer);
|
|
227
|
+
}
|
|
228
|
+
&:global(.hsclStyle-hover-border-block-end):hover {
|
|
229
|
+
border-block-end: var(--hscl-#{$component-name}-hover-border-block-end, revert-layer);
|
|
230
|
+
}
|
|
231
|
+
&:global(.hsclStyle-hover-border-inline-start):hover {
|
|
232
|
+
border-inline-start: var(--hscl-#{$component-name}-hover-border-inline-start, revert-layer);
|
|
233
|
+
}
|
|
234
|
+
&:global(.hsclStyle-hover-border-inline-end):hover {
|
|
235
|
+
border-inline-end: var(--hscl-#{$component-name}-hover-border-inline-end, revert-layer);
|
|
236
|
+
}
|
|
237
|
+
&:global(.hsclStyle-hover-opacity):hover {
|
|
238
|
+
opacity: var(--hscl-#{$component-name}-hover-opacity, revert-layer);
|
|
239
|
+
}
|
|
240
|
+
&:global(.hsclStyle-hover-transform):hover {
|
|
241
|
+
transform: var(--hscl-#{$component-name}-hover-transform, revert-layer);
|
|
242
|
+
}
|
|
243
|
+
&:global(.hsclStyle-hover-box-shadow):hover {
|
|
244
|
+
box-shadow: var(--hscl-#{$component-name}-hover-box-shadow, revert-layer);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
// Style-related props that generate CSS variables
|
|
2
|
+
|
|
3
|
+
import { ChoiceFieldType } from '@hubspot/cms-components/fields';
|
|
4
|
+
|
|
5
|
+
// Typography types
|
|
6
|
+
export type TextAlign = 'left' | 'center' | 'right' | 'justify';
|
|
7
|
+
export type TextTransform = 'none' | 'uppercase' | 'lowercase' | 'capitalize';
|
|
8
|
+
export type FontWeight = 'lighter' | 'normal' | 'bold' | 'bolder' | number;
|
|
9
|
+
export type FontStyle = 'normal' | 'italic' | 'oblique';
|
|
10
|
+
|
|
11
|
+
export const STYLE_COMPONENT_PROPS = {
|
|
12
|
+
// CSS property props
|
|
13
|
+
color: String,
|
|
14
|
+
background: String,
|
|
15
|
+
backgroundColor: String,
|
|
16
|
+
fontSize: [String, Number] as const,
|
|
17
|
+
fontStyle: String as () => FontStyle,
|
|
18
|
+
fontWeight: String as () => FontWeight,
|
|
19
|
+
fontFamily: String,
|
|
20
|
+
lineHeight: [String, Number] as const,
|
|
21
|
+
letterSpacing: [String, Number] as const,
|
|
22
|
+
textAlign: String as () => TextAlign,
|
|
23
|
+
textTransform: String as () => TextTransform,
|
|
24
|
+
textDecoration: String,
|
|
25
|
+
cursor: String,
|
|
26
|
+
opacity: Number,
|
|
27
|
+
boxShadow: String,
|
|
28
|
+
|
|
29
|
+
// Transition properties
|
|
30
|
+
transition: String,
|
|
31
|
+
transitionProperty: String,
|
|
32
|
+
transitionDuration: String,
|
|
33
|
+
transitionTimingFunction: String,
|
|
34
|
+
transitionDelay: String,
|
|
35
|
+
|
|
36
|
+
// Transform properties
|
|
37
|
+
transform: String,
|
|
38
|
+
transformOrigin: String,
|
|
39
|
+
|
|
40
|
+
// Border props
|
|
41
|
+
borderBlockStart: String,
|
|
42
|
+
borderBlockEnd: String,
|
|
43
|
+
borderInlineStart: String,
|
|
44
|
+
borderInlineEnd: String,
|
|
45
|
+
border: String,
|
|
46
|
+
borderRadius: [String, Number] as const,
|
|
47
|
+
outline: String,
|
|
48
|
+
|
|
49
|
+
// Hover state props
|
|
50
|
+
hoverColor: String,
|
|
51
|
+
hoverBackground: String,
|
|
52
|
+
hoverBackgroundColor: String,
|
|
53
|
+
hoverBorderBlockStart: String,
|
|
54
|
+
hoverBorderBlockEnd: String,
|
|
55
|
+
hoverBorderInlineStart: String,
|
|
56
|
+
hoverBorderInlineEnd: String,
|
|
57
|
+
hoverOpacity: Number,
|
|
58
|
+
hoverTransform: String,
|
|
59
|
+
hoverBoxShadow: String,
|
|
60
|
+
|
|
61
|
+
// Space props (margin/padding) - flexible user-defined values
|
|
62
|
+
marginInline: String,
|
|
63
|
+
marginBlock: String,
|
|
64
|
+
margin: String,
|
|
65
|
+
paddingInline: String,
|
|
66
|
+
paddingBlock: String,
|
|
67
|
+
padding: String,
|
|
68
|
+
|
|
69
|
+
// Position props
|
|
70
|
+
position: String as () =>
|
|
71
|
+
| 'static'
|
|
72
|
+
| 'relative'
|
|
73
|
+
| 'absolute'
|
|
74
|
+
| 'fixed'
|
|
75
|
+
| 'sticky',
|
|
76
|
+
top: [String, Number] as const,
|
|
77
|
+
right: [String, Number] as const,
|
|
78
|
+
bottom: [String, Number] as const,
|
|
79
|
+
left: [String, Number] as const,
|
|
80
|
+
zIndex: Number,
|
|
81
|
+
|
|
82
|
+
// Size props
|
|
83
|
+
width: [String, Number] as const,
|
|
84
|
+
height: [String, Number] as const,
|
|
85
|
+
minWidth: [String, Number] as const,
|
|
86
|
+
minHeight: [String, Number] as const,
|
|
87
|
+
maxWidth: [String, Number] as const,
|
|
88
|
+
maxHeight: [String, Number] as const,
|
|
89
|
+
|
|
90
|
+
// Display props
|
|
91
|
+
display: String as () =>
|
|
92
|
+
| 'block'
|
|
93
|
+
| 'inline'
|
|
94
|
+
| 'inline-block'
|
|
95
|
+
| 'flex'
|
|
96
|
+
| 'inline-flex'
|
|
97
|
+
| 'grid'
|
|
98
|
+
| 'none',
|
|
99
|
+
overflow: String as () => 'visible' | 'hidden' | 'scroll' | 'auto',
|
|
100
|
+
visibility: String as () => 'visible' | 'hidden' | 'collapse',
|
|
101
|
+
|
|
102
|
+
// Flex layout properties
|
|
103
|
+
justifyContent: String as () =>
|
|
104
|
+
| 'flex-start'
|
|
105
|
+
| 'center'
|
|
106
|
+
| 'flex-end'
|
|
107
|
+
| 'space-between'
|
|
108
|
+
| 'space-around'
|
|
109
|
+
| 'space-evenly'
|
|
110
|
+
| 'start'
|
|
111
|
+
| 'end',
|
|
112
|
+
alignItems: String as () =>
|
|
113
|
+
| 'stretch'
|
|
114
|
+
| 'flex-start'
|
|
115
|
+
| 'center'
|
|
116
|
+
| 'flex-end'
|
|
117
|
+
| 'baseline'
|
|
118
|
+
| 'start'
|
|
119
|
+
| 'end',
|
|
120
|
+
flexDirection: String as () => 'row' | 'column',
|
|
121
|
+
gap: [String, Number] as const,
|
|
122
|
+
|
|
123
|
+
// Flex item properties
|
|
124
|
+
alignSelf: String as () =>
|
|
125
|
+
| 'auto'
|
|
126
|
+
| 'stretch'
|
|
127
|
+
| 'flex-start'
|
|
128
|
+
| 'center'
|
|
129
|
+
| 'flex-end'
|
|
130
|
+
| 'baseline'
|
|
131
|
+
| 'start'
|
|
132
|
+
| 'end',
|
|
133
|
+
flex: [Number] as const,
|
|
134
|
+
flexGrow: Number,
|
|
135
|
+
flexShrink: Number,
|
|
136
|
+
flexBasis: [String, Number] as const,
|
|
137
|
+
order: Number,
|
|
138
|
+
|
|
139
|
+
// Enhanced text properties
|
|
140
|
+
whiteSpace: String as () =>
|
|
141
|
+
| 'normal'
|
|
142
|
+
| 'nowrap'
|
|
143
|
+
| 'pre'
|
|
144
|
+
| 'pre-line'
|
|
145
|
+
| 'pre-wrap'
|
|
146
|
+
| 'break-spaces',
|
|
147
|
+
wordBreak: String as () => 'normal' | 'break-all' | 'keep-all' | 'break-word',
|
|
148
|
+
textWrap: String as () => 'wrap' | 'nowrap' | 'balance' | 'pretty' | 'stable',
|
|
149
|
+
overflowWrap: String as () => 'normal' | 'anywhere' | 'break-word',
|
|
150
|
+
textOverflow: String as () => 'clip' | 'ellipsis' | string,
|
|
151
|
+
wordWrap: String as () => 'normal' | 'break-word' | 'anywhere',
|
|
152
|
+
|
|
153
|
+
// Interaction properties
|
|
154
|
+
pointerEvents: String as () =>
|
|
155
|
+
| 'auto'
|
|
156
|
+
| 'none'
|
|
157
|
+
| 'visiblePainted'
|
|
158
|
+
| 'visibleFill'
|
|
159
|
+
| 'visibleStroke'
|
|
160
|
+
| 'visible'
|
|
161
|
+
| 'painted'
|
|
162
|
+
| 'fill'
|
|
163
|
+
| 'stroke'
|
|
164
|
+
| 'all',
|
|
165
|
+
|
|
166
|
+
// Media/object properties
|
|
167
|
+
objectFit: String as () =>
|
|
168
|
+
| 'fill'
|
|
169
|
+
| 'contain'
|
|
170
|
+
| 'cover'
|
|
171
|
+
| 'none'
|
|
172
|
+
| 'scale-down',
|
|
173
|
+
aspectRatio: String,
|
|
174
|
+
} as const;
|
|
175
|
+
|
|
176
|
+
// Helper type to convert config to proper TypeScript types
|
|
177
|
+
type ConvertMapToType<T> = T extends readonly [
|
|
178
|
+
StringConstructor,
|
|
179
|
+
NumberConstructor
|
|
180
|
+
]
|
|
181
|
+
? string | number
|
|
182
|
+
: T extends StringConstructor
|
|
183
|
+
? string
|
|
184
|
+
: T extends NumberConstructor
|
|
185
|
+
? number
|
|
186
|
+
: T extends ObjectConstructor
|
|
187
|
+
? React.CSSProperties
|
|
188
|
+
: T extends () => infer U
|
|
189
|
+
? U
|
|
190
|
+
: never;
|
|
191
|
+
|
|
192
|
+
// Single source of truth for all base component props
|
|
193
|
+
export const BASE_COMPONENT_PROPS_CONFIG = {
|
|
194
|
+
// Basic element props
|
|
195
|
+
className: String,
|
|
196
|
+
style: Object as () => React.CSSProperties,
|
|
197
|
+
|
|
198
|
+
// Spread in all style props
|
|
199
|
+
...STYLE_COMPONENT_PROPS,
|
|
200
|
+
|
|
201
|
+
// Accessibility props
|
|
202
|
+
role: String,
|
|
203
|
+
} as const;
|
|
204
|
+
|
|
205
|
+
// Generate TypeScript type from the config
|
|
206
|
+
export type BaseComponentProps = {
|
|
207
|
+
[K in keyof typeof BASE_COMPONENT_PROPS_CONFIG]?: ConvertMapToType<
|
|
208
|
+
(typeof BASE_COMPONENT_PROPS_CONFIG)[K]
|
|
209
|
+
>;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// Generate runtime keys directly from the config - exported for use in utils
|
|
213
|
+
export const BASE_COMPONENT_PROP_KEYS = new Set(
|
|
214
|
+
Object.keys(BASE_COMPONENT_PROPS_CONFIG) as (keyof BaseComponentProps)[]
|
|
215
|
+
);
|
|
216
|
+
// ==============================================================================
|
|
217
|
+
// Helpers
|
|
218
|
+
// ==============================================================================
|
|
219
|
+
|
|
220
|
+
// Common type constraint for React components
|
|
221
|
+
export type AnyReactComponent = React.ComponentType<unknown> & {
|
|
222
|
+
hsclComponentName: string;
|
|
223
|
+
cssModule: Record<string, string>;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// Props that are generated by the component factory and consumed internally.
|
|
227
|
+
// Think of them as pre-processed props that the developer doesn't need to know about.
|
|
228
|
+
export type HsclInternalProps = {
|
|
229
|
+
hsclInternal?: {
|
|
230
|
+
hsclProcessedClasses?: string;
|
|
231
|
+
hsclCssVars?: CSSVariableMap;
|
|
232
|
+
hsclResolvedProps?: BaseComponentProps;
|
|
233
|
+
hsclProcessedStyles?: React.CSSProperties;
|
|
234
|
+
};
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export type ComponentPropsType<T extends AnyReactComponent> =
|
|
238
|
+
React.ComponentProps<T>;
|
|
239
|
+
|
|
240
|
+
// Alias which merges BaseComponentProps with component props while excluding internal props
|
|
241
|
+
// This type preserves discriminated unions by distributing over union types
|
|
242
|
+
export type InferPublicProps<
|
|
243
|
+
T extends
|
|
244
|
+
| keyof React.JSX.IntrinsicElements
|
|
245
|
+
| React.JSXElementConstructor<unknown>
|
|
246
|
+
> = React.ComponentProps<T> extends infer U
|
|
247
|
+
? U extends unknown
|
|
248
|
+
? BaseComponentProps & Omit<U, keyof HsclInternalProps>
|
|
249
|
+
: never
|
|
250
|
+
: never;
|
|
251
|
+
|
|
252
|
+
export type BaseAndInternalComponentProps = BaseComponentProps &
|
|
253
|
+
HsclInternalProps;
|
|
254
|
+
|
|
255
|
+
export type CSSVariableMap = {
|
|
256
|
+
[variableName: string]: string | number;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// ==============================================================================
|
|
260
|
+
// Chainable API
|
|
261
|
+
// ==============================================================================
|
|
262
|
+
|
|
263
|
+
export interface DimensionOption {
|
|
264
|
+
props: Partial<BaseComponentProps>;
|
|
265
|
+
choiceFieldOptionLabel?: string;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export interface Dimension {
|
|
269
|
+
options: Record<string, DimensionOption>;
|
|
270
|
+
choiceFieldLabel?: string;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export type DimensionConfiguration = Record<string, Dimension>;
|
|
274
|
+
|
|
275
|
+
export type ChainableAPI = {
|
|
276
|
+
setDimension: (key: string) => DimensionAPI;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
export interface DimensionAPI {
|
|
280
|
+
setOption: (key: string) => OptionAPI;
|
|
281
|
+
setDimension: (key: string) => DimensionAPI;
|
|
282
|
+
label: (label: string) => DimensionAPI;
|
|
283
|
+
createDimensionChoiceField: () => DimensionAPI;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export interface OptionAPI extends DimensionAPI {
|
|
287
|
+
setProps: (styleProps: Record<string, unknown>) => OptionAPI;
|
|
288
|
+
label: (label: string) => OptionAPI;
|
|
289
|
+
setOption: (key: string) => OptionAPI;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export type DimensionChoiceField = (
|
|
293
|
+
props: Omit<Partial<ChoiceFieldType>, 'choices'>
|
|
294
|
+
) => React.ReactElement;
|
|
295
|
+
|
|
296
|
+
export interface ComponentState {
|
|
297
|
+
dimensionConfiguration: DimensionConfiguration;
|
|
298
|
+
userConfiguration: DimensionConfiguration | null;
|
|
299
|
+
dimensionLabels: Record<string, string>;
|
|
300
|
+
optionLabels: Record<string, Record<string, string>>;
|
|
301
|
+
currentDimension: string | null;
|
|
302
|
+
dimensionChoiceFields: Record<string, DimensionChoiceField>;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export type ComponentInstanceFactory<T extends AnyReactComponent> =
|
|
306
|
+
ChainableAPI & {
|
|
307
|
+
(props: InferPublicProps<T>): React.ReactElement;
|
|
308
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { ChoiceField, ChoiceFieldType } from '@hubspot/cms-components/fields';
|
|
2
|
+
import {
|
|
3
|
+
generateChoicesWithLabels,
|
|
4
|
+
getDimensionLabel,
|
|
5
|
+
} from './labelGenerator.js';
|
|
6
|
+
import {
|
|
7
|
+
DimensionChoiceField,
|
|
8
|
+
DimensionConfiguration,
|
|
9
|
+
} from '../../types/index.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates prebuilt choice field components with enhanced labeling
|
|
13
|
+
*/
|
|
14
|
+
export const createDimensionChoiceField = (
|
|
15
|
+
configuration: DimensionConfiguration,
|
|
16
|
+
dimensionLabels: Record<string, string>,
|
|
17
|
+
optionLabels: Record<string, Record<string, string>>
|
|
18
|
+
) => {
|
|
19
|
+
const dimensionChoiceFields: Record<string, DimensionChoiceField> = {};
|
|
20
|
+
|
|
21
|
+
for (const [dimensionKey, dimension] of Object.entries(configuration)) {
|
|
22
|
+
if (!dimension?.options) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const dimensionLabel = getDimensionLabel(dimensionKey, dimensionLabels);
|
|
27
|
+
const choices = generateChoicesWithLabels(
|
|
28
|
+
dimensionKey,
|
|
29
|
+
dimension.options,
|
|
30
|
+
optionLabels
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// Create prebuilt component for this dimension
|
|
34
|
+
dimensionChoiceFields[dimensionKey] = (props: Partial<ChoiceFieldType>) => {
|
|
35
|
+
const { name, label, ...restProps } = props;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<ChoiceField
|
|
39
|
+
{...restProps}
|
|
40
|
+
name={name || dimensionKey}
|
|
41
|
+
label={label || dimensionLabel}
|
|
42
|
+
choices={choices}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return dimensionChoiceFields;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Creates a choice field generator factory with predefined configuration
|
|
53
|
+
* Returns functions that have access to the provided configuration and labels
|
|
54
|
+
*/
|
|
55
|
+
export const createChoiceFieldGenerator = (
|
|
56
|
+
configuration: DimensionConfiguration,
|
|
57
|
+
dimensionLabels: Record<string, string>,
|
|
58
|
+
optionLabels: Record<string, Record<string, string>>
|
|
59
|
+
) => {
|
|
60
|
+
return {
|
|
61
|
+
createDimensionChoiceField: () =>
|
|
62
|
+
createDimensionChoiceField(configuration, dimensionLabels, optionLabels),
|
|
63
|
+
};
|
|
64
|
+
};
|