|
| 1 | +import { useState, useMemo } from 'react'; |
| 2 | +import { |
| 3 | + Heading, |
| 4 | + VStack, |
| 5 | + Box, |
| 6 | + Search, |
| 7 | + Label, |
| 8 | + Tag, |
| 9 | + Accordion, |
| 10 | + Table, |
| 11 | +} from '@navikt/ds-react'; |
| 12 | + |
| 13 | +// Define the component data structure |
| 14 | +const akselComponentEvents = [ |
| 15 | + { |
| 16 | + component: "Accordion", |
| 17 | + events: [ |
| 18 | + { name: "accordion åpnet", details: { komponentId: "string", tittelTekst: "string" } }, |
| 19 | + { name: "accordion lukket", details: { komponentId: "string", tittelTekst: "string" } } |
| 20 | + ] |
| 21 | + }, |
| 22 | + { |
| 23 | + component: "Alert", |
| 24 | + events: [ |
| 25 | + { name: "alert lukket", details: { alertType: "string", alertVariant: "string" } } |
| 26 | + ] |
| 27 | + }, |
| 28 | + { |
| 29 | + component: "Button", |
| 30 | + events: [ |
| 31 | + { name: "knapp klikket", details: { knappTekst: "string", knappType: "string", knappVariant: "string" } } |
| 32 | + ] |
| 33 | + }, |
| 34 | + { |
| 35 | + component: "Chat", |
| 36 | + events: [ |
| 37 | + { name: "chat åpnet", details: { chatId: "string" } }, |
| 38 | + { name: "chat lukket", details: { chatId: "string" } }, |
| 39 | + { name: "chat melding sendt", details: { chatId: "string" } } |
| 40 | + ] |
| 41 | + }, |
| 42 | + { |
| 43 | + component: "Checkbox", |
| 44 | + events: [ |
| 45 | + { name: "avkrysningsboks valgt", details: { sjekkboksId: "string", sjekkboksTekst: "string", sjekkboksVerdi: "boolean" } } |
| 46 | + ] |
| 47 | + }, |
| 48 | + { |
| 49 | + component: "Chips", |
| 50 | + events: [ |
| 51 | + { name: "chip valgt", details: { chipTekst: "string", chipVerdi: "string" } }, |
| 52 | + { name: "chip fjernet", details: { chipTekst: "string", chipVerdi: "string" } } |
| 53 | + ] |
| 54 | + }, |
| 55 | + // More components follow the same pattern |
| 56 | + { |
| 57 | + component: "ComboboxBeta", |
| 58 | + events: [ |
| 59 | + { name: "combobox valg endret", details: { valgtVerdi: "string", valgtTekst: "string" } } |
| 60 | + ] |
| 61 | + }, |
| 62 | + { |
| 63 | + component: "ConfirmationPanel", |
| 64 | + events: [ |
| 65 | + { name: "bekreftelsespanel huket av", details: { panelId: "string", panelTekst: "string" } }, |
| 66 | + { name: "bekreftelsespanel avhuket", details: { panelId: "string", panelTekst: "string" } } |
| 67 | + ] |
| 68 | + }, |
| 69 | + { |
| 70 | + component: "CopyButton", |
| 71 | + events: [ |
| 72 | + { name: "tekst kopiert", details: { kopieringsTekst: "string" } } |
| 73 | + ] |
| 74 | + }, |
| 75 | + { |
| 76 | + component: "DatePicker", |
| 77 | + events: [ |
| 78 | + { name: "dato valgt", details: { datoVerdi: "string", datoFelt: "string" } } |
| 79 | + ] |
| 80 | + }, |
| 81 | + { |
| 82 | + component: "Dropdown", |
| 83 | + events: [ |
| 84 | + { name: "dropdown åpnet", details: { dropdownId: "string" } }, |
| 85 | + { name: "dropdown lukket", details: { dropdownId: "string" } }, |
| 86 | + { name: "dropdown valg valgt", details: { valgtVerdi: "string", valgtTekst: "string" } } |
| 87 | + ] |
| 88 | + }, |
| 89 | + { |
| 90 | + component: "ExpansionCard", |
| 91 | + events: [ |
| 92 | + { name: "utvidbart kort åpnet", details: { kortId: "string", kortTittel: "string" } }, |
| 93 | + { name: "utvidbart kort lukket", details: { kortId: "string", kortTittel: "string" } } |
| 94 | + ] |
| 95 | + }, |
| 96 | + { |
| 97 | + component: "FileUpload", |
| 98 | + events: [ |
| 99 | + { name: "fil lastet opp", details: { filNavn: "string", filStørrelse: "number", filType: "string" } }, |
| 100 | + { name: "fil fjernet", details: { filNavn: "string" } } |
| 101 | + ] |
| 102 | + }, |
| 103 | + { |
| 104 | + component: "Modal", |
| 105 | + events: [ |
| 106 | + { name: "modal åpnet", details: { modalId: "string", modalTittel: "string" } }, |
| 107 | + { name: "modal lukket", details: { modalId: "string", modalTittel: "string", lukkMetode: "string" } } |
| 108 | + ] |
| 109 | + }, |
| 110 | + { |
| 111 | + component: "ReadMore", |
| 112 | + events: [ |
| 113 | + { name: "les mer åpnet", details: { lesMerId: "string", lesMerTittel: "string" } }, |
| 114 | + { name: "les mer lukket", details: { lesMerId: "string", lesMerTittel: "string" } } |
| 115 | + ] |
| 116 | + }, |
| 117 | + { |
| 118 | + component: "Search", |
| 119 | + events: [ |
| 120 | + { name: "søk gjennomført", details: { søkeTekst: "string", søkeResultater: "number" } }, |
| 121 | + { name: "søkeforslag valgt", details: { valgtForslag: "string" } } |
| 122 | + ] |
| 123 | + }, |
| 124 | + { |
| 125 | + component: "Select", |
| 126 | + events: [ |
| 127 | + { name: "nedtrekksliste valg endret", details: { valgtVerdi: "string", valgtTekst: "string", listeId: "string" } } |
| 128 | + ] |
| 129 | + }, |
| 130 | + { |
| 131 | + component: "Tabs", |
| 132 | + events: [ |
| 133 | + { name: "fane byttet", details: { faneId: "string", faneTekst: "string", fraFane: "string", tilFane: "string" } } |
| 134 | + ] |
| 135 | + }, |
| 136 | + { |
| 137 | + component: "TextField", |
| 138 | + events: [ |
| 139 | + { name: "tekstfelt utfylt", details: { feltId: "string", feltNavn: "string", harVerdi: "boolean" } } |
| 140 | + ] |
| 141 | + }, |
| 142 | +]; |
| 143 | + |
| 144 | +const getExampleValue = (key: string, componentName: string) => { |
| 145 | + switch (key) { |
| 146 | + // IDs |
| 147 | + case 'komponentId': return `${componentName.toLowerCase()}-1`; |
| 148 | + case 'modalId': return 'bekreftelses-modal'; |
| 149 | + case 'panelId': return 'informasjons-panel'; |
| 150 | + case 'faneId': return 'skjema-oversikt'; |
| 151 | + case 'chatId': return 'bruker-hjelp'; |
| 152 | + case 'sjekkboksId': return 'godta-vilkaar'; |
| 153 | + case 'feltId': return 'bruker-epost'; |
| 154 | + case 'dropdownId': return 'velg-kategori'; |
| 155 | + case 'lesMerId': return 'mer-info'; |
| 156 | + |
| 157 | + // Text content |
| 158 | + case 'knappTekst': return 'Send skjema'; |
| 159 | + case 'tittelTekst': return 'Skjemaoversikt'; |
| 160 | + case 'sjekkboksTekst': return 'Jeg godtar vilkårene'; |
| 161 | + case 'modalTittel': return 'Bekreft valg'; |
| 162 | + case 'chipTekst': return 'Filter: Aktive'; |
| 163 | + case 'kopieringsTekst': return 'https://nav.no/skjema'; |
| 164 | + case 'lesMerTittel': return 'Mer informasjon'; |
| 165 | + case 'feltNavn': return 'epost'; |
| 166 | + |
| 167 | + // Values |
| 168 | + case 'valgtVerdi': return 'deltid'; |
| 169 | + case 'valgtTekst': return 'Deltidsjobb'; |
| 170 | + case 'søkeTekst': return 'stønad'; |
| 171 | + case 'søkeResultater': return '42'; |
| 172 | + case 'datoVerdi': return '2024-06-15'; |
| 173 | + case 'datoFelt': return 'startDato'; |
| 174 | + case 'harVerdi': return 'true'; |
| 175 | + case 'filNavn': return 'vedlegg.pdf'; |
| 176 | + case 'filStørrelse': return '1024'; |
| 177 | + case 'filType': return 'application/pdf'; |
| 178 | + case 'sjekkboksVerdi': return 'true'; |
| 179 | + |
| 180 | + // Types and variants |
| 181 | + case 'alertType': return 'warning'; |
| 182 | + case 'alertVariant': return 'info'; |
| 183 | + case 'knappType': return 'submit'; |
| 184 | + case 'knappVariant': return 'primary'; |
| 185 | + case 'chipVerdi': return 'aktiv'; |
| 186 | + |
| 187 | + // Navigation |
| 188 | + case 'fraFane': return 'oversikt'; |
| 189 | + case 'tilFane': return 'detaljer'; |
| 190 | + case 'faneTekst': return 'Skjemadetaljer'; |
| 191 | + |
| 192 | + // Methods |
| 193 | + case 'lukkMetode': return 'escape'; |
| 194 | + |
| 195 | + default: return 'eksempel-verdi'; |
| 196 | + } |
| 197 | +}; |
| 198 | + |
| 199 | +// Update the ComponentAccordion component |
| 200 | +const ComponentAccordion = ({ component }: { component: { component: string; events: Array<{ name: string; details: Record<string, string | undefined> }> } }) => ( |
| 201 | + <Accordion> |
| 202 | + <Accordion.Item> |
| 203 | + <Accordion.Header> |
| 204 | + <div className="flex justify-between items-center w-full py-2"> |
| 205 | + <span className="font-medium">{component.component}</span> |
| 206 | + <Tag variant="neutral" size="xsmall" className="ml-2 mr-8">{component.events.length} hendelse{component.events.length !== 1 ? 'r' : ''}</Tag> |
| 207 | + </div> |
| 208 | + </Accordion.Header> |
| 209 | + <Accordion.Content> |
| 210 | + {component.events.map((event, eventIndex) => ( |
| 211 | + <div key={event.name} className={eventIndex > 0 ? 'mt-8' : ''}> |
| 212 | + <Tag variant="info"className="mt-2" size="medium">{event.name}</Tag> |
| 213 | + <Table className="mt-6" size="small"> |
| 214 | + <Table.Header> |
| 215 | + <Table.Row> |
| 216 | + <Table.HeaderCell>Detalj</Table.HeaderCell> |
| 217 | + <Table.HeaderCell>Forklaring</Table.HeaderCell> |
| 218 | + <Table.HeaderCell>Eksempel</Table.HeaderCell> |
| 219 | + </Table.Row> |
| 220 | + </Table.Header> |
| 221 | + <Table.Body> |
| 222 | + {Object.entries(event.details).map(([key]) => ( |
| 223 | + <Table.Row key={key}> |
| 224 | + <Table.DataCell> |
| 225 | + <code>{key}</code> |
| 226 | + </Table.DataCell> |
| 227 | + <Table.DataCell> |
| 228 | + {key === 'knappTekst' ? 'Teksten på knappen' : |
| 229 | + key === 'komponentId' ? 'Unik ID for komponenten' : |
| 230 | + key === 'tittelTekst' ? 'Tekst i overskriften' : |
| 231 | + key === 'valgtVerdi' ? 'Verdien som ble valgt' : |
| 232 | + key === 'modalId' ? 'ID for modal-dialogen' : |
| 233 | + key === 'søkeTekst' ? 'Teksten som ble søkt etter' : |
| 234 | + key === 'søkeResultater' ? 'Antall søketreff' : |
| 235 | + key === 'filNavn' ? 'Navnet på filen' : |
| 236 | + key === 'filStørrelse' ? 'Filstørrelse i KB' : |
| 237 | + key === 'filType' ? 'Type fil (MIME)' : |
| 238 | + key === 'harVerdi' ? 'Om feltet har verdi' : |
| 239 | + key === 'lukkMetode' ? 'Hvordan dialogen ble lukket' : |
| 240 | + key === 'faneTekst' ? 'Tekst på fanen' : |
| 241 | + key === 'alertType' ? 'Type varsel' : |
| 242 | + key === 'feltNavn' ? 'Navn på feltet' : |
| 243 | + key === 'knappVariant' ? 'Variant av knappen (primær/sekundær)' : |
| 244 | + 'Identifikator for komponenten'} |
| 245 | + </Table.DataCell> |
| 246 | + <Table.DataCell> |
| 247 | + <code>{getExampleValue(key, component.component)}</code> |
| 248 | + </Table.DataCell> |
| 249 | + </Table.Row> |
| 250 | + ))} |
| 251 | + </Table.Body> |
| 252 | + </Table> |
| 253 | + </div> |
| 254 | + ))} |
| 255 | + </Accordion.Content> |
| 256 | + </Accordion.Item> |
| 257 | + </Accordion> |
| 258 | +); |
| 259 | + |
| 260 | +const AkselComponentEvents = () => { |
| 261 | + const [searchTerm, setSearchTerm] = useState(''); |
| 262 | + |
| 263 | + const filteredComponents = useMemo(() => { |
| 264 | + if (!searchTerm.trim()) { |
| 265 | + return akselComponentEvents; |
| 266 | + } |
| 267 | + |
| 268 | + return akselComponentEvents.filter(component => |
| 269 | + component.component.toLowerCase().includes(searchTerm.toLowerCase()) || |
| 270 | + component.events.some(event => |
| 271 | + event.name.toLowerCase().includes(searchTerm.toLowerCase()) |
| 272 | + ) |
| 273 | + ); |
| 274 | + }, [searchTerm]); |
| 275 | + |
| 276 | + // Group components by first letter for better organization |
| 277 | + const groupedComponents = useMemo(() => { |
| 278 | + const groups: Record<string, typeof akselComponentEvents> = {}; |
| 279 | + |
| 280 | + filteredComponents.forEach(component => { |
| 281 | + const firstLetter = component.component[0].toUpperCase(); |
| 282 | + if (!groups[firstLetter]) { |
| 283 | + groups[firstLetter] = []; |
| 284 | + } |
| 285 | + groups[firstLetter].push(component); |
| 286 | + }); |
| 287 | + |
| 288 | + return Object.entries(groups).sort(([a], [b]) => a.localeCompare(b)); |
| 289 | + }, [filteredComponents]); |
| 290 | + |
| 291 | + // Fix search functionality |
| 292 | + const handleSearchChange = (value: string) => { |
| 293 | + setSearchTerm(value); |
| 294 | + }; |
| 295 | + |
| 296 | + return ( |
| 297 | + <section id="aksel-komponenter"> |
| 298 | + <Heading level="2" size="medium" spacing> |
| 299 | + Hendelser og detaljer for Aksel-komponenter |
| 300 | + </Heading> |
| 301 | + <p className="mb-6"> |
| 302 | + For å sikre konsistent sporing på tvers av team når du bruker Aksel-komponenter, |
| 303 | + anbefaler vi følgende hendelsesnavn og detaljer. Dette gjør det lettere å analysere |
| 304 | + hvordan komponentene brukes og fungerer. |
| 305 | + </p> |
| 306 | + |
| 307 | + <Box |
| 308 | + padding="6" |
| 309 | + borderRadius="medium" |
| 310 | + background="surface-subtle" |
| 311 | + className="mb-8" |
| 312 | + > |
| 313 | + <Label htmlFor="component-search" spacing>Søk etter komponenter eller hendelsesnavn</Label> |
| 314 | + <Search |
| 315 | + id="component-search" |
| 316 | + label="Søk" |
| 317 | + size="medium" |
| 318 | + variant="simple" |
| 319 | + placeholder="F.eks. Button, Modal, accordion åpnet..." |
| 320 | + onChange={handleSearchChange} |
| 321 | + onClear={() => setSearchTerm("")} |
| 322 | + value={searchTerm} |
| 323 | + /> |
| 324 | + |
| 325 | + {searchTerm && ( |
| 326 | + <div className="mt-4 text-sm text-text-subtle"> |
| 327 | + {filteredComponents.length === 0 ? |
| 328 | + 'Ingen komponenter funnet' : |
| 329 | + `Fant ${filteredComponents.length} ${filteredComponents.length === 1 ? 'komponent' : 'komponenter'}` |
| 330 | + } |
| 331 | + </div> |
| 332 | + )} |
| 333 | + </Box> |
| 334 | + |
| 335 | + {/* Simplified structure with just one tab */} |
| 336 | + <Box |
| 337 | + padding="6" |
| 338 | + borderWidth="1" |
| 339 | + borderRadius="medium" |
| 340 | + borderColor="border-subtle" |
| 341 | + className="mt-6 mb-8" |
| 342 | + > |
| 343 | + <div className="flex items-center mb-4 pb-3 border-b border-border-subtle"> |
| 344 | + <div className="flex items-center gap-3"> |
| 345 | + <Heading level="3" size="small" className="m-0">Alle komponenter</Heading> |
| 346 | + <Tag variant="neutral" size="xsmall">{filteredComponents.length}</Tag> |
| 347 | + </div> |
| 348 | + </div> |
| 349 | + |
| 350 | + {filteredComponents.length === 0 ? ( |
| 351 | + <Box padding="6" background="surface-subtle" borderRadius="medium" className="text-center"> |
| 352 | + <p>Ingen komponenter funnet som samsvarer med søket ditt.</p> |
| 353 | + </Box> |
| 354 | + ) : ( |
| 355 | + <div> |
| 356 | + {searchTerm.trim() ? ( |
| 357 | + <VStack gap="0"> |
| 358 | + {filteredComponents.map((component) => ( |
| 359 | + <ComponentAccordion key={component.component} component={component} /> |
| 360 | + ))} |
| 361 | + </VStack> |
| 362 | + ) : ( |
| 363 | + <div> |
| 364 | + {groupedComponents.map(([letter, components]) => ( |
| 365 | + <div key={letter} className="mb-8"> |
| 366 | + <div className="flex items-center mb-4"> |
| 367 | + <div className="w-10 h-10 flex items-center justify-center rounded-full bg-surface-action-subtle text-text-action"> |
| 368 | + <span className="text-xl font-medium">{letter}</span> |
| 369 | + </div> |
| 370 | + <div className="h-px bg-border-subtle flex-grow ml-4"></div> |
| 371 | + </div> |
| 372 | + |
| 373 | + <VStack gap="0" className="ml-2"> |
| 374 | + {components.map((component) => ( |
| 375 | + <ComponentAccordion key={component.component} component={component} /> |
| 376 | + ))} |
| 377 | + </VStack> |
| 378 | + </div> |
| 379 | + ))} |
| 380 | + </div> |
| 381 | + )} |
| 382 | + </div> |
| 383 | + )} |
| 384 | + </Box> |
| 385 | + </section> |
| 386 | + ); |
| 387 | +}; |
| 388 | + |
| 389 | +export default AkselComponentEvents; |
0 commit comments