diff --git a/app/assets/javascripts/vue_shared/components/action_card.stories.js b/app/assets/javascripts/vue_shared/components/action_card.stories.js
new file mode 100644
index 0000000000000000000000000000000000000000..2e3aab27ac412f2d97cf5e8b35c240ef582b887a
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/action_card.stories.js
@@ -0,0 +1,30 @@
+import ActionCard, { VARIANTS } from './action_card.vue';
+
+export default {
+ component: ActionCard,
+ title: 'vue_shared/action_card',
+ argTypes: {
+ variant: {
+ control: 'select',
+ options: Object.values(VARIANTS),
+ },
+ },
+};
+
+const Template = (args, { argTypes }) => ({
+ components: { ActionCard },
+ props: Object.keys(argTypes),
+ template: `
+
+ `,
+});
+
+export const Default = Template.bind({});
+Default.args = {
+ title: 'Create a project',
+ description:
+ 'Projects are where you store your code, access issues, wiki, and other features of GitLab.',
+ icon: 'project',
+ variant: VARIANTS.default,
+ href: 'gitlab.com',
+};
diff --git a/app/assets/javascripts/vue_shared/components/action_card.vue b/app/assets/javascripts/vue_shared/components/action_card.vue
new file mode 100644
index 0000000000000000000000000000000000000000..3138266132af955de927b1cf5d996e6b020fbd6e
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/action_card.vue
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+ {{ title }}
+
+
+
+
+ {{ title }}
+
+
{{ description }}
+
+
+
diff --git a/app/assets/stylesheets/components/action_card_component.scss b/app/assets/stylesheets/components/action_card_component.scss
index 1db8f35b670bcf1530da9e679d99c70ad67fc621..9c6dd2422fc1f74c237c0b73b5725683193032be 100644
--- a/app/assets/stylesheets/components/action_card_component.scss
+++ b/app/assets/stylesheets/components/action_card_component.scss
@@ -64,7 +64,7 @@
text-wrap: balance;
}
- &-controls {
+ &-controls:not(:empty) {
margin-top: $gl-spacing-scale-4;
}
}
diff --git a/spec/components/previews/onboarding/action_card_component_preview.rb b/spec/components/previews/onboarding/action_card_component_preview.rb
index 798a56caf10db5dbf270e1f2b40e51ca91d78e54..844f5a73d403641c9491a8dece7f4868fbb6a883 100644
--- a/spec/components/previews/onboarding/action_card_component_preview.rb
+++ b/spec/components/previews/onboarding/action_card_component_preview.rb
@@ -17,8 +17,8 @@ def default(
description: "Groups are the best way to manage projects and members",
title: "Create a group")
render Onboarding::ActionCardComponent.new(
- title,
- description,
+ title: title,
+ description: description,
icon: icon,
href: href,
variant: variant
diff --git a/spec/frontend/vue_shared/components/action_card_spec.js b/spec/frontend/vue_shared/components/action_card_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..4382c4d31c8f298772fd8d08e09c0268d85cdb64
--- /dev/null
+++ b/spec/frontend/vue_shared/components/action_card_spec.js
@@ -0,0 +1,93 @@
+import { GlLink } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import ActionCard from '~/vue_shared/components/action_card.vue';
+
+describe('Action card', () => {
+ let wrapper;
+
+ const createComponent = ({ propsData = {} } = {}) => {
+ wrapper = shallowMountExtended(ActionCard, {
+ propsData: {
+ title: 'Create a project',
+ description:
+ 'Projects are where you store your code, access issues, wiki, and other features of GitLab.',
+ icon: 'project',
+ ...propsData,
+ },
+ });
+ };
+
+ const findTitle = () => wrapper.findByTestId('action-card-title');
+ const findTitleLink = () => wrapper.findComponent(GlLink);
+ const findDescription = () => wrapper.findByTestId('action-card-description');
+ const findCardIcon = () => wrapper.findByTestId('action-card-icon');
+ const findArrowIcon = () => wrapper.findByTestId('action-card-arrow-icon');
+
+ const baseCardClass = 'action-card';
+
+ describe('default', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('applies default variant styles', () => {
+ expect(wrapper.classes()).toContain(baseCardClass, 'action-card-default');
+ });
+
+ it('renders title', () => {
+ expect(findTitle().text()).toBe('Create a project');
+ });
+
+ it('renders description', () => {
+ expect(findDescription().text()).toBe(
+ 'Projects are where you store your code, access issues, wiki, and other features of GitLab.',
+ );
+ });
+
+ it('renders card icon', () => {
+ expect(findCardIcon().props('name')).toBe('project');
+ });
+
+ it('does not render link', () => {
+ expect(findTitleLink().exists()).toBe(false);
+ });
+ });
+
+ describe('with link', () => {
+ beforeEach(() => {
+ createComponent({ propsData: { href: 'gitlab.com' } });
+ });
+
+ it('renders link', () => {
+ expect(findTitleLink().exists()).toBe(true);
+ expect(findTitleLink().text()).toBe('Create a project');
+ expect(findTitleLink().attributes('href')).toBe('gitlab.com');
+ });
+
+ it('renders card icon', () => {
+ expect(findCardIcon().props('name')).toBe('project');
+ });
+
+ it('renders arrow icon', () => {
+ expect(findArrowIcon().exists()).toBe(true);
+ });
+ });
+
+ describe.each`
+ variant | expectedVariantClass | expectedCardIcon
+ ${'success'} | ${'action-card-success'} | ${'check'}
+ ${'promo'} | ${'action-card-promo'} | ${'project'}
+ `('when variant is $variant', ({ variant, expectedVariantClass, expectedCardIcon }) => {
+ beforeEach(() => {
+ createComponent({ propsData: { variant } });
+ });
+
+ it('applies correct variant styles', () => {
+ expect(wrapper.classes()).toContain(baseCardClass, expectedVariantClass);
+ });
+
+ it('renders correct card icon', () => {
+ expect(findCardIcon().props('name')).toBe(expectedCardIcon);
+ });
+ });
+});