From 4edd6f5b850c11023ff6e787fe077b8b64dcef88 Mon Sep 17 00:00:00 2001 From: Ole Martin Borge Date: Thu, 5 Mar 2026 13:57:36 +0100 Subject: [PATCH 1/5] feat(nve-navigation-card): legg til ny navigasjonskomponent --- .../images/nve-illustrasjoner-ikon-flom.png | Bin 0 -> 11003 bytes .../nve-illustrasjoner-ikon-vannkraft.png | Bin 0 -> 24431 bytes doc-site/components/nve-navigation-card.md | 139 ++++++++++++++++++ public/css/global.css | 5 +- .../nve-navigation-card.component.ts | 88 +++++++++++ .../nve-navigation-card.styles.ts | 83 +++++++++++ src/nve-designsystem.ts | 1 + 7 files changed, 314 insertions(+), 2 deletions(-) create mode 100644 doc-site/assets/images/nve-illustrasjoner-ikon-flom.png create mode 100644 doc-site/assets/images/nve-illustrasjoner-ikon-vannkraft.png create mode 100644 doc-site/components/nve-navigation-card.md create mode 100644 src/components/nve-navigation-card/nve-navigation-card.component.ts create mode 100644 src/components/nve-navigation-card/nve-navigation-card.styles.ts diff --git a/doc-site/assets/images/nve-illustrasjoner-ikon-flom.png b/doc-site/assets/images/nve-illustrasjoner-ikon-flom.png new file mode 100644 index 0000000000000000000000000000000000000000..1da04216cd52b5dbb67186c9d31989339dcf5202 GIT binary patch literal 11003 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 PR)pPQ#~+JrJb(Q^$ccY) literal 0 HcmV?d00001 diff --git a/doc-site/assets/images/nve-illustrasjoner-ikon-vannkraft.png b/doc-site/assets/images/nve-illustrasjoner-ikon-vannkraft.png new file mode 100644 index 0000000000000000000000000000000000000000..dab85efd297c2ad882846f91ba6f6a9c76c8b774 GIT binary patch literal 24431 zcmeFZ_dnI|A3uI<86kv}6$)`|%8U>~!Xe|>WF9iJS17Y%m6^f9y2Icm9;lU6+k(7%rE? z+Z8Q05p9?EAu7S2W_aXgrr|3w8k^&uvxo0|H}JLxh{CeH%I?@ zy(!u4rEI7h=~koW9gZk%5~dPWOSU&RAgxhfnTRekb%+qWPdTP^3$V&=pIHZ=Z%6V)`25dywP^^%0r3C^A`)}|}qO0vVqJ{>4-fCS8pXC@RK|s4+_v#wE zlv_)Zp$?_b^x_Ov06PoNkz1x5DKf0zd2P;Op*t%2G?$H#ySc&;2=d#vwziU)aY-&4 zH;xqO>p98*3zPSB5K3>J$&1Kr>#&PsO9F?Rg-nPWvO#67q>$%99aK%2%4}=hiqlZL z2nfFwc33@w?p15rkTK1)gb{f6Di4z_&T~rrI}k{Dbw7QDW--?r7rlRmbh2bro;Z;5 z6_|O3a6@}V_AT>20j^jTHnRy3u#h&S1n)<_g@NI9BS;=>tCYqF@PxWbL0HYOB1!Oh zN>dN6Kxv;8*M*mmw?G21AM#B4X!&spkrn2Atx1|}lqDG$fto9ZI}A(K-_ z2($5cfRGbTjH9p>f3nGkB@gLMJ3~cTGW8Y-Ano0$OSJAe3oRcx^D1$=J4m1qmfNt3 z)70#>tfm+|R}R{ZEdpd1EgT|i_(;(gFYGg{Y)FC;9R;B$!*=^iK{C{yevKpzc7HPk zGQbQ2Q*@@pjLy#T3H`)*eiPU$mREe@tDrMhO0U3>uQ@mnN(Qmhuf_Y(l-07{c$6+h zU$Jz#a4RfaFH6xGZ<@ z;Vk&O1U2h^y}znGoGE)q@KUHW$7pHEdi#D{Q2xq_6A$xV%rEK1cVZ6Q#*zktB@rl@Z$C!$$RRP0??O`K(HRei5+=lB`ns)TEN>@DHFsgi7JOF~JG3*~3Q z;J()M(senn;{4vKSs)&HptAW(s)98XuFn_{4!V`5a>Xd@cQhIH^cQS%-uJYjB^Eeh zsxQA8;ExYqpv0eTqVjQ$dDs<8&YeAg%Kcgu2=`=pc9)lI+Z-rueN$Tx+xGEte0C3* z@3h0q*3T}!yNPj>D$)^kVs>jjV>4}B1dZ1axpoq0S;RSUb>_+4o1e6MkSg{=491=2 zze;XD;f)i6brufYVw?yV0809xby;b6Hz>P9*gwRec+`a?I&^L)Bq$DjR$j1hvNk(K zdbo1@QtWmnm7L<93ljQ4XIhiZZNo-i zmEof5FMD(;-D8T^d`G6b>i8c7)@t~t3pU^vHpmhaH_-F!n*V;>s!Smx*)Jz2yp>>juhjB*?HA$p3Kf-^3|060B5BI1=~7! z>qq-Fo7R|;pn>zyra9YU>H9XYr!6bws*=2_wpXVb*S`(sPR%ekvh3Q;9E%qX7acQ) zpP0U+3y=rnZm$ogX+4o%g3ldXPw$SaPY)AhTWBz>JHB9sUSB~k#xWb{*p$%b=A-G_ z;y*HH^P-%)_UMW)-x(h~*p)aP`*0qu#G|o&uHJMW zu!cB0+X;pVt}Y{Rh4J5?hPSe6%qExX2d9T38pof^Pk5pGcIL*q=QRA65~VMGnrqI} zoh8+sl_J(7t{4CA&dVUb)E@MGIDasoSO@hM7CKTswj+Ug+-J6Y@Fl&q+Hk${BcU5k zcuPdQp!Cp4SV=ZVU9YUEul5!!|D%MT!mWUDZ;zv>VqJuf_f}Q68oB~?EtM(u)X_cR z-R5wZ-{hx1?Pww)t+>6^+UvgufBv%QhL8TvJD3m=EyYnJZg|eI9K#e=mCN#1v^Dvu zH+>C>{1sjnOtk?JVA-##m$4Pkq_>5H~#p4OgNp`uO>8f_{oAt*n`V=KYyh zH1wi4uQDGtR+cY0o15@b^RIV1pYrhXU}8f15jP$re7}k-adhzX_G#o0dbe}V87=Aa zc1fCRl}M1jDKpyg7ZDO@o;K;C-oWe_yUx;xi#Umgo|1wu{(RJ2;#jrgd~FkYGo_ln z6enSTQRd71MOulUN5|jZzkf$Ydg5F%&T^&^e!~jJN=)ZgwZByj@Js?)B1S;eFfF*N zq4PUv*O7=|9^ugFwIzdSJWJa<-j%d?N70xe!_#=_keDRfEb7Q}7_(X9x)$A=5*tHX z-^hIZX@|e@(f4wGW5>^icKC`Z<~DW^GH|}USnqmxeIKytWf+Tz0$zsA#!;(rL#ELM zwMYI2{z83j(|2<%n#wCH>GaR=IC^H>es1Be#MBP&MJt@A%MN)5c^fEJ`5WrD2z)4G zd>>~qElD^kljuIK@}g)2FY~e6efpHq&(>F0d5M0A-8Jd+z2-Gu=;g-P&c;A%3HxlF z%cDc6-%hBgW0<@0_Tqaw%iHc8Xz9l&j7jgnyj~UR7t$40d1(#(C}r|rutr?h$ef$(%{<64SK@_lW5#_?Emnmz z=84hCp22@guR7g+EFt`-$yd&%);wWDhokz9uZu?`tT4kFp-+3iJykO)8XO8JFGu!5 zRlH{MeTvkB|K;cxCeggGpE$2wikkjycsUN;h`BoX$-if(8}p#`pOXKD%BpUOw24j= zradp4TK=-Cnx^uJ@WtHVVx*N{DTu;S*OFWG?(dt~hw$fa%ViE1(2w3sAg!lGqbB!2 zR|$WbFZ>0qQa&FKNMMxFb$95i60_Uph|>oA5Zp^<%hiGr8OKjTx5=fC0vQ{H{M{B_ z_o9lF3qtPsM2U93-Mq6v1#EVQQ``rdqkp~ZrwXMnq;BvW zwUYJ3i#@Win9K8rtJctQc5>9cS&<=SimAVwPEz! zd&QjTnopj*Nm1w>GmopjYPub+c?57TKajnpza2$(Y%_;dW^ru9(Y+&+XV7$6L;?(>1yB9ByU&*xN`;^~}M@T=WthPi$A- zb4=Ow*Io9q)zvoaX?H*2wJoKbY0CYxVl0XZi}fx~qQ#%j-D@Sxxa&rmHuj;dnM_s& zJ3?qzkfQ`$m)3Ye)mdcF*R6Bc{KogRUh~(N5zNIop1|lFbx5WuUuz+>WJ!yB2vo%jkvJDO4MKelM5&xRYSDoB5 zJ=98xW>!zp?$Zl*%=eZK4AuEA>W$~7j1}qSS9@rcU8K({qeR;|bEQbaC2*mgiCZXx zgxQ-IJ^Lr$$2MsRez<0--oBFC(;>j|_z=KIP8R?&mHEZ#pd}&Ix-nKS+hcWL3a17d1pSo zz0}tlSRJ@q-J&A8cTOO38<89+%(8E`U2JivHi@nbZ5p0(c%~meo7<3ye%xrPHiXRX zJVlH8*4}t~;OVINQ(*4L6PAl(zccEZqeY5P_vhUKCERaZ-cX!H5%zQFf~2C`T*}0r z%2VKYNI3V=?H$svUy|s4_)zcO_RYqSnfOVui>95wX!vPrZ@7?uJxT84M>qee5e^&E zs}Xw2Ef83^zZ$B>hS)*{yc@fHXQ4}elEsyW;rd*}brB&mt|(7Q-FWR+oQLDyIoF%L zHy+>8Cy9Q~hn6^KGr9jLkZwPug>-$Fu)2C1rkDW&GQ8s3=r%Ru{9o7N?5eux8C@CF zem|YeJ#-X{&u&@V>yEOORR|r8y}g7}a&X7rBrx*9YYKW@5Ya17eXgi=P?2a)4)S(z zI>F0EHLh}Cc(`QRh#YNyyomuhjUNbx?}6N_dK>Q3XfLZgQLtMPlpw(Q$i#S5-xv0D z@0~c&x5!mw(AAvxT*EETz!UJ0M3Kee4(WX1?(JD-@w}WM-0HN@- zN8Vlx&aJnTHak15x$a46{F6S35W9+?n*A z{-0)Mu4Y5(F~Hb0$t-EK;D?G36xK!MBk(ttMeB5E<`<@4l}$~%rx;5}M^Jm>0L5rw z0`eul8ilTmItt?|NDdsVd__7l;Ds#APSUf6sJP zCq1{F?~QsuTx-zYOQMCLET&8LLm0ZK~Qc z+k?4F)b1#f=vQg1+iYAj{kG~q`yIuGQk#R$*=6$a+XPaeMXU`T! zQlis-y&yM?``;=JgkgPuf0(r4bbaFiT7pr@ZTn4)80NdpJ*lN17hfl0Lq~3Sn;WeD zXs*glfbLGKUZi(tF;ZyUef&hWJCmXSXI~K?{*|JsE8h3Cj|b`t!bGCItIjTx*}0hO z=QNFB9&2HWP7W+I;ApD<{8+kS{^+pZefl6-EQuQ^Sr8z_-RWpk#Fx!aQ>;i zK&A)sqZE?RxflyAsmq@rd?!O8k5D|r^$cFqi?qs*Tlt0@O`96>zcyn&ZoG%rxxM+Z zTT=t~-lq^X_OsFQSR7|OW7u3Z zhjJN6s#2=sfRuu)8qdl6H{qGyk%2+!oc`=ZNp-e-*nqX)j+i5vHzMqfvX_Nvlc7KX z1$$?!&uOmrBgt*U!ahe#*W^#Atf=X>nJLGjO!;FEa%l^X zu+o`2x9uJe_&b{}gxd{NfDAs|L_FfjLdQXRxq0wJgNraYdWsGXwd~((`HkWC%5U=D zo-wyn{Aq5@&sdX_G`{!P$?WC)oY&6JdzndHd<#^(WLz=ev4!9cQ*3 zq9q+N{|m10V#s&6Kev<#Z=wU8b&5x=XpwYz|9J89mqdJODXqBZ2Z~ z%jJ!}bdvj$&9d$1=W@xx47N)!XGdQ&iWyA>p&U`DxX6#Fe|Zyy$9 z99a5Stor&L?|8`gsJx0BAUy$gQSs$^B}|O-f#0b2gqvVjL0oy&PA?KYJ@2I?Rpujc~gIM9iy#EEF0b$Ozj3Gcw7e{UUL0t9u zW!B_;eV=Lb)&YXtWrBSgW)kxDtr2>TWb#`(i}q(b!e#moexduC%Orj*f2PX(Zgg9& z`TF!6L$Bp-W7;GsPl`u*b9{+ED~+h%en!;Bdi}D++hd&l2=_kl5%RT+?!k0yXuk`k zT(|9Y(;cF=I+r!Q)s23*$Mr~pD^NPg5vneT;tzgfKIJD5l+XU@I=(|#$7VD8%)>=) zG`4{Jza_vuY}TPMAWzxZquBh^Y=6)`LFo2g#_JyHjdXKXHkaRy$+xq21 zjO)>X+;J?_j?43~=MZP$0XV+W_dxpa9rNSIZBxugVwCK5sxhL5gOX#X$}X372~VD zte6)!Pk60wG|Kve;b{9EeL3e&C!o`LERnJL}#uzQ&vI|EQ@uk0*L^LUpf-K+~;fDxDj-!4Jc zMiy+ZJY(JZ#pH41Y;}wsZa5L0mo<%KCkFG0!MLeuXhDR*@S^~6F+;W93FZu z($9Xd*)V?}EIsyD_6AGGj5#X*Qf}|>-{QG0QonQr?l3Y@Jc``&J}z`$5w4+z%m$wi zCnwKLEE|LE5O6YC@4bG%$K({hU-pk=t`^UUxFKMk-iBzFuy$8CyLo{cu zJ>@*1MxP-nRn2c~RYhkWhRuWiMfBVUkB5Q4T|v1&#OZrU_7~uPBtqH02jV zW?evwOTEufF^)9S#aBm%W{*I443pcQmZ||*K#0>l2-jk|5FdD2^0I+mtWwy-K4hZC z+Y-1QlEo*GSHIHE-gr>Q;hH4P{(W2@YmTE9%4ZK(YQfI@#thB^=IQ~o#EGUE%X{9>%f08W#skSz$ zGd@G36QqIE#*iWFcdS>@KHJWn3JR14r8|zEQ$Mc;y>aLmVK!4B-FIN`c`BHnx&s0K zcrrt*W?cTzF88G7R^iwH0Fb*57WyCxI*e>5#Sh^B)9puX05bQr$WokVK`jxLw)ErH zad!d+LMlH*f&OlT{+6W^e>hq`EdPlo`*}ulWh{exa4JCCOa<>%hwt4Q?&&Doq}!`K z4J?rGXT%T61*&kq1Gh4i%Q{^)4USUyXE&L>N_Np@HXQ&VMFG^1XBv;Qo5d{R+ zo;K)`L!DJ3;m=e6LgVSUy1N{jw9b8ULJ3*QWuG1keek6>0%T5Xt;Bb)+c5ODIWvm-Oz(+M-THm^Ps2!)sL0ZC)_6Cgf zTA|OtGu^8+_623Hz9rzu-U=C*aw-6k0@)T`uDA{Phrck7>57y;PS_(MSU?Wp1a9fS zY#CHQ9^tcm&|6e^#rvCY+r8r<6Qo-kX=K+{`Q}hg4roBT^ZmE!Vh!!)j?}~uc6ET{ zD%ab4e(-Q6ra*?$!r3)D%8mt1>12_0@^2H#p_&3k2Zdsm7Dp;mPW$ zgjV&}{OOYN zet7pW!*hIGC^Fkj&pTRE0y5Mtm8U}nHGR#v|B;LQIeratKT`8_1!&A*z9{4wn6j89 zW@94jZgVs{sOl*LX?p({L2O{kZyZ_}aiM1YwH}z?tv>HY)n6p&|G!)S?4y0fEpP#D z4Jhu zL6(2NO-O>nv7MA_7msQ@PZr_sXQhU8A`iF`-BKQS{U({%|`*F;YdG15RhG_ zzmk@yEtpRl8QBt1%gr!aOg~Bx@Qfe6l2eRR{%k>E8yr()wiaOy3Ykc@i7Pv#SNt!% zU~Ch%^QL70$d!fEG~yHjCCrtM6V`n;*o+=yL@?*)KM)AQqUyJIGL>aGSgEB>Tgw0- z?4Lf$o|FR39zC8jT3rliL4K7qO0-qr}Rhv5aV z)~=~X^xEOWpM7kJ$bT()JP{)(^EpXT@490hCZBOSSk+Z+g&{4n%EWsSI;RK;zt=~ZYqQtsbI!9RL6DjF5q3!15QvA3 ziboB+F0BWXUN;_Sk+aZ%X}#Yddrn68R+YrhkvID1ZdYZt&~6eiA<01*r-X}iigzk( z>=yotghVG2hV;D;vnE#=?t>s^lJO#3t_1b}4K>&J>6<9=)Jc?^%;9zq^X6@)e zD6$+W5k8Dm*R?}GR(9Er?21kNE3=<|U`QcOBh^5hFeqLz1}wwsJ`@kW=G9GSc}Vyu zI*}INShUuZ1!TNBS2Cw2k_>X7Ufv25&hOsjJ6pXOlK?Mvw2g8<%nq%JifopKN^WPj=Q?ujJO8$Ac@W;>j(;#-4=vE%uhU9!mW7S+N^O?3&oDPflHcI96^%m{gOD$aO$or01xX}^4ZLBBf@IMQB-^D>YYYS~` z6o0OjE|xMoGd0rv;oK(?{}`O=&mWQ5$0C;c#!Z=}(*8=&QbgF4sf?MUS1mL8Rw zVal@qFdOtcz~$HIO+)b>b-OGy4o+WmD$ionGeiO}DLtt^0$k^K!hrdNhjHAvAxFX^ zTKUPwch_O9d)Y6lV8%4OWS7Z_u64lu0|%avk02}`IWj7no}M`}qMo3TYTyUhH688U zo+msu^{4vd7!G|9x<}q`W32U?RZzChYDx;JuK)>-f-iM=23Qq`cXRu%8=#HMO#^L= z(4zg;!UX%Pkf2zJyn7mvV&0sHzbUsBJ{j_Diz+nlKJ2S+T4PI@EK}7=pMO5L?XdYQ z^dYV{wdOoz2(U}~0pv+ephcfPRH73gGd zMG6F`-72EShTW@4>{6w8bEh8ODW!J|o4{ed@oYlm>3WBw+B`uNoH4+g(o-NFEQ%FJ z3PQz8(RX{sXm9qQO4(t03ZzfWH9=)|5d#q@0L@M#$9%?oa%$XWtffq3g!@cburSh3 z$aT|st*Dp56f*iE2(TrU{b4723#bZbcvF@GzZsGqu&<;qF?E z+RUbfYm0#VMdlZN-zyfKon-_tENX6?(tHCr3LNE` z;ClX;sy$cH(Vl1@tx^MNkWYFQh2aA$^O(RlL^GsGgg|OrRkJtWjq#Vtl*rbTXGY3G zfF0nDKdwjyl3Tmq<~_Zj=809lj8dv{*@Y;&WUGBbN1gF$E}fLkZkLIK-Px1igNp3^ zHb>o|8DMj%{<-Zf1|5IP?f~Iu9!o&j>6$?CLa&oRyB!-Gwj!{9r$G^dF?qzWs}Be< zG?Jg*Nho){X}c5X@uIG2woL51QEYsK{KRZ8dU%!Hh~#uAZ+4!bGY+$6k~J(Ihp@AIMKYK6ccjg6dVe(em>4RsqW;1F=^W1g zdB6#M;pIJMF4b-ruEj9M-pgsDgJCJ@$mBYgsVNC!vW`Aux=%NcUiAMiVZeN#vv=}$ zBrRsf2c2B%B`?m}Um8{^+)lce-x)idI;Kr5focqyCHi4^*5vVGBhXx{v3bAg_!l}> zn0E5IsG6O2m}UwHSGe0Nr&W!x&!auMP9iG}{Je3wbG<5~a>ihch9_!&hBkNUHa-Q$ zLE+u5s$#>y5WiI`8A@L5jW{+JZ#vhNsw1+jnU&>}X#i zc@9BSzJiBdWSfoB_C-0p*&Xf` zQ;(E$L~6p0qA2WLSNPz;5U`;a_c_ar#TQrC4f-JMW-{7;2!zqOKL+7h3|sm-Up5E z7#EqtvbPiz1{FJ7bCd|yqTRlzhtwTsaONtq&|J_ia z0l1Nvh)52$Uf!%LJtlFet8ue78I@E{`p~?{LhoGVZp@9F5$A|zTh9+jQ-^j{iA~1v z@)Zz@A)YO$ea~+z$>uS*o}Ks1$1KYT7H28>jNVO&$zUyPBO4>Ld!VfB&MU`}RUe*F zz2>EPygww9`WAog$o64s+;?|7Wv|Ksn*>p&CmSeHo(A);z!Lr5jncPCJ#)t_Uy&6B zU2|bP!jJ5{OXx;%qy9Ggdv0e~FNLcmA$U zR@%yk|B^r>JG?*-$x2^35HD#u7)tEaF_8)j$SCCj!25CV zRwcPDo8nAF+GvW1KEtjh+WM_%>q3wPX=9ZMa9#}YmVunum0x12jEaSNcNcFaH>bFn zC&W6!c!KFpPV17+h3d?-o8D;C34Hpd@#2vjm=kuiB#!svc%unNdYQvhi1+#QL%u9V zk8lyXL*JgxR)WL{q#d~!$4jUCZ5g1h^x9w;!A4Y= z%Y!lycGH0+nSE;t|Ko6U97T{x&$iELtT6PQvcx3^Y#iZ<*F3YTh}5|Jgez$Iu`opxzVUGoY9;WPVwCuN}6Paqq^`{CdGZzc#rHHGsTtTNsWy}o#Tpsaxv zDUj-Q(Y`3Tt=TjDISacGC-PRlKXt(>KA1heAFqr3cl&>P7_@P%9BZ z7x4fWAqR{G$E1cOJ;!!MMex(VWVRLc65_CTR8{=*ED2<`pvP7`Tm)`il7T-3Xb4pyQ95MxbOY9qxx6aXfiO3Lm$ z97&)wIILR^GKwP|yRhn~YLJ%C{BUuQ^u2$8KliuHE4HM&B%?b83$Sf^2LPzYad5et z1R5|~B#Jz$f$7rxQg&2SCvrsDCLyoTrIOadiL=rXiAnbq)=%ak4#O!FWq%~y0vfr7} zaYDnTH|ua`^q9#cQnZzo5DQA_LHHL%3tm^>)h4rRyN`>wNQm5oMQ+|Qfk2!GV`oFM z3h#2H-o>AzciAC(7d(_$h?2bvTuOu4h#2Ig4B6Y?UBdCw?1%n0O3&rqg0xICZ94j~ zbp@KVT25JrUB z$5fTjkA>pkB3{%H6)8x_cYNjK=bq1exK=kOesIroI(-W#%;wiB+GSejb~G}qh0$0c ztNst^v%9D1+s*)dS(6-pA%(GC_0}l@#%YNIwj+?MsyIElDUp z9h$sFd-|&tuo6?WnEv^u+FEvO6JJL21U9*tT5) z(E<);(5+9ixue2>$FmG-!cowg7xwO4Q?OuKxFA9RkmAK?;1foCuKEeFB?R)|yg4(D z`CtZfOcUNGhQ=FwhR(pFRv#x|=S=9USvn|3VMzBiA`d*dT4bOfA3 z4gE6@u#>Nus6L#$p&yY1fQKrfo7T_E$&+-F`{kOq+iZ5xDPZ3sgE~?MaI3R#6_af| zU5Ls^ecJmF<~iL&Iu!Bvc9Gnxj(o>E+Tf^qYVj2flL31C$82lM!%lmdBsr#)8|L_P zC*7xffZ{(iFvK!A7V8B9k!t&?JL)=iZqazN0MpgApk~wG-Mh7sD3m`rcO?Tfrmu=$ z(#9z*xRNC8c0V1LJa*VMuiJ}@FTvARG~)`~!azSx;uqW;-TF2a8cnt74wfztDlZj* z1CuUwGN8}?im0LgCqjh_bm_0;xax53C!i|;j+bP=L6HdalXu`>d=q=n7+l@ber5xtaZwsS_wc0iq$D6cS5VE zpjoB+)umio%|22tr%7(-9SDtgwRxs#=5hMa7y49XX(`ggkg z;F$TDSRe z92+!aSmCc-;hcsboxe!U%UauqCy1J&m;-@{{-NFyLH~)(&BH>2soJ!Jqk%quDK3Yz zhB|2@5PBqLJY?n6TYR5Wy%)56p?OsM8sJA3(?ovN09+;8>iT$&kTUoDAcFA-6&S#{ z9Q2kPy@$^J9)TM7OQLNyJalu7CK{DQg=pRPb`wA_LUNiMPR!F#HX@q;MW`Ucv*4Fw zf-Z?%DKLH+y7=si1Yf*J^;0VSGB}=6lK$x7p6(>s4=BF@i{7S2Q+7cwJ(?+&U55IzxjVsSyIg(L z8ZlIba2FK2+NSmObx;8yMGcpv>w?GL;xo?5=WnCaXAeL~(>$ayCdjkBr>T6s>$`U{ zR)~0VTu}2ZE^cqkXVTy?duwWtrb6N?1}}LM)U864`2(%X2Qt|PjMLYj-I_y1S!Z7{ z7q&1_x_4>kj&CXWQaH_!l%h$!^I@XKaMbAZzsaO3AHPPUBRz^6FN;T$ET;YI9bOOF z53T*Gw;@v{vCdjcsBF}1S>$8t7g;%2+tRZ#E9OUA^)StqWKV+Xw{QcYS)`gBKRSTZ z=Q{G8%p}nV{=5Z(o+%sULPwYbTb;MDj<{(J4{G@X6$0+x6sd%)qDlQahJ6l{&$K8k zIQKAyZ_faitj|TQJypWD_}0*hy0X_8`sM?bDp=`rFW6$5wvO_dtvI3e!Cjw6LZ(&b z4zHh1tl(D(D5=UvW|!wIS|PLwQ|8e|$AgDkJGpz`!kcZ2=d^1NKPPeY0$|hp(rg3f z##k{sQ#1V!a6o> z-??#A*%XxqE1U9G7BK8}ZLE(Vu{NJlH;S6iyyuyQNz7ROqMWg^KHeb(lG*QB3~|im z?Z>)i!X;IiG(U5glSY8yP7uGVYzVmfCBB)(`)wuc2{RxG)*7@fzp*hE-{2-}m=J$| z+e}RVjv0FtFFCS1JG2VSB(=-y`DfeWrhJwV#g&iQ9c2dh47XZSzul;t<^$8B9-k-k z2OBdR-=*=I>DLRNWvF`NY1;k74Ha%cv*2LHgdDN%cRtt0+jH@7^j&^mtgGZJIK5*8 zkSK?Kkx&0Yhl|HWyE!>uJ>0u(JBHuSd<*)@csy8$8my66oe8a4^Bwb9YrnMHOOq7+ z-G8h@U@8XVRSKu`@-%gPB6PYydezexem}I!Nb{)-BA%5Wx&cVF0Pq$eXk1ZKE^I5g zst-WjdmHgl`mdv%a&i;^PwIf9>k^7XF#avV&lF^ zjjn!;ymI9IQHqmsd?Qa#D|whJ6i0C< zp7+kkGda+b5y_;v&-P)E(!D$htjFY^5qOHA0m)p;E!RczK)TFdR=Lp(|FsUP3xLsN zdeDRzZRf3{7CwAHF<-gS{;BcH2CQljyb?a8lcoZ2W!9G)YFxtyhKbVf8FQs|2L&G< z3mzh&Jr3BunYZlhd^^@4f4*Wv@L{ zh1dCX1+rpyUQ}w9(di*BRPNBkiu?S*RnP6qq-V*G{v`1LxSh*J`5<+aF-Jt1Lu>_C z{MTaE^_>u2}hDwRh0m-Bl(IsxaUd9_z-7_L^ICy0jriqJ)dGqeJsoCaX36d*d9+^O$I^ zdygx)lp8(#=eh1lai@7kxSB0skaob~r_M_d9Cwi*B5SQK%|o#~dt9REh1#$m_D} zyt(}O)sQE)V>wvNh?o`>3yi8@h@R`N?wE4h_omp=Q})LpvFX!-pszjlT4Wtn5GlZR zwli(ozWi*qzyiuyB&tXzjG+sxh~DJ6qsmjTZT-)a<6K3jB#4ABUyObaGH{O2i9zfYR;$;z#N%u9d%#UZk-V4AyK7osC&pOPQpbq5xRXi8zFDZSj-7iY_XctPdbv$}?l+~taDcR0I zJM7SWk6g)AhQ|Va)n;rlC_l!iZX!ZA)CdA$yZ#6jeU2G6Zp9rkWcO-t~%86e|^NdgPtmdSl)sv<rV->hbz!e9c~*UktY9M?y6FuPLU5#bH{#bSjFcb4=piPEU5l#dryEs3 zZTrAI7=D;1)dTY24^1v9;MEpxH_6#SHP%Aj*#bw}V(d&1{zJ zMYX;vdmZIY*bd6>&LjK<1_A}75M^z_f*vc8_M6}{3$(?%WF0spcE%;M<0-Y#U|#?L zOW54WG~jW1D+G}4-MZMgQA$-H4Q*iq#H7W^P1j5lM*x(C9%21}rO>w*OE(x=Jb^4m zRx}Jtpj0Yuo=lnyNg z2?G^xF%C7x_+6FX0%Esh^|4Gq1621?H60+~k=~BpGx?H@sOMz3&|5#N?t=3A z?^?hh$T8JJndt#y0u!7a#8ikL$K{T!KcDbpZ-X-e8yZ$z*_c)MRt5$IqyoSaAq*I( zjj~P5Fw_AIi8(Nzhn+6(H0lPWAhE7473ac{b4Ki7FO;&>t1BS%OtpIkgN#6mzLc;}@F@PN`feTWd5&jK`#orcB$PNQy z{oehMMm+&x|7h2xpD@cxt&0uBv!(*Tt=mr)9Bos>->PtAD}*iR+&O?8~!9osrEu>qr`p#`LI4rkk zTwZmY2sTVAaRcTI39w^2la3}?Kq~v%MgsE5YV7F6vP)nDPFnCykFK4Ys3Rmi_+xg1 zsjp}OE0)GXUi1B%h0m8R1|32cmEFhd&dWXV6ZxUSi`gRCfWu;pbnV=~1?o0`vhgXa zDb=;&vI|KME$1fg>i{auo!;*1DpUO-?7hy3exyC9(i~^R<75Ki-DZB<0pY-%T4Eir zn7^tKx`1%QrJ#z3y}=}ADUtCC*(Z>7E1(LXP)OIw6TwPBmMYL^a)QP56pM1ipEUxL zZmBLk|8LKD@W78cM2wJp|8K064>ut2y0zO|O-3JJgxPliuL*EI;PI}bCxUtHoT3oX zWRi6AM@FEua(Fis{0@ZK-FS=nDJMYA!+}8Mrm_`z@H%n9Q*=XtG)UJmhetR8p8%`k zv9h&%?Z~5IGI}C#*Mk7#Jexzw=LSlubn>+~1LEI4_%21;nuCpWQ&Q?=qc!9WdVSB&yc;tF@-aU8Q?z<66$S47=v&G`oiuHqry(QkhHu?5o{ z?Fa^uVF_{0B6&_{?+z zUs~xbQB>v~_zp$y0hXi@7+{4VokP;gIcCT8tsNndv)ox7$E7sD{^x8A-JEzjBP1qQ zJ41`POB4xyj)U@Hwu<8s-)?oSH%8C zL4e#u`!Pt7htedv+%Nj#KzkKV&|Vcmj8b0}QiT;Z7!mlQ#G*FwK7jixcWXZMTF|+y ziX|D(F4sVZ=OB?`jo^~q#(O%A0^i;xq=M*o94z%*qBiQ@TEp$j)+EP`u};6d2WfzO zyHSn!kD~ndJTAv&ms}DO1~{Ti>Sn0Fh4591ru{VerjP;B>=9GIp?0-1*KfvzF{bmq z`zaNMGZ6@y{!CL8K09cGHau+fT+WnkLW0IGj7`V8FUFd8Y?hDnlPuWq%3=T)MdG3` zKmd3ZU6IyL8DIJ(eU18ki!)kKI|bQ8pc2cd77$YJDd{2iN-J2}qv86z6ytYX{xlux zu8-yXgk8W{(16o_6E;$|{9Dvf)!6rn7te2;L0JtGbc4k-6kC{@8D|;-JZ+cBL~&7i zY-}VRz__6mUn#mT=6}kAQu>7EDP=iY4LyNFs6bIyEX%a5B<^%{zE*mnrjq7`#ex>= z0t9=XeD}Qt46kp!*XKYcQqWikYBSmxfE)f85XoN+9A0BD{$&{V78!ljz+QiFY}e4d z44sRb9`e6hZMzS5*Z--4uxWn+A*<_pqxI9=-QtV!^^Bdb&ud-4f;>}PAUG3#xqgYI zPuFi(2H|mwGtq>zdb~KXd`AMDJY4d_Ua5)px+pKVtM!vbUb-(V}Y> zyT)F`qX%?R8O_TtF4rnUah!A~mog0+Vi=!f)@@vQzzN2C3l%0Rt&N`Rb1G%_y2d7d zqX2`(Ct{J?6_^;I@c&!7F`+r1mrVe89I>bV{@xfuQ9z7m2bb~jJ&=QQN|Z7fojAK= z>EsMp8EY=!_)PvGeAXg$JoPqZ*efFyJ^s6F+Qr!W`HtQ6^|J{AP_`imtu$Wmy~r!| zYdh~~!rU8oUE1B+1V4lUlDzJ7H^??Hc>T&=Fo|RP<8OD6fe%yHr$K8;$?c1>g=ZTf>mpo~Q zw>y!7HFIv8Mpf7UdYUgOUhqL8Zvaj>Rb9fI5{Fxrvq2Nc>^F$!YvyroTXTrmI794f zyI@-90MLCR!0TJI(3s>QSC3x&E_Uy{32`+E1$fcXF5Z2xicBCHNj4dd*Pd1rRu(0{Tsee?W@(14iVqH2v`^Htp^=5ti* zuKFIgtNxDW7T$6=xT=#?gP3_Y9F`l+oD%DEPm;B9ZG;8-)taMtXbD1=2MjYuvF!k0 zbc1Uj0&30gvdQhKhdQNAbxF>`amav@o`|?e3Vo+gyC>t${{OUd<&RKyZFq`O2~Cmg zB|?^|#E6EnWGP8yNuiKzGxkQ-=(Q%2E!!kyAF{7w-y-{NjKO4IhAe|Id}rS8r|*CG z=Er%S=PdU*=RWs2_jO&;*Y~AmZcP7A^w>MdIVxpD-kcyrg3N z5fD?tCV#C)$BMn)jL0If@pnI9#r5H-z7$rR(H}c|EAvL?pG;-h)ZE)(#1?5y;fiU$ zf(`a)du0#DhwVqA)AqGwyGdLZ095I{2t9|QFuT6GIirwYy}5!NK5nwGLfe0~uwu`% zHB>4oKGM@-18c#M^$3I;wYv(&wAJ?SG!6Tta`F#>M9-H!a{y5Qj^bv{SLw`xBR)=4 z+DGtUvYzsQpU=}Gq88XWL(LxcvM{^qIho{d&8Of#X*U6$5-tb=p263`_DAsJp>#ioiV1b-JeU8xw2G*Bi+?PXLPOvtUJI&7{YbRi*9O-)S}F6*UFIe2BG= z;p4RnOD)?DT7A-kMoASo4czy+#jwRjf!payI}NVDGq3UttF(l%WM9gfPd)SbZShUW zB?U~}02`4Kld3L#4#v0RPyY4n_yqEeaLMREK(Uh&9_i2bHff@Je}M24-#SuoH_7q= zSC0UYtkHQJCbZO=?!MQa^qgvMNu3IRLH3%~&4~%X6|?7V%c@jgc`3NL?a%OF-?VlQ z;1x|1xC>1w*KGYCii1O!1_xl-{Yv&l<4dr39@E+)M;#9pI&phk^s-yvTfMt$NOecY zmw+>d$hbI<3EO^XLaDn?aGLsd(0-WqvMA1|CfYh3{-c%U~X2knzz4a031A>dnc>=a3V#`7uU4v znHN&h)cj!6ZN~_=C>=J(TrYHdu|zAPzz~)$8%g{t+M$cQxDIw_rhhY*8;UkwGhJ97 zK*rj#0O$iv5wndwf*zo{2JPK@k3?Zm<9>NU#^5Mx@b++4OIAsI!Mbrt@$r1IoZ;~i z20$9DTZ!^+F~D(pB$UdzEGcW&htR7pR%dQB@$}{{ z4sSh`Gz5avixKqppZ6{6v3{LaC4e_VzUAUT)Li2r@e`6TpslcNYV*I)g4R!~ueJM5 z1FaJ{;0Ey<9yiehhXd&^G3?#Zk_%gt_?L-Xo(`nuS@zt;7Mb}{cHUmZBjU1_tdDa2 zs|%rG49Xxfx$OXZ$=Cgw`143J)XS-z>zkArw`0PeTr%+ot7Gy`tL z8gs=A2a#b$R{sio_`tx>@_{ajygFtgrkjHU%+rBKKNs;~+xlljPFg=7`L*;);ym_I z)iRa$Xea`ssU=6)b*S865vIeaqJZHlgo|2AHE*p`>-=VQX+}%auBI zC*=W97`hYUf*4lV^l!&Ox&5odn7zRs%dwwb$Ws`{IILmV--jKJ$D&_=&s!eTj8R5bkmZEx1c(=F%Fj0QhsQT zHgC$~82)O^ny}&zZ7%h=I%W<{$LCISQ|Fj^D{ln)&?smH9~xSObuF4C+CcshG_n&Z zU|)&AC+FWTEI*@GZY=eUt@Oqyrbi)Ea2|E@Rxl$v_-*C5086(JH;<9dq<_fiN}M!w z9UBseO~XDCZ|BXc`_sHR}~S-+C9)V5MvG_()j(ZGH3kBz}Ld z@4#!;2kIyja;7?N+SPfzuY)r-#R|x%HD6urW3M6d0$G0K*@WJAv`<1R>b#Y>L4Ed>WZCw)&Y<$K zQv5hoFD73?4Qj`kYFJ_%`T^61rs3xIcbk{ir_>ib=DNCT6?Qv`Uz^@28V_dwjTY5+ zfnVi@RTOHE4r-wNrfb|iRRBjhvj=qVQ;@+`{?eG|pR@ez=W=`;C0h;?EL|Pe7i=Tn z)PA*@al8P<>oSI~lye1L=04r5&|nwU%{*hiw{(;5)>+f?@u}E7(yx44`zzE^_lq8a zSnrjQq+&4=xSjm29Q^hf8|V^JeNCU+lWiivP6v!OXF<=-E_c7)be6S-V(1Kh!X(rs z$ID}=V!v#7ns{*8%erx3x_|hJsr&4JBD|>61r+L-P{_b%$55mN=Be;V`FUtBwrX~@ zryjnATx-mSI6;yO4*sr;9Q2x(;3k7fq+gufiQ_7xTC#W1S9RxNlQ0Y60kLPp*s#}! zHyJOg7@Q4#=Fzz03`-=)Q5r^Pg5>y`ySBX+_WNHZN-SgK6fyfwrX$F@Kcg_k^BWuF zp>031iP9(-jT9__fK_kG=)yU?y_NBk2dX=?N~XTC7jUtiYy;p>j{C)pl|zLARbu!! z>^;YSmT8T%+j8>xo`}&GSH4lV&?HW8#b;TJRhd;7^x~`yxj1}vOv1%jF4TzuZ<%KM zWZTT==Iw7*c2*!qb2Vm7)WQ zf(7@z4@l_JDi$t|MbC}D`1K_6mHaucV(G(Koo)di_wUYjb5%-;l9Ize_48KqV#1?@ zZ!gh$9fYTLdgOdL&uR>W!$&W%2kONrtCbGy_`F@etp`R`tzZeVqbmO>k-Klm_Iid zkc*>xxqK|2+@|6{T-El#_=gS9&=JJ>q^p+2{Az22OtYT@?FR&LU7GQCn$6ZR8Qy?8 z=3t?#wn0`4K3X48WM$R;UV`E_>6iLCjfZSXCG!aU_PG$bTEnE|=z04Vg{c9Rup+(5 z|FLUcWR8DlKGK;jY3zr^)U{H<>BsC-WqM^x9i3Ql-C|z5{YMdf`l=E$kGYR3(0kBO z7%+tWyP6^0e7Ax8hxwb2h~maVAe)S4A6|I#p~~tLXEuy`nzSipIeiL!Sx`3qgzHT0 z$&TK<&mIcM_O>r|%qLkYah)0)0?%{w-?6zfE@B$;Wi5(kN_(h}-mGF<3;-mfXM1gU) z!zA<-I3~Wv=kE+L%x!wTkyQsLrL;9zE58+dNS)+77bBi_Ei`CF)XzaxH?pRpV^|_4 zJVw#~`rV+lxL{W?mXVS5{-pmu?Z(5z`!_wsQHXTI=7Vcj3cN^-JNX$8S@LLI zoR~~^&#UPB6?GMyT_|-Sta*kzDM*eez`_TA5W{GqVb`-_9FoK&{WPbye|S;;NcQqZ z6|dR~$!UOC82lWm#)V2fQJz5AYXV!+S!n+7burfELD2>j2_1To159P{=d3i9lqsI2 zq<0=Xg^ZZmVVnN@Fw++yDN;@qo`y#r+IAnIpxVFr-k(zbfy`5F3UN0M1DAm|pB*-g zn!Nm8X$9LGkaNy8(?1GINcfQ6-7r`mnmW=}a@`9PBg%X4oo_L;$e0-a5gTN<0$fo% z=EQ*7q(8S%4OSs6Xht3$CuC9*7PPbcij76I<6;cT!|-IypnjC`sq}DK-o&aw!>7b$ z6mK*1_gn(_RD04tUdUvI53HIWs^-M0WMtd6sC8Tmz5cKSg{9b(5j`bw$yC-wEp`G^ zdi`7KIP)8LeEfrV^~L9Hg^s^w~YQQedk3Rl^82eqwMO>MxR`W&((j)2}-Hz<}AHAsdW@K zAmK=}RZY>ho4LeoaM_shL7y-70j-@}P|{YLE+4%ms_zO1b}9y^3K&bNO=UdT_xs}LPkIv6ig}T7OU=&B!F|%Z5$@E_fiJrLXo++i?f&UyKP>(k7 zlUk0TKbwjw%+gXOC*!NQ7g$f1|2J?-IDMi9ea7Y0H%1dH4Q-wgK~^qzTe+L&nw^(C zbYmE>_bSgFjG+)AomS6W=HJVotnNu}axGBcv%378MZ6aV)(P!uEzAJ;_KGrTd`QYt zLwp{ygTqnS&CHg*fFQw5%xE{Vr>UeR+CG;wFJ|CnAUOTt&pGchy6d;OowaQXLLTk#*0|OAL$f z<`tfKsZpXSC&~=aw;5k#f*@2WyA(cSIbdq08Z6T0^gKCp6A6jbDaK4*DouE%o&GIsCzjkez) zVhb{hE?FIV-RSg#3t|Kac^2ssj)uW|1|G<#49U@qCT&kg&*$ciO}HZ4AXehSEDnqy zv~LbF{1{}aeQjS(Fvi6al8j|EkyAcSm~djRr$lnFQq3w}u|kYAC;cy5fj#(M({7O5 zySDG8DTai>xFhd}9ycA&LnIvN{I4 zPe8JT*N#G=*XOspRH2>}Bcey;?Xe3GBkjp!DEgo3y-z6KTezjy`KAyngiLjvWP2F% zM`(d}xDHpK$`#1hOGjZ2f%u2ERZjRo*huE_ItkrA$g&M+muhc|1hB1Hr1O#YG$06t z0LKAlXu+ha48)SI5q@WA07&J2mH}G9??Dg%fmnb_As21wD&o(8ViTk-{@=G4O(2j$ lfHe63tN*va|5#w<@Q65X-$`_Yz6}KYG}Rue<|)7Q`w!|94AuYu literal 0 HcmV?d00001 diff --git a/doc-site/components/nve-navigation-card.md b/doc-site/components/nve-navigation-card.md new file mode 100644 index 00000000..24693c38 --- /dev/null +++ b/doc-site/components/nve-navigation-card.md @@ -0,0 +1,139 @@ +--- +layout: component +--- + + + +```html + + + +``` + + + +## Eksempler + +### Tittel + +Tittel vises alltid øverst i kortet, og under ikon dersom ikon er lagt inn med `iconPath`. Tittelen er det viktigste innholdet og bør være kort og beskrivende. + + + +```html + +``` + + + +### Med tilleggstekst + +Du kan legge til en ekstra tekst under hovedlenkens overskrift ved å bruke `additionalText`-egenskapen. Både overskriften og den tilhørende teksten blir lest opp av skjermlesere, så sørg for at teksten er kortfattet og lett å forstå. + + + +```html + +``` + + + +### Med lang tilleggstekst + +Tilleggstekst vises maksimalt på 3 linjer. Dersom teksten er lengre, kuttes den av med `...`. + + + +```html + +``` + + + +### Med ikon fra ikon-fil + +Ikonsettet som skal brukes er de som hos NVE kalles [Illustrasjonsikoner](https://nve.frontify.com/d/n2ujvoktZ3dr/nve-profil#/ikoner-illustrasjoner/illustrasjonsikoner-1). Ikon skal **ikke** kombineres med ekstratekst (`additionalText`). Dersom både `additionalText` og `iconPath` er brukt, vil ikonet vises og `additionalText` skjules. + + + +```html + + + + +``` + + + +## Bruk med klient-side routing i SPA-applikasjoner + +Når man benytter klientside-routing, for eksempel med `routerLink` (Vue) eller `Link` (React), genereres et eget ``-element av rammeverket. +I disse tilfellene blir `nve-navigation-card` pakket inn i en ``. For å unngå ugyldig HTML-struktur med ``-elementer inni hverandre, sjekker `nve-navigation-card` derfor om dets direkte forelder er et ``. Hvis dette er tilfelle, rendres kortet som et `
` i stedet for et ``. + +På denne måten beholdes mest funksjonalitet og styling fra `nve-navigation-card`, samtidig som man unngår semantiske og tekniske problemer med nestede lenker. + +**Eksempel i Vue:** + +```vue + + + +``` + +**Eksempel i React:** + +```jsx + + + +``` + +## Retningslinjer + +- **Bruk `nve-navigation-card`** for hovednavigasjon på oversikts- eller inngangssider der brukeren skal velge mellom flere hovedtemaer eller seksjoner. +- **Bruk [`nve-link-card`](/components/nve-link-card)** for sekundære lenker, handlinger, eller når du trenger støtte for eksterne lenker, nedlasting eller e-post. +- Ikke bruk `nve-navigation-card` for valg, ekspanderbare paneler eller andre interaktive kort – bruk dedikerte komponenter for dette. +- Komponentet har både minimum og maksimum høyde for konsistent layout. +- **Ikon skal ikke kombineres med ekstratekst** (`additionalText`). Hvis `ikonPath` er lagt inn, vises ikke tilleggstekst. +- **Kun illustrasjonsikoner skal brukes som ikon**. Disse finnes i [NVE Frontify – Illustrasjonsikoner](https://nve.frontify.com/d/n2ujvoktZ3dr/nve-profil#/ikoner-illustrasjoner/illustrasjonsikoner-1/nedlasting). Illustrasjonsikonene illustrerer NVEs virksomhetsområder og er detaljrike. De skal ikke brukes for å indikere navigasjon eller handling, og fungerer dårlig i små størrelser. +- Bruk alltid komponenten i et grid- eller flex-oppsett for å sikre riktig spacing og responsivitet. + +## Tilgjengelighet + +- **Tittel (`title`-feltet) rendres som `

