Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Create StudioBreadcrumbs component #13975

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7afd631
Refactor EditOptions.tsx
ErlingHauan Oct 22, 2024
f2f7d2d
Add modal and language keys
ErlingHauan Oct 22, 2024
5018e83
Merge branch 'main' of https://github.com/Altinn/altinn-studio into 1…
ErlingHauan Oct 23, 2024
3be6102
Fix types
ErlingHauan Oct 25, 2024
bba3972
Create custom hooks
ErlingHauan Oct 25, 2024
7a61186
Lint and format
ErlingHauan Oct 25, 2024
01b6e3b
Add debounce for API calls
ErlingHauan Oct 30, 2024
ce873ce
Reuse established text keys
ErlingHauan Oct 30, 2024
7d91d16
Fetch the old codelist from main and add feature flag
ErlingHauan Oct 30, 2024
9157cdd
Add tests for the new implementation
ErlingHauan Oct 30, 2024
416f1e2
Small fixes plus two test cases
ErlingHauan Oct 30, 2024
a8ef646
Add error message component to the new implementation
ErlingHauan Oct 30, 2024
151aa17
Fix tests
ErlingHauan Oct 30, 2024
5c8e89c
Add return types
ErlingHauan Oct 31, 2024
6f3a8eb
Merge remote-tracking branch 'origin/main' into 13505-implement-studi…
ErlingHauan Oct 31, 2024
1ad70d0
Fix PR comments in EditManualOptionsWithEditor
ErlingHauan Oct 31, 2024
65892dc
Make modal expand only downwards
ErlingHauan Nov 1, 2024
f49b98c
Make OptionTabs component
ErlingHauan Nov 1, 2024
b30e077
Add prop validation
ErlingHauan Nov 1, 2024
6798bc9
Remove waitFor in tests
ErlingHauan Nov 1, 2024
991a2f3
Merge branch 'main' into 13505-implement-studiocodelisteditor-in-comp…
ErlingHauan Nov 4, 2024
d5ceecf
Add new texts
ErlingHauan Nov 4, 2024
1aa904c
Remove unneeded word 'derfor' from text
ErlingHauan Nov 4, 2024
a7f5690
Fix test
ErlingHauan Nov 4, 2024
f829b09
Create StudioBreadcrumbs component
ErlingHauan Nov 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/dashboard/components/RepoList/RepoList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const RepoList = ({
},
{
accessor: 'description',
heading: t('dashboard.description'),
heading: t('general.description'),
sortable: true,
},
{
Expand Down
18 changes: 13 additions & 5 deletions frontend/language/src/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@
"dashboard.created_by": "Opprettet av",
"dashboard.creating_your_service": "Oppretter appen din",
"dashboard.data_models": "Datamodeller",
"dashboard.description": "Beskrivelse",
"dashboard.edit_app": "Endre {{appName}} i Studio",
"dashboard.error_getting_organization_data.message": "Det oppsto en feil da vi skulle hente de organisasjonene som trengs for å kjøre appen.",
"dashboard.error_getting_organization_data.title": "Kunne ikke laste inn organisasjoner",
Expand Down Expand Up @@ -272,6 +271,7 @@
"general.date_time_format": "{{date}} kl. {{time}}",
"general.delete": "Slett",
"general.delete_item": "Slett {{item}}",
"general.description": "Beskrivelse",
"general.edit": "Endre",
"general.empty_string": "Tom tekst",
"general.error_message": "Det har oppstått en feil. Hvis problemet fortsetter, <a>ta kontakt med oss</a>.",
Expand All @@ -287,6 +287,7 @@
"general.loading": "Laster...",
"general.next": "Neste",
"general.no_options": "Ingen alternativer tilgjengelige",
"general.option": "Alternativ",
"general.options": "Alternativer",
"general.page": "Side",
"general.page_error_message": "Vi vet ikke helt hva, men <a>ta kontakt med oss</a>, så graver vi i det sammen.",
Expand Down Expand Up @@ -1487,8 +1488,16 @@
"ux_editor.modal_header_type_helper": "Velg titteltype",
"ux_editor.modal_new_option": "Legg til alternativ",
"ux_editor.modal_properties_add_radio_button_options": "Hvordan vil du legge til radioknapper?",
"ux_editor.modal_properties_code_list_custom_list": "Egendefinert kodeliste",
"ux_editor.modal_properties_code_list_delete_item": "Slett alternativ {{number}}",
"ux_editor.modal_properties_code_list_empty": "Kodelisten er tom.",
"ux_editor.modal_properties_code_list_general_error": "Kodelisten inneholder feil og kan ikke lagres.",
"ux_editor.modal_properties_code_list_helper": "Velg kodeliste",
"ux_editor.modal_properties_code_list_id": "Kodeliste-ID",
"ux_editor.modal_properties_code_list_item_description": "Beskrivelse for alternativ {{number}}",
"ux_editor.modal_properties_code_list_item_helpText": "Hjelpetekst for alternativ {{number}}",
"ux_editor.modal_properties_code_list_item_label": "Ledetekst for alternativ {{number}}",
"ux_editor.modal_properties_code_list_item_value": "Verdi for alternativ {{number}}",
"ux_editor.modal_properties_code_list_read_more": "<0 href=\"{{optionsDocs}}\" >Les mer om kodelister</0>",
"ux_editor.modal_properties_code_list_read_more_dynamic": "<0 href=\"{{optionsDocs}}\" >Les mer om dynamiske kodelister</0>",
"ux_editor.modal_properties_code_list_read_more_static": "<0 href=\"{{optionsDocs}}\" >Les mer om statiske kodelister</0>",
Expand Down Expand Up @@ -1643,7 +1652,7 @@
"ux_editor.options.codelist_create_info.step4": "Skriv inn kodelisten i tekstfeltet midt på siden. Kodelisten må være i JSON-format.",
"ux_editor.options.codelist_create_info.step5": "Velg \"Commit endringer\".",
"ux_editor.options.codelist_create_info.step6": "Du er nå ferdig i Gitea for denne gang. Gå tilbake til Altinn Studio-fanen, eller klikk på Altinn-logoen øverst til venstre i Gitea for å komme tilbake til Altinn Studio.",
"ux_editor.options.codelist_only": "Denne komponenten støtter kun oppsett med kodelister.",
"ux_editor.options.codelist_only": "Denne komponenten støtter kun oppsett med predefinerte kodelister.",
"ux_editor.options.codelist_referenceId.description": "Her kan du legge til en referanse-ID til en dynamisk kodeliste som er satt opp i koden.",
"ux_editor.options.codelist_referenceId.description_details": "Du bruker dynamiske kodelister for å tilpasse alternativer for brukerne. Det kan for eksempel være tilpasninger ut fra geografisk plassering, eller valg brukeren gjør tidligere i skjemaet.",
"ux_editor.options.codelist_upload_info.heading": "Steg for å laste opp kodelister manuelt",
Expand All @@ -1652,13 +1661,12 @@
"ux_editor.options.codelist_upload_info.step3": "Filen må ligge i mappen \"App/options\". Sørg for at den blir plassert der ved å oppgi denne stien i opplastingsfeltet. Når du skriver \"App/options/\", blir feltet automatisk oppdatert med mappesti.",
"ux_editor.options.codelist_upload_info.step4": "Velg \"Commit endringer\".",
"ux_editor.options.codelist_upload_info.step5": "Du er nå ferdig i Gitea for denne gang. Gå tilbake til Altinn Studio-fanen, eller klikk på Altinn-logoen øverst til venstre i Gitea for å komme tilbake til Altinn Studio.",
"ux_editor.options.multiple": "{{value}} alternativer",
"ux_editor.options.section_heading": "Valg for kodelister",
"ux_editor.options.single": "{{value}} alternativ",
"ux_editor.options.tab_codelist": "Velg kodeliste",
"ux_editor.options.tab_manual": "Sett opp egne alternativer",
"ux_editor.options.tab_referenceId": "Angi referanse-ID",
"ux_editor.options_text_description": "Beskrivelse",
"ux_editor.options_text_help_text": "Hjelpetekst",
"ux_editor.options_text_label": "Ledetekst",
"ux_editor.page": "Side",
"ux_editor.page_config_pdf_abort_converting_page_to_pdf": "Avbryt å gjøre om siden til PDF",
"ux_editor.page_config_pdf_card_heading": "Siden skal være en PDF",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Meta, Controls, Primary, Canvas } from '@storybook/blocks';
import * as StudioBreadcrumbsStories from './StudioBreadcrumbs.stories';

<Meta of={StudioBreadcrumbsStories} />

# StudioBreadcrumbs

This is a copy of Designsystemet's `Breadcrumb` component from v1.0.0-next.35. It should eventually be replaced with an import from Designsystemet when we upgrade to v1.

`StudioBreadcrumbs` is a navigation with a visible breadcrumb trail. Use this component to help users understand where they are within a structure, such as on a website. This allows them to more easily switch between the different levels of the structure.

<Primary />
<Controls of={StudioBreadcrumbsStories.Preview} />

## How to use `Breadcrumb`

The last link in the breadcrumb trail is automatically marked with `aria-current="page"`.

```tsx
<Breadcrumbs aria-label='You are here:'>
<Breadcrumbs.List>
<Breadcrumbs.Item>
<Breadcrumbs.Link href='https://designsystemet.no/'>Level 1</Breadcrumbs.Link>
</Breadcrumbs.Item>
<Breadcrumbs.Item>
<Breadcrumbs.Link href='https://designsystemet.no/niva-2/'>Level 2</Breadcrumbs.Link>
</Breadcrumbs.Item>
</Breadcrumbs.List>
</Breadcrumbs>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
.ds-breadcrumbs {
--dsc-breadcrumbs-spacing: var(--fds-spacing-2);
--dsc-breadcrumbs-chevron-size: var(--fds-sizing-6);
--dsc-breadcrumbs-icon-url: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24'%3E%3Cpath d='M9.47 5.97a.75.75 0 0 1 1.06 0l5.5 5.5a.75.75 0 0 1 0 1.06l-5.5 5.5a.75.75 0 1 1-1.06-1.06L14.44 12 9.47 7.03a.75.75 0 0 1 0-1.06'/%3E%3C/svg%3E");

& > :is(ol, ul) {
display: flex;
flex-wrap: wrap;
list-style-type: none;
margin: 0;
padding: 0;
gap: var(--dsc-breadcrumbs-spacing) 0;
}

& a:not(:focus-visible) {
color: inherit;
}

& a[aria-current='page'] {
text-decoration: none;
}

/* Draw chevron between items and before back link */
& li:where(:not(:last-child))::after,
& > :not(ol, ul)::before {
background: currentcolor;
content: '';
display: inline-block;
height: var(--dsc-breadcrumbs-chevron-size);
margin-inline: var(--dsc-breadcrumbs-spacing);
mask: center / contain no-repeat var(--dsc-breadcrumbs-icon-url);
vertical-align: middle;
width: var(--dsc-breadcrumbs-chevron-size);
}

/* When link is direct child of Breadcrumbs, make it back button */
& > :not(ol, ul)::before {
margin: 0;
rotate: 180deg;
}

@media (width < 650px) {
& > :is(ol, ul):not(:only-child) {
display: none; /* Hide list when mobile and having back link */
}
}

@media (min-width: 650px) {
& > :is(:not(ol, ul)):not(:only-child) {
display: none; /* Hide back link when desktop and having list */
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { Meta, StoryFn } from '@storybook/react';
import React from 'react';

import { StudioBreadcrumbs } from '.';

export default {
title: 'Components/StudioBreadcrumbs',
component: StudioBreadcrumbs,
args: {
'aria-label': 'You are here:',
},
} as Meta;

export const Preview: StoryFn<typeof StudioBreadcrumbs> = (args) => (
<StudioBreadcrumbs {...args}>
<StudioBreadcrumbs.List>
<StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Link href='#'>Level 1</StudioBreadcrumbs.Link>
</StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Link href='#'>Level 2</StudioBreadcrumbs.Link>
</StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Link href='#'>Level 3</StudioBreadcrumbs.Link>
</StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Link href='#'>Level 4</StudioBreadcrumbs.Link>
</StudioBreadcrumbs.Item>
</StudioBreadcrumbs.List>
</StudioBreadcrumbs>
);

export const Back: StoryFn<typeof StudioBreadcrumbs> = (args) => (
<StudioBreadcrumbs {...args}>
<StudioBreadcrumbs.Link href='#' aria-label='Back to level 3'>
Level 3
</StudioBreadcrumbs.Link>
</StudioBreadcrumbs>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import type { StudioBreadcrumbsProps } from './StudioBreadcrumbs';

import { StudioBreadcrumbs } from './';

const renderWithRoot = (props?: StudioBreadcrumbsProps) =>
render(
<StudioBreadcrumbs aria-label='Du er her:' {...props}>
<StudioBreadcrumbs.Link href='#' aria-label='Tilbake til Nivå 3'>
Nivå 3
</StudioBreadcrumbs.Link>
<StudioBreadcrumbs.List>
<StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Link href='#'>Nivå 1</StudioBreadcrumbs.Link>
</StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Link href='#'>Nivå 2</StudioBreadcrumbs.Link>
</StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Link href='#'>Nivå 3</StudioBreadcrumbs.Link>
</StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Link href='#'>Nivå 4</StudioBreadcrumbs.Link>
</StudioBreadcrumbs.Item>
</StudioBreadcrumbs.List>
</StudioBreadcrumbs>,
);

describe('StudioBreadcrumbs', () => {
it('should render correctly with default props', () => {
renderWithRoot();

expect(screen.getByRole('navigation')).toBeInTheDocument();
});
});

describe('StudioBreadcrumbs.List', () => {
it('should render with aria-current on last item', () => {
renderWithRoot();
const links = screen.getAllByRole('link');
expect(links.at(0)).not.toHaveAttribute('aria-current', 'page');
expect(links.at(1)).not.toHaveAttribute('aria-current', 'page');
expect(links.at(2)).not.toHaveAttribute('aria-current', 'page');
expect(links.at(-1)).toHaveAttribute('aria-current', 'page');
});

it('should move aria-current to item when re-rendering', () => {
renderWithRoot();
const links = screen.getAllByRole('link');
expect(links.at(-1)).toHaveAttribute('aria-current', 'page');

// Re-render with additional level
render(
<StudioBreadcrumbs aria-label='Du er her:'>
<StudioBreadcrumbs.Link href='#' aria-label='Tilbake til Nivå 3'>
Nivå 3
</StudioBreadcrumbs.Link>
<StudioBreadcrumbs.List>
<StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Link href='#'>Nivå 1</StudioBreadcrumbs.Link>
</StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Link href='#'>Nivå 2</StudioBreadcrumbs.Link>
</StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Link href='#'>Nivå 3</StudioBreadcrumbs.Link>
</StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Link href='#'>Nivå 4</StudioBreadcrumbs.Link>
</StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Item>
<StudioBreadcrumbs.Link href='#'>Nivå 5</StudioBreadcrumbs.Link>
</StudioBreadcrumbs.Item>
</StudioBreadcrumbs.List>
</StudioBreadcrumbs>,
);

expect(links.at(-2)).not.toHaveAttribute('aria-current', 'page');
expect(links.at(-1)).toHaveAttribute('aria-current', 'page');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { type HTMLAttributes, forwardRef } from 'react';
import classes from './StudioBreadcrumbs.module.css';

export type StudioBreadcrumbsProps = {
'aria-label'?: string;
} & HTMLAttributes<HTMLElement>;

const StudioBreadcrumbs = forwardRef<HTMLElement, StudioBreadcrumbsProps>(
({ 'aria-label': ariaLabel = 'You are here:', className, ...rest }, ref) => (
<nav
aria-label={ariaLabel}
className={`${classes['ds-breadcrumbs']} ${className}`}
ref={ref}
{...rest}
/>
),
);

StudioBreadcrumbs.displayName = 'StudioBreadcrumbs';

export { StudioBreadcrumbs };
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { type HTMLAttributes, forwardRef } from 'react';
import React from 'react';

export type StudioBreadcrumbsItemProps = HTMLAttributes<HTMLLIElement>;

export const StudioBreadcrumbsItem = forwardRef<HTMLLIElement, StudioBreadcrumbsItemProps>(
function BreadcrumbsItem({ className, ...rest }, ref) {
return <li ref={ref} {...rest} />;
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React, { forwardRef } from 'react';

import { Link } from '@digdir/designsystemet-react';
import type { LinkProps } from '@digdir/designsystemet-react';

export type StudioBreadcrumbsLinkProps = LinkProps;

export const StudioBreadcrumbsLink = forwardRef<HTMLAnchorElement, StudioBreadcrumbsLinkProps>(
function BreadcrumbsLink(rest, ref) {
return <Link ref={ref} {...rest} />;
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useMergeRefs } from '@floating-ui/react';
import { type HTMLAttributes, forwardRef, useEffect, useRef } from 'react';
import React from 'react';

export type StudioBreadcrumbsListProps = HTMLAttributes<HTMLOListElement>;

export const StudioBreadcrumbsList = forwardRef<HTMLOListElement, StudioBreadcrumbsListProps>(
function BreadcrumbsList(rest, ref) {
const innerRef = useRef<HTMLOListElement>(null);
const mergedRefs = useMergeRefs([innerRef, ref]);

// Set aria-current on last link
useEffect(() => {
const links = innerRef.current?.querySelectorAll(':scope > * > *') || [];
const lastLink = links[links?.length - 1];

lastLink?.setAttribute('aria-current', 'page');
return () => lastLink?.removeAttribute('aria-current'); // Remove on re-render as React can re-use DOM elements
});

return <ol ref={mergedRefs} {...rest} />;
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { StudioBreadcrumbs as StudioBreadcrumbsParent } from './StudioBreadcrumbs';
import { StudioBreadcrumbsItem } from './StudioBreadcrumbsItem';
import { StudioBreadcrumbsLink } from './StudioBreadcrumbsLink';
import { StudioBreadcrumbsList } from './StudioBreadcrumbsList';

export const StudioBreadcrumbs = Object.assign(StudioBreadcrumbsParent, {
List: StudioBreadcrumbsList,
Item: StudioBreadcrumbsItem,
Link: StudioBreadcrumbsLink,
});

StudioBreadcrumbs.List.displayName = 'StudioBreadcrumbs.List';
StudioBreadcrumbs.Item.displayName = 'StudioBreadcrumbs.Item';
StudioBreadcrumbs.Link.displayName = 'StudioBreadcrumbs.Link';

export type { StudioBreadcrumbsProps } from './StudioBreadcrumbs';
export type { StudioBreadcrumbsListProps } from './StudioBreadcrumbsList';
export type { StudioBreadcrumbsItemProps } from './StudioBreadcrumbsItem';
export type { StudioBreadcrumbsLinkProps } from './StudioBreadcrumbsLink';
export { StudioBreadcrumbsList, StudioBreadcrumbsItem, StudioBreadcrumbsLink };
3 changes: 2 additions & 1 deletion frontend/packages/shared/src/utils/featureToggleUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export type SupportedFeatureFlags =
| 'exportForm'
| 'addComponentModal'
| 'subform'
| 'summary2';
| 'summary2'
| 'codeListEditor';

/*
* Please add all the features that you want to be toggle on by default here.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@

.texts {
background-color: var(--fds-semantic-surface-neutral-default);
padding: var(--fds-spacing-5) 0;
padding-block: var(--fds-spacing-5) 0;
padding-inline: 0;
}
Loading