@skyscanner/backpack-web 41.3.0 → 41.5.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/bpk-component-bubble/src/BpkBubble.module.css +1 -1
- package/bpk-component-card-list/src/BpkCardListRowRail/BpkCardListCarousel.module.css +1 -1
- package/bpk-component-code/index.d.ts +6 -0
- package/bpk-component-code/index.js +3 -1
- package/bpk-component-code/src/BpkCode.d.ts +9 -0
- package/bpk-component-code/src/BpkCode.js +14 -33
- package/bpk-component-code/src/BpkCodeBlock.d.ts +9 -0
- package/bpk-component-code/src/BpkCodeBlock.js +15 -28
- package/bpk-component-fieldset/src/BpkFieldset.js +0 -1
- package/bpk-component-label/index.d.ts +3 -0
- package/bpk-component-label/index.js +3 -1
- package/bpk-component-label/src/BpkLabel.d.ts +11 -0
- package/bpk-component-label/src/BpkLabel.js +11 -22
- package/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.js +3 -2
- package/bpk-component-nudger/src/BpkNudger.js +0 -1
- package/bpk-component-segmented-control/index.d.ts +3 -2
- package/bpk-component-segmented-control/index.js +2 -1
- package/bpk-component-segmented-control/src/BpkSegmentedControl.d.ts +36 -1
- package/bpk-component-segmented-control/src/BpkSegmentedControl.js +138 -13
- package/bpk-component-text/src/BpkText.module.css +1 -1
- package/bpk-component-theme-toggle/index.d.ts +4 -0
- package/bpk-component-theme-toggle/src/BpkThemeToggle.d.ts +16 -0
- package/bpk-component-theme-toggle/src/BpkThemeToggle.js +10 -7
- package/bpk-component-theme-toggle/src/theming.d.ts +136 -0
- package/bpk-component-theme-toggle/src/updateOnThemeChange.d.ts +42 -0
- package/bpk-component-theme-toggle/src/updateOnThemeChange.js +10 -8
- package/bpk-component-theme-toggle/src/utils.d.ts +3 -0
- package/bpk-component-theme-toggle/src/utils.js +1 -1
- package/bpk-mixins/_typography.scss +3 -3
- package/bpk-stylesheets/base.css +1 -1
- package/bpk-stylesheets/font.css +1 -1
- package/bpk-stylesheets/font.scss +423 -75
- package/bpk-stylesheets/index.scss +1 -1
- package/bpk-stylesheets/larken.css +1 -1
- package/bpk-stylesheets/larken.scss +268 -95
- package/package.json +2 -2
|
@@ -15,4 +15,4 @@
|
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/
|
|
18
|
-
.bpk-bubble{position:relative;display:inline-flex;width:auto;height:1.25rem;padding:0 .5rem;flex-direction:column;justify-content:center;border-radius:.25rem;background-color:#e70866;font-family:"Larken","Noto Sans Arabic","Noto
|
|
18
|
+
.bpk-bubble{position:relative;display:inline-flex;width:auto;height:1.25rem;padding:0 .5rem;flex-direction:column;justify-content:center;border-radius:.25rem;background-color:#e70866;font-family:var(--bpk-larken-font-stack, "Larken", "Noto Sans Arabic", "Noto Serif Hebrew", "Noto Serif", "Noto Serif Devanagari", "Noto Serif Thai", "Noto Serif SC", "Noto Serif TC", "Noto Serif JP", "Noto Serif KR", sans-serif);text-align:center;white-space:nowrap;font-size:.75rem;line-height:1rem;font-weight:400}.bpk-bubble__arrow{position:absolute;bottom:-5px;left:50%;transform:translateX(-50%);color:#e70866}
|
|
@@ -15,4 +15,4 @@
|
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/
|
|
18
|
-
.bpk-card-list-row-rail__row,.bpk-card-list-row-rail__rail{display:flex;overflow-x:hidden;box-sizing:border-box;gap
|
|
18
|
+
.bpk-card-list-row-rail__row,.bpk-card-list-row-rail__rail{--spacing-offset: 0.5rem;--carousel-card-gap: 1.25rem;display:flex;overflow-x:hidden;box-sizing:border-box;gap:var(--carousel-card-gap);margin-block:-1.5rem;margin-inline:-0.5rem;padding-block:1.5rem;padding-inline:.5rem;scroll-snap-stop:always;scroll-snap-type:x mandatory;scrollbar-width:none}@media(max-width: 32rem){.bpk-card-list-row-rail__row,.bpk-card-list-row-rail__rail{--spacing-offset: 1rem;--carousel-card-gap: 1rem;overflow-x:scroll}}.bpk-card-list-row-rail__row::-webkit-scrollbar,.bpk-card-list-row-rail__rail::-webkit-scrollbar{display:none}.bpk-card-list-row-rail__row__card,.bpk-card-list-row-rail__rail__card{position:relative;flex:0 0 calc((100% - (var(--carousel-card-gap)*(var(--initially-shown-cards, 3) - 1) + var(--spacing-offset)*2/var(--initially-shown-cards, 3)))/var(--initially-shown-cards, 3));overflow:visible;box-sizing:border-box;scroll-margin-inline:var(--spacing-offset);scroll-snap-align:start}@media(max-width: 32rem){.bpk-card-list-row-rail__row__card,.bpk-card-list-row-rail__rail__card{flex:0 0 calc((100% - var(--carousel-card-gap)*(var(--initially-shown-cards, 3) - 1))/max(1,var(--initially-shown-cards, 3) - .8))}}.bpk-card-list-row-rail__rail{-webkit-overflow-scrolling:touch}@media(max-width: 32rem){.bpk-card-list-row-rail__rail{margin-inline:calc(-1*var(--spacing-offset));padding-inline:var(--spacing-offset)}}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import BpkCode from './src/BpkCode';
|
|
2
|
+
import BpkCodeBlock from './src/BpkCodeBlock';
|
|
3
|
+
import type { Props as BpkCodeProps } from './src/BpkCode';
|
|
4
|
+
import type { Props as BpkCodeBlockProps } from './src/BpkCodeBlock';
|
|
5
|
+
export type { BpkCodeProps, BpkCodeBlockProps };
|
|
6
|
+
export { BpkCode, BpkCodeBlock };
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
|
-
*/
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import BpkCode from "./src/BpkCode";
|
|
18
20
|
import BpkCodeBlock from "./src/BpkCodeBlock";
|
|
19
21
|
export { BpkCode, BpkCodeBlock };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export type Props = {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
alternate?: boolean;
|
|
5
|
+
className?: string;
|
|
6
|
+
[rest: string]: any;
|
|
7
|
+
};
|
|
8
|
+
declare const BpkCode: ({ alternate, children, className, ...rest }: Props) => import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export default BpkCode;
|
|
@@ -14,42 +14,23 @@
|
|
|
14
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
|
-
*/
|
|
17
|
+
*/
|
|
18
|
+
|
|
18
19
|
import { cssModules } from "../../bpk-react-utils";
|
|
19
20
|
import STYLES from "./BpkCode.module.css";
|
|
20
21
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
21
22
|
const getClassName = cssModules(STYLES);
|
|
22
|
-
const BpkCode =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
classNames.push(className);
|
|
35
|
-
}
|
|
36
|
-
return (
|
|
37
|
-
/*#__PURE__*/
|
|
38
|
-
// $FlowFixMe[cannot-spread-inexact] - inexact rest. See 'decisions/flowfixme.md'.
|
|
39
|
-
_jsx("code", {
|
|
40
|
-
className: classNames.join(' '),
|
|
41
|
-
...rest,
|
|
42
|
-
children: children
|
|
43
|
-
})
|
|
44
|
-
);
|
|
45
|
-
};
|
|
46
|
-
BpkCode.propTypes = {
|
|
47
|
-
children: PropTypes.node.isRequired,
|
|
48
|
-
className: PropTypes.string,
|
|
49
|
-
alternate: PropTypes.bool
|
|
50
|
-
};
|
|
51
|
-
BpkCode.defaultProps = {
|
|
52
|
-
className: null,
|
|
53
|
-
alternate: false
|
|
23
|
+
const BpkCode = ({
|
|
24
|
+
alternate = false,
|
|
25
|
+
children,
|
|
26
|
+
className,
|
|
27
|
+
...rest
|
|
28
|
+
}) => {
|
|
29
|
+
const classNames = getClassName('bpk-code', alternate && 'bpk-code--alternate', className);
|
|
30
|
+
return /*#__PURE__*/_jsx("code", {
|
|
31
|
+
className: classNames,
|
|
32
|
+
...rest,
|
|
33
|
+
children: children
|
|
34
|
+
});
|
|
54
35
|
};
|
|
55
36
|
export default BpkCode;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export type Props = {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
alternate?: boolean;
|
|
5
|
+
className?: string;
|
|
6
|
+
[rest: string]: any;
|
|
7
|
+
};
|
|
8
|
+
declare const BpkCodeBlock: ({ alternate, children, className, ...rest }: Props) => import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export default BpkCodeBlock;
|
|
@@ -14,40 +14,27 @@
|
|
|
14
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
|
-
*/
|
|
17
|
+
*/
|
|
18
|
+
|
|
18
19
|
import { cssModules } from "../../bpk-react-utils";
|
|
19
20
|
import STYLES from "./BpkCodeBlock.module.css";
|
|
20
21
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
21
22
|
const getClassName = cssModules(STYLES);
|
|
22
|
-
const BpkCodeBlock =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
} = props;
|
|
23
|
+
const BpkCodeBlock = ({
|
|
24
|
+
alternate = false,
|
|
25
|
+
children,
|
|
26
|
+
className,
|
|
27
|
+
...rest
|
|
28
|
+
}) => {
|
|
29
29
|
const preClassNames = getClassName('bpk-code__pre', alternate && 'bpk-code__pre--alternate', className);
|
|
30
30
|
const codeClassNames = getClassName('bpk-code', 'bpk-code--block');
|
|
31
|
-
return (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
_jsx("
|
|
35
|
-
className:
|
|
36
|
-
|
|
37
|
-
children: /*#__PURE__*/_jsx("code", {
|
|
38
|
-
className: codeClassNames,
|
|
39
|
-
children: children
|
|
40
|
-
})
|
|
31
|
+
return /*#__PURE__*/_jsx("pre", {
|
|
32
|
+
className: preClassNames,
|
|
33
|
+
...rest,
|
|
34
|
+
children: /*#__PURE__*/_jsx("code", {
|
|
35
|
+
className: codeClassNames,
|
|
36
|
+
children: children
|
|
41
37
|
})
|
|
42
|
-
);
|
|
43
|
-
};
|
|
44
|
-
BpkCodeBlock.propTypes = {
|
|
45
|
-
children: PropTypes.node.isRequired,
|
|
46
|
-
alternate: PropTypes.bool,
|
|
47
|
-
className: PropTypes.string
|
|
48
|
-
};
|
|
49
|
-
BpkCodeBlock.defaultProps = {
|
|
50
|
-
alternate: false,
|
|
51
|
-
className: null
|
|
38
|
+
});
|
|
52
39
|
};
|
|
53
40
|
export default BpkCodeBlock;
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
import { cloneElement } from 'react';
|
|
20
20
|
// @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`.
|
|
21
21
|
import BpkFormValidation from "../../bpk-component-form-validation";
|
|
22
|
-
// @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`.
|
|
23
22
|
import BpkLabel from "../../bpk-component-label";
|
|
24
23
|
import { cssModules } from "../../bpk-react-utils";
|
|
25
24
|
import STYLES from "./BpkFieldset.module.css";
|
|
@@ -14,5 +14,7 @@
|
|
|
14
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
|
-
*/
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import BpkLabel from "./src/BpkLabel";
|
|
18
20
|
export default BpkLabel;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
2
|
+
export type Props = {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
className?: string;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
valid?: boolean | null;
|
|
7
|
+
required?: boolean;
|
|
8
|
+
white?: boolean;
|
|
9
|
+
} & ComponentPropsWithoutRef<'label'>;
|
|
10
|
+
declare const BpkLabel: ({ children, className, disabled, required, valid, white, ...rest }: Props) => import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export default BpkLabel;
|
|
@@ -14,14 +14,15 @@
|
|
|
14
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
|
-
*/
|
|
17
|
+
*/
|
|
18
|
+
|
|
18
19
|
import { cssModules } from "../../bpk-react-utils";
|
|
19
20
|
import STYLES from "./BpkLabel.module.css";
|
|
20
21
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
21
22
|
const getClassName = cssModules(STYLES);
|
|
22
23
|
const BpkLabel = ({
|
|
23
24
|
children,
|
|
24
|
-
className
|
|
25
|
+
className,
|
|
25
26
|
disabled = false,
|
|
26
27
|
required = false,
|
|
27
28
|
valid = null,
|
|
@@ -30,25 +31,13 @@ const BpkLabel = ({
|
|
|
30
31
|
}) => {
|
|
31
32
|
const invalid = valid === false;
|
|
32
33
|
const classNames = getClassName('bpk-label', white && 'bpk-label--white', invalid && 'bpk-label--invalid', disabled && 'bpk-label--disabled', white && disabled && 'bpk-label--disabled--white', className);
|
|
33
|
-
return (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
className:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
children: "*"
|
|
42
|
-
})]
|
|
43
|
-
})
|
|
44
|
-
);
|
|
45
|
-
};
|
|
46
|
-
BpkLabel.propTypes = {
|
|
47
|
-
children: PropTypes.node.isRequired,
|
|
48
|
-
className: PropTypes.string,
|
|
49
|
-
disabled: PropTypes.bool,
|
|
50
|
-
valid: PropTypes.bool,
|
|
51
|
-
required: PropTypes.bool,
|
|
52
|
-
white: PropTypes.bool
|
|
34
|
+
return /*#__PURE__*/_jsxs("label", {
|
|
35
|
+
className: classNames,
|
|
36
|
+
...rest,
|
|
37
|
+
children: [children, !disabled && required && /*#__PURE__*/_jsx("span", {
|
|
38
|
+
className: getClassName('bpk-label__asterisk'),
|
|
39
|
+
children: "*"
|
|
40
|
+
})]
|
|
41
|
+
});
|
|
53
42
|
};
|
|
54
43
|
export default BpkLabel;
|
|
@@ -82,6 +82,7 @@ const BpkNavigationTabGroup = ({
|
|
|
82
82
|
children: tabs.map((tab, index) => {
|
|
83
83
|
const selected = index === selectedTab;
|
|
84
84
|
const {
|
|
85
|
+
badgeText,
|
|
85
86
|
icon,
|
|
86
87
|
text,
|
|
87
88
|
...tabWrapItem
|
|
@@ -103,10 +104,10 @@ const BpkNavigationTabGroup = ({
|
|
|
103
104
|
textStyle: TEXT_STYLES.label2,
|
|
104
105
|
children: text
|
|
105
106
|
})]
|
|
106
|
-
}),
|
|
107
|
+
}), badgeText && /*#__PURE__*/_jsx("span", {
|
|
107
108
|
className: getClassName('bpk-navigation-tab-bubble-wrapper'),
|
|
108
109
|
children: /*#__PURE__*/_jsx(BpkBubble, {
|
|
109
|
-
children:
|
|
110
|
+
children: badgeText
|
|
110
111
|
})
|
|
111
112
|
})]
|
|
112
113
|
})
|
|
@@ -21,7 +21,6 @@ import BpkButton, { BUTTON_TYPES } from "../../bpk-component-button";
|
|
|
21
21
|
import { withButtonAlignment } from "../../bpk-component-icon";
|
|
22
22
|
import MinusIcon from "../../bpk-component-icon/sm/minus";
|
|
23
23
|
import PlusIcon from "../../bpk-component-icon/sm/plus";
|
|
24
|
-
// @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`.
|
|
25
24
|
import BpkLabel from "../../bpk-component-label";
|
|
26
25
|
import BpkText, { TEXT_STYLES } from "../../bpk-component-text";
|
|
27
26
|
import { cssModules, setNativeValue } from "../../bpk-react-utils";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import BpkSegmentedControl, { type Props as BpkSegmentControlProps } from './src/BpkSegmentedControl';
|
|
2
|
-
export type { BpkSegmentControlProps };
|
|
1
|
+
import BpkSegmentedControl, { useSegmentedControlPanels, type Props as BpkSegmentControlProps, type TabPanelProps } from './src/BpkSegmentedControl';
|
|
2
|
+
export type { BpkSegmentControlProps, TabPanelProps };
|
|
3
|
+
export { useSegmentedControlPanels };
|
|
3
4
|
export default BpkSegmentedControl;
|
|
@@ -16,5 +16,6 @@
|
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import BpkSegmentedControl from "./src/BpkSegmentedControl";
|
|
19
|
+
import BpkSegmentedControl, { useSegmentedControlPanels } from "./src/BpkSegmentedControl";
|
|
20
|
+
export { useSegmentedControlPanels };
|
|
20
21
|
export default BpkSegmentedControl;
|
|
@@ -6,16 +6,51 @@ export declare const SEGMENT_TYPES: {
|
|
|
6
6
|
SurfaceContrast: string;
|
|
7
7
|
};
|
|
8
8
|
export type SegmentTypes = (typeof SEGMENT_TYPES)[keyof typeof SEGMENT_TYPES];
|
|
9
|
+
export type TabPanelProps = {
|
|
10
|
+
id: string;
|
|
11
|
+
role: 'tabpanel';
|
|
12
|
+
'aria-labelledby': string;
|
|
13
|
+
hidden: boolean;
|
|
14
|
+
tabIndex: 0;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Custom hook to manage segmented control and its panels with automatic ID generation.
|
|
18
|
+
* Simplifies the API by eliminating the need to manually track IDs.
|
|
19
|
+
*
|
|
20
|
+
* Note: For optimal performance, memoize the buttonContents array in the parent component
|
|
21
|
+
* to prevent unnecessary recalculations (e.g., using useMemo or defining outside render).
|
|
22
|
+
*
|
|
23
|
+
* @param {Array<string | ReactNode>} buttonContents - Array of button content (strings or ReactNodes)
|
|
24
|
+
* @param {number} selectedIndex - Currently selected tab index
|
|
25
|
+
* @returns {Object} Object with controlProps (for BpkSegmentedControl) and getPanelProps function
|
|
26
|
+
*/
|
|
27
|
+
export declare const useSegmentedControlPanels: (buttonContents: string[] | ReactNode[], selectedIndex: number) => {
|
|
28
|
+
controlProps: {
|
|
29
|
+
id: string;
|
|
30
|
+
buttonContents: string[] | ReactNode[];
|
|
31
|
+
selectedIndex: number;
|
|
32
|
+
};
|
|
33
|
+
getPanelProps: (index: number) => TabPanelProps;
|
|
34
|
+
};
|
|
9
35
|
export type Props = {
|
|
10
36
|
buttonContents: string[] | ReactNode[];
|
|
11
37
|
/**
|
|
12
38
|
* Accessible label for the segmented control group.
|
|
13
39
|
*/
|
|
14
40
|
label?: string;
|
|
41
|
+
/**
|
|
42
|
+
* ID used to link the segmented control with its tab panels for accessibility.
|
|
43
|
+
* Created using controlProps from useSegmentedControlPanels hook.
|
|
44
|
+
*/
|
|
45
|
+
id?: string;
|
|
15
46
|
type?: SegmentTypes;
|
|
47
|
+
/**
|
|
48
|
+
* Callback fired when a tab is selected. Receives the index of the selected tab.
|
|
49
|
+
*/
|
|
16
50
|
onItemClick: (id: number) => void;
|
|
17
51
|
selectedIndex: number;
|
|
18
52
|
shadow?: boolean;
|
|
53
|
+
activationMode?: 'automatic' | 'manual';
|
|
19
54
|
};
|
|
20
|
-
declare const BpkSegmentedControl: ({ buttonContents, label, onItemClick, selectedIndex, shadow, type, }: Props) => import("react/jsx-runtime").JSX.Element;
|
|
55
|
+
declare const BpkSegmentedControl: ({ activationMode, buttonContents, id: providedId, label, onItemClick, selectedIndex, shadow, type, }: Props) => import("react/jsx-runtime").JSX.Element;
|
|
21
56
|
export default BpkSegmentedControl;
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { useState } from 'react';
|
|
20
|
-
import { cssModules } from "../../bpk-react-utils";
|
|
19
|
+
import { useId, useMemo, useRef, useState } from 'react';
|
|
20
|
+
import { cssModules, isRTL } from "../../bpk-react-utils";
|
|
21
21
|
import STYLES from "./BpkSegmentedControl.module.css";
|
|
22
22
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
23
23
|
const getClassName = cssModules(STYLES);
|
|
@@ -27,40 +27,165 @@ export const SEGMENT_TYPES = {
|
|
|
27
27
|
SurfaceDefault: 'surface-default',
|
|
28
28
|
SurfaceContrast: 'surface-contrast'
|
|
29
29
|
};
|
|
30
|
+
const getPanelId = (baseId, index) => `${baseId}-panel-${index}`;
|
|
31
|
+
const getTabId = (baseId, index) => `${baseId}-tab-${index}`;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Helper function to get accessibility props for tab panel elements.
|
|
35
|
+
* Use this to ensure proper ARIA relationships between tabs and their panels.
|
|
36
|
+
*
|
|
37
|
+
* Note: For a simpler API, consider using the useSegmentedControlPanels hook instead,
|
|
38
|
+
* which manages IDs automatically and reduces boilerplate.
|
|
39
|
+
* This function is kept for backward compatibility.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} baseId - The base ID used to generate unique IDs for tabs and panels.
|
|
42
|
+
* @param {number} index - The index of the tab panel.
|
|
43
|
+
* @param {number} selectedIndex - The currently selected tab index.
|
|
44
|
+
* @returns {TabPanelProps} An object containing the necessary props for a tab panel.
|
|
45
|
+
*/
|
|
46
|
+
const getTabPanelProps = (baseId, index, selectedIndex) => ({
|
|
47
|
+
id: getPanelId(baseId, index),
|
|
48
|
+
role: 'tabpanel',
|
|
49
|
+
'aria-labelledby': getTabId(baseId, index),
|
|
50
|
+
hidden: index !== selectedIndex,
|
|
51
|
+
tabIndex: 0
|
|
52
|
+
});
|
|
53
|
+
const getContainerAriaProps = (providedId, label) => {
|
|
54
|
+
const props = {};
|
|
55
|
+
if (providedId) {
|
|
56
|
+
props.role = 'tablist';
|
|
57
|
+
props['aria-orientation'] = 'horizontal';
|
|
58
|
+
}
|
|
59
|
+
if (label) {
|
|
60
|
+
props['aria-label'] = label;
|
|
61
|
+
}
|
|
62
|
+
return props;
|
|
63
|
+
};
|
|
64
|
+
const getButtonAriaProps = (providedId, isSelected, panelId) => {
|
|
65
|
+
if (!providedId) {
|
|
66
|
+
return {};
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
role: 'tab',
|
|
70
|
+
'aria-selected': isSelected,
|
|
71
|
+
'aria-controls': panelId
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
const getTabIndex = (providedId, isSelected) => {
|
|
75
|
+
if (!providedId) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
return isSelected ? 0 : -1;
|
|
79
|
+
};
|
|
80
|
+
const getNextIndex = (current, max) => current === max ? 0 : current + 1;
|
|
81
|
+
const getPrevIndex = (current, max) => current === 0 ? max : current - 1;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Custom hook to manage segmented control and its panels with automatic ID generation.
|
|
85
|
+
* Simplifies the API by eliminating the need to manually track IDs.
|
|
86
|
+
*
|
|
87
|
+
* Note: For optimal performance, memoize the buttonContents array in the parent component
|
|
88
|
+
* to prevent unnecessary recalculations (e.g., using useMemo or defining outside render).
|
|
89
|
+
*
|
|
90
|
+
* @param {Array<string | ReactNode>} buttonContents - Array of button content (strings or ReactNodes)
|
|
91
|
+
* @param {number} selectedIndex - Currently selected tab index
|
|
92
|
+
* @returns {Object} Object with controlProps (for BpkSegmentedControl) and getPanelProps function
|
|
93
|
+
*/
|
|
94
|
+
export const useSegmentedControlPanels = (buttonContents, selectedIndex) => {
|
|
95
|
+
const baseId = useId();
|
|
96
|
+
const controlProps = useMemo(() => ({
|
|
97
|
+
id: baseId,
|
|
98
|
+
buttonContents,
|
|
99
|
+
selectedIndex
|
|
100
|
+
}), [baseId, buttonContents, selectedIndex]);
|
|
101
|
+
const getPanelProps = useMemo(() => index => getTabPanelProps(baseId, index, selectedIndex), [baseId, selectedIndex]);
|
|
102
|
+
return {
|
|
103
|
+
controlProps,
|
|
104
|
+
getPanelProps
|
|
105
|
+
};
|
|
106
|
+
};
|
|
30
107
|
const BpkSegmentedControl = ({
|
|
108
|
+
activationMode = 'automatic',
|
|
31
109
|
buttonContents,
|
|
110
|
+
id: providedId,
|
|
32
111
|
label,
|
|
33
112
|
onItemClick,
|
|
34
113
|
selectedIndex,
|
|
35
114
|
shadow = false,
|
|
36
115
|
type = SEGMENT_TYPES.CanvasDefault
|
|
37
116
|
}) => {
|
|
117
|
+
const buttonRefs = useRef([]);
|
|
118
|
+
const panelIds = useMemo(() => Array.from({
|
|
119
|
+
length: buttonContents.length
|
|
120
|
+
}, (_, i) => providedId ? getPanelId(providedId, i) : undefined), [providedId, buttonContents.length]);
|
|
121
|
+
|
|
122
|
+
// TODO: Consider removing internal state - component is controlled via selectedIndex prop.
|
|
123
|
+
// Internal state may cause sync issues if selectedIndex changes externally.
|
|
38
124
|
const [selectedButton, setSelectedButton] = useState(selectedIndex);
|
|
39
|
-
const handleButtonClick =
|
|
40
|
-
if (
|
|
41
|
-
setSelectedButton(
|
|
42
|
-
onItemClick(
|
|
125
|
+
const handleButtonClick = index => {
|
|
126
|
+
if (index !== selectedButton) {
|
|
127
|
+
setSelectedButton(index);
|
|
128
|
+
onItemClick(index);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const handleKeyDown = (event, currentIndex) => {
|
|
132
|
+
const lastIndex = buttonContents.length - 1;
|
|
133
|
+
const rtl = isRTL();
|
|
134
|
+
let newIndex = currentIndex;
|
|
135
|
+
switch (event.key) {
|
|
136
|
+
case 'ArrowRight':
|
|
137
|
+
newIndex = rtl ? getPrevIndex(currentIndex, lastIndex) : getNextIndex(currentIndex, lastIndex);
|
|
138
|
+
break;
|
|
139
|
+
case 'ArrowLeft':
|
|
140
|
+
newIndex = rtl ? getNextIndex(currentIndex, lastIndex) : getPrevIndex(currentIndex, lastIndex);
|
|
141
|
+
break;
|
|
142
|
+
case 'Home':
|
|
143
|
+
newIndex = 0;
|
|
144
|
+
break;
|
|
145
|
+
case 'End':
|
|
146
|
+
newIndex = lastIndex;
|
|
147
|
+
break;
|
|
148
|
+
case ' ':
|
|
149
|
+
case 'Enter':
|
|
150
|
+
event.preventDefault();
|
|
151
|
+
if (activationMode === 'manual') {
|
|
152
|
+
setSelectedButton(currentIndex);
|
|
153
|
+
onItemClick(currentIndex);
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
default:
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
event.preventDefault();
|
|
160
|
+
if (activationMode === 'automatic') {
|
|
161
|
+
setSelectedButton(newIndex);
|
|
162
|
+
onItemClick(newIndex);
|
|
43
163
|
}
|
|
164
|
+
buttonRefs.current[newIndex]?.focus();
|
|
44
165
|
};
|
|
45
166
|
const containerStyling = getClassName('bpk-segmented-control-group', shadow && 'bpk-segmented-control-group-shadow');
|
|
46
167
|
return /*#__PURE__*/_jsx("div", {
|
|
47
168
|
className: containerStyling,
|
|
48
|
-
|
|
49
|
-
...(label ? {
|
|
50
|
-
'aria-label': label
|
|
51
|
-
} : {}),
|
|
169
|
+
...getContainerAriaProps(providedId, label),
|
|
52
170
|
children: buttonContents.map((content, index) => {
|
|
53
171
|
const isSelected = index === selectedButton;
|
|
54
172
|
const rightOfOption = index === selectedButton + 1;
|
|
55
173
|
const buttonStyling = getClassName('bpk-segmented-control', `bpk-segmented-control--${type}`, isSelected && `bpk-segmented-control--${type}-selected`, rightOfOption && `bpk-segmented-control--${type}-rightOfOption`, shadow && isSelected && `bpk-segmented-control--${type}-selected-shadow`);
|
|
174
|
+
const buttonTabId = providedId ? getTabId(providedId, index) : undefined;
|
|
175
|
+
const tabIndexValue = getTabIndex(providedId, isSelected);
|
|
56
176
|
return /*#__PURE__*/_jsx("button", {
|
|
57
|
-
|
|
177
|
+
ref: el => {
|
|
178
|
+
buttonRefs.current[index] = el;
|
|
179
|
+
},
|
|
180
|
+
id: buttonTabId,
|
|
58
181
|
type: "button",
|
|
59
182
|
onClick: () => handleButtonClick(index),
|
|
183
|
+
onKeyDown: event => handleKeyDown(event, index),
|
|
60
184
|
className: buttonStyling,
|
|
61
|
-
|
|
185
|
+
tabIndex: tabIndexValue,
|
|
186
|
+
...getButtonAriaProps(providedId, isSelected, panelIds[index]),
|
|
62
187
|
children: content
|
|
63
|
-
},
|
|
188
|
+
}, buttonTabId || `${index}`);
|
|
64
189
|
})
|
|
65
190
|
});
|
|
66
191
|
};
|
|
@@ -15,4 +15,4 @@
|
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/
|
|
18
|
-
.bpk-text{margin:0}.bpk-text--xs{font-size:.75rem;line-height:1rem;font-weight:400}.bpk-text--sm{font-size:.875rem;line-height:1.25rem;font-weight:400}.bpk-text--base{font-size:1rem;line-height:1.5rem;font-weight:400}.bpk-text--lg{font-size:1.25rem;line-height:1.75rem;font-weight:400}.bpk-text--xl{font-size:1.5rem;line-height:2rem;font-weight:400}.bpk-text--xxl{font-size:2rem;line-height:2.5rem;font-weight:700}.bpk-text--xxxl{font-size:2.5rem;line-height:3rem;font-weight:700}.bpk-text--xxxxl{font-size:3rem;line-height:3.5rem;font-weight:700;letter-spacing:-0.02em}.bpk-text--xxxxxl{font-size:4rem;line-height:4rem;font-weight:700;letter-spacing:-0.02em}.bpk-text--caption{font-size:.75rem;line-height:1rem;font-weight:400}.bpk-text--footnote{font-size:.875rem;line-height:1.25rem;font-weight:400}.bpk-text--label-1{font-size:1rem;line-height:1.5rem;font-weight:700}.bpk-text--label-2{font-size:.875rem;line-height:1.25rem;font-weight:700}.bpk-text--label-3{font-size:.75rem;line-height:1rem;font-weight:700}.bpk-text--body-default{font-size:1rem;line-height:1.5rem;font-weight:400}.bpk-text--body-longform{font-size:1.25rem;line-height:1.75rem;font-weight:400}.bpk-text--subheading{font-size:1.5rem;line-height:2rem;font-weight:400}.bpk-text--heading-1{font-size:2.5rem;line-height:3rem;font-weight:700}.bpk-text--heading-2{font-size:2rem;line-height:2.5rem;font-weight:700}.bpk-text--heading-3{font-size:1.5rem;line-height:1.75rem;font-weight:700}.bpk-text--heading-4{font-size:1.25rem;line-height:1.5rem;font-weight:700}.bpk-text--heading-5{font-size:1rem;line-height:1.25rem;font-weight:700}.bpk-text--hero-1{font-size:7.5rem;line-height:7.5rem;font-weight:900;letter-spacing:-0.04em}.bpk-text--hero-2{font-size:6rem;line-height:6rem;font-weight:900;letter-spacing:-0.04em}.bpk-text--hero-3{font-size:4.75rem;line-height:4.75rem;font-weight:900;letter-spacing:-0.04em}.bpk-text--hero-4{font-size:4rem;line-height:4rem;font-weight:900;letter-spacing:-0.04em}.bpk-text--hero-5{font-size:3rem;line-height:3rem;font-weight:900;letter-spacing:-0.04em}.bpk-text--hero-6{font-size:2.5rem;line-height:2.5rem;font-weight:900;letter-spacing:-0.04em}.bpk-text--editorial-1{font-family:"Larken","Noto Sans Arabic","Noto
|
|
18
|
+
.bpk-text{margin:0}.bpk-text--xs{font-size:.75rem;line-height:1rem;font-weight:400}.bpk-text--sm{font-size:.875rem;line-height:1.25rem;font-weight:400}.bpk-text--base{font-size:1rem;line-height:1.5rem;font-weight:400}.bpk-text--lg{font-size:1.25rem;line-height:1.75rem;font-weight:400}.bpk-text--xl{font-size:1.5rem;line-height:2rem;font-weight:400}.bpk-text--xxl{font-size:2rem;line-height:2.5rem;font-weight:700}.bpk-text--xxxl{font-size:2.5rem;line-height:3rem;font-weight:700}.bpk-text--xxxxl{font-size:3rem;line-height:3.5rem;font-weight:700;letter-spacing:-0.02em}.bpk-text--xxxxxl{font-size:4rem;line-height:4rem;font-weight:700;letter-spacing:-0.02em}.bpk-text--caption{font-size:.75rem;line-height:1rem;font-weight:400}.bpk-text--footnote{font-size:.875rem;line-height:1.25rem;font-weight:400}.bpk-text--label-1{font-size:1rem;line-height:1.5rem;font-weight:700}.bpk-text--label-2{font-size:.875rem;line-height:1.25rem;font-weight:700}.bpk-text--label-3{font-size:.75rem;line-height:1rem;font-weight:700}.bpk-text--body-default{font-size:1rem;line-height:1.5rem;font-weight:400}.bpk-text--body-longform{font-size:1.25rem;line-height:1.75rem;font-weight:400}.bpk-text--subheading{font-size:1.5rem;line-height:2rem;font-weight:400}.bpk-text--heading-1{font-size:2.5rem;line-height:3rem;font-weight:700}.bpk-text--heading-2{font-size:2rem;line-height:2.5rem;font-weight:700}.bpk-text--heading-3{font-size:1.5rem;line-height:1.75rem;font-weight:700}.bpk-text--heading-4{font-size:1.25rem;line-height:1.5rem;font-weight:700}.bpk-text--heading-5{font-size:1rem;line-height:1.25rem;font-weight:700}.bpk-text--hero-1{font-size:7.5rem;line-height:7.5rem;font-weight:900;letter-spacing:-0.04em}.bpk-text--hero-2{font-size:6rem;line-height:6rem;font-weight:900;letter-spacing:-0.04em}.bpk-text--hero-3{font-size:4.75rem;line-height:4.75rem;font-weight:900;letter-spacing:-0.04em}.bpk-text--hero-4{font-size:4rem;line-height:4rem;font-weight:900;letter-spacing:-0.04em}.bpk-text--hero-5{font-size:3rem;line-height:3rem;font-weight:900;letter-spacing:-0.04em}.bpk-text--hero-6{font-size:2.5rem;line-height:2.5rem;font-weight:900;letter-spacing:-0.04em}.bpk-text--editorial-1{font-family:var(--bpk-larken-font-stack, "Larken", "Noto Sans Arabic", "Noto Serif Hebrew", "Noto Serif", "Noto Serif Devanagari", "Noto Serif Thai", "Noto Serif SC", "Noto Serif TC", "Noto Serif JP", "Noto Serif KR", sans-serif);font-size:3rem;line-height:3.5rem;font-weight:300}.bpk-text--editorial-2{font-family:var(--bpk-larken-font-stack, "Larken", "Noto Sans Arabic", "Noto Serif Hebrew", "Noto Serif", "Noto Serif Devanagari", "Noto Serif Thai", "Noto Serif SC", "Noto Serif TC", "Noto Serif JP", "Noto Serif KR", sans-serif);font-size:2rem;line-height:2.5rem;font-weight:300}.bpk-text--editorial-3{font-family:var(--bpk-larken-font-stack, "Larken", "Noto Sans Arabic", "Noto Serif Hebrew", "Noto Serif", "Noto Serif Devanagari", "Noto Serif Thai", "Noto Serif SC", "Noto Serif TC", "Noto Serif JP", "Noto Serif KR", sans-serif);font-size:1.25rem;line-height:1.75rem;font-weight:400}.bpk-text.bpk-text--text-disabled{color:rgba(0,0,0,.2)}.bpk-text.bpk-text--text-disabled-on-dark{color:hsla(0,0%,100%,.5)}.bpk-text.bpk-text--text-error{color:#e70866}.bpk-text.bpk-text--text-hero{color:#0062e3}.bpk-text.bpk-text--text-link{color:#0062e3}.bpk-text.bpk-text--text-on-dark{color:#fff}.bpk-text.bpk-text--text-on-light{color:#161616}.bpk-text.bpk-text--text-primary{color:#161616}.bpk-text.bpk-text--text-primary-inverse{color:#fff}.bpk-text.bpk-text--text-secondary{color:#626971}.bpk-text.bpk-text--text-success{color:#0c838a}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ChangeEvent } from 'react';
|
|
2
|
+
import { Component } from 'react';
|
|
3
|
+
type Props = Record<string, never>;
|
|
4
|
+
type State = {
|
|
5
|
+
selectedTheme: string;
|
|
6
|
+
};
|
|
7
|
+
declare class BpkThemeToggle extends Component<Props, State> {
|
|
8
|
+
constructor(props: Props);
|
|
9
|
+
componentDidMount(): void;
|
|
10
|
+
componentWillUnmount(): void;
|
|
11
|
+
handleKeyDown: (e: KeyboardEvent) => void;
|
|
12
|
+
handleChange: (e: ChangeEvent<HTMLSelectElement>) => void;
|
|
13
|
+
cycleTheme: () => void;
|
|
14
|
+
render(): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
}
|
|
16
|
+
export default BpkThemeToggle;
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
import { Component } from 'react';
|
|
20
20
|
import BpkLabel from "../../bpk-component-label";
|
|
21
|
+
// @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`.
|
|
21
22
|
import BpkSelect from "../../bpk-component-select";
|
|
22
23
|
import { cssModules } from "../../bpk-react-utils";
|
|
23
24
|
import bpkCustomThemes from "./theming";
|
|
@@ -29,11 +30,13 @@ const getClassName = cssModules(STYLES);
|
|
|
29
30
|
const availableThemes = Object.keys(bpkCustomThemes);
|
|
30
31
|
const setTheme = theme => {
|
|
31
32
|
const htmlElement = getHtmlElement();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
if (htmlElement) {
|
|
34
|
+
htmlElement.dispatchEvent(new CustomEvent(THEME_CHANGE_EVENT, {
|
|
35
|
+
detail: {
|
|
36
|
+
theme
|
|
37
|
+
}
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
37
40
|
};
|
|
38
41
|
class BpkThemeToggle extends Component {
|
|
39
42
|
constructor(props) {
|
|
@@ -58,7 +61,7 @@ class BpkThemeToggle extends Component {
|
|
|
58
61
|
this.setState({
|
|
59
62
|
selectedTheme
|
|
60
63
|
});
|
|
61
|
-
setTheme(bpkCustomThemes[selectedTheme]);
|
|
64
|
+
setTheme(selectedTheme ? bpkCustomThemes[selectedTheme] : undefined);
|
|
62
65
|
};
|
|
63
66
|
cycleTheme = () => {
|
|
64
67
|
let {
|
|
@@ -73,7 +76,7 @@ class BpkThemeToggle extends Component {
|
|
|
73
76
|
this.setState({
|
|
74
77
|
selectedTheme
|
|
75
78
|
});
|
|
76
|
-
setTheme(bpkCustomThemes[selectedTheme]);
|
|
79
|
+
setTheme(selectedTheme ? bpkCustomThemes[selectedTheme] : undefined);
|
|
77
80
|
};
|
|
78
81
|
render() {
|
|
79
82
|
const {
|