@redseed/redseed-ui-vue3 4.2.0 → 5.1.0

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.
package/index.js CHANGED
@@ -31,4 +31,5 @@ export * from './src/components/Progress'
31
31
  export * from './src/components/Social'
32
32
  export * from './src/components/Sorting'
33
33
  export * from './src/components/Switcher'
34
+ export * from './src/components/Table'
34
35
  export * from './src/components/Toggle'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redseed/redseed-ui-vue3",
3
- "version": "4.2.0",
3
+ "version": "5.1.0",
4
4
  "description": "RedSeed UI Vue 3 components",
5
5
  "main": "index.js",
6
6
  "repository": "https://github.com/redseedtraining/redseed-ui",
@@ -10,6 +10,10 @@ const props = defineProps({
10
10
  type: Boolean,
11
11
  default: true,
12
12
  },
13
+ padded: {
14
+ type: Boolean,
15
+ default: true,
16
+ },
13
17
  disabled: {
14
18
  type: Boolean,
15
19
  default: false,
@@ -40,66 +44,43 @@ function onClick() {
40
44
  class="rsui-card__action-layer"
41
45
  @click="onClick"
42
46
  ></div>
47
+
43
48
  <div v-if="$slots.image"
44
49
  class="rsui-card__image"
45
50
  >
46
51
  <slot name="image"></slot>
47
52
  </div>
48
- <div class="rsui-card__content">
49
- <div v-if="$slots.title || $slots.status"
50
- :class="[
51
- 'rsui-card__content-top',
52
- {
53
- 'rsui-card__content-top--action': $slots['title-action'] || $slots.avatar,
54
- 'rsui-card__content-top--no-avatar': !$slots.avatar,
55
- }
56
- ]"
57
- >
58
- <div v-if="$slots.avatar"
59
- class="rsui-card__avatar"
60
- >
61
- <slot name="avatar"></slot>
62
- </div>
63
- <div class="rsui-card__title-wrapper">
64
- <div v-if="$slots.title"
65
- class="rsui-card__title"
66
- >
67
- <slot name="title"></slot>
68
- </div>
69
- <div v-if="$slots.status"
70
- class="rsui-card__title-status"
71
- >
72
- <slot name="status"></slot>
73
- </div>
74
- </div>
75
- <div v-if="$slots['title-action']"
76
- class="rsui-card__title-action"
77
- >
78
- <slot name="title-action"></slot>
79
- </div>
80
- </div>
53
+
54
+ <div class="rsui-card__header"
55
+ v-if="$slots.header"
56
+ >
57
+ <slot name="header"></slot>
58
+ </div>
59
+
60
+ <div :class="[
61
+ 'rsui-card__content',
62
+ { 'rsui-card__content--padded': padded },
63
+ ]">
81
64
  <slot></slot>
65
+
82
66
  <div v-if="$slots.meta"
83
67
  class="rsui-card__meta"
84
68
  >
85
69
  <slot name="meta"></slot>
86
70
  </div>
87
71
  </div>
88
- <div v-if="$slots.actions"
89
- class="rsui-card__actions"
90
- >
91
- <slot name="actions"></slot>
92
- </div>
93
72
  </div>
94
73
  </template>
95
74
  <style lang="scss" scoped>
96
75
  .rsui-card {
97
- @apply relative flex flex-col;
76
+ @apply relative flex flex-col overflow-hidden;
98
77
  @apply select-none transition duration-200;
99
78
  @apply rounded-xl bg-white border border-rsui-grey-400;
79
+
100
80
  &--hoverable {
101
81
  @apply hover:shadow-md;
102
82
  }
83
+
103
84
  &--clickable {
104
85
  @apply cursor-pointer;
105
86
  .rsui-card__image {
@@ -108,51 +89,26 @@ function onClick() {
108
89
  .rsui-card__content {
109
90
  @apply pointer-events-none;
110
91
  }
111
- .rsui-card__actions {
112
- @apply pointer-events-none;
113
- }
114
92
  }
93
+
115
94
  &__action-layer {
116
95
  @apply absolute inset-0 size-full bg-transparent rounded-lg transition;
117
96
  }
97
+
118
98
  &__image {
119
99
  @apply rounded-t-lg overflow-hidden;
120
100
  }
101
+
121
102
  &__content {
122
- @apply min-h-14 p-3 flex-1 flex flex-col relative gap-y-3;
123
- &-top {
124
- @apply flex items-center;
125
- &--action {
126
- @apply items-center;
127
- }
128
- &--no-avatar {
129
- @apply justify-between;
130
- }
103
+ @apply min-h-16 flex-1 flex flex-col relative gap-y-3;
104
+
105
+ &--padded {
106
+ @apply py-5 px-4 sm:px-6;
131
107
  }
132
108
  }
133
- &__avatar {
134
- @apply size-12 mr-3; // Added margin-right
135
- }
136
- &__title-wrapper {
137
- @apply flex-1 flex justify-between items-center;
138
- }
139
- &__title {
140
- @apply max-h-12 text-lg font-semibold leading-6 line-clamp-2;
141
- }
142
- &__title-status {
143
- @apply ml-2 flex-shrink-0 self-start flex items-center;
144
- }
145
- &__title-action {
146
- @apply pointer-events-auto ml-3; // Added margin-left
147
- }
109
+
148
110
  &__meta {
149
111
  @apply grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-2 mt-4;
150
112
  }
151
- &__actions {
152
- @apply relative p-3;
153
- & > * {
154
- @apply mr-2 mb-2 pointer-events-auto;
155
- }
156
- }
157
113
  }
158
114
  </style>
@@ -0,0 +1,150 @@
1
+ <script setup>
2
+ import { ref } from 'vue'
3
+ import ButtonTertiary from '../Button/ButtonTertiary.vue'
4
+ import { useResponsiveWidth } from '../../helpers/ResponsiveWidth'
5
+ import { EllipsisVerticalIcon } from '@heroicons/vue/24/outline'
6
+
7
+ defineProps({
8
+ showAvatar: {
9
+ type: Boolean,
10
+ default: true,
11
+ },
12
+ showBadge: {
13
+ type: Boolean,
14
+ default: true,
15
+ },
16
+ showSubtitle: {
17
+ type: Boolean,
18
+ default: true,
19
+ },
20
+ showActions: {
21
+ type: Boolean,
22
+ default: true,
23
+ },
24
+ showMoreActions: {
25
+ type: Boolean,
26
+ default: true,
27
+ },
28
+ })
29
+
30
+ const cardHeaderElement = ref(null)
31
+
32
+ const { responsiveWidth } = useResponsiveWidth(cardHeaderElement, 640)
33
+
34
+ const emit = defineEmits(['click:more-actions'])
35
+ </script>
36
+ <template>
37
+ <div ref="cardHeaderElement"
38
+ class="rsui-card-header"
39
+ >
40
+ <div class="rsui-card-header__header">
41
+
42
+ <!-- Avatar slot, optional -->
43
+ <div class="rsui-card-header__avatar"
44
+ v-if="showAvatar && $slots.avatar"
45
+ >
46
+ <slot name="avatar"></slot>
47
+ </div>
48
+
49
+ <div class="rsui-card-header__text">
50
+
51
+ <!-- Title slot, default slot -->
52
+ <div class="rsui-card-header__title"
53
+ :title="$slots.default ? $slots.default()[0].children : ''"
54
+ >
55
+ <slot></slot>
56
+
57
+ <!-- Badge slot, optional -->
58
+ <div class="rsui-card-header__badge"
59
+ v-if="showBadge && $slots.badge"
60
+ >
61
+ <slot name="badge"></slot>
62
+ </div>
63
+ </div>
64
+
65
+ <!-- Subtitle slot, optional -->
66
+ <div class="rsui-card-header__subtitle"
67
+ v-if="showSubtitle && $slots.subtitle"
68
+ :title="$slots.subtitle ? $slots.subtitle()[0].children : ''"
69
+ >
70
+ <slot name="subtitle"></slot>
71
+ </div>
72
+ </div>
73
+
74
+ <!-- Actions slot, optional -->
75
+ <div class="rsui-card-header__toolbar"
76
+ v-if="showActions"
77
+ >
78
+ <!-- Desktop actions slot, optional -->
79
+ <div class="rsui-card-header__actions-desktop"
80
+ v-if="responsiveWidth.specific && $slots.actions"
81
+ >
82
+ <slot name="actions"></slot>
83
+ </div>
84
+
85
+ <!-- More actions slot, optional -->
86
+ <div v-if="showMoreActions"
87
+ class="rsui-card-header__more-actions"
88
+ >
89
+ <slot name="more-actions">
90
+ <ButtonTertiary @click="emit('click:more-actions')">
91
+ <EllipsisVerticalIcon class="rsui-card-header__more-actions-icon" />
92
+ </ButtonTertiary>
93
+ </slot>
94
+ </div>
95
+ </div>
96
+ </div>
97
+
98
+ <!-- Mobile actions slot, optional -->
99
+ <div class="rsui-card-header__actions-mobile"
100
+ v-if="!responsiveWidth.specific && $slots.actions"
101
+ >
102
+ <slot name="actions"></slot>
103
+ </div>
104
+ </div>
105
+ </template>
106
+ <style lang="scss" scoped>
107
+ .rsui-card-header {
108
+ @apply flex flex-col gap-x-3 gap-y-5 overflow-hidden;
109
+ @apply md:flex-row md:justify-between md:items-center;
110
+ @apply border-b border-rsui-grey-400 py-4 px-4 sm:px-6;
111
+
112
+ &__header {
113
+ @apply flex-1 flex justify-between md:items-center gap-x-3;
114
+ }
115
+
116
+ &__avatar {
117
+ @apply size-14 rounded-full overflow-hidden flex items-center justify-center bg-rsui-grey-50;
118
+ @apply mt-2.5 md:mt-0;
119
+ }
120
+
121
+ &__text {
122
+ @apply flex-1 flex flex-col gap-y-0.5 pt-2 md:pt-0;
123
+ }
124
+
125
+ &__title {
126
+ @apply text-lg leading-7 font-semibold text-rsui-grey-900 line-clamp-2 md:line-clamp-1;
127
+ @apply flex flex-wrap items-center gap-y-1 gap-x-2;
128
+ }
129
+
130
+ &__subtitle {
131
+ @apply text-sm leading-5 text-rsui-grey-700 line-clamp-3 md:line-clamp-1;
132
+ }
133
+
134
+ &__toolbar {
135
+ @apply flex gap-3 md:items-center justify-end;
136
+ }
137
+
138
+ &__actions-desktop {
139
+ @apply flex gap-3 items-center;
140
+ }
141
+
142
+ &__actions-mobile {
143
+ @apply flex flex-wrap gap-3 items-center;
144
+ }
145
+
146
+ &__more-actions-icon {
147
+ @apply size-5 text-rsui-grey-400;
148
+ }
149
+ }
150
+ </style>
@@ -1,11 +1,13 @@
1
1
  import ButtonCard from './ButtonCard.vue'
2
2
  import Card from './Card.vue'
3
+ import CardHeader from './CardHeader.vue'
3
4
  import CardResource from './CardResource.vue'
4
5
  import RadioCard from './RadioCard.vue'
5
6
 
6
7
  export {
7
8
  ButtonCard,
8
9
  Card,
10
+ CardHeader,
9
11
  CardResource,
10
12
  RadioCard,
11
13
  }
@@ -1,7 +1,6 @@
1
1
  <script setup>
2
2
  import { ref } from 'vue'
3
3
  import ButtonTertiary from '../Button/ButtonTertiary.vue'
4
- import DropdownMenu from '../DropdownMenu/DropdownMenu.vue'
5
4
  import { useResponsiveWidth } from '../../helpers/ResponsiveWidth'
6
5
  import { EllipsisVerticalIcon } from '@heroicons/vue/24/outline'
7
6
 
@@ -0,0 +1,135 @@
1
+ <script setup>
2
+ import { Card, CardHeader } from '../Card'
3
+ import Tr from './Tr.vue'
4
+ import Th from './Th.vue'
5
+ import Td from './Td.vue'
6
+
7
+ const props = defineProps({
8
+ columns: {
9
+ type: Array,
10
+ default: () => [],
11
+ validator: value => {
12
+ if (value.length === 0) return true
13
+ return value.every(column => typeof column === 'object' && column.key && column.name !== undefined)
14
+ },
15
+ },
16
+ rows: {
17
+ type: Array,
18
+ default: () => [],
19
+ validator: value => {
20
+ if (value.length === 0) return true
21
+ return value.every(row => typeof row === 'object' && row.id)
22
+ },
23
+ },
24
+ clickableRows: {
25
+ type: Boolean,
26
+ default: false,
27
+ },
28
+ })
29
+ </script>
30
+
31
+ <template>
32
+ <Card
33
+ :clickable="false"
34
+ :hoverable="false"
35
+ :padded="false"
36
+ >
37
+ <template #header>
38
+ <CardHeader
39
+ v-bind="$attrs.cardHeader"
40
+ @click:more-actions="$emit('click:more-actions')"
41
+ >
42
+ <template #avatar v-if="$slots.avatar">
43
+ <slot name="avatar"></slot>
44
+ </template>
45
+
46
+ <slot name="title"></slot>
47
+
48
+ <template #badge v-if="$slots.badge">
49
+ <slot name="badge"></slot>
50
+ </template>
51
+
52
+ <template #subtitle v-if="$slots.subtitle">
53
+ <slot name="subtitle"></slot>
54
+ </template>
55
+
56
+ <template #actions v-if="$slots.actions">
57
+ <slot name="actions"></slot>
58
+ </template>
59
+
60
+ <template #more-actions v-if="$slots['more-actions']">
61
+ <slot name="more-actions"></slot>
62
+ </template>
63
+ </CardHeader>
64
+ </template>
65
+
66
+ <div class="rsui-table">
67
+ <div class="rsui-table__container">
68
+ <table>
69
+ <thead v-if="columns">
70
+ <Tr>
71
+ <Th v-for="column in columns"
72
+ :key="column.key"
73
+ scope="col"
74
+ :alignment="column?.alignment"
75
+ :sort="column?.sort"
76
+ @sort="$emit('sort', { column: { ...column, ...$event } })"
77
+ >
78
+ {{ column.name }}
79
+ </Th>
80
+ </Tr>
81
+ </thead>
82
+
83
+ <tbody>
84
+ <Tr v-for="row in rows"
85
+ :key="row.id"
86
+ :clickable="clickableRows"
87
+ @click="$emit('click:row', row)"
88
+ >
89
+ <Td v-for="column in columns"
90
+ :key="column.key"
91
+ >
92
+ <slot :name="'td-' + column.key"
93
+ :row="row"
94
+ >
95
+ {{ row[column.key] }}
96
+ </slot>
97
+ </Td>
98
+ </Tr>
99
+ </tbody>
100
+ </table>
101
+ </div>
102
+
103
+ <div class="rsui-table__footer"
104
+ v-if="$slots.footer"
105
+ >
106
+ <slot name="footer"></slot>
107
+ </div>
108
+ </div>
109
+ </Card>
110
+ </template>
111
+ <style lang="scss" scoped>
112
+ .rsui-table {
113
+ @apply w-full;
114
+
115
+ table {
116
+ @apply w-full border-collapse table-auto;
117
+
118
+ tr{
119
+ th {
120
+ @apply bg-rsui-grey-100;
121
+ @apply font-semibold text-xs leading-5 text-rsui-grey-600 hover:text-rsui-grey-700;
122
+ }
123
+ }
124
+ }
125
+
126
+ &__container {
127
+ @apply w-full overflow-x-scroll;
128
+ }
129
+
130
+ &__footer {
131
+ @apply w-full border-t border-rsui-grey-400 overflow-hidden;
132
+ @apply py-3 px-6;
133
+ }
134
+ }
135
+ </style>
@@ -0,0 +1,34 @@
1
+ <script setup>
2
+ const props = defineProps({
3
+ alignment: {
4
+ type: String,
5
+ default: 'left',
6
+ validator: value => ['left', 'center', 'right'].includes(value),
7
+ },
8
+ })
9
+ </script>
10
+
11
+ <template>
12
+ <td :class="[
13
+ 'rsui-td',
14
+ {
15
+ 'rsui-td--center': alignment === 'center',
16
+ 'rsui-td--right': alignment === 'right',
17
+ },
18
+ ]">
19
+ <slot></slot>
20
+ </td>
21
+ </template>
22
+ <style lang="scss" scoped>
23
+ .rsui-td {
24
+ @apply text-left whitespace-nowrap;
25
+ @apply py-3 px-6 text-sm leading-6 text-rsui-grey-700;
26
+
27
+ &--center {
28
+ @apply text-center;
29
+ }
30
+ &--right {
31
+ @apply text-right;
32
+ }
33
+ }
34
+ </style>
@@ -0,0 +1,44 @@
1
+ <script setup>
2
+ import { UserIcon } from '@heroicons/vue/24/outline'
3
+ </script>
4
+
5
+ <template>
6
+ <div class="rsui-td-user">
7
+ <div class="rsui-td-user__avatar">
8
+ <slot name="avatar">
9
+ <UserIcon class="rsui-td-user__avatar-icon" />
10
+ </slot>
11
+ </div>
12
+
13
+ <div class="rsui-td-user__content">
14
+ <slot></slot>
15
+
16
+ <div class="rsui-td-user__supporting-text">
17
+ <slot name="supporting-text"></slot>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ </template>
22
+
23
+ <style lang="scss" scoped>
24
+ .rsui-td-user {
25
+ @apply flex items-center gap-3;
26
+
27
+ &__avatar {
28
+ @apply flex items-center justify-center size-10 rounded-full overflow-hidden shrink-0;
29
+ @apply text-rsui-grey-600 border border-rsui-grey-200 bg-rsui-grey-100;
30
+ }
31
+
32
+ &__avatar-icon {
33
+ @apply size-6;
34
+ }
35
+
36
+ &__content {
37
+ @apply flex flex-col text-rsui-grey-900;
38
+ }
39
+
40
+ &__supporting-text {
41
+ @apply text-rsui-grey-700;
42
+ }
43
+ }
44
+ </style>
@@ -0,0 +1,103 @@
1
+ <script setup>
2
+ import { ref, computed } from 'vue'
3
+ import { ArrowUpIcon, ChevronUpDownIcon } from '@heroicons/vue/24/outline'
4
+
5
+ const props = defineProps({
6
+ alignment: {
7
+ type: String,
8
+ default: 'left',
9
+ validator: value => ['left', 'center', 'right'].includes(value),
10
+ },
11
+ sort: {
12
+ type: [Boolean, String],
13
+ default: false,
14
+ validator: value => ['asc', 'desc', true, false].includes(value),
15
+ },
16
+ })
17
+
18
+ const sortable = computed(() => props.sort !== false)
19
+
20
+ const isAsc = ref(props.sort === 'asc')
21
+
22
+ const isDesc = ref(props.sort === 'desc')
23
+
24
+ const emit = defineEmits(['sort'])
25
+
26
+ function handleSort() {
27
+ if (!sortable.value) return
28
+
29
+ isDesc.value = isAsc.value
30
+ isAsc.value = !isAsc.value
31
+
32
+ emit('sort', {
33
+ sort: isAsc.value ? 'asc' : 'desc',
34
+ })
35
+ }
36
+ </script>
37
+
38
+ <template>
39
+ <th
40
+ :class="[
41
+ 'rsui-th',
42
+ {
43
+ 'rsui-th--center': alignment === 'center',
44
+ 'rsui-th--right': alignment === 'right',
45
+ 'rsui-th--sortable': sortable,
46
+ },
47
+ ]"
48
+ @click="handleSort"
49
+ >
50
+ <div class="rsui-th__content">
51
+ <slot></slot>
52
+
53
+ <div v-if="sortable"
54
+ class="rsui-th__sort"
55
+ >
56
+ <ChevronUpDownIcon v-if="!isAsc && !isDesc"
57
+ class="rsui-th__sort-icon"
58
+ />
59
+ <ArrowUpIcon v-if="isAsc || isDesc"
60
+ :class="[
61
+ 'rsui-th__sort-icon',
62
+ {
63
+ 'rsui-th__sort-icon--asc': isAsc,
64
+ 'rsui-th__sort-icon--desc': isDesc,
65
+ },
66
+ ]"
67
+ />
68
+ </div>
69
+ </div>
70
+ </th>
71
+ </template>
72
+ <style lang="scss" scoped>
73
+ .rsui-th {
74
+ @apply text-left whitespace-nowrap;
75
+ @apply py-3 px-6 text-sm leading-6 text-rsui-grey-700;
76
+
77
+ &--center {
78
+ @apply text-center;
79
+ }
80
+ &--right {
81
+ @apply text-right;
82
+ }
83
+ &--sortable {
84
+ @apply cursor-pointer;
85
+ }
86
+
87
+ &__content {
88
+ @apply inline-flex items-center gap-1;
89
+ }
90
+
91
+ &__sort {
92
+ @apply shrink-0;
93
+ }
94
+
95
+ &__sort-icon {
96
+ @apply size-3 transition-all;
97
+
98
+ &--desc {
99
+ @apply rotate-180;
100
+ }
101
+ }
102
+ }
103
+ </style>
@@ -0,0 +1,39 @@
1
+ <script setup>
2
+ const props = defineProps({
3
+ clickable: {
4
+ type: Boolean,
5
+ default: false,
6
+ },
7
+ })
8
+
9
+ const emit = defineEmits(['click'])
10
+
11
+ function handleClick() {
12
+ if (!props.clickable) return
13
+
14
+ emit('click')
15
+ }
16
+ </script>
17
+
18
+ <template>
19
+ <tr
20
+ :class="[
21
+ 'rsui-tr',
22
+ { 'rsui-tr--clickable': clickable },
23
+ ]"
24
+ @click="handleClick"
25
+ >
26
+ <slot></slot>
27
+ </tr>
28
+ </template>
29
+ <style lang="scss" scoped>
30
+ .rsui-tr {
31
+ @apply transition-all;
32
+ @apply border-b border-rsui-grey-400 rounded-lg;
33
+ @apply last:border-b-transparent only:border-b-rsui-grey-400;
34
+
35
+ &--clickable {
36
+ @apply cursor-pointer hover:bg-rsui-grey-100;
37
+ }
38
+ }
39
+ </style>
@@ -0,0 +1,7 @@
1
+ import Table from './Table.vue'
2
+ import TdUser from './TdUser.vue'
3
+
4
+ export {
5
+ Table,
6
+ TdUser,
7
+ }