diff --git a/frontend/libs/studio-components/src/components/StudioIconCard/StudioIconCard.module.css b/frontend/libs/studio-components/src/components/StudioIconCard/StudioIconCard.module.css new file mode 100644 index 00000000000..12a49b8993f --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioIconCard/StudioIconCard.module.css @@ -0,0 +1,117 @@ +.card { + height: 244px; + width: 244px; + min-width: 244px; + + transition: + 0.2s transform, + 0.2s box-shadow; + + background-color: var(--fds-semantic-background-default); + box-shadow: none; +} + +/* .card:hover { */ +/* box-shadow: var(--fds-shadow-medium); */ +/* transform: translateY(-2px); */ +/* background-color: var(--fds-semantic-surface-info-subtle-hover); */ +/* border: var(--fds-border_radius-small); */ +/* border-style: solid; */ +/* border-color: var(--fds-semantic-surface-action-checked); */ +/* } */ + +.popoverContent { + display: flex; + flex-direction: column; + align-items: flex-start; +} + +.popoverContent button { + width: 100%; + justify-content: left; +} + +.link { + padding: 0px; + display: block; + height: 100%; +} + +.card:not(:hover) .editIcon { + display: none; +} + +.editIcon { + padding: 0px; + position: absolute; + border-radius: var(--fds-border_radius-full); + right: var(--fds-spacing-1); + top: var(--fds-spacing-1); +} + +.iconContainer { + height: var(--fds-spacing-26); + display: flex; + align-items: center; +} + +.iconBackground { + height: var(--fds-sizing-12); + width: var(--fds-sizing-12); + transform: rotate(45deg); + margin: 0 auto; + display: flex; + align-items: center; + justify-content: center; +} + +.iconBackground svg, +.iconBackground img { + transform: rotate(-45deg); + font-size: var(--fds-sizing-9); + color: #4b5563; +} + +.blue { + background-color: var(--fds-colors-blue-100); + color: #23262a; +} + +.red { + background-color: var(--fds-colors-red-100); + color: #23262a; +} + +.green { + background-color: var(--fds-colors-green-200); + color: #23262a; +} + +.grey { + background-color: var(--fds-colors-grey-200); + color: #23262a; +} + +.yellow { + background-color: var(--fds-colors-yellow-200); + color: #23262a; +} + +.title { + padding-bottom: var(--fds-spacing-2); + letter-spacing: 0px; +} + +.content { + display: flex; + flex-direction: column; + justify-content: space-between; + row-gap: var(--fds-spacing-1); + padding: var(--fds-spacing-2); + text-align: center; + font-weight: normal; +} + +.content button { + width: 100%; +} diff --git a/frontend/libs/studio-components/src/components/StudioIconCard/StudioIconCard.test.tsx b/frontend/libs/studio-components/src/components/StudioIconCard/StudioIconCard.test.tsx new file mode 100644 index 00000000000..b7d622d11da --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioIconCard/StudioIconCard.test.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import userEvent from '@testing-library/user-event'; +import { render, screen } from '@testing-library/react'; +import { studioIconCardPopoverTrigger } from '@studio/testing/testids'; +import { StudioIconCard } from './StudioIconCard'; +import { ClipboardIcon } from '@studio/icons'; + +describe('taskcard', () => { + it('should render children as content', async () => { + const divText = 'test-div'; + render( + }> +
{divText}
+
, + ); + expect(screen.getByText(divText)).toBeInTheDocument(); + }); + + it('should render icon prop', async () => { + const iconTestId = 'icon-test-id'; + render( + } iconColor='red'> +
+
, + ); + expect(screen.getByTestId(iconTestId)).toBeInTheDocument(); + }); + + it('should render clickable popover trigger for context buttons', async () => { + const user = userEvent.setup(); + const buttonText = 'button-text'; + const contextButtons = ; + render( + }> +
+
, + ); + await user.click(screen.getByTestId(studioIconCardPopoverTrigger)); + expect(screen.getByRole('button', { name: buttonText })).toBeInTheDocument(); + }); +}); diff --git a/frontend/libs/studio-components/src/components/StudioIconCard/StudioIconCard.tsx b/frontend/libs/studio-components/src/components/StudioIconCard/StudioIconCard.tsx new file mode 100644 index 00000000000..edfa3613a94 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioIconCard/StudioIconCard.tsx @@ -0,0 +1,62 @@ +import React, { type ReactElement, type ReactNode } from 'react'; +import { + StudioCard, + StudioHeading, + StudioPopover, + StudioPopoverContent, + StudioPopoverTrigger, +} from '@studio/components'; +import classes from './StudioIconCard.module.css'; +import cn from 'classnames'; +import type { HeadingProps } from '@digdir/designsystemet-react'; +import { MenuElipsisVerticalIcon } from '@studio/icons'; +import { studioIconCardPopoverTrigger } from '@studio/testing/testids'; + +export type StudioIconCardIconColors = 'blue' | 'red' | 'green' | 'grey' | 'yellow'; + +export type StudioIconCardProps = { + icon: ReactElement; + iconColor?: StudioIconCardIconColors; + header?: string; + headerOptions?: HeadingProps; + contextButtons?: ReactNode; + children: ReactNode; +}; + +export const StudioIconCard = ({ + icon, + iconColor = 'grey', + header, + headerOptions, + contextButtons, + children, +}: StudioIconCardProps) => { + return ( + + {contextButtons && ( + + + + + + {contextButtons} + + + )} +
+
{icon}
+
+ +
+ + {header} + + {children} +
+
+ ); +}; diff --git a/frontend/packages/ux-editor/src/hooks/useLayoutSetIcon.test.tsx b/frontend/packages/ux-editor/src/hooks/useLayoutSetIcon.test.tsx new file mode 100644 index 00000000000..ef5d2d5d4be --- /dev/null +++ b/frontend/packages/ux-editor/src/hooks/useLayoutSetIcon.test.tsx @@ -0,0 +1,30 @@ +import { renderHook } from '@testing-library/react'; +import React from 'react'; +import { useLayoutSetIcon } from './useLayoutSetIcon'; +import type { LayoutSetModel } from 'app-shared/types/api/dto/LayoutSetModel'; +import { QuestionmarkIcon } from '@studio/icons'; + +describe('useLayoutSetIcon', () => { + it('should return default icon for unknown types', () => { + const layoutSet: LayoutSetModel = { + id: 'unknown-id', + dataType: '', + type: 'unknown-type', + }; + const { result } = renderHook(() => useLayoutSetIcon(layoutSet)); + + expect(result.current.icon.type).toBe(().type); + expect(result.current.iconColor).toBe('grey'); + }); + + it('should return icon and iconColor for known types', () => { + const layoutSet: LayoutSetModel = { + id: 'CustomReceipt', + dataType: '', + type: '', + }; + const { result } = renderHook(() => useLayoutSetIcon(layoutSet)); + expect(result.current.icon).toBeTruthy(); + expect(result.current.iconColor).toBeTruthy(); + }); +}); diff --git a/frontend/packages/ux-editor/src/hooks/useLayoutSetIcon.tsx b/frontend/packages/ux-editor/src/hooks/useLayoutSetIcon.tsx new file mode 100644 index 00000000000..a1991b4229e --- /dev/null +++ b/frontend/packages/ux-editor/src/hooks/useLayoutSetIcon.tsx @@ -0,0 +1,23 @@ +import React, { type ReactElement } from 'react'; +import { + ClipboardIcon, + DataTaskIcon, + QuestionmarkIcon, + ReceiptIcon, + SignTaskIcon, +} from '@studio/icons'; +import type { LayoutSetModel } from 'app-shared/types/api/dto/LayoutSetModel'; +import type { StudioIconCardIconColors } from '@studio/components/src/components/StudioIconCard/StudioIconCard'; + +export const useLayoutSetIcon = ( + layoutSetModel: LayoutSetModel, +): { icon: ReactElement; iconColor: StudioIconCardIconColors } => { + if (layoutSetModel.type == 'subform') return { icon: , iconColor: 'blue' }; + + if (layoutSetModel.task?.id == 'CustomReceipt') + return { icon: , iconColor: 'green' }; + if (layoutSetModel.task?.type == 'data') return { icon: , iconColor: 'blue' }; + if (layoutSetModel.task?.type == 'signing') return { icon: , iconColor: 'red' }; + + return { icon: , iconColor: 'grey' }; +}; diff --git a/frontend/testing/testids.js b/frontend/testing/testids.js index e9ddf5df3ec..cadde3acd2a 100644 --- a/frontend/testing/testids.js +++ b/frontend/testing/testids.js @@ -16,3 +16,4 @@ export const resetRepoContainerId = 'reset-repo-container'; export const selectedLayoutSet = 'layout-set-test'; export const typeItemId = (pointer) => `type-item-${pointer}`; export const userMenuItemId = 'user-menu-item'; +export const studioIconCardPopoverTrigger = 'studio-icon-card-popover-trigger';