@dcrackel/meyersquaredui 1.0.37 → 1.0.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/meyersquaredui.es.js +1276 -209
  2. package/dist/meyersquaredui.umd.js +1 -1
  3. package/dist/style.css +1 -1
  4. package/package.json +2 -2
  5. package/src/assets/fonts/Raleway-Thin.ttf +0 -0
  6. package/src/assets/images/portrait1.png +0 -0
  7. package/src/index.js +7 -1
  8. package/src/mocks/getArticles.js +19 -0
  9. package/src/mocks/getTopFencers.js +157 -0
  10. package/src/mocks/getTournamentsMock.js +185 -0
  11. package/src/stories/Atoms/BaseButton/BaseButton.stories.js +35 -32
  12. package/src/stories/Atoms/BaseButton/BaseButton.vue +66 -4
  13. package/src/stories/Atoms/BaseText/BaseText.vue +25 -8
  14. package/src/stories/Atoms/Icon/Icon.vue +6 -1
  15. package/src/stories/Organisms/Cards/ArticleCard/ArticleCard.stories.js +25 -0
  16. package/src/stories/Organisms/Cards/ArticleCard/ArticleCard.vue +48 -0
  17. package/src/stories/Organisms/Cards/FencerCard/FencerCard.stories.js +48 -0
  18. package/src/stories/Organisms/Cards/FencerCard/FencerCard.vue +69 -0
  19. package/src/stories/Organisms/Cards/TournamentCard/TournamentCard.stories.js +24 -0
  20. package/src/stories/Organisms/Cards/TournamentCard/TournamentCard.vue +121 -0
  21. package/src/stories/Organisms/Footer/Foot.stories.js +11 -0
  22. package/src/stories/Organisms/Footer/Footer.vue +94 -0
  23. package/src/stories/Organisms/GridLayout/GridLayout.stories.js +148 -0
  24. package/src/stories/Organisms/GridLayout/GridLayout.vue +98 -0
  25. package/src/stories/Organisms/Headers/Header.stories.js +1 -1
  26. package/src/stories/Organisms/Headers/Header.vue +5 -6
  27. package/src/stories/Organisms/HeroBanners/HomePage/HeroBanner.vue +52 -25
  28. package/src/stories/Organisms/SectionBanners/DoubleButtonBanner/DoubleButtonBanner.stories.js +28 -0
  29. package/src/stories/Organisms/SectionBanners/DoubleButtonBanner/DoubleButtonBanner.vue +118 -0
  30. package/src/stories/Organisms/SectionBanners/SingleButtonBanner/SingleButtonBanner.stories.js +35 -0
  31. package/src/stories/Organisms/SectionBanners/SingleButtonBanner/SingleButtonBanner.vue +48 -0
  32. package/src/stories/Templates/HomePage/{TestPage.stories.js → HomePage.stories.js} +9 -4
  33. package/src/stories/Templates/HomePage/HomePage.vue +111 -0
  34. package/src/components/HelloWorld.vue +0 -43
  35. package/src/stories/Templates/HomePage/TestPage.vue +0 -39
@@ -1,4 +1,5 @@
1
1
  import BaseButton from './BaseButton.vue';
2
+ import Icon from '../../Atoms/Icon/Icon.vue';
2
3
 
3
4
  export default {
4
5
  title: 'Atoms/BaseButton',
@@ -7,6 +8,8 @@ export default {
7
8
  label: 'Click Me',
8
9
  color: 'primary',
9
10
  border: 'none',
11
+ padding: 'px-4 py-2',
12
+ icon: null, // Default: no icon
10
13
  },
11
14
  argTypes: {
12
15
  label: {
@@ -18,12 +21,12 @@ export default {
18
21
  type: 'select',
19
22
  options: ['primary', 'secondary', 'accent'],
20
23
  },
21
- description: 'The background color of the button',
24
+ description: 'The text color of the button',
22
25
  },
23
26
  border: {
24
27
  control: {
25
28
  type: 'select',
26
- options: ['none', 'primary', 'secondary', 'gradient', 'gradient2'],
29
+ options: ['none', 'primary', 'secondary', 'accent', 'gradient1', 'gradient2'],
27
30
  },
28
31
  description: 'The border style of the button',
29
32
  },
@@ -31,12 +34,29 @@ export default {
31
34
  control: 'text',
32
35
  description: 'Optional alt text for accessibility (aria-label)',
33
36
  },
37
+ padding: {
38
+ control: 'text',
39
+ description: 'Custom padding classes for the button (e.g., "px-6 py-3")',
40
+ },
41
+ icon: {
42
+ control: 'none', // We will pass the actual component with props
43
+ description: 'Optional icon component to render inside the button',
44
+ },
45
+ hoverColor: {
46
+ control: {
47
+ type: 'select',
48
+ options: ['primary', 'secondary', 'accent', ''],
49
+ },
50
+ description: 'Optional hover color for the text inside the button',
51
+ },
34
52
  },
35
53
  };
