diff --git a/packages/react/src/components/CourceCard/CourceCard.module.css b/packages/react/src/components/CourceCard/CourceCard.module.css new file mode 100644 index 0000000..92df313 --- /dev/null +++ b/packages/react/src/components/CourceCard/CourceCard.module.css @@ -0,0 +1,158 @@ +.courceCardWrapper { + display: flex; + flex-direction: row; + border-radius: 8px; + border: 1px solid var(--fds-semantic-border-neutral-subtle); + text-decoration: none; + color: var(--fds-semantic-text-neutral-default); + overflow: hidden; + width: 100%; + max-width: 1000px; + position: relative; + box-sizing: border-box; +} + +.courceCardWrapper:hover { + border: 1px solid var(--fds-semantic-border-neutral-strong); +} + +.courceCardWrapper:active { + border: 2px solid var(--fds-semantic-border-neutral-strong); + outline: 2px solid var(--fds-semantic-surface-secondary-secondary-active); +} + +.dateBox { + min-width: 150px; + height: 150px; + margin: auto; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.mobileDateBox { + width: auto; + height: auto; + margin: 0; + padding: 24px; + flex-direction: row; + justify-content: left; + align-items: normal; + font: var(--fds-typography-body-medium); +} + +.mobile span { + justify-content: left; +} + +.mobileInformationBox span { + color: var(--colors-grey-700); + word-break: break-word; +} + +.mobileDateBox span:first-child::after { + content: '.'; +} + +.mobileDateBox span:nth-child(2) { + margin-right: 8px; +} + +.dateDay { + font: var(--fds-typography-heading-large); + margin: 5px 0 0; +} + +.dateMonth { + margin: 5px 0; +} + +.dateYear { + margin: 5px 0 0; +} + +.icon { + margin-right: 8px; + margin-top: 1px; +} + +.informationBox { + display: flex; + flex-direction: column; + justify-content: center; + padding: 38px; + background-color: var(--fds-semantic-background-default); +} + +.mobileInformationBox { + padding: 32px; +} + +.informationBox h2 { + text-decoration: underline; + text-underline-offset: 4px; + margin-top: 0; + font-size: 18px; + line-height: 2rem; +} + +.courceLabels { + display: flex; + gap: 18px; +} + +.courceLabels div { + display: flex; +} + +.mobile { + width: 100%; + max-width: 406px; + flex-direction: column; +} + +.mobileDate { + justify-content: center; + font-size: 18px; + font-weight: 500; + display: flex; + align-items: center; + margin: 0; +} + +.primary { + background-color: var(--fds-semantic-surface-primary-primary); +} + +.primary:hover { + background-color: var(--fds-semantic-surface-primary-primary-hover); +} + +.primary:active { + background-color: var(--fds-semantic-surface-primary-primary-active); +} + +.secondary { + background-color: var(--fds-semantic-surface-secondary-secondary); +} + +.secondary:hover { + background-color: var(--fds-semantic-surface-secondary-secondary-hover); +} + +.secondary:active { + background-color: var(--fds-semantic-surface-secondary-secondary-active); +} + +.tertiary { + background-color: var(--fds-semantic-surface-tertiary-tertiary); +} + +.tertiary:hover { + background-color: var(--fds-semantic-surface-tertiary-tertiary-hover); +} + +.tertiary:active { + background-color: var(--fds-semantic-surface-tertiary-tertiary-active); +} diff --git a/packages/react/src/components/CourceCard/CourceCard.stories.tsx b/packages/react/src/components/CourceCard/CourceCard.stories.tsx new file mode 100644 index 0000000..dc39adf --- /dev/null +++ b/packages/react/src/components/CourceCard/CourceCard.stories.tsx @@ -0,0 +1,57 @@ +import React from 'react'; + +import { CourceCard } from './CourceCard'; + +export default { + title: 'Components/CourceCard', + component: CourceCard, + tags: ['autodocs'], + + parameters: { + layout: 'centered', + backgrounds: { + default: 'Light', + values: [ + { + name: 'Dark', + value: '#1E2B3C', + }, + { + name: 'Light', + value: '#00000', + }, + ], + }, + }, +}; + +export const Primary = { + args: { + date: new Date(2018, 11, 24, 10, 3), + brand: 'primary', + title: 'Unik sommerjobb ', + location: 'Leikanger', + tag: 'Digitalization', + }, +}; + +export const Secondary = { + args: { + date: new Date(2018, 11, 24, 10, 3), + brand: 'secondary', + title: 'U', + location: 'L', + tag: 'D', + }, +}; + +export const Tertiary = { + args: { + date: new Date(2018, 11, 24, 10, 3), + brand: 'tertiary', + breakpoint: 10000, + title: 'Unik sommerjobb for studenter innen digitalisering', + location: 'Leikanger', + tag: 'Digitalization', + }, +}; diff --git a/packages/react/src/components/CourceCard/CourceCard.test.tsx b/packages/react/src/components/CourceCard/CourceCard.test.tsx new file mode 100644 index 0000000..2b40784 --- /dev/null +++ b/packages/react/src/components/CourceCard/CourceCard.test.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { render as renderRtl, screen } from '@testing-library/react'; + +import type { CourceCardProps } from './CourceCard'; +import { CourceCard } from './CourceCard'; + +describe('CourceCard', () => { + it('renders a h2', () => { + render({ + date: new Date(2018, 11, 24, 10, 3), + brand: 'primary', + title: 'Unik sommerjobb ', + location: 'Leikanger', + tag: 'Digitalization', + }); + expect(screen.getByRole('heading', { level: 2 })).toBeInTheDocument; + }); + + it('has the primary class', () => { + render({ + date: new Date(2018, 11, 24, 10, 3), + brand: 'primary', + title: 'Unik sommerjobb ', + location: 'Leikanger', + tag: 'Digitalization', + href: 'https://www.test.com/', + }); + + const cardElement = screen.getByRole('link'); + expect(cardElement).toHaveClass('primary'); + }); + + it('has the secondary class', () => { + render({ + date: new Date(2018, 11, 24, 10, 3), + brand: 'secondary', + title: 'Unik sommerjobb ', + location: 'Leikanger', + tag: 'Digitalization', + href: 'https://www.test.com/', + }); + + const cardElement = screen.getByRole('link'); + expect(cardElement).toHaveClass('secondary'); + }); + + it('can change element', () => { + render({ + date: new Date(2018, 11, 24, 10, 3), + brand: 'primary', + title: 'Unik sommerjobb ', + location: 'Leikanger', + tag: 'Digitalization', + as: 'div', + }); + + const cardElement = screen.getByTestId('course-card'); + expect(cardElement.tagName.toLowerCase()).toBe('div'); + expect(cardElement.tagName.toLowerCase()).not.toBe('a'); + }); + + it('displays the right date', () => { + render({ + date: new Date(2018, 11, 24, 10, 3), + brand: 'primary', + title: 'Unik sommerjobb ', + location: 'Leikanger', + tag: 'Digitalization', + }); + + const correctDate = screen.getByText('2018'); + + expect(correctDate).toBeInTheDocument(); + }); +}); + +const render = (props: CourceCardProps) => + renderRtl( + , + ); diff --git a/packages/react/src/components/CourceCard/CourceCard.tsx b/packages/react/src/components/CourceCard/CourceCard.tsx new file mode 100644 index 0000000..3ff2b32 --- /dev/null +++ b/packages/react/src/components/CourceCard/CourceCard.tsx @@ -0,0 +1,130 @@ +import React, { useEffect, useState } from 'react'; +import cn from 'classnames'; +import { ClockIcon, PinIcon, BookmarkIcon } from '@navikt/aksel-icons'; + +import classes from './CourceCard.module.css'; + +type CourceCardProps = { + date: Date; + brand?: 'primary' | 'secondary' | 'tertiary'; + breakpoint?: number; + title: string; + location: string; + tag: string; + as?: keyof JSX.IntrinsicElements | React.ComponentType; + href?: string; +}; + +const CourceCard = ({ + date = new Date(), + brand = 'primary', + breakpoint = 768, + title, + location, + tag, + as, + href, + ...rest +}: CourceCardProps) => { + const [isMobile, setIsMobile] = useState(window.innerWidth < breakpoint); + + useEffect(() => { + const handleResize = () => { + setIsMobile(window.innerWidth < breakpoint); + }; + + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + }; + }, [breakpoint]); + + const day = date.getDate(); + const month = date.toLocaleString('default', { + month: isMobile ? 'long' : 'short', + }); + const year = date.getFullYear(); + + const formattedMinutes = + date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes(); + const formattedHours = + date.getHours() < 10 ? '0' + date.getHours() : date.getHours(); + + const Component = as || 'a'; + + return ( + +
+ + {day} + + + {month} + + + {year} + +
+
+

{title}

+
+
+
+ + + + + {formattedHours}:{formattedMinutes} + +
+
+ + + + {location} +
+
+ + + + {tag} +
+
+
+
+
+ ); +}; + +export { CourceCard }; +export type { CourceCardProps }; diff --git a/packages/react/src/components/CourceCard/index.ts b/packages/react/src/components/CourceCard/index.ts new file mode 100644 index 0000000..e7f0ec6 --- /dev/null +++ b/packages/react/src/components/CourceCard/index.ts @@ -0,0 +1,2 @@ +export { CourceCard } from './CourceCard'; +export type { CourceCardProps } from './CourceCard';