{contrast}
diff --git a/apps/theme/components/ColorContrasts/ColorContrasts.module.css b/apps/theme/components/ColorContrasts/ColorContrasts.module.css
new file mode 100644
index 0000000000..f7a87a1b7f
--- /dev/null
+++ b/apps/theme/components/ColorContrasts/ColorContrasts.module.css
@@ -0,0 +1,147 @@
+.color {
+ height: 36px;
+ display: block;
+ flex: 1 1;
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
+ border-radius: 2px;
+}
+
+.colors {
+ display: flex;
+ gap: 12px;
+}
+
+.table {
+ background-color: #e2e2e2;
+ margin-bottom: 16px;
+ margin-top: 12px;
+}
+
+.cell {
+ padding: 14px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ height: 100%;
+}
+
+.tagGroup {
+ display: inline-flex;
+ flex-direction: column;
+ gap: 9px;
+ align-items: flex-start;
+ border-top: 1px solid var(--ds-color-neutral-border-subtle);
+ margin-left: -28px;
+ width: calc(100% + 58px);
+ padding: 22px 28px;
+}
+
+.tagGroups {
+ margin-top: 12px;
+}
+
+.tagGroup:last-child {
+ border-bottom: 1px solid var(--ds-color-neutral-border-subtle);
+}
+
+.tag {
+ background-color: #d23e3e;
+ color: white;
+ border-radius: 400px;
+ font-size: 13px;
+ font-weight: 500;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 3px 9px;
+}
+
+.AA {
+ background-color: green;
+}
+
+.AAA {
+ background-color: #407be9;
+}
+
+.AA18 {
+ background-color: #fbb025;
+ color: black;
+}
+
+.meta {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.tagContainer {
+ flex: 1 1;
+ align-items: center;
+ justify-content: center;
+ display: flex;
+}
+
+.contrast {
+ font-size: 16px;
+ font-weight: 500;
+ text-align: center;
+ flex: 1 1;
+}
+
+.table,
+.td,
+.th {
+ border: 1px solid var(--ds-color-neutral-border-subtle);
+ border-collapse: collapse;
+ background-color: var(--ds-color-neutral-background-subtle);
+}
+
+.th {
+ height: 90px;
+ width: 140px;
+ padding: 16px;
+}
+
+.td {
+ height: 90px;
+ width: 140px;
+ vertical-align: top;
+}
+
+.header {
+ font-size: 16px;
+ width: 30px;
+ text-align: left;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ font-weight: 500;
+}
+
+.headerHex {
+ color: var(--ds-color-neutral-text-subtle);
+ font-weight: 400;
+}
+
+.contrastChart {
+ background-color: transparent;
+}
+
+.subHeading {
+ margin-top: 8px;
+ margin-bottom: -4px;
+}
+
+.fieldGroup {
+ margin-top: 4px;
+ margin-bottom: 4px;
+}
+
+.select {
+ width: 25%;
+}
+
+.desc {
+ width: 90%;
+}
diff --git a/apps/theme/components/ColorContrasts/ColorContrasts.tsx b/apps/theme/components/ColorContrasts/ColorContrasts.tsx
new file mode 100644
index 0000000000..0175fe23c9
--- /dev/null
+++ b/apps/theme/components/ColorContrasts/ColorContrasts.tsx
@@ -0,0 +1,271 @@
+import {
+ type ColorInfo,
+ generateThemeForColor,
+ getColorNameFromNumber,
+ getContrastFromHex,
+} from '@/packages/cli/dist/src/colors';
+import {
+ Field,
+ Heading,
+ Paragraph,
+ Select,
+} from '@digdir/designsystemet-react';
+import cl from 'clsx/lite';
+import { useEffect, useState } from 'react';
+import { useThemeStore } from '../../store';
+import classes from './ColorContrasts.module.css';
+
+export const ColorContrasts = () => {
+ const theme = generateThemeForColor('#0062BA');
+ const indexOne = [1, 2, 3, 4, 5];
+ const indexTwo = [6, 7, 8, 12, 13];
+ const [reducedLight, setReducedLight] = useState({
+ themeRange1: theme.light.filter((color) => indexOne.includes(color.number)),
+ themeRange2: theme.light.filter((color) => indexTwo.includes(color.number)),
+ });
+ const colors = useThemeStore((state) => state.colors);
+ const [selectedColor, setSelectedColor] = useState('');
+ const [selectedBaseColor, setSelectedBaseColor] = useState('');
+
+ const indexBaseOne = [0, 1, 2, 3, 14, 15];
+ const indexBaseTwo = [9, 10, 11];
+ const [reducedBaseLight, setReducedBaseLight] = useState({
+ themeRange1: theme.light.filter((color) =>
+ indexBaseOne.includes(color.number),
+ ),
+ themeRange2: theme.light.filter((color) =>
+ indexBaseTwo.includes(color.number),
+ ),
+ });
+
+ useEffect(() => {
+ const newTheme =
+ colors.main.find((color) => color.name === selectedColor)?.colors ||
+ colors.neutral.find((color) => color.name === selectedColor)?.colors ||
+ colors.support.find((color) => color.name === selectedColor)?.colors ||
+ theme;
+
+ setReducedLight({
+ themeRange1: newTheme.light.filter((color) =>
+ indexOne.includes(color.number),
+ ),
+ themeRange2: newTheme.light.filter((color) =>
+ indexTwo.includes(color.number),
+ ),
+ });
+ }, [selectedColor, colors.main]);
+
+ useEffect(() => {
+ const newTheme =
+ colors.main.find((color) => color.name === selectedBaseColor)?.colors ||
+ colors.neutral.find((color) => color.name === selectedBaseColor)
+ ?.colors ||
+ colors.support.find((color) => color.name === selectedBaseColor)
+ ?.colors ||
+ theme;
+
+ setReducedBaseLight({
+ themeRange1: newTheme.light.filter((color) =>
+ indexBaseOne.includes(color.number),
+ ),
+ themeRange2: newTheme.light.filter((color) =>
+ indexBaseTwo.includes(color.number),
+ ),
+ });
+ }, [selectedBaseColor, colors.main]);
+
+ const ThCell = ({ color }: { color: ColorInfo }) => {
+ return (
+
+
+ {getColorNameFromNumber(color.number)}
+
{color.hex}
+
+
+ );
+ };
+
+ const Tag = ({
+ color1,
+ color2,
+ }: { color1: ColorInfo; color2: ColorInfo }) => {
+ const contrast = getContrastFromHex(color1.hex, color2.hex);
+ let type = 'AAA';
+
+ if (contrast < 3) {
+ type = 'FAIL';
+ } else if (contrast < 4.5) {
+ type = 'AA18';
+ } else if (contrast < 7) {
+ type = 'AA';
+ }
+
+ return
{type}
;
+ };
+
+ const TdCell = ({
+ color1,
+ color2,
+ }: { color1: ColorInfo; color2: ColorInfo }) => {
+ return (
+
+
+
+
+
+
+
+ {Math.floor(getContrastFromHex(color1.hex, color2.hex) * 10) / 10}
+ :1
+
+
+
+ );
+ };
+
+ return (
+
+
+
Kontraster mellom farger
+
+ Her vises kontrastene mellom de ulike trinnene i fargeskalaene, samt
+ om fargene oppfyller WCAG-kravene.
+
+
+
+
+
AAA
+
+ Tekst og bakgrunn må ha en kontrast på minst 7:1 for å oppfylle
+ WCAG AAA-kravet.
+
+
+
+
AA
+
+ Tekst og bakgrunn må ha en kontrast på minst 4.5:1 for å oppfylle
+ WCAG AA-kravet.
+
+
+
+
AA18
+
+ Tekst og bakgrunn må ha en kontrast på minst 3:1 og en
+ skriftstørrelse på 18 px eller større for å oppfylle WCAG
+ AA-kravet.
+
+
+
+
FAIL
+
+ Oppfyller ingen kontrastkrav i WCAG og bør kun brukes til
+ dekorative formål.
+
+
+
+
+
+
+ Text og Border mot Background og Surface
+
+
+ Når du bytter mellom fargeskalaene, vil du se at kontrastene mellom
+ fargene i seksjonen nedenfor er nesten identiske. Dette gjør at du kun
+ trenger å vurdere kontrastene for én fargeskala for å forstå hvordan
+ alle fungerer. Siden kontrastene er konsistente, kan du også kombinere
+ ulike farger på tvers av skalaene.
+
+
+ {
+ setSelectedColor(e.target.value);
+ }}
+ >
+ {(['main', 'neutral', 'support'] as Array).map(
+ (group) =>
+ colors[group].map((color, index) => (
+
+ {color.name}
+
+ )),
+ )}
+
+
+
+
+
+ {reducedLight.themeRange1.map((color, index) => (
+
+ ))}
+
+ {reducedLight.themeRange2.map((color2, index) => (
+
+
+ {reducedLight.themeRange1.map((color1, index) => (
+
+
+
+ ))}
+
+ ))}
+
+
Base fargene
+
+ Fargene som blir valgt i verktøyet får tokenet Base Default i hver
+ fargeskala. Dette betyr at det er viktig å velge en farge som har over
+ 3:1 kontrast mot overflatefarger om den skal brukes som en viktig,
+ meningsbærende farge. Verktøyet lager også to kontrastfarger som trygt
+ kan brukes oppå base fargene. Disse kontrastfargene blir enten lyse
+ eller mørke avhengig av base fargen.
+
+
+ {
+ setSelectedBaseColor(e.target.value);
+ }}
+ >
+ {(['main', 'neutral', 'support'] as Array).map(
+ (group) =>
+ colors[group].map((color, index) => (
+
+ {color.name}
+
+ )),
+ )}
+
+
+
+
+
+ {reducedBaseLight.themeRange1.map((color, index) => (
+
+ ))}
+
+ {reducedBaseLight.themeRange2.map((color2, index) => (
+
+
+ {reducedBaseLight.themeRange1.map((color1, index) => (
+
+
+
+ ))}
+
+ ))}
+
+
+
+ );
+};
diff --git a/apps/theme/components/ColorInput/ColorInput.module.css b/apps/theme/components/ColorInput/ColorInput.module.css
new file mode 100644
index 0000000000..fa396cbc49
--- /dev/null
+++ b/apps/theme/components/ColorInput/ColorInput.module.css
@@ -0,0 +1,47 @@
+.component {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ flex: 0 calc(50% - 8px);
+}
+
+.box {
+ border: 1px solid var(--ds-color-neutral-border-subtle);
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 9px;
+ background-color: transparent;
+ cursor: pointer;
+ transition: 0.1s all;
+}
+
+.box:hover {
+ background-color: var(--ds-color-neutral-background-subtle);
+}
+
+.leftContent {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.color {
+ height: 22px;
+ width: 22px;
+ background-color: red;
+ border-radius: 4px;
+}
+
+.name {
+ font-size: 15px;
+}
+
+.hex {
+ font-size: 15px;
+}
+
+.icon {
+ color: var(--ds-color-neutral-text-subtle);
+}
diff --git a/apps/theme/components/ColorInput/ColorInput.tsx b/apps/theme/components/ColorInput/ColorInput.tsx
new file mode 100644
index 0000000000..2ac3ba4a40
--- /dev/null
+++ b/apps/theme/components/ColorInput/ColorInput.tsx
@@ -0,0 +1,31 @@
+import { PencilIcon } from '@navikt/aksel-icons';
+
+import classes from './ColorInput.module.css';
+
+type ColorInputProps = {
+ color: string;
+ name: string;
+ onClick: (e: React.MouseEvent
) => void;
+};
+
+export const ColorInput = ({ name, color, onClick }: ColorInputProps) => {
+ return (
+
+
{name}
+
onClick(e)}>
+
+
+
+
+ );
+};
diff --git a/apps/theme/components/Scales/Scales.module.css b/apps/theme/components/ColorPreview/Card2.module.css
similarity index 50%
rename from apps/theme/components/Scales/Scales.module.css
rename to apps/theme/components/ColorPreview/Card2.module.css
index dd29bb8fb9..7b37c2c660 100644
--- a/apps/theme/components/Scales/Scales.module.css
+++ b/apps/theme/components/ColorPreview/Card2.module.css
@@ -1,21 +1,22 @@
-.rows {
+.card {
+ flex-direction: row !important;
+ gap: 24px;
+ justify-content: space-between;
+}
+
+.text {
display: flex;
- flex-direction: column;
align-items: center;
gap: 8px;
+ justify-content: center;
}
-.row {
- display: flex;
- align-items: end;
- justify-content: space-between;
- gap: 24px;
+.title {
+ width: 140px;
}
-.scaleLabel {
- height: 54px;
+.checkGroup {
display: flex;
align-items: center;
- font-size: 16px;
- color: #505050;
+ gap: 24px;
}
diff --git a/apps/theme/components/ColorPreview/ColorPreview.module.css b/apps/theme/components/ColorPreview/ColorPreview.module.css
new file mode 100644
index 0000000000..1f7671673a
--- /dev/null
+++ b/apps/theme/components/ColorPreview/ColorPreview.module.css
@@ -0,0 +1,43 @@
+.grid {
+ grid-template-columns: repeat(3, 1fr) !important;
+}
+
+.card {
+ background-color: var(--ds-color-neutral-background-default);
+ display: flex;
+ flex-direction: column;
+ border-radius: 8px;
+ height: auto;
+ padding: 18px;
+ border: 1px solid var(--ds-color-accent-border-subtle);
+ background-color: var(--ds-color-accent-surface-default);
+}
+
+.title {
+ margin-bottom: 8px;
+ color: var(--ds-color-accent-text-default);
+ width: 200px;
+}
+
+.desc {
+ color: var(--ds-color-accent-text-subtle);
+}
+
+.checkGroup {
+ margin-top: 16px;
+ margin-bottom: 24px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.btnGroup {
+ display: flex;
+ gap: 16px;
+}
+
+.label {
+ font-weight: 500;
+ margin-bottom: 8px;
+ font-size: 16px;
+}
diff --git a/apps/theme/components/ColorPreview/ColorPreview.tsx b/apps/theme/components/ColorPreview/ColorPreview.tsx
new file mode 100644
index 0000000000..fb3634e190
--- /dev/null
+++ b/apps/theme/components/ColorPreview/ColorPreview.tsx
@@ -0,0 +1,179 @@
+import {
+ type ColorInfo,
+ type ColorNumber,
+ getColorNameFromNumber,
+} from '@/packages/cli/dist/src/colors';
+import {
+ Button,
+ Checkbox,
+ Heading,
+ Paragraph,
+ Switch,
+ ToggleGroup,
+} from '@digdir/designsystemet-react';
+import cl from 'clsx/lite';
+import { useState } from 'react';
+import { useThemeStore } from '../../store';
+import listClasses from './Card2.module.css';
+import classes from './ColorPreview.module.css';
+
+type ViewType = 'list' | 'grid';
+
+export const ColorPreview = () => {
+ const colors = useThemeStore((state) => state.colors);
+ const [view, setView] = useState('grid');
+ const appearance = useThemeStore((state) => state.appearance);
+
+ type CardProps = {
+ color: {
+ name: string;
+ colors: {
+ light: ColorInfo[];
+ dark: ColorInfo[];
+ };
+ };
+ };
+
+ const setStyle = (colors: {
+ light: ColorInfo[];
+ dark: ColorInfo[];
+ }) => {
+ const style = {} as Record;
+
+ let lightColors = colors.light;
+
+ if (appearance === 'dark') {
+ lightColors = colors.dark;
+ }
+
+ for (let i = 0; i < lightColors.length; i++) {
+ const number = (i + 1) as ColorNumber;
+ style[
+ `--ds-color-accent-${getColorNameFromNumber(number)
+ .replace(/\s+/g, '-')
+ .toLowerCase()}`
+ ] = lightColors[i].hex;
+ }
+ return style;
+ };
+
+ const CardWrapper = ({ color }: CardProps) => {
+ if (view === 'list') {
+ return ;
+ }
+ return ;
+ };
+
+ const HorizontalCard = ({ color }: CardProps) => {
+ const [valueOne, setValueOne] = useState(true);
+ return (
+
+
+ {color.name}
+
+
+ Livet er for kort til å være grått. Fyll deg selv og dine dager med
+ farger.
+
+
+ setValueOne(!valueOne)}
+ checked={valueOne}
+ />
+
+
+
+ Primær
+
+ Sekundær
+
+
+
+ );
+ };
+ const VerticalCard = ({ color }: CardProps) => {
+ const [isChecked, setIsChecked] = useState(true);
+ const [isSwitch, setIsSwitch] = useState(true);
+ return (
+
+
+
+ {color.name}
+
+
+ Farger gjør livet mer fargerikt
+
+
+
+ setIsChecked(!isChecked)}
+ />
+ setIsSwitch(!isSwitch)}
+ >
+ Switch
+
+
+
+ Primær
+
+ Sekundær
+
+
+
+ );
+ };
+ return (
+
+
+
Se fargene dine i bruk
+
+ Hver farge som blir valgt med verktøyet får sitt eget kort i seksjonen
+ til høyre slik at du kan se hvordan fargene harmonerer sammen.
+
+
+ Merk at kontrastfargen inne i knappen endrer seg fra hvit til svart,
+ avhengig av om den valgte fargen er lys eller mørk for å oppnå best
+ mulig kontrast.
+
+
+
Visning:
+
setView(value as ViewType)}
+ >
+ Grid
+ Liste
+
+
+
+
+ {colors.main.map((color, index) => (
+
+ ))}
+ {colors.neutral.map((color, index) => (
+
+ ))}
+ {colors.support.map((color, index) => (
+
+ ))}
+
+
+ );
+};
diff --git a/apps/theme/components/ColorTokens/ColorTokens.module.css b/apps/theme/components/ColorTokens/ColorTokens.module.css
new file mode 100644
index 0000000000..53aefd43a1
--- /dev/null
+++ b/apps/theme/components/ColorTokens/ColorTokens.module.css
@@ -0,0 +1,10 @@
+.img {
+ width: 80%;
+ height: auto;
+}
+
+.right {
+ display: flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+}
diff --git a/apps/theme/components/ColorTokens/ColorTokens.tsx b/apps/theme/components/ColorTokens/ColorTokens.tsx
new file mode 100644
index 0000000000..0fdd45a1d0
--- /dev/null
+++ b/apps/theme/components/ColorTokens/ColorTokens.tsx
@@ -0,0 +1,19 @@
+import { Heading, Paragraph } from '@digdir/designsystemet-react';
+import cl from 'clsx/lite';
+import classes from './ColorTokens.module.css';
+export const ColorTokens = () => {
+ return (
+
+
+
Se fargetokens
+
+ Her ser du hvilke tokens som er brukt for å lage kortene i seksjonen
+ over.
+
+
+
+
+
+
+ );
+};
diff --git a/apps/theme/components/Colors/Colors.module.css b/apps/theme/components/Colors/Colors.module.css
new file mode 100644
index 0000000000..a6fb0332c2
--- /dev/null
+++ b/apps/theme/components/Colors/Colors.module.css
@@ -0,0 +1,32 @@
+.rows {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 16px;
+ margin: 32px 0;
+ width: 1380px;
+}
+
+.row {
+ display: flex;
+ align-items: end;
+ justify-content: space-between;
+ gap: 24px;
+ width: 100%;
+}
+
+.scaleLabel {
+ height: 48px;
+ display: flex;
+ align-items: center;
+ font-size: 16px;
+ color: var(--ds-color-neutral-text-default);
+}
+
+.separator {
+ margin-top: 6px;
+ margin-bottom: 7px;
+ height: 1px;
+ width: 100%;
+ background-color: var(--ds-color-neutral-border-subtle);
+}
diff --git a/apps/theme/components/Colors/Colors.tsx b/apps/theme/components/Colors/Colors.tsx
new file mode 100644
index 0000000000..7ec179a55a
--- /dev/null
+++ b/apps/theme/components/Colors/Colors.tsx
@@ -0,0 +1,50 @@
+import type { ColorMode } from '@digdir/designsystemet/color';
+
+import { useThemeStore } from '../../store';
+import { Scale } from '../Scale/Scale';
+import classes from './Colors.module.css';
+
+type ScalesProps = {
+ themeMode: ColorMode;
+};
+
+export const Colors = ({ themeMode }: ScalesProps) => {
+ const colors = useThemeStore((state) => state.colors);
+ return (
+
+ {colors.main.map((color, index) => (
+
+ ))}
+
+ {colors.neutral.map((color, index) => (
+
+ ))}
+
+ {colors.support.map((color, index) => (
+
+ ))}
+
+ );
+};
diff --git a/apps/theme/components/Group/Group.module.css b/apps/theme/components/Group/Group.module.css
index 3487a9a76c..0db991e88a 100644
--- a/apps/theme/components/Group/Group.module.css
+++ b/apps/theme/components/Group/Group.module.css
@@ -1,6 +1,6 @@
.colors {
display: flex;
- gap: 8px;
+ gap: 4px;
border-radius: 0;
}
@@ -11,6 +11,7 @@
text-align: center;
width: 100%;
position: relative;
+ font-size: 16px;
}
.featured {
@@ -29,9 +30,13 @@
}
.names div {
- font-size: 16px;
+ font-size: 14px;
width: 100%;
text-align: center;
+ align-items: center;
+ justify-content: center;
+ display: flex;
+ height: 45px;
}
@media (max-width: 1632px) {
diff --git a/apps/theme/components/Group/Group.tsx b/apps/theme/components/Group/Group.tsx
index f5bda2cb63..d9d9f2b308 100644
--- a/apps/theme/components/Group/Group.tsx
+++ b/apps/theme/components/Group/Group.tsx
@@ -1,19 +1,19 @@
import { RovingFocusItem } from '@digdir/designsystemet-react';
-import type { ColorInfo } from '@digdir/designsystemet/color';
+import type { ThemeInfo } from '@digdir/designsystemet/color';
import cl from 'clsx/lite';
-import type { ThemeColors } from '../../types';
import { Color } from '../Color/Color';
+import { useThemeStore } from '../../store';
import classes from './Group.module.css';
type GroupProps = {
header: string;
- colors: ColorInfo[];
+ colors: number[];
+ colorScale: ThemeInfo;
showColorMeta?: boolean;
names?: string[];
featured?: boolean;
- type: ThemeColors;
};
export const Group = ({
@@ -21,16 +21,13 @@ export const Group = ({
colors,
showColorMeta,
names,
- type,
+ colorScale,
featured = false,
}: GroupProps) => {
+ const appearance = useThemeStore((state) => state.appearance);
return (
- {header && (
-
- {header}
-
- )}
+ {header &&
{header}
}
{header && names && (
{names.map((name, index) => (
@@ -39,17 +36,20 @@ export const Group = ({
)}
-
+
{colors.map((item, index) => (
-
+
))}
diff --git a/apps/theme/components/Previews/Components/Components.module.css b/apps/theme/components/Previews/Components/Components.module.css
index 98dbbfaf1d..b0a8a896fc 100644
--- a/apps/theme/components/Previews/Components/Components.module.css
+++ b/apps/theme/components/Previews/Components/Components.module.css
@@ -1,18 +1,17 @@
.components {
- background-color: var(--background);
- border-radius: 12px;
width: 100%;
- padding: 24px;
display: grid;
grid-template-columns: repeat(12, 1fr);
column-gap: 24px;
row-gap: 24px;
+ margin-bottom: 88px;
}
.card {
background-color: var(--foreground);
- border-radius: min(1rem, var(--ds-border-radius-md));
+ border-radius: 16px;
padding: 24px;
+ background-color: var(--ds-color-neutral-background-default);
}
.cardTitle {
@@ -109,10 +108,6 @@
display: block;
}
-.userTitle {
- text-align: center;
-}
-
.userField {
margin-top: 24px;
}
@@ -158,6 +153,7 @@
.tableAction {
display: flex;
align-items: center;
+ gap: 12px;
}
.tableSelect {
@@ -169,68 +165,5 @@
align-items: center;
justify-content: space-between;
margin-top: 16px;
-}
-
-.tableSearch {
- width: 260px;
-}
-
-.tableBtn {
- margin-left: 20px;
-}
-
-.helpCards {
- grid-column: 1 / 6;
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 20px;
-}
-
-.helpHeading {
- margin-bottom: 20px;
-}
-
-.tagList {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- gap: 16px;
- margin-top: 16px;
-}
-
-.switchGroup {
- display: flex;
- gap: 26px;
- flex-direction: column;
-}
-
-.toggleCombo {
- display: flex;
- flex-direction: column;
- gap: var(--ds-spacing-2);
- margin-top: 12px;
-}
-
-.chips {
- display: flex;
- align-items: center;
- gap: 16px;
-}
-
-.chipsHeading {
- margin-top: 24px;
- margin-bottom: 16px;
-}
-
-.textarea {
- margin-top: 8px;
-}
-
-.textareaLabel {
- margin-top: 24px;
- font-size: 16px;
-}
-
-.comboHeading {
- margin-bottom: 16px;
+ gap: 100px;
}
diff --git a/apps/theme/components/Previews/Previews.module.css b/apps/theme/components/Previews/Previews.module.css
index 2a38f0d755..4cc1ff2d68 100644
--- a/apps/theme/components/Previews/Previews.module.css
+++ b/apps/theme/components/Previews/Previews.module.css
@@ -2,7 +2,7 @@
display: flex;
align-items: center;
justify-content: space-between;
- margin: 48px 0 24px;
+ margin: 32px 0 24px;
}
.menu {
@@ -77,7 +77,6 @@
border-radius: 12px;
display: flex;
gap: 32px;
- box-shadow: var(--ds-shadow-md);
}
.preview[data-ds-color-mode='dark'],
diff --git a/apps/theme/components/Previews/Previews.tsx b/apps/theme/components/Previews/Previews.tsx
index 27e9f354a5..18b8c25fea 100644
--- a/apps/theme/components/Previews/Previews.tsx
+++ b/apps/theme/components/Previews/Previews.tsx
@@ -1,26 +1,22 @@
+'use client';
+
import type { ColorMode } from '@digdir/designsystemet/color';
import cl from 'clsx/lite';
-import { useState } from 'react';
+import { useThemeStore } from '../../store';
import { Components } from './Components/Components';
-import { Dashboard } from './Dashboard/Dashboard';
-import { Landing } from './Landing/Landing';
import classes from './Previews.module.css';
-type previewModeType =
- | 'dashboard'
- | 'landing'
- | 'forms'
- | 'auth'
- | 'components';
-
type PreviewsProps = {
themeMode: ColorMode;
onThemeModeChange: (themeMode: ColorMode) => void;
};
-export const Previews = ({ themeMode, onThemeModeChange }: PreviewsProps) => {
- const [previewMode, setPreviewMode] = useState('components');
+export const Previews = () => {
+ const theme = useThemeStore((state) => state.themePreview);
+ const appearance = useThemeStore((state) => state.appearance);
+ const setTheme = useThemeStore((state) => state.setThemePreview);
+ const setAppearance = useThemeStore((state) => state.setAppearance);
return (
<>
@@ -30,104 +26,67 @@ export const Previews = ({ themeMode, onThemeModeChange }: PreviewsProps) => {
className={cl(
classes.menuItem,
'ds-focus',
- previewMode === 'components' && classes.menuItemActive,
- )}
- onClick={() => setPreviewMode('components')}
- >
- Komponenter
-
- setPreviewMode('dashboard')}
+ onClick={() => {
+ setTheme('one');
+ setAppearance('light');
+ }}
>
- Dashboard
+ Tema 1 lys
{
+ setTheme('one');
+ setAppearance('dark');
+ }}
>
- Landingsside
+ Tema 1 mørk
{
+ setTheme('two');
+ setAppearance('light');
+ }}
>
- Skjemaer
+ Tema 2 mørk
- Autentisering
-
-
-
-
onThemeModeChange('light')}
- >
-
- Lys
-
-
onThemeModeChange('dark')}
- >
-
- Mørk
-
-
onThemeModeChange('contrast')}
+ onClick={() => {
+ setTheme('two');
+ setAppearance('dark');
+ }}
>
-
- Kontrast
+ Tema 2 mørk
-
- {previewMode === 'components' &&
}
- {previewMode === 'dashboard' &&
}
- {previewMode === 'landing' &&
}
+
+
>
);
diff --git a/apps/theme/components/Scale/Scale.module.css b/apps/theme/components/Scale/Scale.module.css
index 2043b0a77b..59f8d2dec6 100644
--- a/apps/theme/components/Scale/Scale.module.css
+++ b/apps/theme/components/Scale/Scale.module.css
@@ -1,7 +1,6 @@
.test {
display: flex;
- gap: 24px;
- flex-wrap: wrap;
+ gap: 16px;
justify-content: center;
}
diff --git a/apps/theme/components/Scale/Scale.tsx b/apps/theme/components/Scale/Scale.tsx
index d12bbddf4e..39ed1ca347 100644
--- a/apps/theme/components/Scale/Scale.tsx
+++ b/apps/theme/components/Scale/Scale.tsx
@@ -1,44 +1,15 @@
import { RovingFocusRoot } from '@digdir/designsystemet-react';
-import type { ColorInfo } from '@digdir/designsystemet/color';
-import { useEffect, useState } from 'react';
-
-import type { ThemeColors, modeType } from '../../types';
+import type { ThemeInfo } from '@digdir/designsystemet/color';
+import type { modeType } from '../../types';
import { Group } from '../Group/Group';
import classes from './Scale.module.css';
type ScaleProps = {
- colorScale: ColorInfo[];
+ colorScale: ThemeInfo;
showHeader?: boolean;
showColorMeta?: boolean;
themeMode: modeType;
- type: ThemeColors;
-};
-
-const setTokens = (lightColors: ColorInfo[], type: string) => {
- const previewElement = document.getElementById('preview');
- if (previewElement) {
- for (let i = 0; i < lightColors.length; i++) {
- previewElement.style.setProperty(
- '--' + type + '-' + (i + 1),
- lightColors[i].hex,
- );
- }
- }
-};
-
-const generateDefaultColors = () => {
- const arr: ColorInfo[] = [];
-
- for (let i = 0; i < 14; i++) {
- arr.push({
- hex: '#ffffff',
- number: 1,
- name: 'Default',
- });
- }
-
- return arr;
};
export const Scale = ({
@@ -46,55 +17,52 @@ export const Scale = ({
showHeader,
showColorMeta,
themeMode,
- type,
}: ScaleProps) => {
- const [colors, setColors] = useState
(generateDefaultColors());
-
- useEffect(() => {
- if (colorScale.length > 0) {
- setColors(colorScale);
- }
- setTokens(colorScale, type);
- }, [colorScale, themeMode, type]);
return (
diff --git a/apps/theme/components/Scales/Scales.tsx b/apps/theme/components/Scales/Scales.tsx
deleted file mode 100644
index 147e12a937..0000000000
--- a/apps/theme/components/Scales/Scales.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import type { ColorMode } from '@digdir/designsystemet/color';
-import cl from 'clsx/lite';
-
-import { useThemeStore } from '../../store';
-import { Scale } from '../Scale/Scale';
-
-import classes from './Scales.module.css';
-
-type ScalesProps = {
- themeMode: ColorMode;
-};
-
-export const Scales = ({ themeMode }: ScalesProps) => {
- const accentTheme = useThemeStore((state) => state.accentTheme);
- const neutralTheme = useThemeStore((state) => state.neutralTheme);
- const brandOneTheme = useThemeStore((state) => state.brandOneTheme);
- const brandTwoTheme = useThemeStore((state) => state.brandTwoTheme);
- const brandThreeTheme = useThemeStore((state) => state.brandThreeTheme);
- return (
-
- );
-};
diff --git a/apps/theme/components/Sidebar/ColorPane/ColorPane.module.css b/apps/theme/components/Sidebar/ColorPane/ColorPane.module.css
new file mode 100644
index 0000000000..0aad8f7515
--- /dev/null
+++ b/apps/theme/components/Sidebar/ColorPane/ColorPane.module.css
@@ -0,0 +1,74 @@
+.colorPage {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ background-color: var(--ds-color-neutral-background-default);
+ padding: 24px;
+ border-radius: 8px;
+ display: none;
+ z-index: 5;
+}
+
+.title {
+ margin-top: 12px;
+ margin-bottom: 16px;
+}
+
+.name {
+ margin-bottom: 24px;
+}
+
+.label {
+ font-size: 16px;
+ font-weight: 500;
+ margin-bottom: 12px;
+}
+
+.topBtnGroup {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.btnGroup {
+ display: flex;
+ margin-top: 24px;
+ gap: 16px;
+}
+
+.removeBtn {
+ margin-right: -8px;
+ margin-top: -4px;
+}
+
+.colorPreviewContainer {
+ border: 1px solid var(--ds-color-neutral-border-subtle);
+ margin-bottom: 16px;
+ border-radius: 8px;
+}
+
+.colorPreview {
+ height: 120px;
+ width: 100%;
+ border-radius: 8px;
+}
+
+.back {
+ margin-left: -16px;
+ margin-top: -4px;
+}
+
+.show {
+ display: block;
+}
+
+.hide {
+ display: none;
+}
+
+.desc {
+ color: var(--ds-color-neutral-text-subtle);
+ margin-bottom: 16px;
+}
diff --git a/apps/theme/components/Sidebar/ColorPane/ColorPane.tsx b/apps/theme/components/Sidebar/ColorPane/ColorPane.tsx
new file mode 100644
index 0000000000..36b80448e3
--- /dev/null
+++ b/apps/theme/components/Sidebar/ColorPane/ColorPane.tsx
@@ -0,0 +1,127 @@
+import {
+ Button,
+ Heading,
+ Paragraph,
+ Textfield,
+} from '@digdir/designsystemet-react';
+import { ChevronLeftIcon, TrashIcon } from '@navikt/aksel-icons';
+import { ColorPicker, type IColor } from 'react-color-palette';
+
+import cl from 'clsx/lite';
+import classes from './ColorPane.module.css';
+
+type ColorPaneProps = {
+ onClose: () => void;
+ onPrimaryClicked: (color: string, name: string) => void;
+ show?: boolean;
+ type: 'addColor' | 'editColor' | 'none';
+ color: IColor;
+ setColor: (color: IColor) => void;
+ name: string;
+ setName: (name: string) => void;
+ onRemove: () => void;
+ colorType: 'main' | 'neutral' | 'support';
+};
+
+export const ColorPane = ({
+ onClose,
+ onPrimaryClicked,
+ show = false,
+ type,
+ color,
+ setColor,
+ name,
+ setName,
+ onRemove,
+ colorType,
+}: ColorPaneProps) => {
+ const getHeading = () => {
+ const t = colorType === 'main' ? 'hovedfarge' : 'støttefarge';
+ return type === 'addColor' ? 'Legg til ' + t : 'Rediger farge';
+ };
+
+ return (
+
+
+ onClose()}
+ className={classes.back}
+ >
+ Gå tilbake
+
+ onRemove()}
+ className={cl(
+ classes.removeBtn,
+ (type !== 'editColor' || colorType === 'neutral') && classes.hide,
+ )}
+ >
+ Fjern farge
+
+
+
+
+ {getHeading()}
+
+ {colorType === 'neutral' && (
+
+ Neutral fargen kan ikke fjernes eller endres navn på.
+
+ )}
+ {colorType !== 'neutral' && (
+
{
+ setName(e.target.value);
+ }}
+ />
+ )}
+ Farge
+
+
+
+ {
+ onPrimaryClicked(color.hex, name);
+ }}
+ >
+ {type === 'addColor' ? 'Legg til' : 'Lagre'}
+
+
+ {
+ onClose();
+ }}
+ >
+ Avbryt
+
+
+
+ );
+};
diff --git a/apps/theme/components/Sidebar/Sidebar.module.css b/apps/theme/components/Sidebar/Sidebar.module.css
new file mode 100644
index 0000000000..cd7efc00b2
--- /dev/null
+++ b/apps/theme/components/Sidebar/Sidebar.module.css
@@ -0,0 +1,101 @@
+.sidebar {
+ position: relative;
+ z-index: 100;
+ right: 20px;
+ top: 20px;
+ height: calc(100vh - 40px);
+ background-color: var(--ds-color-neutral-background-default);
+ border: 1px solid var(--ds-color-neutral-border-subtle);
+ box-shadow: 0px 0px 60px 10px rgba(0, 0, 0, 0.1);
+ width: 410px;
+ flex-basis: 410px;
+ flex-grow: 1;
+ border-radius: 8px;
+ min-height: calc(100vh - 40px);
+ margin-top: -170px;
+}
+
+.scrollContainer {
+ overflow: auto;
+ height: calc(100% - 90px);
+ padding: 24px;
+}
+
+.sticky {
+ position: fixed;
+ margin-top: 0;
+}
+
+.title {
+ margin-bottom: 20px;
+}
+
+.colors {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px;
+}
+
+.separator {
+ margin-bottom: 20px;
+ height: 1px;
+ width: 100%;
+ background-color: var(--ds-color-neutral-border-subtle);
+}
+
+.group {
+ margin-bottom: 24px;
+ margin-top: -8px;
+}
+
+.group:last-child {
+ margin-bottom: 0;
+}
+
+.groupHeader {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 8px;
+ height: 42px;
+}
+
+.AddBtn {
+ margin-right: -8px;
+}
+
+.error {
+ font-weight: 400;
+ font-size: 16px;
+ color: var(--ds-color-neutral-text-subtle);
+}
+
+.bottom {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ left: 0;
+ padding: 24px;
+ border-top: 1px solid var(--ds-color-neutral-border-subtle);
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ background-color: var(--ds-color-info-background-default);
+ border-radius: 0 0 8px 8px;
+}
+
+.label {
+ font-size: 16px;
+ margin-bottom: 8px;
+}
+
+.themeMode {
+ margin-bottom: 24px;
+ margin-top: 4px;
+}
+
+.sizes {
+ display: flex;
+ gap: 8px;
+ margin-top: -4px;
+}
diff --git a/apps/theme/components/Sidebar/Sidebar.tsx b/apps/theme/components/Sidebar/Sidebar.tsx
new file mode 100644
index 0000000000..d89fc3633f
--- /dev/null
+++ b/apps/theme/components/Sidebar/Sidebar.tsx
@@ -0,0 +1,268 @@
+import {
+ type ColorMode,
+ generateThemeForColor,
+} from '@/packages/cli/dist/src/colors';
+import type { CssColor } from '@adobe/leonardo-contrast-colors';
+import { Button, Heading } from '@digdir/designsystemet-react';
+import { PlusIcon, StarIcon } from '@navikt/aksel-icons';
+import cl from 'clsx/lite';
+import { useEffect, useState } from 'react';
+import { ColorService, useColor } from 'react-color-palette';
+import { type ColorTheme, useThemeStore } from '../../store';
+import { ColorInput } from '../ColorInput/ColorInput';
+import { Toggle } from '../Toggle/Toggle';
+import { ColorPane } from './ColorPane/ColorPane';
+import classes from './Sidebar.module.css';
+
+export const Sidebar = () => {
+ type Pages = 'addColor' | 'editColor' | 'none';
+ type ColorType = 'main' | 'neutral' | 'support';
+
+ const colors = useThemeStore((state) => state.colors);
+ const [activePanel, setActivePanel] = useState
('none');
+ const [color, setColor] = useColor('#0062ba');
+ const [name, setName] = useState('');
+ const [index, setIndex] = useState(0);
+ const [colorType, setColorType] = useState('main');
+ const removeColor = useThemeStore((state) => state.removeColor);
+ const addColor = useThemeStore((state) => state.addColor);
+ const updateColor = useThemeStore((state) => state.updateColor);
+ const appearance = useThemeStore((state) => state.appearance);
+ const setAppearance = useThemeStore((state) => state.setAppearance);
+ const [isSticky, setSticky] = useState(false);
+ const [size, setSize] = useState('sm');
+
+ const addNewColor = (color: string, name: string) => {
+ const theme = generateThemeForColor(color as CssColor, 'aa');
+ addColor({ name: name, colors: theme }, colorType);
+ };
+
+ const updateExistingColor = (color: string, name: string) => {
+ const theme = generateThemeForColor(color as CssColor, 'aa');
+ updateColor({ name: name, colors: theme }, index, colorType);
+ };
+
+ const setupEditState = (
+ color: ColorTheme,
+ index: number,
+ type: ColorType,
+ ) => {
+ setActivePanel('editColor');
+ setColor(ColorService.convert('hex', color.colors.light[8].hex));
+ setName(color.name);
+ setIndex(index);
+ setColorType(type);
+ };
+
+ useEffect(() => {
+ const handleScroll = () => {
+ setSticky(window.scrollY > 135);
+ };
+
+ window.addEventListener('scroll', handleScroll);
+ return () => {
+ window.removeEventListener('scroll', handleScroll);
+ };
+ }, []);
+
+ return (
+
+
+
+ Konfigurer tema
+
+
+ {/* APPEARANCE */}
+
+
+
Visning
+
{
+ const val = value;
+ setAppearance(val as ColorMode);
+ }}
+ />
+
+
+
+ {/* MAIN COLORS */}
+
+
+
Hovedfarger
+ {colors.main.length < 4 && (
+
{
+ setActivePanel('addColor');
+ setColorType('main');
+ }}
+ >
+ Legg til
+
+
+ )}
+ {colors.main.length >= 4 && (
+
Maks 4 hovedfarger
+ )}
+
+
+ {colors.main.map((color, index) => (
+ setupEditState(color, index, 'main')}
+ />
+ ))}
+
+
+
+
+
+ {colors.neutral.map((color, index) => (
+ setupEditState(color, index, 'neutral')}
+ />
+ ))}
+
+
+
+ {/* SUPPORT COLORS */}
+
+
+
Støttefarger
+ {colors.support.length < 4 && (
+
{
+ setActivePanel('addColor');
+ setColorType('support');
+ }}
+ >
+ Legg til
+
+
+ )}
+ {colors.support.length >= 4 && (
+
Maks 4 støttefarger
+ )}
+
+
+ {colors.support.map((color, index) => (
+ setupEditState(color, index, 'support')}
+ />
+ ))}
+
+
+
+ {/* BORDER RADIUS */}
+
+
+ Border radius
+
+
+
+
+
+
+ {/* SIZES */}
+ {/*
+
+ Komponentstørrelser
+
+
+ setSize(size)}
+ name='xSmall'
+ size='14px'
+ />
+ setSize(size)}
+ name='Small'
+ size='16px'
+ />
+ setSize(size)}
+ name='Medium'
+ size='18px'
+ />
+ setSize(size)}
+ name='Large'
+ size='21px'
+ />
+
+
*/}
+
+
+
+
+
+ Ta i bruk tema
+
+
+ Del tema
+
+
+
+
{
+ setColor(ColorService.convert('hex', '#0062ba'));
+ setName('');
+ setActivePanel('none');
+ }}
+ onPrimaryClicked={(color, name) => {
+ if (name === '') {
+ return;
+ }
+ if (activePanel === 'addColor') {
+ addNewColor(color, name);
+ } else {
+ updateExistingColor(color, name);
+ }
+ setColor(ColorService.convert('hex', '#0062ba'));
+ setName('');
+ setActivePanel('none');
+ }}
+ onRemove={() => {
+ removeColor(index, colorType);
+ setName('');
+ setActivePanel('none');
+ }}
+ type={activePanel}
+ color={color}
+ name={name}
+ setColor={setColor}
+ setName={setName}
+ colorType={colorType}
+ />
+
+ );
+};
diff --git a/apps/theme/components/SizeInput/SizeInput.module.css b/apps/theme/components/SizeInput/SizeInput.module.css
new file mode 100644
index 0000000000..cb2add142d
--- /dev/null
+++ b/apps/theme/components/SizeInput/SizeInput.module.css
@@ -0,0 +1,32 @@
+.component {
+ display: flex;
+ flex-direction: column;
+ flex: 1 1;
+}
+
+.label {
+ font-size: 16px;
+ margin-bottom: 8px;
+}
+
+.box {
+ width: 100%;
+ text-align: left;
+ background-color: transparent;
+ border: 1px solid var(--ds-color-neutral-border-subtle);
+ border-radius: 4px;
+ padding: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 15px;
+}
+
+.box:hover {
+ cursor: pointer;
+ background-color: var(--ds-color-neutral-background-subtle);
+}
+
+.icon {
+ color: var(--ds-color-neutral-text-subtle);
+}
diff --git a/apps/theme/components/SizeInput/SizeInput.tsx b/apps/theme/components/SizeInput/SizeInput.tsx
new file mode 100644
index 0000000000..3082a6ba92
--- /dev/null
+++ b/apps/theme/components/SizeInput/SizeInput.tsx
@@ -0,0 +1,24 @@
+import { PencilIcon } from '@navikt/aksel-icons';
+import classes from './SizeInput.module.css';
+
+type SizeInputProps = {
+ onChange: (size: string) => void;
+ name: string;
+ size: string;
+};
+
+export const SizeInput = ({ onChange, size, name }: SizeInputProps) => {
+ return (
+
+
{name}
+
onChange(size)}>
+ {size}
+
+
+
+ );
+};
diff --git a/apps/theme/components/ThemeToolbar/ThemeToolbar.module.css b/apps/theme/components/ThemeToolbar/ThemeToolbar.module.css
deleted file mode 100644
index 31f2ad9b25..0000000000
--- a/apps/theme/components/ThemeToolbar/ThemeToolbar.module.css
+++ /dev/null
@@ -1,26 +0,0 @@
-.pickers {
- display: flex;
- align-items: flex-end;
- justify-content: center;
- gap: var(--ds-spacing-6);
- padding: 12px 0;
- flex-wrap: wrap;
-}
-
-.pickersContainer {
- height: 140px;
-}
-
-.contrastSelect {
- width: 150px;
-}
-
-.select {
- display: flex;
- flex-direction: column;
- gap: var(--ds-spacing-2);
-}
-
-.shareBtn {
- margin-left: 24px;
-}
diff --git a/apps/theme/components/ThemeToolbar/ThemeToolbar.tsx b/apps/theme/components/ThemeToolbar/ThemeToolbar.tsx
deleted file mode 100644
index e40f2fdfff..0000000000
--- a/apps/theme/components/ThemeToolbar/ThemeToolbar.tsx
+++ /dev/null
@@ -1,170 +0,0 @@
-import type { CssColor } from '@adobe/leonardo-contrast-colors';
-import { Button, Tooltip } from '@digdir/designsystemet-react';
-import type { ColorError, ContrastMode } from '@digdir/designsystemet/color';
-import cl from 'clsx/lite';
-import { useState } from 'react';
-
-import { useThemeStore } from '../../store';
-import type { ThemeColors } from '../../types';
-import { ColorPicker } from '../ColorPicker/ColorPicker';
-import { TokenModal } from '../TokenModal/TokenModal';
-
-import classes from './ThemeToolbar.module.css';
-
-type ThemeToolbarProps = {
- accentError: ColorError;
- neutralError: ColorError;
- brand1Error: ColorError;
- brand2Error: ColorError;
- brand3Error: ColorError;
- borderRadius: string;
- onColorChanged: (type: ThemeColors, color: CssColor) => void;
- onContrastModeChanged: (mode: 'aa' | 'aaa') => void;
- onBorderRadiusChanged: (radius: string) => void;
- contrastMode: ContrastMode;
-};
-
-export const borderRadii = {
- '0 rem': '0',
- '0.25 rem': '0.25rem',
- '0.5 rem': '0.5rem',
- '0.75 rem': '0.75rem',
- '1 rem': '1rem',
- full: '99999px',
-};
-
-export const ThemeToolbar = ({
- accentError,
- neutralError,
- brand1Error,
- brand2Error,
- brand3Error,
- borderRadius,
- onColorChanged,
- onContrastModeChanged,
- onBorderRadiusChanged,
- contrastMode,
-}: ThemeToolbarProps) => {
- const accentTheme = useThemeStore((state) => state.accentTheme);
- const neutralTheme = useThemeStore((state) => state.neutralTheme);
- const brandOneTheme = useThemeStore((state) => state.brandOneTheme);
- const brandTwoTheme = useThemeStore((state) => state.brandTwoTheme);
- const brandThreeTheme = useThemeStore((state) => state.brandThreeTheme);
-
- const [toolTipText, setToolTipText] = useState('Kopier nettadresse');
- const onButtonClick = () => {
- setToolTipText('Kopiert!');
- navigator.clipboard.writeText(window.location.href).catch((reason) => {
- throw Error(String(reason));
- });
- };
-
- return (
-
-
-
{
- onColorChanged('accent', color);
- }}
- />
- {
- onColorChanged('neutral', color);
- }}
- />
- {
- onColorChanged('brand1', color);
- }}
- />
- {
- onColorChanged('brand2', color);
- }}
- />
- {
- onColorChanged('brand3', color);
- }}
- />
-
- {/*
- Kontrastnivå
- {
- onContrastModeChanged(e.target.value as 'aa' | 'aaa');
- }}
- >
- AA
- AAA (WIP)
-
-
*/}
-
- {/*
-
- Border radius
- onBorderRadiusChanged(e.target.value)}
- style={{
- textTransform: 'capitalize',
- }}
- >
- {Object.entries(borderRadii).map(([key, value]) => (
-
- {key}
-
- ))}
-
-
- */}
-
- onButtonClick()}
- onMouseEnter={() => setToolTipText('Kopier nettadresse')}
- >
- Del tema
-
-
-
-
-
- );
-};
diff --git a/apps/theme/components/ThemeWrapper/ThemeWrapper.tsx b/apps/theme/components/ThemeWrapper/ThemeWrapper.tsx
new file mode 100644
index 0000000000..d769dda660
--- /dev/null
+++ b/apps/theme/components/ThemeWrapper/ThemeWrapper.tsx
@@ -0,0 +1,18 @@
+'use client';
+
+import { useThemeStore } from '../../store';
+
+type ThemeWrapperProps = {
+ children: React.ReactNode;
+};
+
+export const ThemeWrapper = ({ children }: ThemeWrapperProps) => {
+ const appearance = useThemeStore((state) => state.appearance);
+ const theme = useThemeStore((state) => state.themePreview);
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/apps/theme/components/Toggle/Toggle.module.css b/apps/theme/components/Toggle/Toggle.module.css
new file mode 100644
index 0000000000..7a512fd818
--- /dev/null
+++ b/apps/theme/components/Toggle/Toggle.module.css
@@ -0,0 +1,82 @@
+.toggle {
+ display: flex;
+ gap: 8px;
+}
+
+.box {
+ border: 1px solid var(--ds-color-neutral-border-subtle);
+ border-radius: 4px;
+ height: 42px;
+ display: flex;
+ align-items: center;
+ padding-left: 12px;
+ overflow: hidden;
+}
+
+.box:hover {
+ cursor: pointer;
+ background-color: var(--ds-color-neutral-background-subtle);
+}
+
+.box:hover .inner:before {
+ background-color: var(--ds-color-neutral-background-subtle);
+}
+
+.item {
+ text-align: center;
+ font-size: 14px;
+ display: flex;
+ flex: 1 1;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.active .box {
+ outline: 1px solid var(--ds-color-neutral-border-default);
+ border-color: var(--ds-color-neutral-border-default);
+}
+
+.inner {
+ height: 24px;
+ width: 48px;
+ border: 4px solid var(--ds-color-neutral-border-default);
+ position: relative;
+ background-color: var(--ds-color-neutral-surface-default);
+ margin-left: -18px;
+}
+
+.inner:before {
+ content: '';
+ display: block;
+ height: 24px;
+ width: 24px;
+ background-color: white;
+ position: absolute;
+ left: -4px;
+ top: -4px;
+}
+
+.appearance {
+ display: flex;
+ gap: 8px;
+}
+
+.icon {
+ height: 20px;
+ width: 20px;
+ border: 2px solid var(--ds-color-neutral-border-strong);
+ border-radius: 50%;
+ overflow: hidden;
+}
+
+.dark:before {
+ content: '';
+ display: block;
+ height: 20px;
+ width: 9px;
+ background-color: var(--ds-color-neutral-border-strong);
+}
+
+.contrast {
+ background-color: var(--ds-color-neutral-border-strong);
+}
diff --git a/apps/theme/components/Toggle/Toggle.tsx b/apps/theme/components/Toggle/Toggle.tsx
new file mode 100644
index 0000000000..33969bb38d
--- /dev/null
+++ b/apps/theme/components/Toggle/Toggle.tsx
@@ -0,0 +1,50 @@
+import cl from 'clsx/lite';
+import { useState } from 'react';
+import classes from './Toggle.module.css';
+
+type ToggleProps = {
+ type: 'radius' | 'appearance';
+ items: { name: string; type: string; value: string }[];
+ showLabel?: boolean;
+ onChange?: (value: string) => void;
+};
+
+export const Toggle = ({
+ type,
+ items,
+ showLabel = false,
+ onChange,
+}: ToggleProps) => {
+ const [active, setActive] = useState(0);
+
+ return (
+
+ {items.map((item, index) => (
+
setActive(index)}
+ >
+
onChange?.(item.value)}
+ >
+ {type === 'radius' && (
+
+ )}
+ {type === 'appearance' && (
+
+ )}
+
+ {showLabel &&
{item.name}
}
+
+ ))}
+
+ );
+};
diff --git a/apps/theme/components/index.ts b/apps/theme/components/index.ts
index 14e1084fc4..bc943d95bc 100644
--- a/apps/theme/components/index.ts
+++ b/apps/theme/components/index.ts
@@ -3,6 +3,12 @@ export { ColorPicker } from './ColorPicker/ColorPicker';
export { Group } from './Group/Group';
export { Previews } from './Previews/Previews';
export { Scale } from './Scale/Scale';
-export { Scales } from './Scales/Scales';
-export { ThemeToolbar } from './ThemeToolbar/ThemeToolbar';
+export { Colors } from './Colors/Colors';
export { TokenModal } from './TokenModal/TokenModal';
+export { Sidebar } from './Sidebar/Sidebar';
+export { ColorInput } from './ColorInput/ColorInput';
+export { ColorPreview } from './ColorPreview/ColorPreview';
+export { ColorPane } from './Sidebar/ColorPane/ColorPane';
+export { ColorTokens } from './ColorTokens/ColorTokens';
+export { ColorContrasts } from './ColorContrasts/ColorContrasts';
+export { BorderRadius } from './BorderRadius/BorderRadius';
diff --git a/apps/theme/package.json b/apps/theme/package.json
index e381914752..c7f60fa550 100644
--- a/apps/theme/package.json
+++ b/apps/theme/package.json
@@ -22,6 +22,7 @@
"next": "^14.2.5",
"react": "^18.3.1",
"react-color": "^2.19.3",
+ "react-color-palette": "^7.3.0",
"react-dom": "^18.3.1",
"recharts": "^2.12.7",
"zustand": "^4.5.4"
diff --git a/apps/theme/public/img/color-tokens.png b/apps/theme/public/img/color-tokens.png
new file mode 100644
index 0000000000..8e23a04ee0
Binary files /dev/null and b/apps/theme/public/img/color-tokens.png differ
diff --git a/apps/theme/store.ts b/apps/theme/store.ts
index b5dbbbab47..a0a5697882 100644
--- a/apps/theme/store.ts
+++ b/apps/theme/store.ts
@@ -1,82 +1,85 @@
-import type { CssColor } from '@adobe/leonardo-contrast-colors';
-import type { ColorInfo, ThemeInfo } from '@digdir/designsystemet/color';
+import type {
+ ColorInfo,
+ ColorMode,
+ ThemeInfo,
+} from '@digdir/designsystemet/color';
import { create } from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware';
-import { Settings } from './settings';
-import type { ThemeColors } from './types';
-
-type StoreThemeType = {
- theme: ThemeInfo;
- color: CssColor;
+export type ColorTheme = {
+ name: string;
+ colors: ThemeInfo;
};
type ColorStore = {
- accentTheme: StoreThemeType;
- neutralTheme: StoreThemeType;
- brandOneTheme: StoreThemeType;
- brandTwoTheme: StoreThemeType;
- brandThreeTheme: StoreThemeType;
- setAccentTheme: (theme: ThemeInfo, color: CssColor) => void;
- setNeutralTheme: (theme: ThemeInfo, color: CssColor) => void;
- setBrandOneTheme: (theme: ThemeInfo, color: CssColor) => void;
- setBrandTwoTheme: (theme: ThemeInfo, color: CssColor) => void;
- setBrandThreeTheme: (theme: ThemeInfo, color: CssColor) => void;
- selectedColor: { color: ColorInfo; type: ThemeColors };
- setSelectedColor: (color: ColorInfo, type: ThemeColors) => void;
+ colors: {
+ main: ColorTheme[];
+ neutral: ColorTheme[];
+ support: ColorTheme[];
+ };
+ addColor: (
+ newColor: ColorTheme,
+ type: 'main' | 'neutral' | 'support',
+ ) => void;
+ updateColor: (
+ updatedColor: ColorTheme,
+ index: number,
+ type: 'main' | 'neutral' | 'support',
+ ) => void;
+ resetColors: () => void;
+ removeColor: (index: number, type: 'main' | 'neutral' | 'support') => void;
+ selectedColor: { color: ColorInfo; name: string };
+ setSelectedColor: (color: ColorInfo, name: string) => void;
borderRadius: string;
setBorderRadius: (radius: string) => void;
-};
-
-const defaultTheme = () => {
- return {
- light: [],
- dark: [],
- contrast: [],
- };
+ appearance: ColorMode;
+ setAppearance: (appearance: ColorMode) => void;
+ themePreview: 'one' | 'two' | 'three';
+ setThemePreview: (theme: 'one' | 'two' | 'three') => void;
};
export const useThemeStore = create(
subscribeWithSelector((set) => ({
- accentTheme: {
- theme: defaultTheme(),
- color: Settings.accentBaseColor,
- },
- neutralTheme: {
- theme: defaultTheme(),
- color: Settings.neutralBaseColor,
- },
- brandOneTheme: {
- theme: defaultTheme(),
- color: Settings.brand1BaseColor,
- },
- brandTwoTheme: {
- theme: defaultTheme(),
- color: Settings.brand2BaseColor,
- },
- brandThreeTheme: {
- theme: defaultTheme(),
- color: Settings.brand3BaseColor,
- },
selectedColor: {
color: {
hex: '#ffffff',
number: 1,
name: 'Default',
},
- type: 'accent',
+ name: 'Default',
},
borderRadius: '0.25rem',
+ appearance: 'light',
+ themePreview: 'one',
+ colors: {
+ main: [],
+ neutral: [],
+ support: [],
+ },
+ addColor: (newColor, type) =>
+ set((state) => {
+ const updatedColors = state.colors[type].concat(newColor);
+ return { colors: { ...state.colors, [type]: updatedColors } };
+ }),
+ updateColor: (updatedColor, index, type) =>
+ set((state) => {
+ const updatedColors = state.colors[type].map((color, i) =>
+ i === index ? updatedColor : color,
+ );
+ return { colors: { ...state.colors, [type]: updatedColors } };
+ }),
+ resetColors: () => {
+ set({ colors: { main: [], neutral: [], support: [] } });
+ },
+ removeColor: (index, type) =>
+ set((state) => {
+ const updatedColors = state.colors[type].filter((_, i) => i !== index);
+ return { colors: { ...state.colors, [type]: updatedColors } };
+ }),
+ setAppearance: (appearance) => set({ appearance: appearance }),
+ setThemePreview: (themePreview) => set({ themePreview: themePreview }),
setBorderRadius: (radius) => set({ borderRadius: radius }),
- setSelectedColor: (color, type) =>
- set({ selectedColor: { color: color, type: type } }),
- setAccentTheme: (theme, color) => set({ accentTheme: { theme, color } }),
- setNeutralTheme: (theme, color) => set({ neutralTheme: { theme, color } }),
- setBrandOneTheme: (theme, color) =>
- set({ brandOneTheme: { theme, color } }),
- setBrandTwoTheme: (theme, color) =>
- set({ brandTwoTheme: { theme, color } }),
- setBrandThreeTheme: (theme, color) =>
- set({ brandThreeTheme: { theme, color } }),
+ setSelectedColor: (color, name) =>
+ set({ selectedColor: { color: color, name: name } }),
})),
);
diff --git a/yarn.lock b/yarn.lock
index a456fcf5b2..ba17d14947 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -14858,6 +14858,15 @@ __metadata:
languageName: node
linkType: hard
+"react-color-palette@npm:^7.3.0":
+ version: 7.3.0
+ resolution: "react-color-palette@npm:7.3.0"
+ peerDependencies:
+ react: ">=16.8"
+ checksum: 10/49161f76c8c8658b7fa20022c81757cafb816b553d6e43affb4cd67ef4df2025c505ab4a55fd5598a11c223d00327fc1fef2f4bdf221e40864a49d2b75938d7b
+ languageName: node
+ linkType: hard
+
"react-color@npm:^2.19.3":
version: 2.19.3
resolution: "react-color@npm:2.19.3"
@@ -16878,6 +16887,7 @@ __metadata:
next: "npm:^14.2.5"
react: "npm:^18.3.1"
react-color: "npm:^2.19.3"
+ react-color-palette: "npm:^7.3.0"
react-dom: "npm:^18.3.1"
recharts: "npm:^2.12.7"
zustand: "npm:^4.5.4"