`** for å sikre god semantikk og tilgjengelighet. Dette gjør det enklere for brukere med skjermleser å navigere mellom hovedseksjoner på siden. Hvis du har flere navigation-cards på samme side, vil de automatisk utgjøre en oversiktlig seksjonsstruktur. +- Komponentet rendres som `` hvis det ikke ligger inni en lenke, og som `
` hvis det pakkes inn i en `` (for å unngå nestede lenker). +- Ikon og tilleggstekst skal **ikke** vises samtidig. +- Ekstratekst trunkeres automatisk etter 3 linjer for å sikre at kortet ikke blir for høyt og at innholdet er lett å skanne. +- Understrek (text-decoration) på wrapper-`` fjernes av designsystemet ved hjelp av en regel i `global.css` (`a:has(nve-navigation-card) { text-decoration: unset; }`). Dette sikrer at det ikke vises uønsket understrek på lenker som wrapper `nve-navigation-card`, siden webkomponenter bruker Shadow DOM og ikke kan påvirke wrapperens styling direkte. diff --git a/public/css/global.css b/public/css/global.css index 7d7fac21..a12510d0 100644 --- a/public/css/global.css +++ b/public/css/global.css @@ -17,8 +17,9 @@ a { color: var(--color-neutrals-foreground-primary, #0d0d0e); } -/* Brukes for å fjerne standard linje i som ikke er i Shadow-DOMen som f.eks de som skal wrappe */ -a:has(nve-link-card) { +/* Brukes for å fjerne standard linje i som ikke er i Shadow-DOMen som f.eks de som skal wrappe og */ +a:has(nve-link-card), +a:has(nve-navigation-card) { text-decoration: unset; } diff --git a/src/components/nve-navigation-card/nve-navigation-card.component.ts b/src/components/nve-navigation-card/nve-navigation-card.component.ts new file mode 100644 index 00000000..d09278d0 --- /dev/null +++ b/src/components/nve-navigation-card/nve-navigation-card.component.ts @@ -0,0 +1,88 @@ +import { LitElement, html, nothing } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { ifDefined } from 'lit/directives/if-defined.js'; +import { INveComponent } from '@interfaces/NveComponent.interface'; +import styles from './nve-navigation-card.styles'; +import '../nve-icon/nve-icon.component'; + +/** + * Denne komponenten er ment å brukes som hovednavigasjon på sider, for eksempel transportside (i motsetning til `nve-link-card`, som er mindre og brukes der navigasjonen ikke er hovedfokus). + * + * Komponenten brukes i grid-oppsett, har minimum og maksimum høyde, og støtter enten ikon (SVG/PNG, kun dekorativt, angis via path) eller tilleggstekst (maks 3 linjer, trunkeres automatisk) – aldri begge samtidig. + * + * Ikon angis med `iconPath`-prop og rendres automatisk med aria-hidden. Bruk kun illustrasjonsikoner fra NVE. + * + * @csspart base Topp-elementet for kortet + * @csspart title Tittel på kortet + * @csspart icon Ikonet øverst i kortet + * @csspart content Innholdet i kortet + * @csspart additionalText Ekstratekst under tittel + */ +@customElement('nve-navigation-card') +export default class NveNavigationCard extends LitElement implements INveComponent { + /** Test ID som kan brukes i testing og sporing */ + @property({ type: String }) testId = ''; + + /** Tittel som vises øverst på kortet (må settes) */ + @property({ type: String }) title = ''; + + /** + * Lenke for navigasjon (må settes for at kortet skal være klikkbart). + * Hvis du bruker komponenten uten å wrappe den i et rammeverks-router-element (f.eks. Vue Router eller React Router), må `href` settes. + */ + @property({ type: String }) href = ''; + + /** Ekstratekst som vises under tittelen (maks 3 linjer, trunkeres) */ + @property({ type: String }) additionalText: string | undefined = undefined; + + /** Path til ikon som vises øverst i kortet (dekorativt) */ + @property({ type: String }) iconPath: string | undefined = undefined; + + static styles = [styles]; + + /** + * Genererer innholdet i kortet. + * Viser ikon (hvis iconPath), tittel og ev. additionalText. + */ + private renderContent() { + return html` + + hvis ikke parent er en lenke, + * ellers som
for å unngå nestede lenker (SPA-routing). + */ + render() { + const isParentLink = + this.parentElement?.tagName.toLowerCase() === 'a' || this.parentElement?.getAttribute('role') === 'link'; + + if (isParentLink) { + return html` + + `; + } + + return html` + + ${this.renderContent()} + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'nve-navigation-card': NveNavigationCard; + } +} diff --git a/src/components/nve-navigation-card/nve-navigation-card.styles.ts b/src/components/nve-navigation-card/nve-navigation-card.styles.ts new file mode 100644 index 00000000..6faa622c --- /dev/null +++ b/src/components/nve-navigation-card/nve-navigation-card.styles.ts @@ -0,0 +1,83 @@ +import { css } from 'lit'; + +export default css` + .navigation-card { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + box-sizing: border-box; + width: 100%; + height: stretch; + min-height: var(--sizing-card-link-card-height-x-large-min-height); + max-height: var(--sizing-card-link-card-height-x-large-max-height); + padding: var(--spacing-2x-large); + gap: var(--spacing-medium); + border-radius: var(--border-radius-small); + border: var(--border-width-stronger) solid transparent; + background: var(--color-neutrals-background-primary); + cursor: pointer; + text-decoration: none; + transition: border-color 0.2s ease; + } + + .navigation-card__content { + display: flex; + flex-direction: column; + gap: var(--spacing-medium); + } + + .navigation-card__icon { + width: var(--sizing-icon-3x-large); + height: var(--sizing-icon-3x-large); + } + + .navigation-card__title { + font: var(--typography-heading-small); + color: var(--color-neutrals-foreground-primary); + transition: color 0.2s ease; + margin: 0; + } + + .navigation-card__additional-text { + font: var(--typography-body-compact-medium-compact); + color: var(--color-neutrals-foreground-subtle); + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + text-overflow: ellipsis; + margin: 0; + } + + .navigation-card__arrow { + flex-shrink: 0; + color: var(--color-brand-foreground-primary); + margin-right: var(--spacing-x-small); + } + + .navigation-card:hover { + border-color: var(--color-neutrals-border-subtle); + } + + .navigation-card:hover .navigation-card__title { + color: var(--color-brand-foreground-primary); + text-decoration-line: underline; + text-decoration-style: solid; + text-decoration-thickness: 5%; + text-underline-offset: 16%; + } + + .navigation-card:active { + border-color: var(--color-neutrals-border-mute); + } + + .navigation-card:active .navigation-card__title { + text-decoration-thickness: 10%; + text-underline-offset: 16%; + } + + .navigation-card:focus { + outline: var(--color-interactive-primary-border-focus) solid 2px; + } +`; diff --git a/src/nve-designsystem.ts b/src/nve-designsystem.ts index e334bb66..e1e3457f 100644 --- a/src/nve-designsystem.ts +++ b/src/nve-designsystem.ts @@ -24,6 +24,7 @@ export { default as NveLinkCard } from './components/nve-link-card/nve-link-card export { default as NveMenu } from './components/nve-menu/nve-menu.component'; export { default as NveMenuItem } from './components/nve-menu-item/nve-menu-item.component'; export { default as NveMessageCard } from './components/nve-message-card/nve-message-card.component'; +export { default as NveNavigationCard } from './components/nve-navigation-card/nve-navigation-card.component'; export { default as NveOption } from './components/nve-option/nve-option.component'; export { default as NvePopup } from './components/nve-popup/nve-popup.component'; export { default as NveRadio } from './components/nve-radio/nve-radio.component'; From 1327332240cd3eef3079522d00f228ae6369250d Mon Sep 17 00:00:00 2001 From: Ole Martin Borge Date: Thu, 5 Mar 2026 15:05:38 +0100 Subject: [PATCH 2/5] test(nve-navigation-card): legg til tester for komponenten --- src/components/nve-icon/offline-icons.ts | 1 + .../nve-navigation-card.test.ts | 91 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 src/components/nve-navigation-card/nve-navigation-card.test.ts diff --git a/src/components/nve-icon/offline-icons.ts b/src/components/nve-icon/offline-icons.ts index 3b95c317..19b3cf48 100644 --- a/src/components/nve-icon/offline-icons.ts +++ b/src/components/nve-icon/offline-icons.ts @@ -7,6 +7,7 @@ * velg aktuelt ikon, velg 24px størrelse og svart farge og "copy icon to clipboard" */ export const offlineIcons: { [key: string]: string } = { + arrow_forward: ``, check_circle: '', chevron_backward: ``, diff --git a/src/components/nve-navigation-card/nve-navigation-card.test.ts b/src/components/nve-navigation-card/nve-navigation-card.test.ts new file mode 100644 index 00000000..221fd20e --- /dev/null +++ b/src/components/nve-navigation-card/nve-navigation-card.test.ts @@ -0,0 +1,91 @@ +import { afterAll, describe, expect, it } from 'vitest'; +import { fixture, fixtureCleanup } from '@open-wc/testing'; +import { html } from 'lit'; +import NveNavigationCard from './nve-navigation-card.component'; + +if (!customElements.get('nve-navigation-card')) { + customElements.define('nve-navigation-card', NveNavigationCard); +} + +describe('nve-navigation-card', () => { + afterAll(() => fixtureCleanup()); + + it('should render with default properties', async () => { + const el = await fixture(html``); + expect(el.title).toBe('Tittel'); + expect(el.iconPath).toBeUndefined(); + expect(el.additionalText).toBeUndefined(); + }); + + it('should render correct title as h2', async () => { + const el = await fixture(html``); + const h2 = el.shadowRoot?.querySelector('h2[part="title"]'); + expect(h2).not.toBeNull(); + expect(h2?.textContent).toContain('Tittel'); + }); + + it('should render additionalText when iconPath is not set', async () => { + const el = await fixture( + html`` + ); + const p = el.shadowRoot?.querySelector('p[part="additional-text"]'); + expect(p).not.toBeNull(); + expect(p?.textContent).toContain('Ekstra tekst'); + }); + + it('should render icon when iconPath is set and hide additionalText', async () => { + const el = await fixture( + html`` + ); + const img = el.shadowRoot?.querySelector('img[part="icon"]'); + const p = el.shadowRoot?.querySelector('p[part="additional-text"]'); + expect(img).not.toBeNull(); + expect(img?.getAttribute('src')).toBe('test.svg'); + expect(img?.getAttribute('aria-hidden')).toBe('true'); + expect(img?.getAttribute('alt')).toBe(''); + expect(p).toBeNull(); + }); + + it('should render as when not wrapped in link', async () => { + const el = await fixture( + html`` + ); + const a = el.shadowRoot?.querySelector('a[part="base"]'); + expect(a).not.toBeNull(); + expect(a?.getAttribute('href')).toBe('/test'); + }); + + it('should render as
when parent is ', async () => { + const wrapper = await fixture(html``); + const el = wrapper.querySelector('nve-navigation-card') as NveNavigationCard; + const div = el.shadowRoot?.querySelector('div[part="base"]'); + expect(div).not.toBeNull(); + }); + + it('should render nve-icon arrow_forward', async () => { + const el = await fixture(html``); + const icon = el.shadowRoot?.querySelector('nve-icon.navigation-card__arrow'); + expect(icon).not.toBeNull(); + expect(icon?.getAttribute('name')).toBe('arrow_forward'); + expect(icon?.getAttribute('aria-hidden')).toBe('true'); + expect(icon?.getAttribute('style')).toContain('--icon-size: 24px;'); + }); + + it('should set testid attribute', async () => { + const el = await fixture( + html`` + ); + const a = el.shadowRoot?.querySelector('[part="base"]'); + expect(a?.getAttribute('testid')).toBe('123'); + }); + + it('should render only icon and title when iconPath is set', async () => { + const el = await fixture( + html`` + ); + const img = el.shadowRoot?.querySelector('img[part="icon"]'); + const p = el.shadowRoot?.querySelector('p[part="additional-text"]'); + expect(img).not.toBeNull(); + expect(p).toBeNull(); + }); +}); From 170bb61022ca5c203e8304e851a0fa178e83ece4 Mon Sep 17 00:00:00 2001 From: Ole Martin Borge Date: Thu, 5 Mar 2026 15:56:26 +0100 Subject: [PATCH 3/5] =?UTF-8?q?feat(nve-navigation-card):=20legg=20til=20s?= =?UTF-8?q?t=C3=B8tte=20for=20clickAction-varianter=20og=20animasjon=20p?= =?UTF-8?q?=C3=A5=20ikon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc-site/components/nve-navigation-card.md | 51 ++++++++++++++++++ src/components/nve-icon/offline-icons.ts | 1 + .../nve-navigation-card.component.ts | 53 ++++++++++++++++--- .../nve-navigation-card.styles.ts | 19 +++++++ .../nve-navigation-card.test.ts | 51 ++++++++++++++++-- 5 files changed, 163 insertions(+), 12 deletions(-) diff --git a/doc-site/components/nve-navigation-card.md b/doc-site/components/nve-navigation-card.md index 24693c38..272873a3 100644 --- a/doc-site/components/nve-navigation-card.md +++ b/doc-site/components/nve-navigation-card.md @@ -89,6 +89,57 @@ Ikonsettet som skal brukes er de som hos NVE kalles [Illustrasjonsikoner](https: +### Klikkhandlinger + +Man kan velge mellom 3 klikk-handlinger ved bruk av `clickAction`-egenskapen. Handlingen bestemmer både funksjonaliteten og hvilket ikon som vises i kortet. Standardverdi er `internal`. + +#### Intern + +`internal` håndterer intern routing. Brukes når brukeren skal navigere innenfor samme applikasjon. Sett `href` for å definere URL-en. + + + +```html + + +``` + + + +#### Ekstern + +`external` åpner en ekstern side. Automatisk settes `target="_blank"` på ``-elementet. Se anbefalinger for eksterne lenker i seksjonen [Tilgjengelighet](#tilgjengelighet). + + + +```html + + +``` + + + +#### Nedlasting + +`download` starter nedlasting av en fil. Legger til `download`-attributtet slik at nettleseren forstår at lenken ikke skal navigere videre. Dersom du ønsker spesifikk filhåndtering, kan du implementere det selv med en vanlig `onClick`-metode (avhengig av rammeverket du bruker). + + +

Hvis filen ligger på en annen origin enn applikasjonen, vil lenken ikke laste ned filen, men i stedet åpne adressen fra href-attributtet. I slike tilfeller må du selv håndtere nedlastingen med onClick.

+
+ + + +```html + +``` + + + ## Bruk med klient-side routing i SPA-applikasjoner Når man benytter klientside-routing, for eksempel med `routerLink` (Vue) eller `Link` (React), genereres et eget `
`-element av rammeverket. diff --git a/src/components/nve-icon/offline-icons.ts b/src/components/nve-icon/offline-icons.ts index 19b3cf48..9ec17c13 100644 --- a/src/components/nve-icon/offline-icons.ts +++ b/src/components/nve-icon/offline-icons.ts @@ -13,6 +13,7 @@ export const offlineIcons: { [key: string]: string } = { chevron_backward: ``, chevron_forward: ``, close: ``, + download: ``, error: ``, info: ``, lock: ``, diff --git a/src/components/nve-navigation-card/nve-navigation-card.component.ts b/src/components/nve-navigation-card/nve-navigation-card.component.ts index d09278d0..49798ecb 100644 --- a/src/components/nve-navigation-card/nve-navigation-card.component.ts +++ b/src/components/nve-navigation-card/nve-navigation-card.component.ts @@ -12,11 +12,11 @@ import '../nve-icon/nve-icon.component'; * * Ikon angis med `iconPath`-prop og rendres automatisk med aria-hidden. Bruk kun illustrasjonsikoner fra NVE. * - * @csspart base Topp-elementet for kortet + * @csspart navigation-card Topp-elementet for kortet * @csspart title Tittel på kortet - * @csspart icon Ikonet øverst i kortet + * @csspart leading-icon Ikonet øverst i kortet * @csspart content Innholdet i kortet - * @csspart additionalText Ekstratekst under tittel + * @csspart additional-text Ekstratekst under tittel */ @customElement('nve-navigation-card') export default class NveNavigationCard extends LitElement implements INveComponent { @@ -35,11 +35,30 @@ export default class NveNavigationCard extends LitElement implements INveCompone /** Ekstratekst som vises under tittelen (maks 3 linjer, trunkeres) */ @property({ type: String }) additionalText: string | undefined = undefined; + /** Definerer hva som skjer når kortet klikkes: 'internal' (intern lenke), 'download' (nedlasting), 'external' (ekstern lenke) */ + @property({ type: String }) clickAction: 'internal' | 'download' | 'external' = 'internal'; + /** Path til ikon som vises øverst i kortet (dekorativt) */ @property({ type: String }) iconPath: string | undefined = undefined; static styles = [styles]; + /** + * Returnerer ikonnavnet som vises på kortet basert på clickAction. + */ + private getIconName() { + switch (this.clickAction) { + case 'internal': + return 'arrow_forward'; + case 'download': + return 'download'; + case 'external': + return 'open_in_new'; + default: + return 'arrow_forward'; + } + } + /** * Genererer innholdet i kortet. * Viser ikon (hvis iconPath), tittel og ev. additionalText. @@ -48,14 +67,25 @@ export default class NveNavigationCard extends LitElement implements INveCompone return html` - + ${this.renderContent()} `; diff --git a/src/components/nve-navigation-card/nve-navigation-card.styles.ts b/src/components/nve-navigation-card/nve-navigation-card.styles.ts index 6faa622c..ca3cd84e 100644 --- a/src/components/nve-navigation-card/nve-navigation-card.styles.ts +++ b/src/components/nve-navigation-card/nve-navigation-card.styles.ts @@ -54,6 +54,25 @@ export default css` flex-shrink: 0; color: var(--color-brand-foreground-primary); margin-right: var(--spacing-x-small); + transition: + margin-left 300ms cubic-bezier(0, 0, 0.2, 1), + margin-right 300ms cubic-bezier(0, 0, 0.2, 1); + } + + .navigation-card:hover .navigation-card__arrow { + margin-left: var(--spacing-2x-small); + margin-right: var(--spacing-2x-small); + transition: + margin-left 300ms cubic-bezier(0, 0, 0.2, 1), + margin-right 300ms cubic-bezier(0, 0, 0.2, 1); + } + + .navigation-card:active .navigation-card__arrow { + margin-left: var(--spacing-x-small); + margin-right: 0; + transition: + margin-left 100ms cubic-bezier(0, 0, 0.2, 1), + margin-right 100ms cubic-bezier(0, 0, 0.2, 1); } .navigation-card:hover { diff --git a/src/components/nve-navigation-card/nve-navigation-card.test.ts b/src/components/nve-navigation-card/nve-navigation-card.test.ts index 221fd20e..4262a9c5 100644 --- a/src/components/nve-navigation-card/nve-navigation-card.test.ts +++ b/src/components/nve-navigation-card/nve-navigation-card.test.ts @@ -37,7 +37,7 @@ describe('nve-navigation-card', () => { const el = await fixture( html`` ); - const img = el.shadowRoot?.querySelector('img[part="icon"]'); + const img = el.shadowRoot?.querySelector('img[part="leading-icon"]'); const p = el.shadowRoot?.querySelector('p[part="additional-text"]'); expect(img).not.toBeNull(); expect(img?.getAttribute('src')).toBe('test.svg'); @@ -50,7 +50,7 @@ describe('nve-navigation-card', () => { const el = await fixture( html`` ); - const a = el.shadowRoot?.querySelector('a[part="base"]'); + const a = el.shadowRoot?.querySelector('a[part="navigation-card"]'); expect(a).not.toBeNull(); expect(a?.getAttribute('href')).toBe('/test'); }); @@ -58,7 +58,7 @@ describe('nve-navigation-card', () => { it('should render as
when parent is ', async () => { const wrapper = await fixture(html``); const el = wrapper.querySelector('nve-navigation-card') as NveNavigationCard; - const div = el.shadowRoot?.querySelector('div[part="base"]'); + const div = el.shadowRoot?.querySelector('div[part="navigation-card"]'); expect(div).not.toBeNull(); }); @@ -75,7 +75,7 @@ describe('nve-navigation-card', () => { const el = await fixture( html`` ); - const a = el.shadowRoot?.querySelector('[part="base"]'); + const a = el.shadowRoot?.querySelector('[part="navigation-card"]'); expect(a?.getAttribute('testid')).toBe('123'); }); @@ -83,9 +83,50 @@ describe('nve-navigation-card', () => { const el = await fixture( html`` ); - const img = el.shadowRoot?.querySelector('img[part="icon"]'); + const img = el.shadowRoot?.querySelector('img[part="leading-icon"]'); const p = el.shadowRoot?.querySelector('p[part="additional-text"]'); expect(img).not.toBeNull(); expect(p).toBeNull(); }); + + describe('clickAction', () => { + it('should render with clickAction="internal"', async () => { + const el = await fixture( + html`` + ); + const a = el.shadowRoot?.querySelector('a.navigation-card'); + expect(a).not.toBeNull(); + expect(a?.getAttribute('href')).toBe('/intern'); + expect(a?.hasAttribute('download')).toBe(false); + expect(a?.getAttribute('target')).toBeNull(); + const icon = el.shadowRoot?.querySelector('nve-icon.navigation-card__arrow'); + expect(icon?.getAttribute('name')).toBe('arrow_forward'); + }); + + it('should render with clickAction="download"', async () => { + const el = await fixture( + html`` + ); + const a = el.shadowRoot?.querySelector('a.navigation-card'); + expect(a).not.toBeNull(); + expect(a?.getAttribute('href')).toBe('/fil.pdf'); + expect(a?.hasAttribute('download')).toBe(true); + expect(a?.getAttribute('target')).toBeNull(); + const icon = el.shadowRoot?.querySelector('nve-icon.navigation-card__arrow'); + expect(icon?.getAttribute('name')).toBe('download'); + }); + + it('should render with clickAction="external"', async () => { + const el = await fixture( + html`` + ); + const a = el.shadowRoot?.querySelector('a.navigation-card'); + expect(a).not.toBeNull(); + expect(a?.getAttribute('href')).toBe('https://nve.no'); + expect(a?.hasAttribute('download')).toBe(false); + expect(a?.getAttribute('target')).toBe('_blank'); + const icon = el.shadowRoot?.querySelector('nve-icon.navigation-card__arrow'); + expect(icon?.getAttribute('name')).toBe('open_in_new'); + }); + }); }); From 54aabec374ae3afe6b63522f28ae365ec988273d Mon Sep 17 00:00:00 2001 From: Ole Martin Borge Date: Fri, 6 Mar 2026 14:54:26 +0100 Subject: [PATCH 4/5] =?UTF-8?q?fix(nve-navigation-card):=20vis=20testid-at?= =?UTF-8?q?tributt=20kun=20n=C3=A5r=20testId=20har=20verdi,=20ryddet=20opp?= =?UTF-8?q?=20i=20CSS=20transitions=20med=20variabler,=20og=20oppdatert=20?= =?UTF-8?q?dokumentasjon=20og=20eksempler=20etter=20PR-tilbakemeldinger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../theme/components/NveTableDemo.vue | 110 +++++++++--------- doc-site/components/nve-navigation-card.md | 37 +++--- .../assets}/nve-illustrasjoner-ikon-flom.png | Bin .../nve-illustrasjoner-ikon-vannkraft.png | Bin .../nve-navigation-card.component.ts | 14 +-- .../nve-navigation-card.styles.ts | 26 ++--- 6 files changed, 92 insertions(+), 95 deletions(-) rename doc-site/{assets/images => public/assets}/nve-illustrasjoner-ikon-flom.png (100%) rename doc-site/{assets/images => public/assets}/nve-illustrasjoner-ikon-vannkraft.png (100%) diff --git a/doc-site/.vitepress/theme/components/NveTableDemo.vue b/doc-site/.vitepress/theme/components/NveTableDemo.vue index b93978d6..2d592bc9 100644 --- a/doc-site/.vitepress/theme/components/NveTableDemo.vue +++ b/doc-site/.vitepress/theme/components/NveTableDemo.vue @@ -187,61 +187,63 @@ const toggleColumn = (header: TableHeader) => {