@skyscanner/backpack-web 41.7.0-beta-layout → 41.7.0-beta-v2
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/bpk-component-layout/index.d.ts +18 -0
- package/bpk-component-layout/index.js +29 -0
- package/bpk-component-layout/src/BpkBox.d.ts +3 -0
- package/bpk-component-layout/src/BpkBox.js +33 -0
- package/bpk-component-layout/src/BpkFlex.d.ts +3 -0
- package/bpk-component-layout/src/BpkFlex.js +51 -0
- package/bpk-component-layout/src/BpkGrid.d.ts +3 -0
- package/bpk-component-layout/src/BpkGrid.js +57 -0
- package/bpk-component-layout/src/BpkGridItem.d.ts +3 -0
- package/bpk-component-layout/src/BpkGridItem.js +45 -0
- package/bpk-component-layout/src/BpkProvider.d.ts +14 -0
- package/bpk-component-layout/src/BpkProvider.js +42 -0
- package/bpk-component-layout/src/BpkStack.constant.d.ts +2 -0
- package/bpk-component-layout/src/BpkStack.constant.js +22 -0
- package/bpk-component-layout/src/BpkStack.d.ts +5 -0
- package/bpk-component-layout/src/BpkStack.js +57 -0
- package/bpk-component-layout/src/BpkVessel.d.ts +36 -0
- package/bpk-component-layout/src/BpkVessel.js +66 -0
- package/bpk-component-layout/src/commonProps.d.ts +86 -0
- package/bpk-component-layout/src/commonProps.js +1 -0
- package/bpk-component-layout/src/theme.d.ts +36 -0
- package/bpk-component-layout/src/theme.js +229 -0
- package/bpk-component-layout/src/tokenUtils.d.ts +108 -0
- package/bpk-component-layout/src/tokenUtils.js +323 -0
- package/bpk-component-layout/src/tokens.d.ts +96 -0
- package/bpk-component-layout/src/tokens.js +138 -0
- package/bpk-component-layout/src/types.d.ts +227 -0
- package/bpk-component-layout/src/types.js +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Backpack - Skyscanner's Design System
|
|
3
|
+
*
|
|
4
|
+
* Copyright 2016 Skyscanner Ltd
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { defineConfig } from '@chakra-ui/react';
|
|
20
|
+
// Import tokens from Backpack foundations
|
|
21
|
+
// Note: Some tokens may not be in TypeScript definitions but exist at runtime
|
|
22
|
+
|
|
23
|
+
const bpkTokens = require('@skyscanner/bpk-foundations-web/tokens/base.es6');
|
|
24
|
+
|
|
25
|
+
// NOTE:
|
|
26
|
+
// We intentionally do not use the raw breakpoint *values* from foundations here.
|
|
27
|
+
// Foundations exports breakpoint values such as `breakpointMobile = "32rem"` which
|
|
28
|
+
// are used primarily for max-width queries (e.g. `(max-width: 32rem)`).
|
|
29
|
+
//
|
|
30
|
+
// Backpack layout responsive values in this package are mobile-first and behave
|
|
31
|
+
// like Chakra breakpoints (min-width thresholds). To align with Backpack’s
|
|
32
|
+
// intended breakpoint ranges we define lower-bound (min-width) thresholds:
|
|
33
|
+
//
|
|
34
|
+
// - small-mobile: 320px+
|
|
35
|
+
// - mobile: 360px+
|
|
36
|
+
// - small-tablet: 513px+
|
|
37
|
+
// - tablet: 769px+
|
|
38
|
+
// - desktop: 1025px+
|
|
39
|
+
|
|
40
|
+
// Note: Spacing tokens are defined as SCSS functions in Backpack foundations,
|
|
41
|
+
// not as direct values. We need to use the actual rem values from the SCSS functions.
|
|
42
|
+
// Based on @skyscanner/bpk-foundations-web/tokens/base.default.scss:
|
|
43
|
+
// - bpk-spacing-sm() returns .25rem
|
|
44
|
+
// - bpk-spacing-md() returns .5rem
|
|
45
|
+
// - bpk-spacing-lg() returns 1.5rem
|
|
46
|
+
// - bpk-spacing-xl() returns 2rem (needs verification)
|
|
47
|
+
// - bpk-spacing-xxl() returns 2.5rem
|
|
48
|
+
// - bpk-spacing-base() returns 1rem (standard base spacing)
|
|
49
|
+
// TODO: CLOV-1021 - will add spacing tokens to Backpack Foundations package and use them here after we ship the PoC
|
|
50
|
+
const spacingXs = '.125rem'; // 2px
|
|
51
|
+
const spacingSm = '.25rem';
|
|
52
|
+
const spacingBase = '1rem'; // Standard base spacing
|
|
53
|
+
const spacingMd = '.5rem';
|
|
54
|
+
const spacingLg = '1.5rem';
|
|
55
|
+
const spacingXl = '2rem';
|
|
56
|
+
const spacingXxl = '2.5rem';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Backpack Theme Configuration for Chakra UI
|
|
60
|
+
*
|
|
61
|
+
* This theme maps Backpack design tokens from @skyscanner/bpk-foundations-web
|
|
62
|
+
* to Chakra UI's theme structure.
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Maps Backpack spacing tokens to actual rem values.
|
|
67
|
+
* These come directly from @skyscanner/bpk-foundations-web.
|
|
68
|
+
*/
|
|
69
|
+
// Spacing tokens - directly imported from foundations
|
|
70
|
+
const spacingMap = {
|
|
71
|
+
'bpk-spacing-none': {
|
|
72
|
+
value: '0'
|
|
73
|
+
},
|
|
74
|
+
// Temporary: Foundations does not yet export a 2px spacing token. This will be
|
|
75
|
+
// replaced with a foundations value once available.
|
|
76
|
+
'bpk-spacing-xs': {
|
|
77
|
+
value: spacingXs
|
|
78
|
+
},
|
|
79
|
+
'bpk-spacing-sm': {
|
|
80
|
+
value: spacingSm
|
|
81
|
+
},
|
|
82
|
+
'bpk-spacing-base': {
|
|
83
|
+
value: spacingBase
|
|
84
|
+
},
|
|
85
|
+
'bpk-spacing-md': {
|
|
86
|
+
value: spacingMd
|
|
87
|
+
},
|
|
88
|
+
'bpk-spacing-lg': {
|
|
89
|
+
value: spacingLg
|
|
90
|
+
},
|
|
91
|
+
'bpk-spacing-xl': {
|
|
92
|
+
value: spacingXl
|
|
93
|
+
},
|
|
94
|
+
'bpk-spacing-xxl': {
|
|
95
|
+
value: spacingXxl
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Maps Backpack border size tokens to actual border width values
|
|
101
|
+
* These come directly from @skyscanner/bpk-foundations-web
|
|
102
|
+
*/
|
|
103
|
+
const borderSizeMap = {
|
|
104
|
+
'bpk-border-size-sm': bpkTokens.borderSizeSm,
|
|
105
|
+
'bpk-border-size-lg': bpkTokens.borderSizeLg,
|
|
106
|
+
'bpk-border-size-xl': bpkTokens.borderSizeXl
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Maps Backpack border radius tokens to actual radius values.
|
|
111
|
+
* These come directly from @skyscanner/bpk-foundations-web.
|
|
112
|
+
*/
|
|
113
|
+
const borderRadiusMap = {
|
|
114
|
+
'bpk-border-radius-none': '0',
|
|
115
|
+
'bpk-border-radius-xs': bpkTokens.borderRadiusXs,
|
|
116
|
+
'bpk-border-radius-sm': bpkTokens.borderRadiusSm,
|
|
117
|
+
'bpk-border-radius-md': bpkTokens.borderRadiusMd,
|
|
118
|
+
'bpk-border-radius-lg': bpkTokens.borderRadiusLg,
|
|
119
|
+
'bpk-border-radius-xl': bpkTokens.borderRadiusXl,
|
|
120
|
+
'bpk-border-radius-full': bpkTokens.borderRadiusFull
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Maps Backpack shadow tokens to actual box-shadow values
|
|
125
|
+
* These come directly from @skyscanner/bpk-foundations-web
|
|
126
|
+
*/
|
|
127
|
+
const shadowMap = {
|
|
128
|
+
'bpk-shadow-sm': bpkTokens.boxShadowSm,
|
|
129
|
+
'bpk-shadow-lg': bpkTokens.boxShadowLg,
|
|
130
|
+
'bpk-shadow-xl': bpkTokens.boxShadowXl
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Chakra expects raw width values (e.g. "48rem"), not full media queries.
|
|
135
|
+
* The media query construction is handled internally by Chakra's system.
|
|
136
|
+
*
|
|
137
|
+
* We align Backpack breakpoint tokens to Chakra's keys like this:
|
|
138
|
+
* - base: 0 (implicit)
|
|
139
|
+
* - sm: small-mobile (>= 320px)
|
|
140
|
+
* - md: mobile (>= 360px)
|
|
141
|
+
* - lg: small-tablet (>= 513px)
|
|
142
|
+
* - xl: tablet (>= 769px)
|
|
143
|
+
* - 2xl: desktop (>= 1025px)
|
|
144
|
+
*/
|
|
145
|
+
// TODO: CLOV-1021 - will add breakpoint boundary tokens to Backpack Foundations package and use them here after we ship the PoC
|
|
146
|
+
const breakpointMap = {
|
|
147
|
+
base: '0rem',
|
|
148
|
+
sm: '20rem',
|
|
149
|
+
// 320px
|
|
150
|
+
md: '22.5rem',
|
|
151
|
+
// 360px
|
|
152
|
+
lg: '32.0625rem',
|
|
153
|
+
// 513px
|
|
154
|
+
xl: '48.0625rem',
|
|
155
|
+
// 769px
|
|
156
|
+
'2xl': '64.0625rem' // 1025px
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Exports spacing map for use in tokenUtils
|
|
161
|
+
* This allows tokenUtils to look up actual spacing values
|
|
162
|
+
*
|
|
163
|
+
* @returns {Record<string, string>} A map of spacing token names to values.
|
|
164
|
+
*/
|
|
165
|
+
export function getSpacingMap() {
|
|
166
|
+
// Return simple string values for backward compatibility with utilities
|
|
167
|
+
const simpleMap = {};
|
|
168
|
+
Object.entries(spacingMap).forEach(([key, obj]) => {
|
|
169
|
+
simpleMap[key] = obj.value;
|
|
170
|
+
});
|
|
171
|
+
return simpleMap;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Gets the actual spacing value for a Backpack spacing token
|
|
176
|
+
*
|
|
177
|
+
* @param {string} token - Backpack spacing token name.
|
|
178
|
+
* @returns {string | undefined} The actual spacing value.
|
|
179
|
+
*/
|
|
180
|
+
export function getSpacingValue(token) {
|
|
181
|
+
return spacingMap[token]?.value;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Gets the actual border width value for a Backpack border size token
|
|
186
|
+
*
|
|
187
|
+
* @param {string} token - Backpack border size token name.
|
|
188
|
+
* @returns {string | undefined} The actual border width value.
|
|
189
|
+
*/
|
|
190
|
+
export function getBorderSizeValue(token) {
|
|
191
|
+
return borderSizeMap[token];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Gets the actual border radius value for a Backpack border radius token
|
|
196
|
+
*
|
|
197
|
+
* @param {string} token - Backpack border radius token name.
|
|
198
|
+
* @returns {string | undefined} The actual border radius value.
|
|
199
|
+
*/
|
|
200
|
+
export function getBorderRadiusValue(token) {
|
|
201
|
+
return borderRadiusMap[token];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Gets the actual box-shadow value for a Backpack shadow token
|
|
206
|
+
*
|
|
207
|
+
* @param {string} token - Backpack shadow token name.
|
|
208
|
+
* @returns {string | undefined} The actual box-shadow value.
|
|
209
|
+
*/
|
|
210
|
+
export function getShadowValue(token) {
|
|
211
|
+
return shadowMap[token];
|
|
212
|
+
}
|
|
213
|
+
export function createBpkConfig() {
|
|
214
|
+
// Convert breakpoint map to Chakra UI format
|
|
215
|
+
// Breakpoints in Chakra v3 are typically simple strings in the breakpoints object
|
|
216
|
+
const chakraBreakpoints = {};
|
|
217
|
+
Object.entries(breakpointMap).forEach(([token, value]) => {
|
|
218
|
+
chakraBreakpoints[token] = value;
|
|
219
|
+
});
|
|
220
|
+
return defineConfig({
|
|
221
|
+
cssVarsPrefix: 'bpk',
|
|
222
|
+
theme: {
|
|
223
|
+
tokens: {
|
|
224
|
+
spacing: spacingMap
|
|
225
|
+
},
|
|
226
|
+
breakpoints: chakraBreakpoints
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export type BpkLayoutComponentName = 'BpkBox' | 'BpkFlex' | 'BpkGrid' | 'BpkStack';
|
|
2
|
+
/**
|
|
3
|
+
* Allowlisted, component-scoped prop groups that are eligible for Backpack responsive value
|
|
4
|
+
* processing (Backpack breakpoint keys -> Chakra breakpoint keys).
|
|
5
|
+
*
|
|
6
|
+
* NOTE:
|
|
7
|
+
* - Spacing/size/position props are processed separately via `processBpkProps` and therefore
|
|
8
|
+
* are intentionally NOT included here.
|
|
9
|
+
* - These groups are meant to keep the responsive surface predictable per component, while
|
|
10
|
+
* avoiding duplicated per-component breakpoint mapping logic.
|
|
11
|
+
*/
|
|
12
|
+
type BpkResponsivePropGroups = {
|
|
13
|
+
/**
|
|
14
|
+
* Container-level layout props (how children are laid out).
|
|
15
|
+
*/
|
|
16
|
+
container: readonly string[];
|
|
17
|
+
/**
|
|
18
|
+
* Item-level layout props (how this element participates in a parent layout).
|
|
19
|
+
*/
|
|
20
|
+
item?: readonly string[];
|
|
21
|
+
};
|
|
22
|
+
export declare const BPK_RESPONSIVE_PROP_GROUPS_BY_COMPONENT: Record<BpkLayoutComponentName, BpkResponsivePropGroups>;
|
|
23
|
+
export declare const BPK_RESPONSIVE_PROP_KEYS_BY_COMPONENT: Record<BpkLayoutComponentName, readonly string[]>;
|
|
24
|
+
export type ProcessBpkComponentPropsOptions = {
|
|
25
|
+
component: BpkLayoutComponentName;
|
|
26
|
+
/**
|
|
27
|
+
* Optional map of responsive props. When provided, it will be filtered to the
|
|
28
|
+
* allowlist for the given component, then breakpoint-normalised.
|
|
29
|
+
*
|
|
30
|
+
* This is useful for components like `BpkFlex` and `BpkGrid` that expose a
|
|
31
|
+
* public API with different prop names.
|
|
32
|
+
*/
|
|
33
|
+
responsiveProps?: Record<string, any>;
|
|
34
|
+
/**
|
|
35
|
+
* Optional mapping of source prop name -> target prop name.
|
|
36
|
+
* Primarily kept for parity with `processResponsiveProps`.
|
|
37
|
+
*/
|
|
38
|
+
propNameMap?: Record<string, string>;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Process a component's props in one place:
|
|
42
|
+
* - strip className/style
|
|
43
|
+
* - process spacing/size/position props (including breakpoint mapping + token conversion)
|
|
44
|
+
* - process allowlisted non-spacing responsive layout props (breakpoint mapping only)
|
|
45
|
+
*
|
|
46
|
+
* The allowlist is grouped by component via `BPK_RESPONSIVE_PROP_KEYS_BY_COMPONENT`.
|
|
47
|
+
*
|
|
48
|
+
* @param {T} props - The component props to process.
|
|
49
|
+
* @param {ProcessBpkComponentPropsOptions} options - Component processing options (allowlist group + mapping).
|
|
50
|
+
* @returns {Record<string, any>} The processed props ready to pass to Chakra primitives.
|
|
51
|
+
*/
|
|
52
|
+
export declare function processBpkComponentProps<T extends Record<string, any>>(props: T, options: ProcessBpkComponentPropsOptions): Record<string, any>;
|
|
53
|
+
/**
|
|
54
|
+
* Converts Backpack spacing token to Chakra UI compatible value
|
|
55
|
+
* Returns the actual spacing value from the theme, not a token path
|
|
56
|
+
*
|
|
57
|
+
* @param {string} value - Backpack spacing token (e.g., 'bpk-spacing-base') or percentage
|
|
58
|
+
* @returns {string} The actual spacing value in rem or the percentage string
|
|
59
|
+
*/
|
|
60
|
+
export declare function convertBpkSpacingToChakra(value: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Recursively processes responsive values (arrays or objects) to validate and convert tokens
|
|
63
|
+
*
|
|
64
|
+
* @param {*} value - The value to process (could be string, array, or object)
|
|
65
|
+
* @param {Function} converter - Function to convert valid tokens to actual values
|
|
66
|
+
* @param {Function} validator - Function to validate if a token is allowed
|
|
67
|
+
* @param {string} propName - The name of the prop being processed (for warning messages)
|
|
68
|
+
* @returns {*} The processed value with tokens converted, or undefined for invalid tokens
|
|
69
|
+
*/
|
|
70
|
+
export declare function normalizeResponsiveObject<T>(value: Record<string, T>): Record<string, T>;
|
|
71
|
+
export declare function processResponsiveValue(value: any, converter: (v: string) => string, validator: (v: string) => boolean, propName: string): any;
|
|
72
|
+
/**
|
|
73
|
+
* Validates and converts spacing props for Chakra UI
|
|
74
|
+
* Handles all spacing-related properties including padding, margin, gap, size, border radius and position
|
|
75
|
+
*
|
|
76
|
+
* @param {T} props - Component props object
|
|
77
|
+
* @returns {Record<string, any>} Processed props with spacing tokens converted to actual values
|
|
78
|
+
*/
|
|
79
|
+
export declare function processSpacingProps<T extends Record<string, any>>(props: T): Record<string, any>;
|
|
80
|
+
/**
|
|
81
|
+
* Processes all props to convert Backpack tokens to Chakra UI format
|
|
82
|
+
* Also explicitly removes className and style to prevent ad-hoc overrides
|
|
83
|
+
*
|
|
84
|
+
* Processing order:
|
|
85
|
+
* 1. Remove className & style
|
|
86
|
+
* 2. Process spacing props (includes position)
|
|
87
|
+
*
|
|
88
|
+
* @param {T} props - Component props object
|
|
89
|
+
* @returns {Record<string, any>} Processed props with tokens converted and disallowed props removed
|
|
90
|
+
*/
|
|
91
|
+
export declare function processBpkProps<T extends Record<string, any>>(props: T): Record<string, any>;
|
|
92
|
+
/**
|
|
93
|
+
* Processes responsive props that are simple string/enum values (non-spacing)
|
|
94
|
+
* using Backpack breakpoint keys. Array syntax is rejected as in spacing.
|
|
95
|
+
*
|
|
96
|
+
* @param {*} value - The value to process
|
|
97
|
+
* @param {string} propName - The name of the prop being processed
|
|
98
|
+
* @returns {*} The processed value with breakpoint keys mapped to Chakra keys
|
|
99
|
+
*/
|
|
100
|
+
export declare function processResponsiveStringProp(value: any, propName: string): any;
|
|
101
|
+
/**
|
|
102
|
+
* Processes a collection of responsive props.
|
|
103
|
+
* @param {Record<string, any>} props - Object containing prop values.
|
|
104
|
+
* @param {Record<string, string>} propNameMap - Map of prop name to CSS/Chakra property name (for error messages and mapping).
|
|
105
|
+
* @returns {Record<string, any>} Processed props object.
|
|
106
|
+
*/
|
|
107
|
+
export declare function processResponsiveProps(props: Record<string, any>, propNameMap?: Record<string, string>): Record<string, any>;
|
|
108
|
+
export {};
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Backpack - Skyscanner's Design System
|
|
3
|
+
*
|
|
4
|
+
* Copyright 2016 Skyscanner Ltd
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import StackOptionKeys from "./BpkStack.constant";
|
|
20
|
+
import { getSpacingValue } from "./theme";
|
|
21
|
+
import { BpkBreakpointToChakraKey, isValidSpacingValue, isValidSizeValue, isValidPositionValue, isPercentage } from "./tokens";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Allowlisted, component-scoped prop groups that are eligible for Backpack responsive value
|
|
25
|
+
* processing (Backpack breakpoint keys -> Chakra breakpoint keys).
|
|
26
|
+
*
|
|
27
|
+
* NOTE:
|
|
28
|
+
* - Spacing/size/position props are processed separately via `processBpkProps` and therefore
|
|
29
|
+
* are intentionally NOT included here.
|
|
30
|
+
* - These groups are meant to keep the responsive surface predictable per component, while
|
|
31
|
+
* avoiding duplicated per-component breakpoint mapping logic.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
export const BPK_RESPONSIVE_PROP_GROUPS_BY_COMPONENT = {
|
|
35
|
+
BpkBox: {
|
|
36
|
+
container: [
|
|
37
|
+
// Display
|
|
38
|
+
'display',
|
|
39
|
+
// Flex container props
|
|
40
|
+
'flexDirection', 'flexWrap', 'justifyContent', 'alignItems', 'alignContent',
|
|
41
|
+
// Grid container props
|
|
42
|
+
'gridTemplateColumns', 'gridTemplateRows', 'gridTemplateAreas', 'gridAutoFlow', 'gridAutoRows', 'gridAutoColumns'],
|
|
43
|
+
item: [
|
|
44
|
+
// Flex item props
|
|
45
|
+
'flex', 'flexGrow', 'flexShrink', 'flexBasis', 'order', 'alignSelf', 'justifySelf',
|
|
46
|
+
// Grid item placement props (useful on Box when composing grids)
|
|
47
|
+
'gridColumn', 'gridRow']
|
|
48
|
+
},
|
|
49
|
+
// Note: BpkFlex maps its public API props to these Chakra keys.
|
|
50
|
+
BpkFlex: {
|
|
51
|
+
container: ['flexDirection', 'justifyContent', 'alignItems', 'flexWrap'],
|
|
52
|
+
item: ['flexGrow', 'flexShrink', 'flexBasis']
|
|
53
|
+
},
|
|
54
|
+
// Note: BpkGrid maps its public API props to these Chakra keys.
|
|
55
|
+
BpkGrid: {
|
|
56
|
+
container: ['justifyContent', 'alignItems', 'gridTemplateColumns', 'gridTemplateRows', 'gridTemplateAreas', 'gridAutoFlow', 'gridAutoRows', 'gridAutoColumns'],
|
|
57
|
+
item: [
|
|
58
|
+
// Used when placing the grid itself within a parent grid.
|
|
59
|
+
'gridColumn', 'gridRow']
|
|
60
|
+
},
|
|
61
|
+
// Note: BpkStack uses Chakra Stack option prop names directly.
|
|
62
|
+
BpkStack: {
|
|
63
|
+
container: StackOptionKeys
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
export const BPK_RESPONSIVE_PROP_KEYS_BY_COMPONENT = {
|
|
67
|
+
BpkBox: [...BPK_RESPONSIVE_PROP_GROUPS_BY_COMPONENT.BpkBox.container, ...(BPK_RESPONSIVE_PROP_GROUPS_BY_COMPONENT.BpkBox.item ?? [])],
|
|
68
|
+
BpkFlex: [...BPK_RESPONSIVE_PROP_GROUPS_BY_COMPONENT.BpkFlex.container, ...(BPK_RESPONSIVE_PROP_GROUPS_BY_COMPONENT.BpkFlex.item ?? [])],
|
|
69
|
+
BpkGrid: [...BPK_RESPONSIVE_PROP_GROUPS_BY_COMPONENT.BpkGrid.container, ...(BPK_RESPONSIVE_PROP_GROUPS_BY_COMPONENT.BpkGrid.item ?? [])],
|
|
70
|
+
BpkStack: [...BPK_RESPONSIVE_PROP_GROUPS_BY_COMPONENT.BpkStack.container]
|
|
71
|
+
};
|
|
72
|
+
function filterToAllowlist(props, allowlist) {
|
|
73
|
+
const allowed = new Set(allowlist);
|
|
74
|
+
const result = {};
|
|
75
|
+
Object.keys(props).forEach(key => {
|
|
76
|
+
if (allowed.has(key) && props[key] !== undefined) {
|
|
77
|
+
result[key] = props[key];
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Process a component's props in one place:
|
|
85
|
+
* - strip className/style
|
|
86
|
+
* - process spacing/size/position props (including breakpoint mapping + token conversion)
|
|
87
|
+
* - process allowlisted non-spacing responsive layout props (breakpoint mapping only)
|
|
88
|
+
*
|
|
89
|
+
* The allowlist is grouped by component via `BPK_RESPONSIVE_PROP_KEYS_BY_COMPONENT`.
|
|
90
|
+
*
|
|
91
|
+
* @param {T} props - The component props to process.
|
|
92
|
+
* @param {ProcessBpkComponentPropsOptions} options - Component processing options (allowlist group + mapping).
|
|
93
|
+
* @returns {Record<string, any>} The processed props ready to pass to Chakra primitives.
|
|
94
|
+
*/
|
|
95
|
+
export function processBpkComponentProps(props, options) {
|
|
96
|
+
const processed = processBpkProps(props);
|
|
97
|
+
const allowlist = BPK_RESPONSIVE_PROP_KEYS_BY_COMPONENT[options.component];
|
|
98
|
+
const responsiveSource = options.responsiveProps ? filterToAllowlist(options.responsiveProps, allowlist) : filterToAllowlist(processed, allowlist);
|
|
99
|
+
if (Object.keys(responsiveSource).length === 0) {
|
|
100
|
+
return processed;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Ensure allowlisted layout props do NOT fall through unprocessed (e.g. array responsive values).
|
|
104
|
+
// These props must be provided via the responsive processing pipeline only.
|
|
105
|
+
const cleanedProcessed = {
|
|
106
|
+
...processed
|
|
107
|
+
};
|
|
108
|
+
allowlist.forEach(key => {
|
|
109
|
+
if (key in cleanedProcessed) {
|
|
110
|
+
delete cleanedProcessed[key];
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
const responsiveProcessed = processResponsiveProps(responsiveSource, options.propNameMap);
|
|
114
|
+
|
|
115
|
+
// Remove keys that ended up as `undefined` (e.g. array responsive values are rejected).
|
|
116
|
+
Object.keys(responsiveProcessed).forEach(key => {
|
|
117
|
+
if (responsiveProcessed[key] === undefined) {
|
|
118
|
+
delete responsiveProcessed[key];
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
return {
|
|
122
|
+
...cleanedProcessed,
|
|
123
|
+
...responsiveProcessed
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Converts Backpack spacing token to Chakra UI compatible value
|
|
129
|
+
* Returns the actual spacing value from the theme, not a token path
|
|
130
|
+
*
|
|
131
|
+
* @param {string} value - Backpack spacing token (e.g., 'bpk-spacing-base') or percentage
|
|
132
|
+
* @returns {string} The actual spacing value in rem or the percentage string
|
|
133
|
+
*/
|
|
134
|
+
export function convertBpkSpacingToChakra(value) {
|
|
135
|
+
if (isPercentage(value)) {
|
|
136
|
+
return value; // Percentages pass through
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Look up the actual spacing value from the theme
|
|
140
|
+
const spacingValue = getSpacingValue(value);
|
|
141
|
+
if (spacingValue !== undefined) {
|
|
142
|
+
return spacingValue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Fallback: if token not found, return the value as-is (will cause a warning)
|
|
146
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
147
|
+
// eslint-disable-next-line no-console
|
|
148
|
+
console.warn(`Spacing token "${value}" not found in theme. Returning as-is.`);
|
|
149
|
+
}
|
|
150
|
+
return value;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Recursively processes responsive values (arrays or objects) to validate and convert tokens
|
|
155
|
+
*
|
|
156
|
+
* @param {*} value - The value to process (could be string, array, or object)
|
|
157
|
+
* @param {Function} converter - Function to convert valid tokens to actual values
|
|
158
|
+
* @param {Function} validator - Function to validate if a token is allowed
|
|
159
|
+
* @param {string} propName - The name of the prop being processed (for warning messages)
|
|
160
|
+
* @returns {*} The processed value with tokens converted, or undefined for invalid tokens
|
|
161
|
+
*/
|
|
162
|
+
export function normalizeResponsiveObject(value) {
|
|
163
|
+
const normalized = {};
|
|
164
|
+
Object.entries(value).forEach(([key, val]) => {
|
|
165
|
+
if (key === 'base') {
|
|
166
|
+
normalized.base = val;
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const chakraKey = BpkBreakpointToChakraKey[key];
|
|
170
|
+
if (chakraKey) {
|
|
171
|
+
normalized[chakraKey] = val;
|
|
172
|
+
} else if (process.env.NODE_ENV !== 'production') {
|
|
173
|
+
// eslint-disable-next-line no-console
|
|
174
|
+
console.warn(`Unknown breakpoint "${key}" used in responsive prop. ` + 'Use Backpack breakpoint tokens such as mobile, tablet or desktop.');
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
return normalized;
|
|
178
|
+
}
|
|
179
|
+
export function processResponsiveValue(value, converter, validator, propName) {
|
|
180
|
+
if (value === undefined || value === null) {
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
if (Array.isArray(value)) {
|
|
184
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
185
|
+
// eslint-disable-next-line no-console
|
|
186
|
+
console.warn(`Array-based responsive values are not supported for prop "${propName}". ` + `Please use Backpack breakpoint keys instead.`);
|
|
187
|
+
}
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
if (typeof value === 'object') {
|
|
191
|
+
const normalized = normalizeResponsiveObject(value);
|
|
192
|
+
const result = {};
|
|
193
|
+
Object.keys(normalized).forEach(key => {
|
|
194
|
+
const processedValue = processResponsiveValue(normalized[key], converter, validator, propName);
|
|
195
|
+
if (processedValue !== undefined) {
|
|
196
|
+
result[key] = processedValue;
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
200
|
+
}
|
|
201
|
+
const strValue = String(value);
|
|
202
|
+
if (!validator(strValue)) {
|
|
203
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
204
|
+
// eslint-disable-next-line no-console
|
|
205
|
+
console.warn(`Invalid value "${strValue}" for prop "${propName}". ` + `Only Backpack tokens are allowed.`);
|
|
206
|
+
}
|
|
207
|
+
return undefined; // Invalid values are removed
|
|
208
|
+
}
|
|
209
|
+
return converter(strValue);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Validates and converts spacing props for Chakra UI
|
|
214
|
+
* Handles all spacing-related properties including padding, margin, gap, size, border radius and position
|
|
215
|
+
*
|
|
216
|
+
* @param {T} props - Component props object
|
|
217
|
+
* @returns {Record<string, any>} Processed props with spacing tokens converted to actual values
|
|
218
|
+
*/
|
|
219
|
+
export function processSpacingProps(props) {
|
|
220
|
+
const spacingKeys = [
|
|
221
|
+
// Padding props
|
|
222
|
+
'padding', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft', 'paddingStart', 'paddingEnd', 'paddingInline',
|
|
223
|
+
// Margin props
|
|
224
|
+
'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft', 'marginStart', 'marginEnd', 'marginInline',
|
|
225
|
+
// Gap and spacing
|
|
226
|
+
'gap', 'spacing', 'rowGap', 'columnGap',
|
|
227
|
+
// Size props
|
|
228
|
+
'width', 'height', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight',
|
|
229
|
+
// Position props
|
|
230
|
+
'top', 'right', 'bottom', 'left'];
|
|
231
|
+
const processed = {
|
|
232
|
+
...props
|
|
233
|
+
};
|
|
234
|
+
spacingKeys.forEach(key => {
|
|
235
|
+
if (key in processed && processed[key] !== undefined) {
|
|
236
|
+
const sizeKeys = ['width', 'height', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight'];
|
|
237
|
+
const positionKeys = ['top', 'right', 'bottom', 'left'];
|
|
238
|
+
const isSizeProp = sizeKeys.includes(key);
|
|
239
|
+
const isPositionProp = positionKeys.includes(key);
|
|
240
|
+
let converter;
|
|
241
|
+
if (isSizeProp || isPositionProp) {
|
|
242
|
+
converter = v => v;
|
|
243
|
+
} else {
|
|
244
|
+
converter = convertBpkSpacingToChakra;
|
|
245
|
+
}
|
|
246
|
+
let validator;
|
|
247
|
+
if (isSizeProp) {
|
|
248
|
+
validator = isValidSizeValue;
|
|
249
|
+
} else if (isPositionProp) {
|
|
250
|
+
validator = isValidPositionValue;
|
|
251
|
+
} else {
|
|
252
|
+
validator = isValidSpacingValue;
|
|
253
|
+
}
|
|
254
|
+
const processedValue = processResponsiveValue(processed[key], converter, validator, key);
|
|
255
|
+
if (processedValue !== undefined) {
|
|
256
|
+
processed[key] = processedValue;
|
|
257
|
+
} else {
|
|
258
|
+
delete processed[key];
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
return processed;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Processes all props to convert Backpack tokens to Chakra UI format
|
|
267
|
+
* Also explicitly removes className and style to prevent ad-hoc overrides
|
|
268
|
+
*
|
|
269
|
+
* Processing order:
|
|
270
|
+
* 1. Remove className & style
|
|
271
|
+
* 2. Process spacing props (includes position)
|
|
272
|
+
*
|
|
273
|
+
* @param {T} props - Component props object
|
|
274
|
+
* @returns {Record<string, any>} Processed props with tokens converted and disallowed props removed
|
|
275
|
+
*/
|
|
276
|
+
export function processBpkProps(props) {
|
|
277
|
+
// Explicitly remove className and style to prevent style overrides
|
|
278
|
+
const {
|
|
279
|
+
className,
|
|
280
|
+
style,
|
|
281
|
+
...cleanProps
|
|
282
|
+
} = props;
|
|
283
|
+
if (className !== undefined && process.env.NODE_ENV !== 'production') {
|
|
284
|
+
// eslint-disable-next-line no-console
|
|
285
|
+
console.warn('className prop is not allowed on Backpack layout components. ' + 'It has been removed to maintain design system consistency.');
|
|
286
|
+
}
|
|
287
|
+
if (style !== undefined && process.env.NODE_ENV !== 'production') {
|
|
288
|
+
// eslint-disable-next-line no-console
|
|
289
|
+
console.warn('style prop is not allowed on Backpack layout components. ' + 'It has been removed to maintain design system consistency.');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Process spacing props (includes position)
|
|
293
|
+
return processSpacingProps(cleanProps);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Processes responsive props that are simple string/enum values (non-spacing)
|
|
298
|
+
* using Backpack breakpoint keys. Array syntax is rejected as in spacing.
|
|
299
|
+
*
|
|
300
|
+
* @param {*} value - The value to process
|
|
301
|
+
* @param {string} propName - The name of the prop being processed
|
|
302
|
+
* @returns {*} The processed value with breakpoint keys mapped to Chakra keys
|
|
303
|
+
*/
|
|
304
|
+
export function processResponsiveStringProp(value, propName) {
|
|
305
|
+
return processResponsiveValue(value, v => v, () => true, propName);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Processes a collection of responsive props.
|
|
310
|
+
* @param {Record<string, any>} props - Object containing prop values.
|
|
311
|
+
* @param {Record<string, string>} propNameMap - Map of prop name to CSS/Chakra property name (for error messages and mapping).
|
|
312
|
+
* @returns {Record<string, any>} Processed props object.
|
|
313
|
+
*/
|
|
314
|
+
export function processResponsiveProps(props, propNameMap) {
|
|
315
|
+
const processed = {};
|
|
316
|
+
Object.keys(props).forEach(key => {
|
|
317
|
+
if (props[key] !== undefined) {
|
|
318
|
+
const targetPropName = propNameMap ? propNameMap[key] || key : key;
|
|
319
|
+
processed[targetPropName] = processResponsiveStringProp(props[key], targetPropName);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
return processed;
|
|
323
|
+
}
|