1- import type { Color , CssColor , ThemeInfo } from '@digdir/designsystemet/color' ;
21import {
3- type CreateTokensOptions ,
4- cliOptions ,
5- formatThemeCSS ,
6- } from '@digdir/designsystemet/tokens' ;
7- import {
8- Button ,
92 Dialog ,
103 Divider ,
114 Heading ,
125 Input ,
136 Link ,
147 Paragraph ,
15- Switch ,
8+ Tabs ,
169} from '@digdir/designsystemet-react' ;
17- import { CodeBlock } from '@internal/components' ;
1810import { InformationSquareIcon , StarIcon } from '@navikt/aksel-icons' ;
19- import { useRef , useState } from 'react' ;
11+ import { useRef } from 'react' ;
2012import { useTranslation } from 'react-i18next' ;
21- import { useLoaderData } from 'react-router ' ;
22- import { useThemebuilder } from '~/routes/themebuilder/_utils/use-themebuilder ' ;
13+ import Cli from './steps/cli ' ;
14+ import Config from './steps/config ' ;
2315import classes from './token-modal.module.css' ;
24-
25- type ColorTheme = {
26- name : string ;
27- colors : ThemeInfo ;
28- } ;
29-
30- const colorCliOptions = cliOptions . theme . colors ;
31-
32- const getBaseDefault = ( colorTheme : Color [ ] ) =>
33- colorTheme . find ( ( color ) => color . name === 'base-default' ) ;
34-
35- const LOADING_CSS_MESSAGE = 'Generating CSS...' ;
36- const FEAT_THEME_CSS = false ; // TODO set to false before merging
16+ import { useTokenModal } from './use-token-modal' ;
3717
3818export const TokenModal = ( ) => {
3919 const { t } = useTranslation ( ) ;
4020 const modalRef = useRef < HTMLDialogElement > ( null ) ;
41- const { isProduction } = useLoaderData ( ) ;
42-
43- // Use separate selectors for better performance
44- const { colors, baseBorderRadius } = useThemebuilder ( ) ;
45-
46- const [ themeName , setThemeName ] = useState ( 'theme' ) ;
47- const [ themeCSS , setThemeCSS ] = useState ( '' ) ;
48- const [ formatWin , setFormatWin ] = useState (
49- navigator . userAgent . includes ( 'Windows' ) ,
50- ) ;
51-
52- const setCliColors = ( colorTheme : ColorTheme [ ] ) => {
53- if ( ! colorTheme . length ) return '' ;
54-
55- return (
56- colorTheme
57- . map ( ( theme ) => {
58- const baseColor = getBaseDefault ( theme . colors . light ) ;
59- return `"${ theme . name } :${ baseColor ?. hex } "` ;
60- } )
61- . join ( ' ' ) + ' '
62- ) ;
63- } ;
64-
65- const packageWithTag = `@digdir/designsystemet${ isProduction ? '@latest' : '@next' } ` ;
66- const buildSnippet = `npx ${ packageWithTag } tokens build` ;
67- const seperator = formatWin ? ' ^\n' : ' \\\n' ;
68-
69- const cliSnippet = [
70- `npx ${ packageWithTag } tokens create` ,
71- `--${ colorCliOptions . main } ${ setCliColors ( colors . main ) . trimEnd ( ) } ` ,
72- `--${ colorCliOptions . neutral } "${ getBaseDefault ( colors . neutral [ 0 ] ?. colors . light ) ?. hex } "` ,
73- `${ colors . support . length > 0 ? `--${ colorCliOptions . support } ${ setCliColors ( colors . support ) . trimEnd ( ) } ` : '' } ` ,
74- `--border-radius ${ baseBorderRadius } ` ,
75- `--theme "${ themeName } "` ,
76- ]
77- . filter ( Boolean )
78- . join ( seperator ) ;
79-
80- const theme : CreateTokensOptions = {
81- name : themeName ,
82- colors : {
83- main : colors . main . reduce (
84- ( acc , color ) => {
85- acc [ color . name ] = getBaseDefault ( color . colors . light ) ?. hex || '#' ;
86- return acc ;
87- } ,
88- { } as Record < string , CssColor > ,
89- ) ,
90- support : colors . support . reduce (
91- ( acc , color ) => {
92- acc [ color . name ] = getBaseDefault ( color . colors . light ) ?. hex || '#' ;
93- return acc ;
94- } ,
95- { } as Record < string , CssColor > ,
96- ) ,
97- neutral : getBaseDefault ( colors . neutral [ 0 ] ?. colors . light ) ?. hex || '#' ,
98- } ,
99- borderRadius : baseBorderRadius ,
100- typography : {
101- fontFamily : 'Inter' ,
102- } ,
103- } ;
104-
105- const onThemeButtonClick = ( ) => {
106- if ( themeCSS !== LOADING_CSS_MESSAGE ) {
107- setThemeCSS ( LOADING_CSS_MESSAGE ) ;
108-
109- formatThemeCSS ( theme ) . then ( ( css ) => {
110- setThemeCSS ( css ) ;
111- } ) ;
112- }
113- } ;
21+ const { themeName, setThemeName, cliSnippet, buildSnippet, configSnippet } =
22+ useTokenModal ( ) ;
11423
11524 return (
11625 < Dialog . TriggerContext >
@@ -143,7 +52,6 @@ export const TokenModal = () => {
14352 </ Heading >
14453 < Paragraph > { t ( 'themeModal.theme-name-description' ) } </ Paragraph >
14554 < Input
146- aria-label = { t ( 'themeModal.theme-name-label' ) }
14755 name = 'themeName'
14856 value = { themeName }
14957 onChange = { ( e ) => {
@@ -162,72 +70,24 @@ export const TokenModal = () => {
16270 < Dialog . Block >
16371 < div className = { classes . content } >
16472 < div className = { classes . rightSection } >
165- { FEAT_THEME_CSS && (
166- < div className = { classes [ 'snippet-themecss' ] } >
167- < Button
168- onClick = { onThemeButtonClick }
169- loading = { themeCSS === LOADING_CSS_MESSAGE }
170- >
171- { themeCSS === LOADING_CSS_MESSAGE
172- ? t ( 'themeModal.generating-css' )
173- : t ( 'themeModal.generate-css' ) }
174- </ Button >
175- { themeCSS && themeCSS !== LOADING_CSS_MESSAGE && (
176- < CodeBlock language = 'css' > { themeCSS } </ CodeBlock >
177- ) }
178- </ div >
179- ) } { ' ' }
180- < div className = { classes . step } >
181- < span > 1</ span >
182- < Paragraph >
183- { t ( 'themeModal.step-one' ) } { ' ' }
184- < Link
185- target = '_blank'
186- href = 'https://www.figma.com/community/plugin/1382044395533039221/designsystemet-beta'
187- >
188- { t ( 'themeModal.figma-plugin' ) }
189- </ Link > { ' ' }
190- { t ( 'themeModal.in' ) } { ' ' }
191- < Link
192- target = '_blank'
193- href = 'https://www.figma.com/community/file/1322138390374166141'
194- >
195- { t ( 'themeModal.core-ui-kit' ) }
196- </ Link > { ' ' }
197- { t ( 'themeModal.to-update' ) } { ' ' }
198- < Link
199- target = '_blank'
200- href = 'https://www.designsystemet.no/no/fundamentals/themebuilder/own-theme'
201- >
202- { t ( 'themeModal.own-theme' ) }
203- </ Link > { ' ' }
204- { t ( 'themeModal.page' ) }
205- </ Paragraph >
206- </ div >
207- < div className = { classes . snippet } >
208- < Switch
209- style = { { marginInlineStart : 'auto' , width : 'fit-content' } }
210- position = 'end'
211- label = { t ( 'themeModal.format' ) }
212- checked = { formatWin }
213- onChange = { ( e ) => {
214- setFormatWin ( e . currentTarget . checked ) ;
215- } }
216- />
217- < CodeBlock language = 'bash' > { cliSnippet } </ CodeBlock >
218- </ div >
219- < div
220- className = { classes . step }
221- style = { {
222- marginTop : 'var(--ds-size-4)' ,
223- } }
224- >
225- < span > 2</ span >
226- < Paragraph > { t ( 'themeModal.step-two' ) } </ Paragraph >
227- </ div >
228- < div className = { classes . snippet } >
229- < CodeBlock language = 'bash' > { buildSnippet } </ CodeBlock >
230- </ div >
73+ < Tabs defaultValue = 'config' >
74+ < Tabs . List >
75+ < Tabs . Tab value = 'config' > Config File</ Tabs . Tab >
76+ < Tabs . Tab value = 'cli' > CLI</ Tabs . Tab >
77+ </ Tabs . List >
78+ < Tabs . Panel value = 'cli' className = { classes . tabpanel } >
79+ < Cli
80+ cliSnippet = { cliSnippet }
81+ buildSnippet = { buildSnippet . cli }
82+ />
83+ </ Tabs . Panel >
84+ < Tabs . Panel value = 'config' className = { classes . tabpanel } >
85+ < Config
86+ configSnippet = { configSnippet }
87+ buildSnippet = { buildSnippet . config }
88+ />
89+ </ Tabs . Panel >
90+ </ Tabs >
23191 < Divider />
23292 < div className = { classes . contact } >
23393 < div className = { classes . contact__icon } >
0 commit comments