Skip to content

Commit

Permalink
SELF-243: Add Steps Component (#472)
Browse files Browse the repository at this point in the history
* feat: add icon button

* feat: add steps component

* refactor: Ken PR review
  • Loading branch information
NateWaldschmidt committed Apr 11, 2024
1 parent c90549a commit d24c4bd
Show file tree
Hide file tree
Showing 27 changed files with 329 additions and 103 deletions.
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

## v2.0.32

- Add `<Steps />` component
- Adds the icons:
- `Back`
- `CampaignAutomated`
- `CampaignOneTime`
- `Next`
- `Success`

## v2.0.32

- Add `<IconButton />` component

## v2.0.31
Expand All @@ -25,7 +35,7 @@

## v2.0.29

- Make `PrimeVue` and external dependency
- Make `PrimeVue` an external dependency

## v2.0.28

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lob/ui-components",
"version": "2.0.32",
"version": "2.0.33",
"engines": {
"node": ">=20.2.0",
"npm": ">=10.2.0"
Expand Down
12 changes: 0 additions & 12 deletions src/components/ConditionalWrapper.vue

This file was deleted.

3 changes: 3 additions & 0 deletions src/components/Icon/svgs/Back.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/components/Icon/svgs/CampaignAutomated.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/components/Icon/svgs/CampaignOneTime.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/components/Icon/svgs/Next.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/components/Icon/svgs/Success.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/components/Icon/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ export const IconName = {
APP_WINDOWS: 'AppWindows',
ARROW_DOWN_TO_LINE: 'ArrowDownToLine',
ARROW_TREND_UP: 'ArrowTrendUp',
BACK: 'Back',
BANK_ACCOUNT: 'BankAccount',
BARS: 'Bars',
BELL: 'Bell',
BULLHORN: 'Bullhorn',
CAMPAIGN_AUTOMATED: 'CampaignAutomated',
CAMPAIGN_ONE_TIME: 'CampaignOneTime',
CAR: 'Car',
CIRCLE_CHECK: 'CircleCheck',
CIRCLE_CLOSE: 'CircleClose',
Expand All @@ -41,6 +44,7 @@ export const IconName = {
LIGHTNING: 'Lightning',
LOCATION_PIN: 'LocationPin',
MONEY_BILL: 'MoneyBill',
NEXT: 'Next',
OPEN_BOOK: 'OpenBook',
PIE_CHART_SLICE: 'PieChartSlice',
PLAN_BUSINESS: 'PlanBusiness',
Expand All @@ -51,6 +55,7 @@ export const IconName = {
PLAN_STARTUP: 'PlanStartup',
PLUS: 'Plus',
SIGNAL: 'Signal',
SUCCESS: 'Success',
TRIANGLE_EXCLAMATION: 'TriangleExclamation',
USER: 'User',
USERS: 'Users',
Expand Down
2 changes: 1 addition & 1 deletion src/components/IconButton/IconButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import Icon from '@/components/Icon/Icon.vue';
import { IconName } from '@/components/Icon/types';
import { Size } from '@/types';
import ConditionalWrapper from '@/components/ConditionalWrapper.vue';
import ConditionalWrapper from '@/utils/ConditionalWrapper.vue';
import Button from 'primevue/button';
import { AnchorHTMLAttributes, computed, defineOptions } from 'vue';
Expand Down
24 changes: 21 additions & 3 deletions src/components/IconButton/__tests__/IconButton.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
import '@testing-library/jest-dom';
import { IconName } from '@/main';
import {
IconButtonColor,
IconButtonSize,
IconButtonVariant,
IconName
} from '@/main';
import { render } from '@testing-library/vue';

import IconButton from '../IconButton.vue';

describe('IconButton', () => {
it('renders', () => {
it.each([
{ icon: IconName.APP_WINDOWS, size: IconButtonSize.SM },
{ icon: IconName.APP_WINDOWS, size: IconButtonSize.MD },
{ icon: IconName.APP_WINDOWS, size: IconButtonSize.LG },
{ icon: IconName.APP_WINDOWS, size: IconButtonSize.XL },
{ icon: IconName.APP_WINDOWS, color: IconButtonColor.ERROR },
{ icon: IconName.APP_WINDOWS, color: IconButtonColor.INFO },
{ icon: IconName.APP_WINDOWS, color: IconButtonColor.NEUTRAL },
{ icon: IconName.APP_WINDOWS, color: IconButtonColor.SUCCESS },
{ icon: IconName.APP_WINDOWS, color: IconButtonColor.UPGRADE },
{ icon: IconName.APP_WINDOWS, color: IconButtonColor.WARNING },
{ icon: IconName.APP_WINDOWS, variant: IconButtonVariant.OUTLINED },
{ icon: IconName.APP_WINDOWS, variant: IconButtonVariant.PRIMARY }
])('renders', (props) => {
const { getByTestId } = render(IconButton, {
props: { icon: IconName.APP_WINDOWS }
props
});
expect(getByTestId('uic-icon-button')).toBeVisible();
});
Expand Down
10 changes: 10 additions & 0 deletions src/components/Steps/Steps.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Canvas, ArgTypes, PRIMARY_STORY } from '@storybook/addon-docs';
import { Primary } from './Steps.stories';

# Steps

<Canvas of={Primary} />

## Props

<ArgTypes story={PRIMARY_STORY} />
26 changes: 26 additions & 0 deletions src/components/Steps/Steps.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Meta, StoryObj } from '@storybook/vue3';
import mdx from './Steps.mdx';
// @ts-ignore No types from Vue file
import Steps from './Steps.vue';

const meta: Meta<typeof Steps> = {
title: 'Components/Steps',
component: Steps,
parameters: {
docs: {
page: mdx
}
}
};

export default meta;

export const Primary: StoryObj<typeof Steps> = {
args: {
items: [
{ label: 'Step 1', status: 'success' },
{ label: 'Step 2' },
{ label: 'Step 3' }
]
}
};
119 changes: 119 additions & 0 deletions src/components/Steps/Steps.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<template>
<Steps
v-model:activeStep="activeStep"
:model="primeVueItems"
:pt="{ menu: { class: 'uic-steps-menu' } }"
:readonly="false"
data-testid="uic-steps"
>
<template #item="{ active, index, label }">
<span :class="['uic-steps-action', { active }]">
<span :class="['uic-steps-step', { icon: Boolean(stepIcons[index]) }]">
<Icon
v-if="stepIcons[index]"
:icon="stepIcons[index]"
data-testid="uic-steps-icon"
/>
<template v-else>
{{ index + 1 }}
</template>
</span>
<span class="uic-steps-label">{{ label }}</span>
<Transition>
<span v-if="active" class="uic-steps-active-indicator" />
</Transition>
</span>
</template>
</Steps>
</template>

<script setup lang="ts">
import Steps, { StepsProps } from 'primevue/steps';
import { computed, defineModel } from 'vue';
import { Icon, IconName } from '../Icon';
import { StepItem } from './types';
const props = withDefaults(
defineProps<{
items: StepItem[];
readonly?: boolean;
}>(),
{
readonly: false
}
);
const activeStep = defineModel<number>('activeStep', { default: 0 });
const primeVueItems = computed<StepsProps['model']>(() => {
return props.items.map((item) => ({
label: item.label
}));
});
const stepIcons = computed<Record<number, IconName>>(() => {
const icons: Record<number, IconName> = {};
props.items.forEach((item, index) => {
if (item.status === 'success') {
icons[index] = IconName.SUCCESS;
}
});
return icons;
});
</script>

<style lang="scss">
.uic-steps-menu {
@apply flex gap-12;
}
.uic-steps-action {
@apply relative;
@apply cursor-pointer;
@apply flex gap-2 items-center;
@apply py-4;
}
.uic-steps-label {
@apply type-small-600 text-gray-600;
@apply transition-colors;
.active & {
@apply text-upgrade;
}
}
.uic-steps-step {
@apply flex items-center justify-center;
@apply w-5 h-5 rounded-full;
@apply bg-gray-50;
@apply type-xs-600 text-gray-600;
@apply transition-colors;
.active & {
@apply bg-transparent;
@apply border border-upgrade;
@apply text-upgrade;
}
&.icon {
@apply bg-upgrade;
@apply text-white;
@apply border-none;
}
}
.uic-steps-active-indicator {
@apply absolute bottom-0 left-0 right-0;
@apply bg-upgrade h-[2px];
&.v-enter-active,
&.v-leave-active {
transition: opacity 0.25s ease-in-out;
}
&.v-enter-from,
&.v-leave-to {
opacity: 0;
}
}
</style>
42 changes: 42 additions & 0 deletions src/components/Steps/__tests__/Steps.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import '@testing-library/jest-dom';
import { render, within } from '@testing-library/vue';

import Steps from '../Steps.vue';

type PropsType = InstanceType<typeof Steps>['$props'];

const DEFAULT_PROPS: PropsType = {
items: [{ label: 'Step 1' }, { label: 'Step 2' }, { label: 'Step 3' }]
};

describe('Steps', () => {
let props: PropsType;

beforeEach(() => {
props = { ...DEFAULT_PROPS };
});

it('renders', () => {
const { getByTestId } = render(Steps, { props });

const steps = getByTestId('uic-steps');
expect(steps).toBeVisible();
const { getByText: stepsGetByText } = within(steps);
expect(stepsGetByText('Step 1')).toBeVisible();
expect(stepsGetByText('Step 2')).toBeVisible();
expect(stepsGetByText('Step 3')).toBeVisible();
});

it('renders icons with statuses', async () => {
props = {
...props,
items: [
{ label: 'Step 1', status: 'success' },
{ label: 'Step 2' },
{ label: 'Step 3' }
]
};
const { findByTestId } = render(Steps, { props });
expect(await findByTestId('uic-steps-icon')).toBeVisible();
});
});
4 changes: 4 additions & 0 deletions src/components/Steps/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const StepsStatus = {
SUCCESS: 'success'
} as const;
export type StepsStatus = (typeof StepsStatus)[keyof typeof StepsStatus];
3 changes: 3 additions & 0 deletions src/components/Steps/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './constants';
export { default as Steps } from './Steps.vue';
export * from './types';
6 changes: 6 additions & 0 deletions src/components/Steps/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { StepsStatus } from './constants';

export interface StepItem {
label: string;
status?: StepsStatus;
}
5 changes: 3 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ const ComponentLibrary = {

// TODO Utilize the components export but first we will have to remove global usage.
export * from './components/Badge';
export * from './components/ColorPicker';
export * from './components/Icon';
export * from './components/IconButton';
export * from './components/Modal';
export * from './components/ImageFileUpload';
export * from './components/ColorPicker';
export * from './components/Modal';
export * from './components/Steps';

export default ComponentLibrary;

Expand Down
Loading

0 comments on commit d24c4bd

Please sign in to comment.