feat: FDButton component with dark/light hover, fix yellow button on navy

This commit is contained in:
Jeffrey 2026-02-19 20:45:11 +01:00
parent 80be2c4098
commit f1462cf7c3
6 changed files with 71 additions and 82 deletions

2
next-env.d.ts vendored
View File

@ -1,6 +1,6 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts"; import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View File

@ -453,6 +453,35 @@ html[data-theme='light'] {
box-shadow: 0 4px 12px -2px rgba(254, 204, 2, 0.25); box-shadow: 0 4px 12px -2px rgba(254, 204, 2, 0.25);
} }
/* Same as fd-btn-primary but hovers to WHITE use on navy/dark backgrounds.
Applied automatically via <FDButton variant="primary" onDark={true}> */
.fd-btn-primary-dark {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.75rem 2rem;
background-color: var(--color-fd-yellow);
color: var(--color-fd-navy);
font-family: var(--font-joey-bold);
font-size: var(--text-fd-btn);
line-height: var(--text-fd-btn--line-height);
border-radius: 9999px;
text-decoration: none;
cursor: pointer;
border: 2px solid transparent;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.fd-btn-primary-dark:hover {
background-color: #ffffff;
color: var(--color-fd-navy);
box-shadow: 0 8px 24px -4px rgba(14, 35, 56, 0.3);
transform: translateY(-2px);
}
.fd-btn-primary-dark:active {
transform: translateY(0);
box-shadow: 0 4px 12px -2px rgba(255, 255, 255, 0.15);
}
.fd-btn-secondary { .fd-btn-secondary {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@ -628,6 +657,7 @@ html[data-theme='light'] {
@media (max-width: 47.9375rem) { @media (max-width: 47.9375rem) {
/* Full-width buttons on mobile for clear tap targets */ /* Full-width buttons on mobile for clear tap targets */
.fd-btn-primary, .fd-btn-primary,
.fd-btn-primary-dark,
.fd-btn-secondary, .fd-btn-secondary,
.fd-btn-secondary-dark, .fd-btn-secondary-dark,
.fd-btn-navy, .fd-btn-navy,

View File

@ -1,5 +1,7 @@
import React from 'react' import React from 'react'
import type { Metadata } from 'next' import type { Metadata } from 'next'
import { FDButton } from '@/components/FDButton'
export const metadata: Metadata = { export const metadata: Metadata = {
title: '404 Sidan hittades inte | Fiber Direkt', title: '404 Sidan hittades inte | Fiber Direkt',
@ -41,9 +43,9 @@ export default function NotFound() {
{/* CTA */} {/* CTA */}
<div className="mt-2"> <div className="mt-2">
<a href="/" className="fd-btn-primary"> <FDButton href="/" variant="primary" onDark={false}>
Tillbaka till startsidan Tillbaka till startsidan
</a> </FDButton>
</div> </div>
</div> </div>

View File

@ -5,34 +5,30 @@ export const FDCtaSideImageBlock: Block = {
interfaceName: 'FDCtaSideImageBlock', interfaceName: 'FDCtaSideImageBlock',
labels: { labels: {
singular: 'FD CTA med bild', singular: 'FD CTA med bild',
plural: 'FD CTA med bild', plural: 'FD CTA med bilder',
}, },
fields: [ fields: [
{ {
name: 'heading', name: 'heading',
type: 'text', type: 'text',
localized: true,
required: true, required: true,
label: 'Rubrik', label: 'Rubrik',
}, },
{ {
name: 'body', name: 'body',
type: 'textarea', type: 'textarea',
localized: true,
required: true, required: true,
label: 'Brödtext', label: 'Brödtext',
}, },
{ {
name: 'ctaText', name: 'ctaText',
type: 'text', type: 'text',
localized: true,
label: 'CTA-knapp text', label: 'CTA-knapp text',
defaultValue: 'Läs mer', defaultValue: 'Läs mer',
}, },
{ {
name: 'ctaLink', name: 'ctaLink',
type: 'text', type: 'text',
localized: true,
label: 'CTA-knapp länk', label: 'CTA-knapp länk',
defaultValue: '#', defaultValue: '#',
}, },
@ -40,8 +36,7 @@ export const FDCtaSideImageBlock: Block = {
name: 'image', name: 'image',
type: 'upload', type: 'upload',
relationTo: 'media', relationTo: 'media',
required: true, label: 'Bild (valfri)',
label: 'Bild',
}, },
{ {
name: 'imagePosition', name: 'imagePosition',
@ -61,58 +56,6 @@ export const FDCtaSideImageBlock: Block = {
options: [ options: [
{ label: 'Ljust', value: 'light' }, { label: 'Ljust', value: 'light' },
{ label: 'Mörkt', value: 'dark' }, { label: 'Mörkt', value: 'dark' },
{ label: 'Anpassad färg', value: 'custom' },
],
},
{
name: 'customBackgroundColor',
type: 'text',
label: 'Anpassad bakgrundsfärg',
admin: {
condition: (_, siblingData) => siblingData?.theme === 'custom',
description: 'Valfri HEX-färg, t.ex. #1a3a5c eller #fecc02',
},
},
{
name: 'customTextLight',
type: 'checkbox',
label: 'Ljus text (för mörka bakgrunder)',
defaultValue: true,
admin: {
condition: (_, siblingData) => siblingData?.theme === 'custom',
description: 'Aktivera för vit text på mörk anpassad bakgrund',
},
},
{
name: 'imageOverlay',
type: 'select',
label: 'Bild-overlay',
defaultValue: 'none',
admin: {
description: 'Tonad overlay över bilden med varumärkesfärg',
},
options: [
{ label: 'Ingen', value: 'none' },
{ label: 'Navy', value: 'navy' },
{ label: 'Gul', value: 'yellow' },
{ label: 'Svart', value: 'black' },
],
},
{
name: 'imageOverlayOpacity',
type: 'select',
label: 'Overlay-styrka',
defaultValue: '30',
admin: {
condition: (_, siblingData) =>
Boolean(siblingData?.imageOverlay) && siblingData?.imageOverlay !== 'none',
description: 'Hur stark overlay över bilden',
},
options: [
{ label: 'Lätt (20%)', value: '20' },
{ label: 'Medium (30%)', value: '30' },
{ label: 'Stark (50%)', value: '50' },
{ label: 'Mycket stark (70%)', value: '70' },
], ],
}, },
], ],

View File

@ -1,42 +1,56 @@
import React from 'react' import React from 'react'
type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement>
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement>
type FDButtonProps = { type FDButtonProps = {
href: string
children: React.ReactNode children: React.ReactNode
variant?: 'primary' | 'outline' variant?: 'primary' | 'outline'
onDark?: boolean onDark?: boolean
className?: string className?: string
} & (
| ({ as?: 'a' } & AnchorProps)
| ({ as: 'button' } & ButtonProps)
)
const classMap = {
'primary-light': 'fd-btn-primary',
'primary-dark': 'fd-btn-primary-dark',
'outline-light': 'fd-btn-secondary',
'outline-dark': 'fd-btn-secondary-dark',
} }
/** /**
* FDButton shared button component for all FD blocks. * FDButton single button component for all FD blocks.
* *
* variant="primary" onDark={true} yellow bg, hover white (use on navy backgrounds) * Renders as <a> by default, or <button> when as="button" (e.g. forms, newsletter).
* variant="primary" onDark={false} yellow bg, hover yellow/80 (use on light backgrounds) *
* variant="outline" onDark={true} white border + text, hover white/10 * variant="primary" onDark={false} fd-btn-primary yellow navy hover (light backgrounds)
* variant="outline" onDark={false} navy border + text, hover navy/5 * variant="primary" onDark={true} fd-btn-primary-dark yellow white hover (navy backgrounds)
* variant="outline" onDark={false} fd-btn-secondary outline on light
* variant="outline" onDark={true} fd-btn-secondary-dark outline on dark
*/ */
export const FDButton: React.FC<FDButtonProps> = ({ export const FDButton = ({
href,
children, children,
variant = 'primary', variant = 'primary',
onDark = false, onDark = false,
className = '', className = '',
}) => { as: Tag = 'a',
const base = ...rest
'inline-flex items-center justify-center px-8 py-2.5 rounded-full font-joey-bold text-lg md:text-2xl leading-[38px] transition-colors' }: FDButtonProps) => {
const key = `${variant}-${onDark ? 'dark' : 'light'}` as keyof typeof classMap
const cls = `${classMap[key]} ${className}`.trim()
const styles = { if (Tag === 'button') {
'primary-dark': 'bg-fd-yellow text-fd-navy hover:bg-white hover:text-fd-navy', return (
'primary-light': 'bg-fd-yellow text-fd-navy hover:bg-fd-yellow/80', <button className={cls} {...(rest as ButtonProps)}>
'outline-dark': 'border-2 border-white text-white hover:bg-white/10', {children}
'outline-light': 'border-2 border-fd-navy text-fd-navy hover:bg-fd-navy/5', </button>
)
} }
const key = `${variant}-${onDark ? 'dark' : 'light'}` as keyof typeof styles
return ( return (
<a href={href} className={`${base} ${styles[key]} ${className}`}> <a className={cls} {...(rest as AnchorProps)}>
{children} {children}
</a> </a>
) )

File diff suppressed because one or more lines are too long