Merge pull request #28136 from backstage/canon-text-component
Canon - Text + Heading components
This commit is contained in:
@@ -11,6 +11,7 @@ import '../src/css/components.css';
|
||||
|
||||
// Custom themes
|
||||
import './themes/backstage.css';
|
||||
import { ThemeProvider } from '../src/theme/context';
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
@@ -28,6 +29,46 @@ const preview: Preview = {
|
||||
method: 'alphabetical',
|
||||
},
|
||||
},
|
||||
viewport: {
|
||||
viewports: {
|
||||
small: {
|
||||
name: 'Small',
|
||||
styles: {
|
||||
width: '640px',
|
||||
height: '100%',
|
||||
},
|
||||
},
|
||||
medium: {
|
||||
name: 'Medium',
|
||||
styles: {
|
||||
width: '768px',
|
||||
height: '100%',
|
||||
},
|
||||
},
|
||||
large: {
|
||||
name: 'Large',
|
||||
styles: {
|
||||
width: '1024px',
|
||||
height: '100%',
|
||||
},
|
||||
},
|
||||
xlarge: {
|
||||
name: 'XLarge',
|
||||
styles: {
|
||||
width: '1280px',
|
||||
height: '100%',
|
||||
},
|
||||
},
|
||||
'2xl': {
|
||||
name: '2XL',
|
||||
styles: {
|
||||
width: '1536px',
|
||||
height: '100%',
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultViewport: 'small',
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
withThemeByDataAttribute<ReactRenderer>({
|
||||
@@ -48,7 +89,11 @@ const preview: Preview = {
|
||||
'var(--canon-background)';
|
||||
});
|
||||
|
||||
return <Story />;
|
||||
return (
|
||||
<ThemeProvider>
|
||||
<Story />
|
||||
</ThemeProvider>
|
||||
);
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ export const Text = ({
|
||||
style?: React.CSSProperties;
|
||||
}) => {
|
||||
return (
|
||||
<div className="text" style={style}>
|
||||
<div className="sb-text" style={style}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.text {
|
||||
.sb-text {
|
||||
font-family: 'Geist', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 28px;
|
||||
|
||||
@@ -31,7 +31,7 @@ export const Title = ({
|
||||
if (type === 'h3') Component = 'h3';
|
||||
|
||||
return React.createElement(Component, {
|
||||
className: `title ${type}`,
|
||||
className: `sb-title ${type}`,
|
||||
style,
|
||||
children,
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.title {
|
||||
.sb-title {
|
||||
font-family: 'Geist', sans-serif;
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Heading } from './Heading';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Heading',
|
||||
component: Heading,
|
||||
} satisfies Meta<typeof Heading>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: 'Heading',
|
||||
},
|
||||
};
|
||||
|
||||
export const Responsive: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
variant: {
|
||||
// xs: 'title4',
|
||||
sm: 'display',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomTag: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
variant: 'title5',
|
||||
as: 'h2',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { forwardRef } from 'react';
|
||||
import { HeadingProps } from './types';
|
||||
import { useTheme } from '../../theme/context';
|
||||
import { getResponsiveValue } from '../../utils/getResponsiveValue';
|
||||
|
||||
export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(
|
||||
(props, ref) => {
|
||||
const { children, variant = 'title1', as = 'h1', ...restProps } = props;
|
||||
const { breakpoint } = useTheme();
|
||||
const responsiveVariant = getResponsiveValue(variant, breakpoint);
|
||||
|
||||
let Component = as;
|
||||
if (variant === 'title2') Component = 'h2';
|
||||
if (variant === 'title3') Component = 'h3';
|
||||
if (variant === 'title4') Component = 'h4';
|
||||
if (variant === 'title5') Component = 'h5';
|
||||
if (as) Component = as;
|
||||
|
||||
return (
|
||||
<Component
|
||||
ref={ref}
|
||||
{...restProps}
|
||||
className={`text ${
|
||||
responsiveVariant ? `text-${responsiveVariant}` : ''
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Heading.displayName = 'Heading';
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { Heading } from './Heading';
|
||||
export type { HeadingProps } from './types';
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.text {
|
||||
font-family: var(--canon-font-regular);
|
||||
color: var(--canon-text-primary);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
&.text-display {
|
||||
font-size: var(--canon-font-size-display);
|
||||
line-height: 100%;
|
||||
font-weight: var(--canon-font-bold);
|
||||
}
|
||||
|
||||
&.text-title1 {
|
||||
font-size: var(--canon-font-size-title1);
|
||||
line-height: 100%;
|
||||
font-weight: var(--canon-font-bold);
|
||||
}
|
||||
|
||||
&.text-title2 {
|
||||
font-size: var(--canon-font-size-title2);
|
||||
line-height: 100%;
|
||||
font-weight: var(--canon-font-bold);
|
||||
}
|
||||
|
||||
&.text-title3 {
|
||||
font-size: var(--canon-font-size-title3);
|
||||
line-height: 100%;
|
||||
font-weight: var(--canon-font-bold);
|
||||
}
|
||||
|
||||
&.text-title4 {
|
||||
font-size: var(--canon-font-size-title4);
|
||||
line-height: 100%;
|
||||
font-weight: var(--canon-font-bold);
|
||||
}
|
||||
|
||||
&.text-title5 {
|
||||
font-size: var(--canon-font-size-title5);
|
||||
line-height: 100%;
|
||||
font-weight: var(--canon-font-bold);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Breakpoint } from '../../layout/types';
|
||||
|
||||
/** @public */
|
||||
export interface HeadingProps {
|
||||
children: React.ReactNode;
|
||||
variant?:
|
||||
| 'display'
|
||||
| 'title1'
|
||||
| 'title2'
|
||||
| 'title3'
|
||||
| 'title4'
|
||||
| 'title5'
|
||||
| Partial<
|
||||
Record<
|
||||
Breakpoint,
|
||||
'display' | 'title1' | 'title2' | 'title3' | 'title4' | 'title5'
|
||||
>
|
||||
>;
|
||||
as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Text } from './Text';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Text',
|
||||
component: Text,
|
||||
} satisfies Meta<typeof Text>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children:
|
||||
' Lorem ipsum dolor sit amet consectetur. Nec arcu vel lacus magna adipiscing nisi mauris tortor viverra. Enim rhoncus quisque consectetur ligula diam ac lacus massa. Id interdum id pellentesque justo ut massa nibh amet. Odio massa in scelerisque tortor massa integer purus amet enim. Eros sit neque nullam facilisis. Purus massa dignissim aliquet purus eu in. Urna consequat ullamcorper arcu amet dictumst. Commodo praesent turpis fringilla tristique congue volutpat in. Nulla in nulla ultrices lacus. In ultrices id tellus ut. Semper duis in in non proin et pulvinar. Sit amet vulputate lorem sit ipsum quis amet pulvinar ultricies. Imperdiet enim molestie habitant eget. Aenean mollis aenean dolor sodales tempus blandit.',
|
||||
},
|
||||
};
|
||||
|
||||
export const Responsive: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
variant: {
|
||||
xs: 'label',
|
||||
md: 'body',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { forwardRef } from 'react';
|
||||
import { TextProps } from './types';
|
||||
import { useTheme } from '../../theme/context';
|
||||
import { getResponsiveValue } from '../../utils/getResponsiveValue';
|
||||
|
||||
export const Text = forwardRef<HTMLParagraphElement, TextProps>(
|
||||
(props, ref) => {
|
||||
const {
|
||||
children,
|
||||
variant = 'body',
|
||||
weight = 'regular',
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
const { breakpoint } = useTheme();
|
||||
|
||||
const responsiveVariant = getResponsiveValue(variant, breakpoint);
|
||||
const responsiveWeight = getResponsiveValue(weight, breakpoint);
|
||||
|
||||
return (
|
||||
<p
|
||||
ref={ref}
|
||||
{...restProps}
|
||||
className={`text ${
|
||||
responsiveVariant ? `text-${responsiveVariant}` : ''
|
||||
} ${responsiveWeight ? `text-${responsiveWeight}` : ''}`}
|
||||
>
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Text.displayName = 'Text';
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { Text } from './Text';
|
||||
export type { TextProps } from './types';
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.text {
|
||||
font-family: var(--canon-font-regular);
|
||||
color: var(--canon-text-primary);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
&.text-body {
|
||||
font-size: var(--canon-font-size-body);
|
||||
line-height: 140%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Breakpoint } from '../../layout/types';
|
||||
|
||||
/** @public */
|
||||
export interface TextProps {
|
||||
children: React.ReactNode;
|
||||
variant?:
|
||||
| 'subtitle'
|
||||
| 'body'
|
||||
| 'caption'
|
||||
| 'label'
|
||||
| Partial<Record<Breakpoint, 'subtitle' | 'body' | 'caption' | 'label'>>;
|
||||
weight?: 'regular' | 'bold' | Partial<Record<Breakpoint, 'regular' | 'bold'>>;
|
||||
}
|
||||
@@ -23,3 +23,5 @@
|
||||
@import '../components/Icon/styles.css';
|
||||
@import '../components/Checkbox/styles.css';
|
||||
@import '../components/Table/styles.css';
|
||||
@import '../components/Text/styles.css';
|
||||
@import '../components/Heading/styles.css';
|
||||
|
||||
@@ -17,6 +17,12 @@
|
||||
/* Normalize */
|
||||
@import './normalize.css';
|
||||
|
||||
*,
|
||||
html,
|
||||
body {
|
||||
font-family: var(--canon-font-regular);
|
||||
}
|
||||
|
||||
/* Light theme tokens */
|
||||
:root {
|
||||
/* Colors */
|
||||
@@ -45,10 +51,10 @@
|
||||
--canon-box-shadow-large: 0 0 0 1px rgba(0, 0, 0, 0.2);
|
||||
|
||||
/* Font families */
|
||||
--canon-font-regular: 'Geist', serif;
|
||||
--canon-font-regular: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
--canon-font-monospace: 'Monospace', monospace;
|
||||
--canon-font-emoji: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
||||
'Noto Color Emoji';
|
||||
|
||||
/* Font weights */
|
||||
--canon-font-normal: 400;
|
||||
@@ -59,12 +65,12 @@
|
||||
--canon-font-size-caption: 0.75rem; /* 12px */
|
||||
--canon-font-size-body: 0.875rem; /* 14px */
|
||||
--canon-font-size-subtitle: 1rem; /* 16px */
|
||||
--canon-font-size-title1: 1.25rem; /* 20px */
|
||||
--canon-font-size-title2: 1.5rem; /* 24px */
|
||||
--canon-font-size-title5: 1.25rem; /* 20px */
|
||||
--canon-font-size-title4: 1.5rem; /* 24px */
|
||||
--canon-font-size-title3: 2rem; /* 32px */
|
||||
--canon-font-size-title4: 3rem; /* 48px */
|
||||
--canon-font-size-title5: 4rem; /* 64px */
|
||||
--canon-font-size-display: 5.75rem; /* 48px */
|
||||
--canon-font-size-title2: 3rem; /* 48px */
|
||||
--canon-font-size-title1: 4rem; /* 64px */
|
||||
--canon-font-size-display: 5.75rem; /* 92px */
|
||||
|
||||
/* Spacing */
|
||||
--canon-spacing-5xs: 0.125rem; /* 2px */
|
||||
@@ -91,12 +97,6 @@
|
||||
/* Container */
|
||||
--canon-container-max-width: 1200px;
|
||||
--canon-container-padding: 1rem;
|
||||
|
||||
*,
|
||||
html,
|
||||
body {
|
||||
font-family: var(--canon-font-regular);
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark theme tokens */
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { useEffect, useLayoutEffect } from 'react';
|
||||
|
||||
export const useIsomorphicLayoutEffect =
|
||||
typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect';
|
||||
|
||||
type UseMediaQueryOptions = {
|
||||
defaultValue?: boolean;
|
||||
initializeWithValue?: boolean;
|
||||
};
|
||||
|
||||
const IS_SERVER = typeof window === 'undefined';
|
||||
|
||||
export function useMediaQuery(
|
||||
query: string,
|
||||
{
|
||||
defaultValue = false,
|
||||
initializeWithValue = true,
|
||||
}: UseMediaQueryOptions = {},
|
||||
): boolean {
|
||||
const getMatches = (query: string): boolean => {
|
||||
if (IS_SERVER) {
|
||||
return defaultValue;
|
||||
}
|
||||
return window.matchMedia(query).matches;
|
||||
};
|
||||
|
||||
const [matches, setMatches] = useState<boolean>(() => {
|
||||
if (initializeWithValue) {
|
||||
return getMatches(query);
|
||||
}
|
||||
return defaultValue;
|
||||
});
|
||||
|
||||
// Handles the change event of the media query.
|
||||
function handleChange() {
|
||||
setMatches(getMatches(query));
|
||||
}
|
||||
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
const matchMedia = window.matchMedia(query);
|
||||
|
||||
// Triggered at the first client-side load and if query changes
|
||||
handleChange();
|
||||
|
||||
// Use deprecated `addListener` and `removeListener` to support Safari < 14 (#135)
|
||||
if (matchMedia.addListener) {
|
||||
matchMedia.addListener(handleChange);
|
||||
} else {
|
||||
matchMedia.addEventListener('change', handleChange);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (matchMedia.removeListener) {
|
||||
matchMedia.removeListener(handleChange);
|
||||
} else {
|
||||
matchMedia.removeEventListener('change', handleChange);
|
||||
}
|
||||
};
|
||||
}, [query]);
|
||||
|
||||
return matches;
|
||||
}
|
||||
@@ -17,28 +17,59 @@
|
||||
import React, { createContext, useContext, ReactNode } from 'react';
|
||||
import { IconMap, IconNames } from '../components/Icon/types';
|
||||
import { defaultIcons } from '../components/Icon/icons';
|
||||
import { useMediaQuery } from '../hooks/useMediaQuery';
|
||||
import { Breakpoints } from '../types';
|
||||
|
||||
const defaultBreakpoints: Breakpoints = {
|
||||
xs: '0px',
|
||||
sm: '640px',
|
||||
md: '768px',
|
||||
lg: '1024px',
|
||||
xl: '1280px',
|
||||
'2xl': '1536px',
|
||||
};
|
||||
|
||||
interface ThemeContextProps {
|
||||
icons: IconMap;
|
||||
breakpoint: keyof Breakpoints;
|
||||
}
|
||||
|
||||
const ThemeContext = createContext<ThemeContextProps>({
|
||||
icons: defaultIcons,
|
||||
breakpoint: 'md',
|
||||
});
|
||||
|
||||
interface ThemeProviderProps {
|
||||
children?: ReactNode;
|
||||
overrides?: Partial<Record<IconNames, React.ComponentType>>;
|
||||
breakpoints?: Partial<Breakpoints>;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const ThemeProvider = ({
|
||||
children,
|
||||
overrides,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
overrides: Partial<Record<IconNames, React.ComponentType>>;
|
||||
}) => {
|
||||
export const ThemeProvider = (props: ThemeProviderProps) => {
|
||||
const { children, overrides, breakpoints = defaultBreakpoints } = props;
|
||||
|
||||
// Merge provided overrides with default icons
|
||||
const combinedIcons = { ...defaultIcons, ...overrides };
|
||||
|
||||
const isBreakpointSm = useMediaQuery(`(min-width: ${breakpoints.sm})`);
|
||||
const isBreakpointMd = useMediaQuery(`(min-width: ${breakpoints.md})`);
|
||||
const isBreakpointLg = useMediaQuery(`(min-width: ${breakpoints.lg})`);
|
||||
const isBreakpointXl = useMediaQuery(`(min-width: ${breakpoints.xl})`);
|
||||
const isBreakpoint2xl = useMediaQuery(`(min-width: ${breakpoints['2xl']})`);
|
||||
|
||||
// Determine the current breakpoint
|
||||
const breakpoint = (() => {
|
||||
if (isBreakpoint2xl) return '2xl';
|
||||
if (isBreakpointXl) return 'xl';
|
||||
if (isBreakpointLg) return 'lg';
|
||||
if (isBreakpointMd) return 'md';
|
||||
if (isBreakpointSm) return 'sm';
|
||||
return 'xs';
|
||||
})();
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ icons: combinedIcons }}>
|
||||
<ThemeContext.Provider value={{ icons: combinedIcons, breakpoint }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export interface Breakpoints {
|
||||
xs: string;
|
||||
sm: string;
|
||||
md: string;
|
||||
lg: string;
|
||||
xl: string;
|
||||
'2xl': string;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Breakpoints } from '../types';
|
||||
|
||||
export const getResponsiveValue = (
|
||||
value: any,
|
||||
breakpoint: keyof Breakpoints,
|
||||
) => {
|
||||
if (typeof value === 'object') {
|
||||
const breakpointsOrder: (keyof Breakpoints)[] = [
|
||||
'xs',
|
||||
'sm',
|
||||
'md',
|
||||
'lg',
|
||||
'xl',
|
||||
'2xl',
|
||||
];
|
||||
const index = breakpointsOrder.indexOf(breakpoint);
|
||||
|
||||
for (let i = index; i >= 0; i--) {
|
||||
if (value[breakpointsOrder[i]]) {
|
||||
return value[breakpointsOrder[i]];
|
||||
}
|
||||
}
|
||||
return value['xs'];
|
||||
}
|
||||
return value;
|
||||
};
|
||||
Reference in New Issue
Block a user