-
Notifications
You must be signed in to change notification settings - Fork 0
#1076 - Egen komponent for Card i designsystemet #54
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
base: main
Are you sure you want to change the base?
Changes from 4 commits
823dfd9
38c65fa
d045465
15816c2
930c247
eda1755
1c9ee31
e4d3917
3e85a17
539a6cf
a5a29b1
5ef59e4
00dd855
81a79eb
c6d8baf
b03d8be
bf2f81b
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,32 @@ | ||
| 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' }, | ||
| image: { control: 'text' }, | ||
| href: { control: 'text' }, | ||
| }, | ||
| }; | ||
|
|
||
| export default meta; | ||
| type Story = StoryObj; | ||
|
|
||
| export const Default: Story = { | ||
| args: { | ||
| title: 'Title', | ||
| image: 'https://picsum.photos/500/220', | ||
| }, | ||
| render: (args) => html` | ||
| <cx-card title="${args.title}" image="${args.image}" href="${args.href || ''}"> | ||
| <span slot="subtitle">14:00 - 16:00 • Oslo, Norway</span> | ||
| <span slot="other">Other content</span> | ||
| </cx-card> | ||
| `, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,201 @@ | ||
| 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 { | ||
| display: block; | ||
| } | ||
|
|
||
| .card { | ||
| position: relative; | ||
| height: 440px; | ||
| width: 100%; | ||
| max-width: 500px; | ||
|
rebalv marked this conversation as resolved.
Outdated
|
||
| border-radius: 24px; | ||
| overflow: hidden; | ||
| display: flex; | ||
| flex-direction: column; | ||
| text-decoration: none; | ||
| color: inherit; | ||
| } | ||
|
|
||
| .card.clickable { | ||
| cursor: pointer; | ||
| } | ||
|
|
||
| .card-image { | ||
| width: 100%; | ||
| height: 50%; | ||
| position: relative; | ||
| flex-shrink: 0; | ||
| } | ||
|
|
||
| .card-image img, | ||
| .card-image ::slotted(img), | ||
| .card-image ::slotted(video), | ||
| .card-image ::slotted(*) { | ||
| width: 100%; | ||
| height: 100%; | ||
| object-fit: cover; | ||
| transition: filter 0.3s ease; | ||
| } | ||
|
|
||
| /* Blue filter on img for hover */ | ||
| .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); | ||
| opacity: 0; | ||
| transition: opacity 0.3s ease; | ||
| } | ||
|
|
||
| .card-info-wrapper { | ||
| flex: 1; | ||
| padding: var(--cx-spacing-6); | ||
| color: var(--cx-color-text-primary); | ||
| background-color: var(--cx-color-background-accent-1-soft); | ||
| } | ||
|
|
||
| .card-info { | ||
| display: flex; | ||
| flex-direction: column; | ||
| height: 100%; | ||
| } | ||
|
|
||
| .card-subtitle { | ||
| display: flex; | ||
| flex-wrap: wrap; | ||
| color: var(--cx-color-text-less-important); | ||
| gap: var(--cx-spacing-2); | ||
| margin-bottom: var(--cx-spacing-4); | ||
| } | ||
|
|
||
| .card-title { | ||
| display: -webkit-box; | ||
| -webkit-box-orient: vertical; | ||
| -webkit-line-clamp: 2; | ||
| line-clamp: 2; | ||
| max-height: 65px; | ||
|
rebalv marked this conversation as resolved.
Outdated
|
||
| min-height: 65px; | ||
| overflow: hidden; | ||
| font-size: 24px; | ||
|
rebalv marked this conversation as resolved.
Outdated
|
||
| font-weight: 400; | ||
| margin-bottom: var(--cx-spacing-2); | ||
| line-height: 2rem; | ||
| align-content: center; | ||
| } | ||
|
|
||
| .card-other { | ||
| display: flex; | ||
| gap: var(--cx-spacing-2); | ||
| flex-wrap: wrap; | ||
| margin-top: auto; | ||
| } | ||
|
|
||
| /* Hover effects - only on image */ | ||
| .card:hover .card-image img, | ||
| .card:hover .card-image ::slotted(img), | ||
| .card:hover .card-image ::slotted(video), | ||
| .card:hover .card-image ::slotted(*) { | ||
| filter: grayscale(1); | ||
| } | ||
|
|
||
| .card:hover .card-image::after { | ||
| opacity: 1; | ||
| } | ||
|
|
||
| @media (max-width: 750px) { | ||
| .card { | ||
| flex-direction: column; | ||
| height: auto; | ||
| max-width: 343px; | ||
| } | ||
|
|
||
| .card-image { | ||
| width: 100%; | ||
| height: 242px; | ||
| } | ||
|
|
||
| .card-info-wrapper { | ||
| width: 100%; | ||
| padding: var(--cx-spacing-4); | ||
| } | ||
|
|
||
| .card-info { | ||
| flex-direction: column-reverse; | ||
| gap: var(--cx-spacing-2); | ||
| } | ||
|
|
||
| .card-subtitle { | ||
| font-size: 12px; | ||
|
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 }) | ||
| image = ''; | ||
|
|
||
| @property({ type: String, reflect: true }) | ||
| href = ''; | ||
|
|
||
| render() { | ||
| const cardContent = html` | ||
| <div class="card-image"> | ||
| ${this.image ? html` | ||
| <img src="${this.image}" alt="" /> | ||
| ` : html` | ||
| <slot name="image"></slot> | ||
| `} | ||
| </div> | ||
| <div class="card-info-wrapper"> | ||
| <div class="card-info"> | ||
| <div class="card-subtitle"> | ||
| <slot name="subtitle"></slot> | ||
| </div> | ||
| ${this.title ? html` | ||
| <div class="card-title">${this.title}</div> | ||
| ` : html` | ||
| <div class="card-title"> | ||
| <slot name="title"></slot> | ||
| </div> | ||
| `} | ||
| <div class="card-other"> | ||
| <slot name="other"></slot> | ||
| </div> | ||
| </div> | ||
|
rebalv marked this conversation as resolved.
|
||
| </div> | ||
| `; | ||
|
|
||
| return this.href ? html` | ||
| <a href="${this.href}" class="card clickable"> | ||
|
Collaborator
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. Vi burde egentlig unngå bake in <a tags i komponentene våre fordi mange rammeverk har sine egne Lenke komponenter for å fungere som en SPA. En vanlig <a tag vil gi en full refreshe og navigasjon på siden. Det blir også problemer med skjermleser hvis card inneholder knapper i tillegg. Så tenker at om man heller lar konsumenten av card f.eks wrappe den i en Lenke komponent selv hvis det skal brukes som lenke så vil det alltid funke :) |
||
| ${cardContent} | ||
| </a> | ||
| ` : html` | ||
| <div class="card"> | ||
| ${cardContent} | ||
| </div> | ||
| `; | ||
| } | ||
| } | ||
|
|
||
| declare global { | ||
| interface HTMLElementTagNameMap { | ||
| 'cx-card': Card; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.