Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions packages/lib/components/card/card.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { Meta, StoryObj } from '@storybook/web-components';
import { html } from 'lit';
import './card.js';

const meta: Meta = {
title: 'Components/Card',
component: 'cx-card',
parameters: {
layout: 'centered',
},
argTypes: {
title: { control: 'text' },
subtitle1: { control: 'text' },
subtitle2: { control: 'text' },
image: { control: 'text' },
imageAlt: { control: 'text' },
},
};

export default meta;
type Story = StoryObj;

export const Default: Story = {
args: {
title: 'Title',
subtitle1: '14:00 - 16:00',
subtitle2: 'Oslo, Norway',
image: 'https://picsum.photos/500/220',
imageAlt: 'Image',
Comment thread
rebalv marked this conversation as resolved.
Outdated
},
render: (args) => html`
<cx-card
title="${args.title}"
Comment thread
rebalv marked this conversation as resolved.
Outdated
subtitle1="${args.subtitle1}"
Comment thread
rebalv marked this conversation as resolved.
Outdated
subtitle2="${args.subtitle2}"
image="${args.image}"
image-alt="${args.imageAlt}"
>
</cx-card>
`,
};
203 changes: 203 additions & 0 deletions packages/lib/components/card/card.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import { LitElement, html, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';

@customElement('cx-card')
export class Card extends LitElement {
static styles = css`
:host {
Comment thread
rebalv marked this conversation as resolved.
Outdated
display: block;
}

.card {
position: relative;
height: 440px;
width: 100%;
max-width: 500px;
Comment thread
rebalv marked this conversation as resolved.
Outdated
border-radius: 24px;
overflow: hidden;
padding: 0;
border: none;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

.card-image {
width: 100%;
height: 50%;
position: relative;
}

.card-image img,
.card-image ::slotted(img) {
Comment thread
rebalv marked this conversation as resolved.
Outdated
width: 100%;
height: 100%;
object-fit: cover;
transition: filter 0.3s ease;
}

.card-image::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
mix-blend-mode: multiply;
background: var(--cx-color-background-accent-5, #4a90e2);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}

.card-info-wrapper {
position: absolute;
Comment thread
rebalv marked this conversation as resolved.
Outdated
width: 100%;
Comment thread
rebalv marked this conversation as resolved.
Outdated
bottom: -74px;
padding: var(--cx-spacing-6, 24px);
color: var(--cx-color-text-primary, #333);
transition: bottom 0.3s ease;
background-color: var(--cx-color-background-accent-1-soft, rgba(255, 255, 255, 0.9));
Comment thread
rebalv marked this conversation as resolved.
Outdated
}

.card-info {
display: flex;
flex-direction: column;
}

.card-metadata {
display: flex;
flex-wrap: wrap;
color: var(--cx-color-text-less-important, #666);
gap: var(--cx-spacing-2, 8px);
margin-bottom: var(--cx-spacing-4, 16px);
}

.card-metadata-section {
display: inline-flex;
flex: 1 1 100%;
align-items: center;
gap: var(--cx-spacing-2, 8px);
}

.card-title {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
max-height: 65px;
Comment thread
rebalv marked this conversation as resolved.
Outdated
min-height: 65px;
overflow: hidden;
font-size: 24px;
Comment thread
rebalv marked this conversation as resolved.
Outdated
font-weight: 400;
margin: 0;
line-height: 2rem;
align-content: center;
color: var(--cx-color-text-primary, #333);
}

.card:hover .card-image img,
.card:hover .card-image ::slotted(img) {
filter: grayscale(1);
}

.card:hover .card-image::after {
opacity: 1;
}

.card:hover .card-info-wrapper {
bottom: 0;
}

@media (max-width: 750px) {
.card {
display: flex;
flex-direction: row;
justify-content: flex-end;
width: 100%;
max-width: 343px;
Comment thread
rebalv marked this conversation as resolved.
Outdated
height: auto;
min-height: unset;
max-height: unset;
}

.card-image {
flex: 0 0 35%;
height: auto;
}

.card-info-wrapper {
position: relative;
width: 65%;
bottom: unset;
align-self: flex-end;
padding: var(--cx-spacing-4, 16px);
Comment thread
rebalv marked this conversation as resolved.
Outdated
}

.card-info {
flex-direction: column-reverse;
gap: var(--cx-spacing-2, 8px);
}

.card-metadata-section {
font-size: 12px;
Comment thread
rebalv marked this conversation as resolved.
Outdated
}

.card-title {
margin-top: unset;
min-height: unset;
font-size: 18px;
}
}
`;

@property({ type: String, reflect: true })
title = '';

@property({ type: String, reflect: true })
subtitle1 = '';

@property({ type: String, reflect: true })
subtitle2 = '';

@property({ type: String, reflect: true })
image = '';

@property({ type: String, reflect: true })
imageAlt = 'Card image';

render() {
return html`
<div class="card">
<div class="card-image">
${this.image ? html`
<img src="${this.image}" alt="${this.imageAlt}" />
` : html`
<slot name="image"></slot>
`}
</div>
<div class="card-info-wrapper">
Comment thread
rebalv marked this conversation as resolved.
Outdated
<div class="card-info">
<div class="card-metadata">
${this.subtitle1 ? html`
<div class="card-metadata-section">
${this.subtitle1}
</div>
` : ''}
${this.subtitle2 ? html`
<div class="card-metadata-section">
${this.subtitle2}
</div>
` : ''}
</div>
${this.title ? html`<h3 class="card-title">${this.title}</h3>` : ''}
</div>
</div>
Comment thread
rebalv marked this conversation as resolved.
</div>
`;
}
}

declare global {
interface HTMLElementTagNameMap {
'cx-card': Card;
}
}
Loading