diff --git a/packages/css/package.json b/packages/css/package.json index 73740e31..a623695c 100644 --- a/packages/css/package.json +++ b/packages/css/package.json @@ -1,6 +1,6 @@ { "name": "@miljodirektoratet/md-css", - "version": "1.0.23", + "version": "1.0.24", "description": "CSS for Miljødirektoratet", "author": "Miljødirektoratet", "main": "./src/index.css", diff --git a/packages/css/src/index.css b/packages/css/src/index.css index 99124156..a8801f70 100644 --- a/packages/css/src/index.css +++ b/packages/css/src/index.css @@ -8,6 +8,7 @@ @import './filelist/filelist.css'; @import './accordionitem/accordionitem.css'; @import './modal/modal.css'; +@import './stepper/stepper.css'; @import './tile/tile.css'; @import './tabs/tabs.css'; @import './loadingSpinner/loadingSpinner.css'; diff --git a/packages/css/src/modal/README.md b/packages/css/src/modal/README.md index 693ba82e..9b405da8 100644 --- a/packages/css/src/modal/README.md +++ b/packages/css/src/modal/README.md @@ -8,37 +8,32 @@ See [Storybook](https://miljodir.github.io/md-components) for examples and more ```html
-
-
-
- - +
-
{heading}
- +
{heading}
+
-
- MODAL CONTENT GOES HERE -
-
-
+
MODAL CONTENT GOES HERE
+
+
``` ## Z-index + This component uses z-index to place itself as the top layer in the viewport. The default z-indexes are: + - `md-modal`: 900 - `md-model__overlay`: 800 diff --git a/packages/css/src/stepper/README.md b/packages/css/src/stepper/README.md new file mode 100644 index 00000000..05cc95f5 --- /dev/null +++ b/packages/css/src/stepper/README.md @@ -0,0 +1,36 @@ +# Structure + +To use the `Stepper` css in `@miljodirektoratet/md-css` as a standalone, without the accompanying React component, please use the following HTML structure. + +Class names in brackets [] are optional-/togglable-/decorator- or state dependant classes. + +See [Storybook](https://miljodir.github.io/md-components) for examples and more info. + +```html +
+
+
+ +
+
+
+
+ [, index+1] + +
+
+

{step-title}

+
+
+
+
+ STEP CONTENT GOES HERE + +
+
+
+ +
+
+
+``` diff --git a/packages/css/src/stepper/stepper.css b/packages/css/src/stepper/stepper.css new file mode 100644 index 00000000..f122d2a9 --- /dev/null +++ b/packages/css/src/stepper/stepper.css @@ -0,0 +1,156 @@ +.md-stepper__stepper-container { + padding-top: 40px; +} + +.md-stepper__stepper-container .md-stepper__stepper-list { + display: flex; + flex-direction: column; + gap: 32px; +} + +.md-stepper__stepper-container .md-stepper__stepper-list .md-stepper__stepper-list-item .md-stepper__step-title { + display: flex; + flex-direction: row; + gap: 16px; +} +.md-stepper__stepper-container .md-stepper__stepper-list .md-stepper__stepper-list-item .md-stepper__step-title h4 { + font-family: 'SofiaPro-Regular' !important; + font-size: 20px; + font-weight: 400; + line-height: 24px; + letter-spacing: 0em; + text-align: left; + margin: 0; + + height: 40px; + + display: flex; + align-items: center; +} +.md-stepper__stepper-container + .md-stepper__stepper-list + .md-stepper__stepper-list-item + .md-stepper__step-title + h4.md-stepper__disabled { + color: var(--mdGreyColor60); +} + +.md-stepper__stepper-container + .md-stepper__stepper-list + .md-stepper__stepper-list-item + .md-stepper__step-title + h4.md-stepper__completed { + color: var(--mdGreyColor80); +} + +.md-stepper__stepper-container + .md-stepper__stepper-list + .md-stepper__stepper-list-item + .md-stepper__step-title + .md-stepper__step-title-badge-outer-border { + width: 36px; + height: 36px; + display: flex; + justify-content: center; + align-items: center; +} +.md-stepper__stepper-container + .md-stepper__stepper-list + .md-stepper__stepper-list-item + .md-stepper__step-title + .md-stepper__step-title-badge-outer-border.selected { + border-radius: 50%; + border: 1px solid var(--mdPrimaryColor80); + background-color: var(--mdPrimaryColor20); +} + +.md-stepper__stepper-container + .md-stepper__stepper-list + .md-stepper__stepper-list-item + .md-stepper__step-title + .md-stepper__step-title-badge { + border-radius: 50%; + font-family: Open Sans; + font-size: 16px; + font-weight: 600; + line-height: 22px; + letter-spacing: 0em; + text-align: left; + width: 28px; + height: 28px; + display: flex; + justify-content: center; + align-items: center; +} +.md-stepper__stepper-container + .md-stepper__stepper-list + .md-stepper__stepper-list-item + .md-stepper__step-title + .md-stepper__step-title-badge.completed { + background-color: var(--mdPrimaryColor80); + color: white; +} + +.md-stepper__stepper-container + .md-stepper__stepper-list + .md-stepper__stepper-list-item + .md-stepper__step-title + .md-stepper__step-title-badge.selected { + background-color: var(--mdPrimaryColor); + color: white; +} + +.md-stepper__stepper-container + .md-stepper__stepper-list + .md-stepper__stepper-list-item + .md-stepper__step-title + .md-stepper__step-title-badge.disabled { + background-color: var(--mdGreyColor20); + color: var(--mdGreyColor60); +} + +.md-stepper__stepper-container + .md-stepper__stepper-list + .md-stepper__stepper-list-item + .md-stepper__step-content-container { + display: flex; + flex-direction: row; +} + +.md-stepper__stepper-container + .md-stepper__stepper-list + .md-stepper__stepper-list-item + .md-stepper__step-content-container + .md-stepper__step-content-sideline { + width: 2px; + height: auto; + background-color: var(--mdPrimaryColor); + margin-left: 17px; + margin-right: 17px; + margin-top: 8px; +} + +.md-stepper__stepper-container + .md-stepper__stepper-list + .md-stepper__stepper-list-item + .md-stepper__step-content-container + .md-stepper__step-content-sideline.disabled { + background-color: var(--mdGreyColor20); +} + +.md-stepper__stepper-container + .md-stepper__stepper-list + .md-stepper__stepper-list-item + .md-stepper__step-content-container + .md-stepper__step-content-children { + padding-left: 16px; + padding-top: 16px; + min-height: 24px; +} +.md-stepper__stepper-container + .md-stepper__stepper-list + .md-stepper__stepper-list-item + .md-stepper__step-content-container + .md-stepper__step-content-children.completed { + color: var(--mdGreyColor80); +} diff --git a/packages/react/package.json b/packages/react/package.json index d7de22e6..57451b69 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@miljodirektoratet/md-react", - "version": "1.0.46", + "version": "1.0.47", "description": "React-komponenter for Miljødirektoratet", "author": "Miljødirektoratet", "main": "./dist/index.js", diff --git a/packages/react/src/index.tsx b/packages/react/src/index.tsx index a778d539..c96557a5 100644 --- a/packages/react/src/index.tsx +++ b/packages/react/src/index.tsx @@ -115,6 +115,8 @@ import MdLoadingSpinner, { MdLoadingSpinnerProps } from './loadingSpinner/MdLoad import MdAlertMessage, { MdAlertMessageProps } from './messages/MdAlertMessage'; import MdInfoBox, { MdInfoBoxProps } from './messages/MdInfoBox'; import MdModal, { MdModalProps } from './modal/MdModal'; +import MdStep, { MdStepProps } from './stepper/MdStep'; +import MdStepper, { MdStepperProps } from './stepper/MdStepper'; import MdTab, { MdTabProps } from './tabs/MdTab'; import MdTabTitle, { MdTabTitleProps } from './tabs/MdTabTitle'; import MdTabs, { MdTabsProps } from './tabs/MdTabs'; @@ -280,4 +282,8 @@ export { MdDataColumnRow, MdDataColumnRowValue, MdDataColumnRightAlignedContent, + MdStepper, + MdStep, + MdStepProps, + MdStepperProps, }; diff --git a/packages/react/src/stepper/MdStep.tsx b/packages/react/src/stepper/MdStep.tsx new file mode 100644 index 00000000..f38b59fb --- /dev/null +++ b/packages/react/src/stepper/MdStep.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +export interface MdStepProps { + title: string; + children: React.ReactNode; + completedContent?: React.ReactNode; +} + +const MdStep: React.FunctionComponent = ({ children }: MdStepProps) => { + return
{children}
; +}; + +export default MdStep; diff --git a/packages/react/src/stepper/MdStepper.tsx b/packages/react/src/stepper/MdStepper.tsx new file mode 100644 index 00000000..f196dc29 --- /dev/null +++ b/packages/react/src/stepper/MdStepper.tsx @@ -0,0 +1,106 @@ +import React from 'react'; +import MdCheckIcon from '../icons/MdCheckIcon'; + +export interface MdStepperProps { + activeStep: number; + children: React.ReactElement[]; +} + +const MdStepper: React.FunctionComponent = ({ activeStep, children }: MdStepperProps) => { + return ( +
+
+ {children.map((item, index) => { + return ( +
+ + + {item} + +
+ ); + })} +
+
+ ); +}; + +export default MdStepper; + +interface StepTitleProps { + title: string; + index: number; + activeStep: number; +} + +const StepTitle = ({ title, index, activeStep }: StepTitleProps) => { + if (index === activeStep) { + // Step selected + return ( +
+
+
{index + 1}
+
+

{title}

+
+ ); + } else if (index < activeStep) { + // Step completed + return ( +
+
+
+ +
+
+

{title}

+
+ ); + } else { + // Step not reached yet + return ( +
+
+
{index + 1}
{' '} +
+

{title}

+
+ ); + } +}; + +interface StepContentProps { + index: number; + activeStep: number; + completedContent?: React.ReactNode; + children: React.ReactNode; +} + +const StepContent = ({ index, activeStep, completedContent, children }: StepContentProps) => { + if (index === activeStep) { + return ( +
+
+
{children}
+
+ ); + } else if (index < activeStep) { + // Step completed + return ( +
+
+
+ {completedContent ? completedContent : children} +
+
+ ); + } else { + // Step not reached yet + return ( +
+
+
+
+ ); + } +}; diff --git a/stories/Stepper.stories.tsx b/stories/Stepper.stories.tsx new file mode 100644 index 00000000..13ccb6d9 --- /dev/null +++ b/stories/Stepper.stories.tsx @@ -0,0 +1,212 @@ +import { Title, Subtitle, Description, Controls, Primary, Markdown } from '@storybook/addon-docs'; +import { useArgs } from '@storybook/client-api'; +import React from 'react'; + +import Readme from '../packages/css/src/stepper/README.md'; +import MdButton from '../packages/react/src/button/MdButton'; +import MdStep from '../packages/react/src/stepper/MdStep'; +import MdStepper from '../packages/react/src/stepper/MdStepper'; +import type { Args, StoryFn } from '@storybook/react'; + +export default { + title: 'Components/Stepper', + component: MdStepper, + parameters: { + docs: { + page: () => { + return ( + <> + + <Subtitle /> + <Description /> + <Primary /> + <Controls /> + <Markdown>{Readme.toString()}</Markdown> + </> + ); + }, + description: { + // eslint-disable-next-line quotes + component: "Stepper component.<br/><br/>`import { MdStepper, MdStep } from '@miljodirektoratet/md-react'`", + }, + }, + }, + argTypes: { + activeStep: { + type: { name: 'number' }, + description: 'The active step in the stepper. Starts at 0.', + table: { + defaultValue: { summary: 0 }, + type: { + summary: 'number', + }, + }, + control: { type: 'number' }, + }, + children: { + type: { name: 'ReactNode' }, + description: 'The steps in the stepper. Must be MdStep components.', + table: { + type: { + summary: 'ReactNode', + }, + }, + control: { type: 'ReactNode' }, + }, + MdStep_title: { + type: { name: 'string' }, + description: 'Arg for MdStep. The title of the step.', + table: { + type: { + summary: 'string', + }, + }, + control: { type: 'string' }, + }, + MdStep_completedContent: { + type: { name: 'ReactNode' }, + description: 'Arg for MdStep. The content to show when the step is completed. This is optional', + table: { + type: { + summary: 'ReactNode', + }, + }, + control: { type: 'ReactNode' }, + }, + MdStep_children: { + type: { name: 'ReactNode' }, + description: 'Arg for MdStep. The content of the step.', + table: { + type: { + summary: 'ReactNode', + }, + }, + control: { type: 'ReactNode' }, + }, + }, +}; + +export const Stepper: StoryFn<typeof MdStepper> = (args: Args) => { + const [, updateArgs] = useArgs(); + + const nextStep = () => { + updateArgs({ activeStep: args.activeStep + 1 }); + }; + + const prevStep = () => { + updateArgs({ activeStep: args.activeStep - 1 }); + }; + + return ( + <> + <MdStepper activeStep={args.activeStep}> + <MdStep + title="Step 1" + completedContent={ + <> + This is a completed step, which can be shown differently if the "completedContent" prop is + provided + </> + } + > + <div> + This is a step + <MdButton onClick={nextStep} small> + Next + </MdButton> + </div> + </MdStep> + <MdStep + title="Step 2" + completedContent={<>This is what Step 2 looks like when completed, this is completely customizable!</>} + > + <div> + This is the content of a MdStep. It can contain anything you want, text or HTML. + <p>Here is a paragraph!</p> + <MdButton onClick={nextStep} small> + Next + </MdButton> + <MdButton onClick={prevStep} theme="secondary" small> + Previous + </MdButton> + </div> + </MdStep> + <MdStep title="Step 3"> + <div> + This is the last step in this example + <MdButton onClick={prevStep} theme="secondary" small> + Previous + </MdButton> + </div> + </MdStep> + </MdStepper> + </> + ); +}; +Stepper.args = { + activeStep: 1, +}; + +/* +const Template: StoryFn<typeof MdStepper> = (args: Args) => { + const [, updateArgs] = useArgs(); + + const nextStep = () => { + updateArgs({ activeStep: args.activeStep + 1 }); + }; + + const prevStep = () => { + updateArgs({ activeStep: args.activeStep - 1 }); + }; + + return ( + <div> + <MdStepper activeStep={args.activeStep}> + <MdStep + title="Step 1" + completedContent={ + <> + This is a completed step, which can be shown differently if the "completedContent" prop is + provided + </> + } + > + <div> + This is a step + <MdButton onClick={nextStep} small> + Next + </MdButton> + </div> + </MdStep> + <MdStep + title="Step 2" + completedContent={<>This is what Step 2 looks like when completed, this is completely customizable!</>} + > + <div> + This is the content of a MdStep. It can contain anything you want, text or HTML. + <p>Here is a paragraph!</p> + <MdButton onClick={nextStep} small> + Next + </MdButton> + <MdButton onClick={prevStep} theme="secondary" small> + Previous + </MdButton> + </div> + </MdStep> + <MdStep title="Step 3"> + <div> + This is the last step in this example + <MdButton onClick={prevStep} theme="secondary" small> + Previous + </MdButton> + </div> + </MdStep> + </MdStepper> + </div> + ); +}; + +export const Stepper = Template.bind({}); +Stepper.args = { + activeStep: 1, +}; */