Skip to content

Commit 00ef069

Browse files
authored
Add Testimonial variants and FrostedGlassVFX component (#863)
1 parent 8223135 commit 00ef069

File tree

43 files changed

+983
-188
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+983
-188
lines changed

.changeset/old-roses-brush.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@primer/react-brand': patch
3+
---
4+
5+
Added new `Testimonial` variants.
6+
7+
Use `variant="default"` or `variant="subtle"` for an alternative visual appearance. The current design will be referred to as `minimal` going forward.
8+
9+
🔗 [See the documentation for usage examples](https://primer.style/brand/components/Testimonial/react#variants)

.changeset/smart-starfishes-lick.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@primer/brand-primitives': patch
3+
---
4+
5+
Added new design tokens for `Testimonial` and `FrostedGlassVFX` components
6+
7+
```
8+
:root {
9+
--brand-Testimonial-borderMask-default
10+
--brand-Testimonial-borderMask-subtle
11+
--brand-FrostedGlassVFX-bgColor
12+
--brand-FrostedGlassVFX-boxShadow
13+
--brand-FrostedGlassVFX-borderMask
14+
--brand-FrostedGlassVFX-blurIntensity-high
15+
--brand-FrostedGlassVFX-blurIntensity-medium
16+
--brand-FrostedGlassVFX-blurIntensity-low
17+
}
18+
```

.changeset/smooth-ways-retire.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
'@primer/react-brand': minor
3+
---
4+
5+
Added a new `FrostedGlassVFX` component for applying a frosted glass-effect texture to nested components.
6+
7+
⚠️ This is an experimental component, and not tested for compatibility with other Primer Brand components.
8+
9+
When using this component, ensure there is sufficient contrast between the foreground text and the background imagery, as the frosted glass effect can significantly reduce legibility.
10+
11+
Usage example:
12+
13+
```jsx
14+
<FrostedGlassVFX>
15+
<Testimonial variant="default">
16+
<Testimonial.Quote>
17+
GitHub helps us ensure that we have our security controls baked into our pipelines all the way from the first line
18+
of code we&apos;re writing.
19+
</Testimonial.Quote>
20+
<Testimonial.Name />
21+
<Testimonial.Avatar />
22+
</Testimonial>
23+
</FrostedGlassVFX>
24+
```
25+
26+
🔗 (See Storybook for an example)[https://primer.style/brand/storybook/?path=/story/components-testimonial-examples--with-frosted-glass]

apps/docs/content/components/Testimonial/react.mdx

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ title: Testimonial
33
figma: 'https://www.figma.com/file/BJ95AjraesmRCWsKA013GS/Primer-Brand?node-id=1852%3A27522'
44
source: https://github.com/primer/brand/blob/main/packages/react/src/Testimonial/Testimonial.tsx
55
storybook: '/brand/storybook/?path=/story/components-testimonial'
6-
description: Use the testimonial component to display a quote from a customer or user.
6+
description: Use testimonial to display a quote from a customer or user.
77
---
88

99
import ComponentLayout from '../../../src/layouts/component-layout'
1010
export default ComponentLayout
1111

12-
import {TestimonialQuoteMarkColors} from '@primer/react-brand'
12+
import {
13+
TestimonialQuoteMarkColors,
14+
TestimonialVariants,
15+
defaultTestimonialVariant,
16+
} from '@primer/react-brand'
1317
import {Box as Container, Label} from '@primer/react'
1418
import {PropTableValues} from '../../../src/components'
1519

@@ -37,6 +41,41 @@ import {Testimonial} from '@primer/react-brand'
3741
</Testimonial>
3842
```
3943

44+
### Variants
45+
46+
For an alternative visual appearance and layout, without altering semantic meaning.
47+
48+
```jsx live
49+
<Stack direction="horizontal" gap="spacious" padding="none">
50+
<Testimonial variant="default">
51+
<Testimonial.Quote>
52+
GitHub helps us ensure that we have our security controls baked into our
53+
pipelines all the way from the first line of code we&apos;re writing.
54+
</Testimonial.Quote>
55+
<Testimonial.Name position="Staff Security Engineer">
56+
David Ross
57+
</Testimonial.Name>
58+
<Testimonial.Avatar
59+
src="/brand/assets/avatar-mona.png"
60+
alt="Circular avatar from David Ross's GitHub profile"
61+
/>
62+
</Testimonial>
63+
<Testimonial variant="subtle">
64+
<Testimonial.Quote>
65+
GitHub helps us ensure that we have our security controls baked into our
66+
pipelines all the way from the first line of code we&apos;re writing.
67+
</Testimonial.Quote>
68+
<Testimonial.Name position="Staff Security Engineer">
69+
David Ross
70+
</Testimonial.Name>
71+
<Testimonial.Avatar
72+
src="/brand/assets/avatar-mona.png"
73+
alt="Circular avatar from David Ross's GitHub profile"
74+
/>
75+
</Testimonial>
76+
</Stack>
77+
```
78+
4079
### Logo
4180

4281
A logo (or similar) visual can be provided as an alternative to avatars.
@@ -129,6 +168,7 @@ Pass additional content about the testimonial provider using `position`.
129168
| `ref` | `React.RefObject` | | Forward a Ref to the underlying DOM node |
130169
| `size` | <PropTableValues addLineBreaks values={['small','large']}/> | `'small'` | Set size of quote's text |
131170
| `quoteMarkColor` | <PropTableValues addLineBreaks values={TestimonialQuoteMarkColors}/> | `'default'` | Color or gradient fill of the the quote mark. |
171+
| `variant` | <PropTableValues addLineBreaks values={TestimonialVariants}/> | `'minimal'` | Alternative visual appearance |
132172

133173
<h3 id="Testimonial-name">
134174
Testimonial.Name <Label>Required</Label>

packages/design-tokens/scripts/build-tokens.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ const darkJson = require('../src/tokens/base/colors/dark')
272272
`tokens/functional/components/river-story-scroll/colors.js`,
273273
`tokens/functional/components/pricing-options/colors.json`,
274274
`tokens/functional/components/icon/colors.json`,
275+
`tokens/functional/components/frosted-glass-vfx/colors.js`,
275276
]
276277

277278
for (const path of filesForColorModes) {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"FrostedGlassVFX": {
3+
"blurIntensity": {
4+
"low": {
5+
"value": "var(--base-size-12)"
6+
},
7+
"medium": {
8+
"value": "var(--base-size-48)"
9+
},
10+
"high": {
11+
"value": "var(--base-size-80)"
12+
}
13+
}
14+
}
15+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module.exports = {
2+
FrostedGlassVFX: {
3+
bgColor: {
4+
value: 'rgba(255, 255, 255, 0.7)',
5+
dark: 'rgba(156, 156, 156, .1)',
6+
},
7+
boxShadow: {
8+
value: '0 4px 10px 0 rgba(0, 0, 0, 0.02)',
9+
dark: '0 4px 10px 0 rgba(0, 0, 0, 0.02)',
10+
},
11+
borderMask: {
12+
value: 'linear-gradient(to bottom, #D8DEE480, #D8DEE45E 100%)',
13+
dark: 'linear-gradient(#000 0 0) exclude, linear-gradient(#000 0 0) content-box',
14+
},
15+
},
16+
}

packages/design-tokens/src/tokens/functional/components/testimonial/colors.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
{
22
"Testimonial": {
3+
"borderMask": {
4+
"default": {
5+
"value": "linear-gradient(180deg, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.3))",
6+
"dark": "linear-gradient(180deg, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.1))"
7+
},
8+
"subtle": {
9+
"value": "linear-gradient(180deg, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.3))",
10+
"dark": "linear-gradient(180deg, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.1))"
11+
}
12+
},
313
"quoteMarkColor": {
414
"default": {
515
"value": "var(--brand-color-text-default)",

packages/e2e/scripts/playwright/playwright.generate-tests.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@
9494
'components-textrevealanimation-examples--with-hero': 3000, // for the animation
9595
'components-textrevealanimation-features--animation-on-scroll': 3000, // for the animation
9696
'components-footnotes-examples--rivers-with-citations': 3000, // for the images
97+
'components-pillar-features--frosted-glass-effect': 3000, // for image to load
98+
'components-testimonial-examples--with-frosted-glass': 4000, // for animation to complete
99+
'components-testimonial-examples--with-frosted-glass-dark': 4000, // for animation to complete
97100
}
98101

99102
/**
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
.FrostedGlassVFX {
2+
position: relative;
3+
background-color: var(--brand-FrostedGlassVFX-bgColor);
4+
}
5+
6+
.FrostedGlassVFX--rounded-corners {
7+
border-radius: var(--brand-borderRadius-xlarge);
8+
}
9+
10+
.FrostedGlassVFX--intensity-low {
11+
backdrop-filter: blur(var(--brand-FrostedGlassVFX-blurIntensity-low));
12+
}
13+
14+
.FrostedGlassVFX--intensity-medium {
15+
backdrop-filter: blur(var(--brand-FrostedGlassVFX-blurIntensity-medium));
16+
}
17+
18+
.FrostedGlassVFX--intensity-high {
19+
backdrop-filter: blur(var(--brand-FrostedGlassVFX-blurIntensity-high));
20+
}
21+
22+
.FrostedGlassVFX::before {
23+
content: '';
24+
position: absolute;
25+
z-index: -1;
26+
inset: 0;
27+
padding: var(--brand-borderWidth-thin);
28+
background: var(--brand-FrostedGlassVFX-borderMask);
29+
mask: linear-gradient(var(--base-color-scale-black-0) 0 0) exclude,
30+
linear-gradient(var(--base-color-scale-black-0) 0 0) content-box;
31+
}
32+
33+
.FrostedGlassVFX--rounded-corners::before {
34+
border-radius: var(--brand-borderRadius-xlarge);
35+
}
36+
37+
.FrostedGlassVFX > * {
38+
background-color: transparent;
39+
}
40+
41+
.FrostedGlassVFX > *,
42+
.FrostedGlassVFX > *::before {
43+
border-color: transparent;
44+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
declare const styles: {
2+
readonly "FrostedGlassVFX": string;
3+
readonly "FrostedGlassVFX--rounded-corners": string;
4+
readonly "FrostedGlassVFX--intensity-low": string;
5+
readonly "FrostedGlassVFX--intensity-medium": string;
6+
readonly "FrostedGlassVFX--intensity-high": string;
7+
};
8+
export = styles;
9+
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React from 'react'
2+
import clsx from 'clsx'
3+
import type {BaseProps} from '../component-helpers'
4+
5+
/**
6+
* Design tokens
7+
*/
8+
import '@primer/brand-primitives/lib/design-tokens/css/tokens/functional/components/frosted-glass-vfx/base.css'
9+
import '@primer/brand-primitives/lib/design-tokens/css/tokens/functional/components/frosted-glass-vfx/colors-with-modes.css'
10+
11+
/** * Main Stylesheet (as a CSS Module) */
12+
import styles from './FrostedGlassVFX.module.css'
13+
14+
export const FrostedGlassVFXIntensity = ['low', 'medium', 'high'] as const
15+
export type FrostedGlassVFXIntensity = (typeof FrostedGlassVFXIntensity)[number]
16+
export const defaultFrostedGlassVFXIntensity: FrostedGlassVFXIntensity = 'medium'
17+
18+
export type FrostedGlassVFXProps = BaseProps<HTMLDivElement> &
19+
React.HTMLAttributes<HTMLDivElement> & {
20+
/**
21+
* Applies rounded corners
22+
*/
23+
hasBorderRadius?: boolean
24+
/**
25+
* Controls the blur amount
26+
*/
27+
intensity?: FrostedGlassVFXIntensity
28+
}
29+
30+
/**
31+
* FrostedGlassVFX component
32+
* {@link https://primer.style/brand/components/FrostedGlassVFX/ See usage examples}.
33+
*/
34+
export function FrostedGlassVFX({
35+
hasBorderRadius = true,
36+
children,
37+
className,
38+
intensity = defaultFrostedGlassVFXIntensity,
39+
...rest
40+
}: FrostedGlassVFXProps) {
41+
return (
42+
<div
43+
className={clsx(
44+
styles.FrostedGlassVFX,
45+
hasBorderRadius && styles['FrostedGlassVFX--rounded-corners'],
46+
styles[`FrostedGlassVFX--intensity-${intensity}`],
47+
className,
48+
)}
49+
{...rest}
50+
>
51+
{children}
52+
</div>
53+
)
54+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './FrostedGlassVFX'

packages/react/src/Pillar/Pillar.features.stories.tsx

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import React from 'react'
22
import {StoryFn, Meta} from '@storybook/react'
3+
import clsx from 'clsx'
34
import {Pillar, PillarIconColors} from '.'
4-
import {Stack, Grid} from '..'
5+
import {Stack, Grid, FrostedGlassVFX, Box, ThemeProvider, Image} from '..'
56
import {CopilotIcon, RocketIcon, GitBranchIcon} from '@primer/octicons-react'
7+
import startShape from '../fixtures/images/testimonial-bg-1.png'
8+
import endShape from '../fixtures/images/testimonial-bg-2.png'
9+
10+
import styles from './Pillar.stories.module.css'
611

712
export default {
813
title: 'Components/Pillar/features',
@@ -199,3 +204,49 @@ export const StackedWithLink: StoryFn<typeof Pillar> = () => {
199204
</Stack>
200205
)
201206
}
207+
208+
export const FrostedGlassEffect: StoryFn<typeof Pillar> = () => {
209+
return (
210+
<Stack direction={{narrow: 'vertical', regular: 'horizontal'}} gap="spacious" padding="spacious">
211+
{fixtureData.map(({heading, description, icon, iconColor}, id) => {
212+
return (
213+
<FrostedGlassVFX key={id}>
214+
<Box backgroundColor="subtle" borderRadius="xlarge" padding="normal">
215+
<Pillar key={id} style={{flex: 1}}>
216+
<Pillar.Icon icon={icon} color={iconColor} />
217+
<Pillar.Heading>{heading}</Pillar.Heading>
218+
<Pillar.Description>{description}</Pillar.Description>
219+
</Pillar>
220+
</Box>
221+
</FrostedGlassVFX>
222+
)
223+
})}
224+
</Stack>
225+
)
226+
}
227+
FrostedGlassEffect.decorators = [
228+
Story => (
229+
<ThemeProvider colorMode="light" className={styles.container}>
230+
<Box
231+
backgroundColor="subtle"
232+
paddingBlockStart={128}
233+
paddingBlockEnd={128}
234+
className={styles.innerContainer}
235+
role="region"
236+
tabIndex={0}
237+
aria-label="Scrollable content"
238+
>
239+
<Image src={startShape} alt="Starting shape" className={clsx(styles.exampleShape)} width={612} />
240+
<Image src={endShape} alt="Ending shape" className={styles.exampleShape} width={612} />
241+
<Grid>
242+
<Grid.Column>
243+
<Story />
244+
</Grid.Column>
245+
</Grid>
246+
</Box>
247+
</ThemeProvider>
248+
),
249+
]
250+
FrostedGlassEffect.parameters = {
251+
layout: 'full',
252+
}

0 commit comments

Comments
 (0)