-
Notifications
You must be signed in to change notification settings - Fork 3
[Groundwork] [OSS-Components] Create an OSS::FeatureCard component & OSS::FeatureCardsContainer
#662
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Groundwork] [OSS-Components] Create an OSS::FeatureCard component & OSS::FeatureCardsContainer
#662
Changes from all commits
82d473b
5b993fc
1b35098
e5b7f8d
ece5c07
08f87b9
a2ae70f
135d2a2
f2a6131
e17b8e9
550b254
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| <div class={{this.computedClasses}} ...attributes> | ||
| <div class="oss-feature-card__content"> | ||
| <span class="font-size-md font-weight-semibold">{{@title}}</span> | ||
| <p class="oss-feature-card__description text-ellipsis">{{@description}}</p> | ||
| </div> | ||
|
|
||
| <img class="oss-feature-card__illustration" src={{@image.src}} alt={{this.imageAlt}} /> | ||
| </div> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| import { hbs } from 'ember-cli-htmlbars'; | ||
|
|
||
| const ColorVariants = ['blue', 'violet', 'yellow']; | ||
| const ShadowVariants = ['sm', 'lg']; | ||
|
|
||
| export default { | ||
| title: 'Components/OSS::FeatureCard', | ||
| component: 'feature-card', | ||
| argTypes: { | ||
| title: { | ||
| description: 'Card title', | ||
| table: { | ||
| type: { summary: 'string' }, | ||
| defaultValue: { summary: 'undefined' } | ||
| }, | ||
| control: { type: 'text' }, | ||
| type: { required: true } | ||
| }, | ||
| description: { | ||
| description: 'Card description', | ||
| table: { | ||
| type: { summary: 'string' }, | ||
| defaultValue: { summary: 'undefined' } | ||
| }, | ||
| control: { type: 'text' }, | ||
| type: { required: true } | ||
| }, | ||
| image: { | ||
| description: 'Image object including src and optional alt', | ||
| table: { | ||
| type: { summary: '{ src: string; alt?: string }' }, | ||
| defaultValue: { summary: 'undefined' } | ||
| }, | ||
| control: { type: 'object' }, | ||
| type: { required: true } | ||
| }, | ||
| colorVariant: { | ||
| description: 'Card background color variant', | ||
| table: { | ||
| type: { summary: ColorVariants.join('|') }, | ||
| defaultValue: { summary: 'violet' } | ||
| }, | ||
| options: ColorVariants, | ||
| control: { type: 'select' } | ||
| }, | ||
| shadowVariant: { | ||
| description: 'Card shadow variant', | ||
| table: { | ||
| type: { summary: ShadowVariants.join('|') }, | ||
| defaultValue: { summary: 'sm' } | ||
| }, | ||
| options: ShadowVariants, | ||
| control: { type: 'select' } | ||
| } | ||
| }, | ||
| parameters: { | ||
| docs: { | ||
| description: { | ||
| component: 'Displays a card with title, description and illustration. It includes color and shadow variants.' | ||
| } | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| const defaultArgs = { | ||
| title: 'Audience & content insights', | ||
| description: 'Pull demographics and media performance into your BI to target smarter and report faster.', | ||
| image: { | ||
| src: '/@upfluence/oss-components/assets/images/no-image.svg', | ||
| alt: 'No image illustration' | ||
| }, | ||
| colorVariant: 'violet', | ||
| shadowVariant: 'lg' | ||
| }; | ||
|
|
||
| const Template = (args) => ({ | ||
| template: hbs` | ||
| <OSS::FeatureCard | ||
| @title={{this.title}} | ||
| @description={{this.description}} | ||
| @image={{this.image}} | ||
| @colorVariant={{this.colorVariant}} | ||
| @shadowVariant={{this.shadowVariant}} | ||
| /> | ||
| `, | ||
| context: args | ||
| }); | ||
|
|
||
| export const Default = Template.bind({}); | ||
| Default.args = defaultArgs; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| import Component from '@glimmer/component'; | ||
| import { assert } from '@ember/debug'; | ||
| import { isSafeString } from '@upfluence/oss-components/utils'; | ||
| import type { IntlService } from 'ember-intl'; | ||
|
|
||
| export const COLOR_VARIANTS = ['blue', 'violet', 'yellow'] as const; | ||
| export type OSSFeatureCardColorVariant = (typeof COLOR_VARIANTS)[number]; | ||
| const DEFAULT_COLOR_VARIANT = 'violet' as const satisfies OSSFeatureCardColorVariant; | ||
|
|
||
| export const SHADOW_VARIANTS = ['sm', 'lg'] as const; | ||
| export type OSSFeatureCardShadowVariant = (typeof SHADOW_VARIANTS)[number]; | ||
| const DEFAULT_SHADOW_VARIANT = 'sm' as const satisfies OSSFeatureCardShadowVariant; | ||
|
|
||
| export type OSSFeatureCardImage = { | ||
| src: string; | ||
| alt?: string; | ||
| }; | ||
|
|
||
| export type OSSFeatureCardArgs = { | ||
| title: string | ReturnType<IntlService['t']>; | ||
| description: string | ReturnType<IntlService['t']>; | ||
| image: OSSFeatureCardImage; | ||
| colorVariant?: OSSFeatureCardColorVariant; | ||
| shadowVariant?: OSSFeatureCardShadowVariant; | ||
| }; | ||
|
|
||
| export default class OSSFeatureCard extends Component<OSSFeatureCardArgs> { | ||
| constructor(owner: unknown, args: OSSFeatureCardArgs) { | ||
| super(owner, args); | ||
|
|
||
| assert( | ||
| '[OSS::FeatureCard] The @title parameter is mandatory', | ||
| (typeof args.title === 'string' || isSafeString(args.title)) && args.title.toString().trim().length > 0 | ||
| ); | ||
| assert( | ||
| '[OSS::FeatureCard] The @description parameter is mandatory', | ||
| (typeof args.description === 'string' || isSafeString(args.description)) && | ||
| args.description.toString().trim().length > 0 | ||
| ); | ||
| assert( | ||
| '[OSS::FeatureCard] The @image parameter is mandatory and must contain a src key', | ||
| typeof args?.image?.src === 'string' && args.image.src.trim().length > 0 | ||
| ); | ||
| if (args.colorVariant) { | ||
| assert( | ||
| `[OSS::FeatureCard] @colorVariant must be one of: ${COLOR_VARIANTS.join(', ')}`, | ||
| COLOR_VARIANTS.includes(args.colorVariant) | ||
| ); | ||
| } | ||
| if (args.shadowVariant) { | ||
| assert( | ||
| `[OSS::FeatureCard] @shadowVariant must be one of: ${SHADOW_VARIANTS.join(', ')}`, | ||
| SHADOW_VARIANTS.includes(args.shadowVariant) | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| get colorVariant(): OSSFeatureCardColorVariant { | ||
| return this.args.colorVariant ?? DEFAULT_COLOR_VARIANT; | ||
| } | ||
|
|
||
| get shadowVariant(): OSSFeatureCardShadowVariant { | ||
| return this.args.shadowVariant ?? DEFAULT_SHADOW_VARIANT; | ||
| } | ||
|
Comment on lines
+58
to
+64
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happened if the set a wrong color or shadow value?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From my understanding, if that happens we fail one of the |
||
|
|
||
| get computedClasses(): string { | ||
| return [ | ||
| 'oss-feature-card', | ||
| `oss-feature-card--color-${this.colorVariant}`, | ||
| `oss-feature-card--shadow-${this.shadowVariant}` | ||
| ].join(' '); | ||
| } | ||
|
|
||
| get imageAlt(): string { | ||
| return this.args.image.alt ?? ''; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| <div class="oss-feature-cards-container" ...attributes> | ||
| {{#each this.cardsWithComputedVariants as |card|}} | ||
| <OSS::FeatureCard | ||
| @title={{card.title}} | ||
| @description={{card.description}} | ||
| @image={{card.image}} | ||
| @colorVariant={{card.colorVariant}} | ||
| @shadowVariant={{card.shadowVariant}} | ||
| class="oss-feature-cards-container__item" | ||
| /> | ||
| {{/each}} | ||
| </div> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| import { hbs } from 'ember-cli-htmlbars'; | ||
|
|
||
| export default { | ||
| title: 'Components/OSS::FeatureCardsContainer', | ||
| component: 'feature-cards-container', | ||
| argTypes: { | ||
| cards: { | ||
| description: | ||
| '1 to 3 feature cards. Uses the same argument shape as OSS::FeatureCard; overlap and rotation are handled by the container. If colorVariant or shadowVariant are omitted, container defaults are applied.', | ||
| table: { | ||
| type: { | ||
| summary: | ||
| 'Array<{ title: string; description: string; image: { src: string; alt?: string }; colorVariant?: "blue"|"violet"|"yellow"; shadowVariant?: "sm"|"lg" }>' | ||
| }, | ||
| defaultValue: { summary: 'undefined' } | ||
| }, | ||
| control: { type: 'object' }, | ||
| type: { required: true } | ||
| } | ||
| }, | ||
| parameters: { | ||
| docs: { | ||
| description: { | ||
| component: | ||
| 'Wrapper that lays out 1 to 3 OSS::FeatureCard components with angles and overlap. Card color/shadow values are passed through from each card object, with defaults when variants are omitted. See [OSS::FeatureCard](?path=/story/components-oss-featurecard--default) for individual card details.' | ||
| } | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| const defaultImage = { | ||
| src: '/@upfluence/oss-components/assets/images/no-image.svg', | ||
| alt: 'No image illustration' | ||
| }; | ||
|
|
||
| const threeCardsArgs = { | ||
| cards: [ | ||
| { | ||
| title: 'Creator discovery at scale', | ||
| description: | ||
| 'Discover and enrich creators via API using platform, region, and key attributes to power precise scouting.', | ||
| image: defaultImage, | ||
| colorVariant: 'blue', | ||
| shadowVariant: 'sm' | ||
| }, | ||
| { | ||
| title: 'Audience & content insights', | ||
| description: 'Pull demographics and media performance into your BI to target smarter and report faster.', | ||
| image: defaultImage, | ||
| colorVariant: 'violet', | ||
| shadowVariant: 'lg' | ||
| }, | ||
| { | ||
| title: 'Campaign performance tracking', | ||
| description: 'Track contribution stages, orders and ROI, then sync results to your CRM.', | ||
| image: defaultImage, | ||
| colorVariant: 'yellow', | ||
| shadowVariant: 'sm' | ||
| } | ||
| ] | ||
| }; | ||
|
|
||
| const twoCardsArgs = { | ||
| cards: threeCardsArgs.cards.slice(0, 2) | ||
| }; | ||
|
|
||
| const threeCardsWithDefaultsArgs = { | ||
| cards: threeCardsArgs.cards.map(({ title, description, image }) => ({ | ||
| title, | ||
| description, | ||
| image | ||
| })) | ||
| }; | ||
|
|
||
| const oneCardArgs = { | ||
| cards: threeCardsArgs.cards.slice(0, 1) | ||
| }; | ||
|
|
||
| const Template = (args) => ({ | ||
| template: hbs` | ||
| <div style="display: flex; justify-content: center; overflow-x: auto; min-inline-size: 950px;"> | ||
| <OSS::FeatureCardsContainer @cards={{this.cards}} /> | ||
| </div> | ||
| `, | ||
| context: args | ||
| }); | ||
|
|
||
| export const ThreeCards = Template.bind({}); | ||
| ThreeCards.args = threeCardsArgs; | ||
|
|
||
| export const ThreeCardsWithContainerDefaults = Template.bind({}); | ||
| ThreeCardsWithContainerDefaults.args = threeCardsWithDefaultsArgs; | ||
|
|
||
| export const TwoCards = Template.bind({}); | ||
| TwoCards.args = twoCardsArgs; | ||
|
|
||
| export const OneCard = Template.bind({}); | ||
| OneCard.args = oneCardArgs; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import { assert } from '@ember/debug'; | ||
| import Component from '@glimmer/component'; | ||
|
|
||
| import type { OSSFeatureCardArgs, OSSFeatureCardColorVariant, OSSFeatureCardShadowVariant } from './feature-card'; | ||
|
|
||
| type OSSFeatureCardsContainerArgs = { | ||
| cards: OSSFeatureCardArgs[]; | ||
| }; | ||
|
|
||
| function isCenterCard(cardsCount: number, index: number): boolean { | ||
| return cardsCount === 1 || (cardsCount === 3 && index === 1); | ||
| } | ||
|
|
||
| function getDefaultCardColorVariant(cardsCount: number, index: number): OSSFeatureCardColorVariant { | ||
| if (isCenterCard(cardsCount, index)) return 'violet'; | ||
|
|
||
| if (cardsCount === 2 || cardsCount === 3) return index === 0 ? 'blue' : 'yellow'; | ||
|
|
||
| assert('[OSS::FeatureCardsContainer] Internal layout configuration mismatch', false); | ||
| } | ||
|
|
||
| function getDefaultCardShadowVariant(cardsCount: number, index: number): OSSFeatureCardShadowVariant { | ||
| if (isCenterCard(cardsCount, index)) return 'lg'; | ||
|
|
||
| if (cardsCount === 2 || cardsCount === 3) return 'sm'; | ||
|
|
||
| assert('[OSS::FeatureCardsContainer] Internal layout configuration mismatch', false); | ||
| } | ||
|
|
||
| export default class OSSFeatureCardsContainer extends Component<OSSFeatureCardsContainerArgs> { | ||
| constructor(owner: unknown, args: OSSFeatureCardsContainerArgs) { | ||
| super(owner, args); | ||
|
|
||
| assert('[OSS::FeatureCardsContainer] The @cards parameter is mandatory', Array.isArray(args.cards)); | ||
| assert( | ||
| '[OSS::FeatureCardsContainer] @cards must contain between 1 and 3 cards', | ||
| args.cards.length >= 1 && args.cards.length <= 3 | ||
| ); | ||
|
Miexil marked this conversation as resolved.
|
||
| } | ||
|
|
||
| get cardsWithComputedVariants(): OSSFeatureCardArgs[] { | ||
| const cards = this.args.cards ?? []; | ||
|
|
||
| return cards.map((card, index) => { | ||
| const cardCount = cards.length; | ||
| const colorVariant = card.colorVariant ?? getDefaultCardColorVariant(cardCount, index); | ||
| const shadowVariant = card.shadowVariant ?? getDefaultCardShadowVariant(cardCount, index); | ||
|
|
||
| return { | ||
| ...card, | ||
| colorVariant, | ||
| shadowVariant | ||
| }; | ||
| }); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { default } from '@upfluence/oss-components/components/o-s-s/feature-card'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { default } from '@upfluence/oss-components/components/o-s-s/feature-cards-container'; |
Uh oh!
There was an error while loading. Please reload this page.