feat: FDButton component with dark/light hover, fix yellow button on navy
This commit is contained in:
parent
80be2c4098
commit
f1462cf7c3
2
next-env.d.ts
vendored
2
next-env.d.ts
vendored
@ -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.
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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>
|
||||||
|
|
||||||
|
|||||||
@ -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' },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -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
Loading…
Reference in New Issue
Block a user