Skip to content

Commit

Permalink
Add Testimonial variants and FrostedGlassVFX component (#863)
Browse files Browse the repository at this point in the history
  • Loading branch information
rezrah authored Dec 18, 2024
1 parent 8223135 commit 00ef069
Show file tree
Hide file tree
Showing 43 changed files with 983 additions and 188 deletions.
9 changes: 9 additions & 0 deletions .changeset/old-roses-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@primer/react-brand': patch
---

Added new `Testimonial` variants.

Use `variant="default"` or `variant="subtle"` for an alternative visual appearance. The current design will be referred to as `minimal` going forward.

🔗 [See the documentation for usage examples](https://primer.style/brand/components/Testimonial/react#variants)
18 changes: 18 additions & 0 deletions .changeset/smart-starfishes-lick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@primer/brand-primitives': patch
---

Added new design tokens for `Testimonial` and `FrostedGlassVFX` components

```
:root {
--brand-Testimonial-borderMask-default
--brand-Testimonial-borderMask-subtle
--brand-FrostedGlassVFX-bgColor
--brand-FrostedGlassVFX-boxShadow
--brand-FrostedGlassVFX-borderMask
--brand-FrostedGlassVFX-blurIntensity-high
--brand-FrostedGlassVFX-blurIntensity-medium
--brand-FrostedGlassVFX-blurIntensity-low
}
```
26 changes: 26 additions & 0 deletions .changeset/smooth-ways-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
'@primer/react-brand': minor
---

Added a new `FrostedGlassVFX` component for applying a frosted glass-effect texture to nested components.

⚠️ This is an experimental component, and not tested for compatibility with other Primer Brand components.

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.

Usage example:

```jsx
<FrostedGlassVFX>
<Testimonial variant="default">
<Testimonial.Quote>
GitHub helps us ensure that we have our security controls baked into our pipelines all the way from the first line
of code we&apos;re writing.
</Testimonial.Quote>
<Testimonial.Name />
<Testimonial.Avatar />
</Testimonial>
</FrostedGlassVFX>
```

🔗 (See Storybook for an example)[https://primer.style/brand/storybook/?path=/story/components-testimonial-examples--with-frosted-glass]
44 changes: 42 additions & 2 deletions apps/docs/content/components/Testimonial/react.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ title: Testimonial
figma: 'https://www.figma.com/file/BJ95AjraesmRCWsKA013GS/Primer-Brand?node-id=1852%3A27522'
source: https://github.com/primer/brand/blob/main/packages/react/src/Testimonial/Testimonial.tsx
storybook: '/brand/storybook/?path=/story/components-testimonial'
description: Use the testimonial component to display a quote from a customer or user.
description: Use testimonial to display a quote from a customer or user.
---

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

import {TestimonialQuoteMarkColors} from '@primer/react-brand'
import {
TestimonialQuoteMarkColors,
TestimonialVariants,
defaultTestimonialVariant,
} from '@primer/react-brand'
import {Box as Container, Label} from '@primer/react'
import {PropTableValues} from '../../../src/components'

Expand Down Expand Up @@ -37,6 +41,41 @@ import {Testimonial} from '@primer/react-brand'
</Testimonial>
```

### Variants

For an alternative visual appearance and layout, without altering semantic meaning.

```jsx live
<Stack direction="horizontal" gap="spacious" padding="none">
<Testimonial variant="default">
<Testimonial.Quote>
GitHub helps us ensure that we have our security controls baked into our
pipelines all the way from the first line of code we&apos;re writing.
</Testimonial.Quote>
<Testimonial.Name position="Staff Security Engineer">
David Ross
</Testimonial.Name>
<Testimonial.Avatar
src="/brand/assets/avatar-mona.png"
alt="Circular avatar from David Ross's GitHub profile"
/>
</Testimonial>
<Testimonial variant="subtle">
<Testimonial.Quote>
GitHub helps us ensure that we have our security controls baked into our
pipelines all the way from the first line of code we&apos;re writing.
</Testimonial.Quote>
<Testimonial.Name position="Staff Security Engineer">
David Ross
</Testimonial.Name>
<Testimonial.Avatar
src="/brand/assets/avatar-mona.png"
alt="Circular avatar from David Ross's GitHub profile"
/>
</Testimonial>
</Stack>
```

### Logo

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

<h3 id="Testimonial-name">
Testimonial.Name <Label>Required</Label>
Expand Down
1 change: 1 addition & 0 deletions packages/design-tokens/scripts/build-tokens.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ const darkJson = require('../src/tokens/base/colors/dark')
`tokens/functional/components/river-story-scroll/colors.js`,
`tokens/functional/components/pricing-options/colors.json`,
`tokens/functional/components/icon/colors.json`,
`tokens/functional/components/frosted-glass-vfx/colors.js`,
]

for (const path of filesForColorModes) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"FrostedGlassVFX": {
"blurIntensity": {
"low": {
"value": "var(--base-size-12)"
},
"medium": {
"value": "var(--base-size-48)"
},
"high": {
"value": "var(--base-size-80)"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module.exports = {
FrostedGlassVFX: {
bgColor: {
value: 'rgba(255, 255, 255, 0.7)',
dark: 'rgba(156, 156, 156, .1)',
},
boxShadow: {
value: '0 4px 10px 0 rgba(0, 0, 0, 0.02)',
dark: '0 4px 10px 0 rgba(0, 0, 0, 0.02)',
},
borderMask: {
value: 'linear-gradient(to bottom, #D8DEE480, #D8DEE45E 100%)',
dark: 'linear-gradient(#000 0 0) exclude, linear-gradient(#000 0 0) content-box',
},
},
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
{
"Testimonial": {
"borderMask": {
"default": {
"value": "linear-gradient(180deg, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.3))",
"dark": "linear-gradient(180deg, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.1))"
},
"subtle": {
"value": "linear-gradient(180deg, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.3))",
"dark": "linear-gradient(180deg, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.1))"
}
},
"quoteMarkColor": {
"default": {
"value": "var(--brand-color-text-default)",
Expand Down
3 changes: 3 additions & 0 deletions packages/e2e/scripts/playwright/playwright.generate-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@
'components-textrevealanimation-examples--with-hero': 3000, // for the animation
'components-textrevealanimation-features--animation-on-scroll': 3000, // for the animation
'components-footnotes-examples--rivers-with-citations': 3000, // for the images
'components-pillar-features--frosted-glass-effect': 3000, // for image to load
'components-testimonial-examples--with-frosted-glass': 4000, // for animation to complete
'components-testimonial-examples--with-frosted-glass-dark': 4000, // for animation to complete
}

/**
Expand Down
44 changes: 44 additions & 0 deletions packages/react/src/FrostedGlassVFX/FrostedGlassVFX.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
.FrostedGlassVFX {
position: relative;
background-color: var(--brand-FrostedGlassVFX-bgColor);
}

.FrostedGlassVFX--rounded-corners {
border-radius: var(--brand-borderRadius-xlarge);
}

.FrostedGlassVFX--intensity-low {
backdrop-filter: blur(var(--brand-FrostedGlassVFX-blurIntensity-low));
}

.FrostedGlassVFX--intensity-medium {
backdrop-filter: blur(var(--brand-FrostedGlassVFX-blurIntensity-medium));
}

.FrostedGlassVFX--intensity-high {
backdrop-filter: blur(var(--brand-FrostedGlassVFX-blurIntensity-high));
}

.FrostedGlassVFX::before {
content: '';
position: absolute;
z-index: -1;
inset: 0;
padding: var(--brand-borderWidth-thin);
background: var(--brand-FrostedGlassVFX-borderMask);
mask: linear-gradient(var(--base-color-scale-black-0) 0 0) exclude,
linear-gradient(var(--base-color-scale-black-0) 0 0) content-box;
}

.FrostedGlassVFX--rounded-corners::before {
border-radius: var(--brand-borderRadius-xlarge);
}

.FrostedGlassVFX > * {
background-color: transparent;
}

.FrostedGlassVFX > *,
.FrostedGlassVFX > *::before {
border-color: transparent;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
declare const styles: {
readonly "FrostedGlassVFX": string;
readonly "FrostedGlassVFX--rounded-corners": string;
readonly "FrostedGlassVFX--intensity-low": string;
readonly "FrostedGlassVFX--intensity-medium": string;
readonly "FrostedGlassVFX--intensity-high": string;
};
export = styles;

54 changes: 54 additions & 0 deletions packages/react/src/FrostedGlassVFX/FrostedGlassVFX.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react'
import clsx from 'clsx'
import type {BaseProps} from '../component-helpers'

/**
* Design tokens
*/
import '@primer/brand-primitives/lib/design-tokens/css/tokens/functional/components/frosted-glass-vfx/base.css'
import '@primer/brand-primitives/lib/design-tokens/css/tokens/functional/components/frosted-glass-vfx/colors-with-modes.css'

/** * Main Stylesheet (as a CSS Module) */
import styles from './FrostedGlassVFX.module.css'

export const FrostedGlassVFXIntensity = ['low', 'medium', 'high'] as const
export type FrostedGlassVFXIntensity = (typeof FrostedGlassVFXIntensity)[number]
export const defaultFrostedGlassVFXIntensity: FrostedGlassVFXIntensity = 'medium'

export type FrostedGlassVFXProps = BaseProps<HTMLDivElement> &
React.HTMLAttributes<HTMLDivElement> & {
/**
* Applies rounded corners
*/
hasBorderRadius?: boolean
/**
* Controls the blur amount
*/
intensity?: FrostedGlassVFXIntensity
}

/**
* FrostedGlassVFX component
* {@link https://primer.style/brand/components/FrostedGlassVFX/ See usage examples}.
*/
export function FrostedGlassVFX({
hasBorderRadius = true,
children,
className,
intensity = defaultFrostedGlassVFXIntensity,
...rest
}: FrostedGlassVFXProps) {
return (
<div
className={clsx(
styles.FrostedGlassVFX,
hasBorderRadius && styles['FrostedGlassVFX--rounded-corners'],
styles[`FrostedGlassVFX--intensity-${intensity}`],
className,
)}
{...rest}
>
{children}
</div>
)
}
1 change: 1 addition & 0 deletions packages/react/src/FrostedGlassVFX/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './FrostedGlassVFX'
53 changes: 52 additions & 1 deletion packages/react/src/Pillar/Pillar.features.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import React from 'react'
import {StoryFn, Meta} from '@storybook/react'
import clsx from 'clsx'
import {Pillar, PillarIconColors} from '.'
import {Stack, Grid} from '..'
import {Stack, Grid, FrostedGlassVFX, Box, ThemeProvider, Image} from '..'
import {CopilotIcon, RocketIcon, GitBranchIcon} from '@primer/octicons-react'
import startShape from '../fixtures/images/testimonial-bg-1.png'
import endShape from '../fixtures/images/testimonial-bg-2.png'

import styles from './Pillar.stories.module.css'

export default {
title: 'Components/Pillar/features',
Expand Down Expand Up @@ -199,3 +204,49 @@ export const StackedWithLink: StoryFn<typeof Pillar> = () => {
</Stack>
)
}

export const FrostedGlassEffect: StoryFn<typeof Pillar> = () => {
return (
<Stack direction={{narrow: 'vertical', regular: 'horizontal'}} gap="spacious" padding="spacious">
{fixtureData.map(({heading, description, icon, iconColor}, id) => {
return (
<FrostedGlassVFX key={id}>
<Box backgroundColor="subtle" borderRadius="xlarge" padding="normal">
<Pillar key={id} style={{flex: 1}}>
<Pillar.Icon icon={icon} color={iconColor} />
<Pillar.Heading>{heading}</Pillar.Heading>
<Pillar.Description>{description}</Pillar.Description>
</Pillar>
</Box>
</FrostedGlassVFX>
)
})}
</Stack>
)
}
FrostedGlassEffect.decorators = [
Story => (
<ThemeProvider colorMode="light" className={styles.container}>
<Box
backgroundColor="subtle"
paddingBlockStart={128}
paddingBlockEnd={128}
className={styles.innerContainer}
role="region"
tabIndex={0}
aria-label="Scrollable content"
>
<Image src={startShape} alt="Starting shape" className={clsx(styles.exampleShape)} width={612} />
<Image src={endShape} alt="Ending shape" className={styles.exampleShape} width={612} />
<Grid>
<Grid.Column>
<Story />
</Grid.Column>
</Grid>
</Box>
</ThemeProvider>
),
]
FrostedGlassEffect.parameters = {
layout: 'full',
}
Loading

0 comments on commit 00ef069

Please sign in to comment.