36
54
 
37
55
  const Template = (args) => ({
38
- components: { BaseButton },
56
+ components: { BaseButton, Icon },
39
57
  setup() {
58
+ // Attach icon component to args
59
+ args.icon = Icon;
40
60
  return { args };
41
61
  },
42
62
  template: '<BaseButton v-bind="args" />',
@@ -48,36 +68,19 @@ Default.args = {
48
68
  color: 'secondary',
49
69
  backgroundColor: 'primary',
50
70
  border: 'none',
71
+ padding: 'px-4 py-2',
72
+ icon: null,
51
73
  };
52
74
 
53
- export const PrimaryWithGradientBorder = Template.bind({});
54
- PrimaryWithGradientBorder.args = {
55
- label: 'Primary Button',
56
- color: 'secondary',
57
- backgroundColor: 'primary',
58
- border: 'gradient1',
59
- };
60
-
61
- export const PrimaryWithGradientBorderTwo = Template.bind({});
62
- PrimaryWithGradientBorderTwo.args = {
63
- label: 'Primary Button',
64
- color: 'secondary',
65
- backgroundColor: 'primary',
66
- border: 'gradient2',
67
- };
68
-
69
- export const SecondaryWithAccentBorder = Template.bind({});
70
- SecondaryWithAccentBorder.args = {
71
- label: 'Secondary Button',
72
- color: 'secondary',
73
- backgroundColor: 'primary',
74
- border: 'accent',
75
- };
76
-
77
- export const BlackBorderWithWhiteBackground = Template.bind({});
78
- BlackBorderWithWhiteBackground.args = {
79
- label: 'Secondary Button',
75
+ export const ButtonWithIcon = Template.bind({});
76
+ ButtonWithIcon.args = {
77
+ label: 'Icon Button',
80
78
  color: 'primary',
81
79
  backgroundColor: 'secondary',
82
- border: 'primary',
83
- };
80
+ hoverColor: 'accent',
81
+ border: 'accent',
82
+ padding: 'px-4 py-2',
83
+ iconName: 'fa-thin fa-arrow-right',
84
+ iconColor: 'primary',
85
+ iconSize: 'lg'
86
+ };
@@ -1,7 +1,8 @@
1
1
  <template>
2
2
  <button
3
3
  :class="[
4
- 'px-4 py-2 rounded-md transition duration-300 ease-in-out',
4
+ 'rounded-md transition duration-300 ease-in-out',
5
+ paddingClass,
5
6
  textColorClass,
6
7
  backgroundClass,
7
8
  borderClass,
@@ -10,13 +11,23 @@
10
11
  :aria-label="altText || label"
11
12
  @click="$emit('click')"
12
13
  >
13
- {{ label }}
14
+ <div :class="['flex items-center', alignmentClass, 'space-x-2']">
15
+ <BaseText :color="color" :hoverColor="hoverColor" :size="size" :weight="weight">{{ label }}</BaseText>
16
+ <Icon :icon="iconName" :color="iconColor" :size="iconSize" :type="iconType"/>
17
+ </div>
14
18
  </button>
15
19
  </template>
16
20
 
17
21
  <script>
22
+ import BaseText from "../BaseText/BaseText.vue";
23
+ import Icon from '../../Atoms/Icon/Icon.vue';
24
+
18
25
  export default {
19
26
  name: 'BaseButton',
27
+ components: {
28
+ BaseText,
29
+ Icon
30
+ },
20
31
  props: {
21
32
  label: {
22
33
  type: String,
@@ -26,11 +37,24 @@ export default {
26
37
  type: String,
27
38
  default: null,
28
39
  },
40
+ size: {
41
+ type: String,
42
+ default: 'md',
43
+ },
44
+ weight: {
45
+ type: String,
46
+ default: 'normal',
47
+ },
29
48
  color: {
30
49
  type: String,
31
50
  default: 'primary',
32
51
  validator: (value) => ['primary', 'secondary', 'accent'].includes(value),
33
52
  },
53
+ hoverColor: {
54
+ type: String,
55
+ default: '',
56
+ validator: (value) => ['primary', 'secondary', 'accent'].includes(value),
57
+ },
34
58
  backgroundColor: {
35
59
  type: String,
36
60
  default: 'primary',
@@ -42,8 +66,38 @@ export default {
42
66
  validator: (value) =>
43
67
  ['none', 'primary', 'secondary', 'accent', 'gradient1', 'gradient2'].includes(value),
44
68
  },
69
+ padding: {
70
+ type: String,
71
+ default: 'px-4 py-2',
72
+ },
73
+ iconName: {
74
+ type: String,
75
+ default: ''
76
+ },
77
+ iconColor: {
78
+ type: String,
79
+ default: 'primary',
80
+ validator: (value) => ['primary', 'secondary', 'accent'].includes(value),
81
+ },
82
+ iconSize: {
83
+ control: { type: 'select', options: ['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'] },
84
+ description: 'Icon size',
85
+ },
86
+ iconType: {
87
+ type: String,
88
+ default: 'fa-solid',
89
+ validator: (value) => ['fa-thin', 'fa-sharp'].includes(value),
90
+ },
91
+ align: {
92
+ type: String,
93
+ default: 'center',
94
+ validator: (value) => ['left', 'center', 'right', 'between'].includes(value),
95
+ },
45
96
  },
46
97
  computed: {
98
+ paddingClass() {
99
+ return this.padding;
100
+ },
47
101
  textColorClass() {
48
102
  const textColor = {
49
103
  primary: 'text-primary',
@@ -55,7 +109,7 @@ export default {
55
109
  backgroundClass() {
56
110
  const colorMap = {
57
111
  primary: 'bg-primary hover:bg-accent',
58
- secondary: 'bg-secondary hover:bg-accent',
112
+ secondary: 'bg-secondary hover:bg-primary',
59
113
  accent: 'bg-accent hover:bg-accent',
60
114
  };
61
115
  return colorMap[this.backgroundColor] || 'bg-primary';
@@ -71,6 +125,15 @@ export default {
71
125
  };
72
126
  return borderMap[this.border] || '';
73
127
  },
128
+ alignmentClass() {
129
+ const alignmentMap = {
130
+ left: 'justify-start',
131
+ center: 'justify-center',
132
+ right: 'justify-end',
133
+ between: 'justify-between', // New "between" option added
134
+ };
135
+ return alignmentMap[this.align] || 'justify-center';
136
+ },
74
137
  },
75
138
  };
76
139
  </script>
@@ -106,5 +169,4 @@ export default {
106
169
  background: linear-gradient(to right, #009DFF, #25E07C);
107
170
  color: white; /* Optional: You can change the text color on hover as well */
108
171
  }
109
-
110
172
  </style>
@@ -16,20 +16,24 @@ export default {
16
16
  size: {
17
17
  type: String,
18
18
  default: 'md',
19
- validator: (value) => ['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl', '4xl', '5xl'].includes(value),
20
19
  },
21
20
  color: {
22
21
  type: String,
23
22
  default: 'primary',
24
23
  },
24
+ hoverColor: {
25
+ type: String,
26
+ default: '',
27
+ },
25
28
  weight: {
26
29
  type: String,
27
30
  default: 'normal',
28
- validator: (value) => ['light', 'normal', 'semibold', 'bold'].includes(value),
31
+ validator: (value) => ['thin','light', 'normal','semibold','bold'].includes(value),
29
32
  },
30
33
  },
31
34
  computed: {
32
35
  textClasses() {
36
+ // Map for size classes
33
37
  const sizeClasses = {
34
38
  xs: 'text-xs',
35
39
  sm: 'text-sm',
@@ -39,17 +43,30 @@ export default {
39
43
  '2xl': 'text-2xl',
40
44
  '3xl': 'text-3xl',
41
45
  '4xl': 'text-4xl',
42
- '5xl': 'text-5xl'
46
+ '5xl': 'text-5xl',
43
47
  };
44
48
 
45
49
  const weightClasses = {
46
- light: 'font-light',
47
- normal: 'font-normal',
48
- semibold: 'font-semibold',
49
- bold: 'font-bold',
50
+ thin: 'font-thin font-[100]',
51
+ light: 'font-light font-[300]',
52
+ normal: 'font-normal font-[400]',
53
+ semibold: 'font-semibold font-[600]',
54
+ bold: 'font-bold font-[700]',
50
55
  };
51
56
 
52
- return `${sizeClasses[this.size]} ${weightClasses[this.weight]} text-${this.color}`;
57
+ const hoverClass = this.hoverColor ? `hover:text-${this.hoverColor}` : '';
58
+
59
+ // Parse the size prop to allow responsive classes
60
+ const sizeClassList = this.size
61
+ .split(' ')
62
+ .map((size) => {
63
+ const [prefix, value] = size.includes(':') ? size.split(':') : [null, size];
64
+ return prefix ? `${prefix}:${sizeClasses[value]}` : sizeClasses[value];
65
+ })
66
+ .filter(Boolean)
67
+ .join(' ');
68
+
69
+ return `${sizeClassList} ${weightClasses[this.weight]} text-${this.color} ${hoverClass} font-raleway`;
53
70
  },
54
71
  },
55
72
  };
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <i :class="[ 'fas', icon, colorClasses, sizeClasses ]" @click="$emit('click')" />
2
+ <i :class="[ type, icon, colorClasses, sizeClasses ]" @click="$emit('click')" />
3
3
  </template>
4
4
 
5
5
  <script>
@@ -20,6 +20,11 @@ export default {
20
20
  default: 'md',
21
21
  validator: (value) => ['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'].includes(value),
22
22
  },
23
+ type: {
24
+ type: String,
25
+ default: 'fa-solid',
26
+ validator: (value) => ['fa-thin', 'fa-sharp'].includes(value),
27
+ }
23
28
  },
24
29
  computed: {
25
30
  colorClasses() {
@@ -0,0 +1,25 @@
1
+ import ArticleCard from './ArticleCard.vue';
2
+ import mockArticles from "../../../../mocks/getArticles.js";
3
+
4
+ export default {
5
+ title: 'Organisms/Cards/ArticleCard',
6
+ component: ArticleCard,
7
+ argTypes: {
8
+ onReadMore: { action: 'read-more' }
9
+ }
10
+ };
11
+
12
+ const Template = (args) => ({
13
+ components: { ArticleCard },
14
+ setup() {
15
+ return { args };
16
+ },
17
+ template: '<ArticleCard v-bind="args" @read-more="args.onReadMore" />',
18
+ });
19
+
20
+ export const Default = Template.bind({});
21
+ Default.args = {
22
+ image: 'https://via.placeholder.com/300x200',
23
+ articleTitle: 'Exciting News in HEMA',
24
+ description: 'Learn about the latest developments and events happening in the HEMA community. Stay informed and get the latest updates.',
25
+ };
@@ -0,0 +1,48 @@
1
+ <template>
2
+ <div class="w-72 p-4 bg-secondary rounded-lg flex flex-col items-start">
3
+ <img :src="image" alt="Article Image" class="w-full h-40 object-cover rounded-t-md mb-4" />
4
+
5
+ <BaseText tag="h3" size="xl" weight="semibold" class="mb-2">{{ articleTitle }}</BaseText>
6
+ <BaseText tag="p" size="sm" weight="normal" class="mb-4 h-20">{{ truncatedDescription }}</BaseText>
7
+
8
+ <a href="#" @click.prevent="onReadMore" class="w-full flex justify-end pr-2">
9
+ <BaseText tag="p" size="sm" weight="simibold" class="mb-4 border-b border-secondary hover:border-accent">Read More &rarr;</BaseText>
10
+ </a>
11
+ </div>
12
+ </template>
13
+
14
+ <script>
15
+ import BaseText from "../../../Atoms/BaseText/BaseText.vue";
16
+
17
+ export default {
18
+ name: 'ArticleCard',
19
+ components: {
20
+ BaseText
21
+ },
22
+ props: {
23
+ data: {
24
+ type: Object,
25
+ required: true,
26
+ },
27
+ },
28
+ computed: {
29
+ image() {
30
+ return this.data.image;
31
+ },
32
+ articleTitle() {
33
+ return this.data.articleTitle;
34
+ },
35
+ truncatedDescription() {
36
+ if (this.data.description.length > 120) {
37
+ return this.data.description.substring(0, 120) + ' ...';
38
+ }
39
+ return this.data.description;
40
+ }
41
+ },
42
+ methods: {
43
+ onReadMore() {
44
+ this.$emit('read-more');
45
+ }
46
+ }
47
+ };
48
+ </script>
@@ -0,0 +1,48 @@
1
+ import FencerCard from './FencerCard.vue';
2
+
3
+ export default {
4
+ title: 'Organisms/Cards/FencerCard',
5
+ component: FencerCard,
6
+ argTypes: {
7
+ portrait: { control: 'text' },
8
+ displayName: { control: 'text' },
9
+ clubName: { control: 'text' },
10
+ m2Rating: { control: 'number' },
11
+ hrRating: { control: 'number' },
12
+ },
13
+ };
14
+
15
+ const Template = (args) => ({
16
+ components: { FencerCard },
17
+ setup() {
18
+ return { args };
19
+ },
20
+ template: '<FencerCard v-bind="args" />',
21
+ });
22
+
23
+ export const Default = Template.bind({});
24
+ Default.args = {
25
+ portrait: 'https://randomuser.me/api/portraits/women/26.jpg',
26
+ displayName: 'John Doe',
27
+ clubName: 'Awesome Fencing Club',
28
+ m2Rating: 'A24',
29
+ hrRating: 1567,
30
+ };
31
+
32
+ export const HighRating = Template.bind({});
33
+ HighRating.args = {
34
+ portrait: '',
35
+ displayName: 'Jane Smith',
36
+ clubName: 'Elite Fencing Academy',
37
+ m2Rating: 1500,
38
+ hrRating: 1300,
39
+ };
40
+
41
+ export const NoRating = Template.bind({});
42
+ NoRating.args = {
43
+ portrait: '',
44
+ displayName: 'Unknown Fencer',
45
+ clubName: 'No Club',
46
+ m2Rating: null,
47
+ hrRating: null,
48
+ };
@@ -0,0 +1,69 @@
1
+ <template>
2
+ <section class="mr-4 md:mr-2 items-center">
3
+ <div class="flex flex-col m-auto justify-center mb-4 items-center w-[160px]">
4
+ <img :src="portrait" alt="Fencer Portrait" class="w-[120px] h-[120px] md:w-40 md:h-40 rounded-full object-cover z-1 flex-shrink-0" />
5
+ </div>
6
+ <!-- Ratings -->
7
+ <div class="flex flex-col md:flex-row justify-center md:justify-around -mt-2 border-b border-borderGray z-10 pb-2 mb-2">
8
+ <div class="flex items-center justify-center">
9
+ <BaseText tag="p" size="xs md:sm" weight="semibold" class="primary mr-1 block z-50">M2: </BaseText>
10
+ <BaseText tag="p" size="sm md:md" weight="bold" class="text-accent">{{ m2Rating }}</BaseText>
11
+ </div>
12
+ <div class="hidden md:flex items-center justify-center">
13
+ <BaseText tag="p" size="md" weight="bold" class="text-accent">{{ hrRating }}</BaseText>
14
+ <BaseText tag="p" size="sm" weight="semibold" class="primary mr-1">:HR</BaseText>
15
+ </div>
16
+ </div>
17
+ <BaseText tag="h3" size="sm md:xl" weight="semibold" class="text-center">{{ displayName }}</BaseText>
18
+ <BaseText tag="p" size="xs md:sm" weight="normal" class="text-center h-10">{{ clubName }}</BaseText>
19
+ </section>
20
+ </template>
21
+
22
+ <script>
23
+ import BaseText from "../../../Atoms/BaseText/BaseText.vue";
24
+ import defaultPortrait from '../../../../assets/images/portrait1.png'
25
+
26
+ // :style="{ flexShrink: '0', minWidth: '160px', minHeight: '160px' }"
27
+ export default {
28
+ name: 'FencerCard',
29
+ components: {
30
+ BaseText,
31
+ defaultPortrait
32
+ },
33
+ props: {
34
+ data: {
35
+ type: Object,
36
+ required: true,
37
+ },
38
+ index: {
39
+ type: Number,
40
+ required: true,
41
+ },
42
+ },
43
+ data() {
44
+ return {
45
+ };
46
+ },
47
+ computed: {
48
+ portrait() {
49
+ return this.data.ProfileImage && this.data.ProfileImage.URL
50
+ ? this.data.ProfileImage.URL
51
+ : defaultPortrait;
52
+ },
53
+ displayName() {
54
+ return this.data.DisplayName;
55
+ },
56
+ clubName() {
57
+ return this.data.Club.Name;
58
+ },
59
+ m2Rating() {
60
+ const m2RatingForWeapon = this.data.M2Ratings.find(rating => rating.WeaponId === 1);
61
+ return m2RatingForWeapon ? m2RatingForWeapon.FormattedRating : 'U';
62
+ },
63
+ hrRating() {
64
+ return this.data.HEMARatings && this.data.HEMARatings[0] ? this.data.HEMARatings[0].Rating : 0;
65
+ },
66
+ }
67
+ };
68
+ </script>
69
+
@@ -0,0 +1,24 @@
1
+ import TournamentCard from './TournamentCard.vue';
2
+
3
+ export default {
4
+ title: 'Organisms/Cards/TournamentCard',
5
+ component: TournamentCard,
6
+ };
7
+
8
+ const Template = (args) => ({
9
+ components: { TournamentCard },
10
+ setup() {
11
+ return { args };
12
+ },
13
+ template: '<TournamentCard v-bind="args" />',
14
+ });
15
+
16
+ export const Default = Template.bind({});
17
+ Default.args = {
18
+ image: 'https://assets.simpleviewinc.com/simpleview/image/upload/c_fill,f_avif,h_800,q_65,w_1920/v1/clients/wausauwi/download_5__d27db8e0-a64a-4db5-8126-17033dab6712.jpg',
19
+ title: 'National Fencing Tournament',
20
+ location: 'New York City, NY',
21
+ startDate: 'Jan 1, 2024',
22
+ endDate: 'Jan 3, 2024',
23
+ numberOfFencers: 120,
24
+ };
@@ -0,0 +1,121 @@
1
+ <template>
2
+ <div class="md:w-full mb-8">
3
+ <!-- Desktop Version -->
4
+ <section class="hidden md:block">
5
+ <img
6
+ :src="image"
7
+ alt="Tournament Image"
8
+ class="w-full h-52 object-cover mb-2 rounded-md"
9
+ @error="imageError"
10
+ :style="fallbackStyle"
11
+ />
12
+ <BaseText tag="h3" size="xl" weight="semibold" class="mb-1">{{ title }}</BaseText>
13
+ <BaseText tag="p" size="sm" weight="semibold" class="mb-2">{{ location }}</BaseText>
14
+ <div class="flex items-center mb-1">
15
+ <span class="flex w-6 justify-center">
16
+ <Icon icon="fa-calendar" color="accent" size="sm" class="mr-3 w-4"/>
17
+ </span>
18
+ <BaseText tag="p" size="sm" class="">{{ startDate }} to {{ endDate }}</BaseText>
19
+ </div>
20
+ <div class="flex items-center">
21
+ <span class="flex w-6 justify-center">
22
+ <Icon icon="fa-user-friends" color="accent" size="sm" class="mr-3"/>
23
+ </span>
24
+ <BaseText tag="p" size="sm" class="">{{ numberOfFencers }} Registered Fencers</BaseText>
25
+ </div>
26
+ </section>
27
+
28
+ <!-- Mobile Version -->
29
+ <section class="md:hidden flex-col items-start gap-2 border-b border-borderGray">
30
+
31
+ <div class="flex flex-row">
32
+ <img :src="image"
33
+ alt="Tournament Image"
34
+ class="w-28 h-16 object-cover rounded-md"
35
+ @error="imageError"
36
+ :style="fallbackStyle"
37
+ />
38
+ <div class="flex flex-col h-16 justify-center pl-2">
39
+ <BaseText tag="h3" size="lg" weight="semibold" class="mb-1">{{ title }}</BaseText>
40
+ <BaseText tag="p" size="xs" weight="semibold" class="mb-1">{{ location }}</BaseText>
41
+ </div>
42
+ </div>
43
+ <div class="flex justify-between pt-4 pb-2">
44
+ <div class="flex justify-start w-5/12">
45
+ <Icon icon="fa-user-friends" color="accent" size="xs" class="mr-1" />
46
+ <BaseText tag="p" size="xs" class="">{{ numberOfFencers }} Registered</BaseText>
47
+ </div>
48
+ <div class="flex justify-start mb-1 w-7/12">
49
+ <Icon icon="fa-calendar" color="accent" size="xs" class="mr-1 w-4" />
50
+ <BaseText tag="p" size="xs" class="">{{ startDate }} to {{ endDate }}</BaseText>
51
+ </div>
52
+ </div>
53
+ </section>
54
+ </div>
55
+ </template>
56
+
57
+ <script>
58
+ import BaseText from "../../../Atoms/BaseText/BaseText.vue";
59
+ import Icon from "../../../Atoms/Icon/Icon.vue";
60
+
61
+ export default {
62
+ name: 'TournamentCard',
63
+ components: {
64
+ BaseText,
65
+ Icon
66
+ },
67
+ props: {
68
+ data: {
69
+ type: Object,
70
+ required: true,
71
+ }
72
+ },
73
+ data() {
74
+ return {
75
+ isImageError: false
76
+ };
77
+ },
78
+ mounted() {
79
+ console.log(this.data);
80
+ },
81
+ computed: {
82
+ image() {
83
+ return !this.isImageError && this.data.Images && this.data.Images.length > 0
84
+ ? this.data.Images[0].URL
85
+ : 'https://via.placeholder.com/300x200';
86
+ },
87
+ fallbackStyle() {
88
+ return this.isImageError
89
+ ? {backgroundColor: '#f5f5f5', display: 'flex', alignItems: 'center', justifyContent: 'center'}
90
+ : {};
91
+ },
92
+ title() {
93
+ return this.data && this.data.Name || '';
94
+ },
95
+ location() {
96
+ return this.data && this.data.Address ? `${this.data.Address.Name} • ${this.data.Address.City}, ${this.data.Address.State}` : 'Location Unavailable';
97
+ },
98
+ startDate() {
99
+ return this.data && this.formatDate(this.data.StartDate);
100
+ },
101
+ endDate() {
102
+ return this.data && this.formatDate(this.data.EndDate);
103
+ },
104
+ numberOfFencers() {
105
+ return this.data && this.data.NumberOfFencers ? this.data.NumberOfFencers : 0;
106
+ }
107
+ },
108
+ methods: {
109
+ formatDate(date) {
110
+ return new Date(date).toLocaleDateString('en-US', {
111
+ year: 'numeric',
112
+ month: 'short',
113
+ day: 'numeric',
114
+ });
115
+ },
116
+ imageError() {
117
+ this.isImageError = true;
118
+ }
119
+ }
120
+ };
121
+ </script>
@@ -0,0 +1,11 @@
1
+ import Footer from './Footer.vue';
2
+
3
+ export default {
4
+ title: 'Organisms/Footers/Footer',
5
+ component: Footer,
6
+ };
7
+
8
+ export const Default = () => ({
9
+ components: { Footer },
10
+ template: '<Footer />',
11
+ });