@globalbrain/sefirot 3.16.0 → 3.17.1

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.
@@ -15,6 +15,7 @@ defineProps<{
15
15
  v-for="item, index in list"
16
16
  :key="index"
17
17
  :lead-icon="item.leadIcon"
18
+ :label="item.label"
18
19
  :text="item.text"
19
20
  :link="item.link"
20
21
  :on-click="item.onClick"
@@ -1,16 +1,25 @@
1
1
  <script setup lang="ts">
2
2
  import { type IconifyIcon } from '@iconify/vue/dist/offline'
3
+ import { computed } from 'vue'
3
4
  import SIcon from './SIcon.vue'
4
5
  import SLink from './SLink.vue'
5
6
 
6
7
  export interface ActionListItem {
7
8
  leadIcon?: IconifyIcon
8
- text: string
9
9
  link?: string
10
+ label?: string
11
+ disabled?: boolean
10
12
  onClick?(): void
13
+
14
+ /** @deprecated Use `:label` instead. */
15
+ text?: string
11
16
  }
12
17
 
13
- defineProps<ActionListItem>()
18
+ const props = defineProps<ActionListItem>()
19
+
20
+ const _label = computed(() => {
21
+ return props.label ?? props.text
22
+ })
14
23
  </script>
15
24
 
16
25
  <template>
@@ -23,7 +32,7 @@ defineProps<ActionListItem>()
23
32
  <span v-if="leadIcon" class="lead-icon">
24
33
  <SIcon class="lead-icon-svg" :icon="leadIcon" />
25
34
  </span>
26
- <span class="text">{{ text }}</span>
35
+ <span class="text">{{ _label }}</span>
27
36
  </component>
28
37
  </template>
29
38
 
@@ -0,0 +1,84 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import { type DropdownSection, useManualDropdownPosition } from '../composables/Dropdown'
4
+ import { useFlyout } from '../composables/Flyout'
5
+ import SButton, { type Mode, type Size, type Tooltip, type Type } from './SButton.vue'
6
+ import SDropdown from './SDropdown.vue'
7
+
8
+ export type { Mode, Size, Tooltip, Type }
9
+
10
+ const props = defineProps<{
11
+ tag?: string
12
+ size?: Size
13
+ type?: Type
14
+ mode?: Mode
15
+ icon?: any
16
+ iconMode?: Mode
17
+ label?: string
18
+ labelMode?: Mode
19
+ rounded?: boolean
20
+ block?: boolean
21
+ loading?: boolean
22
+ disabled?: boolean
23
+ tooltip?: Tooltip
24
+ options: DropdownSection[]
25
+ }>()
26
+
27
+ const container = ref<any>(null)
28
+
29
+ const { isOpen, toggle } = useFlyout(container)
30
+ const { position, update: updatePosition } = useManualDropdownPosition(container)
31
+
32
+ async function onOpen() {
33
+ if (!props.disabled) {
34
+ updatePosition()
35
+ toggle()
36
+ }
37
+ }
38
+ </script>
39
+
40
+ <template>
41
+ <div class="SActionMenu" :class="[block]" ref="container">
42
+ <div class="button">
43
+ <SButton
44
+ :tag="tag"
45
+ :size="size"
46
+ :type="type"
47
+ :mode="mode"
48
+ :icon="icon"
49
+ :icon-mode="iconMode"
50
+ :label="label"
51
+ :label-mode="labelMode"
52
+ :rounded="rounded"
53
+ :block="block"
54
+ :loading="loading"
55
+ :disabled="disabled"
56
+ :tooltip="tooltip"
57
+ @click="onOpen"
58
+ />
59
+ </div>
60
+ <div v-if="isOpen" class="dropdown" :class="position">
61
+ <SDropdown :sections="options" />
62
+ </div>
63
+ </div>
64
+ </template>
65
+
66
+ <style scoped lang="postcss">
67
+ .SActionMenu {
68
+ position: relative;
69
+ display: inline-block;
70
+ }
71
+
72
+ .dropdown {
73
+ position: absolute;
74
+ left: 0;
75
+ z-index: var(--z-index-dropdown);
76
+
77
+ &.top { bottom: calc(100% + 8px); }
78
+ &.bottom { top: calc(100% + 8px); }
79
+ }
80
+
81
+ .SActionMenu.block {
82
+ display: block;
83
+ }
84
+ </style>
@@ -0,0 +1,43 @@
1
+ <script setup lang="ts">
2
+ import { useControlSize } from '../composables/Control'
3
+ import { type DropdownSection } from '../composables/Dropdown'
4
+ import SActionMenu, { type Mode, type Tooltip, type Type } from './SActionMenu.vue'
5
+
6
+ defineProps<{
7
+ tag?: string
8
+ type?: Type
9
+ mode?: Mode
10
+ icon?: any
11
+ iconMode?: Mode
12
+ label?: string
13
+ labelMode?: Mode
14
+ href?: string
15
+ loading?: boolean
16
+ disabled?: boolean
17
+ tooltip?: Tooltip
18
+ options: DropdownSection[]
19
+ }>()
20
+
21
+ const size = useControlSize()
22
+ </script>
23
+
24
+ <template>
25
+ <div class="SControlActionMenu">
26
+ <SActionMenu
27
+ :tag="tag"
28
+ :size="size"
29
+ :type="type"
30
+ :mode="mode"
31
+ :icon="icon"
32
+ :icon-mode="iconMode"
33
+ :label="label"
34
+ :label-mode="labelMode"
35
+ :href="href"
36
+ :tooltip="tooltip"
37
+ :options="options"
38
+ block
39
+ :loading="loading"
40
+ :disabled="disabled"
41
+ />
42
+ </div>
43
+ </template>
@@ -9,6 +9,11 @@ defineProps<{
9
9
  perPage: number
10
10
  }>()
11
11
 
12
+ defineEmits<{
13
+ (e: 'prev'): void
14
+ (e: 'next'): void
15
+ }>()
16
+
12
17
  const size = useControlSize()
13
18
  const position = useControlPosition()
14
19
  </script>
@@ -21,6 +26,8 @@ const position = useControlPosition()
21
26
  :total="total"
22
27
  :page="page"
23
28
  :per-page="perPage"
29
+ @prev="$emit('prev')"
30
+ @next="$emit('next')"
24
31
  />
25
32
  </div>
26
33
  </template>
@@ -21,7 +21,7 @@ defineProps<{
21
21
  .SDropdown {
22
22
  border: 1px solid var(--c-divider);
23
23
  border-radius: 6px;
24
- min-width: 256px;
24
+ min-width: 288px;
25
25
  max-height: 384px;
26
26
  overflow-y: auto;
27
27
  white-space: normal;
@@ -2,6 +2,7 @@
2
2
  import { type DropdownSection } from '../composables/Dropdown'
3
3
  import SDropdownSectionActions from './SDropdownSectionActions.vue'
4
4
  import SDropdownSectionComponent from './SDropdownSectionComponent.vue'
5
+ import SDropdownSectionDateRange from './SDropdownSectionDateRange.vue'
5
6
  import SDropdownSectionFilter from './SDropdownSectionFilter.vue'
6
7
  import SDropdownSectionMenu from './SDropdownSectionMenu.vue'
7
8
 
@@ -22,8 +23,13 @@ defineProps<{
22
23
  :options="section.options"
23
24
  :on-click="section.onClick"
24
25
  />
26
+ <SDropdownSectionDateRange
27
+ v-else-if="section.type === 'date-range'"
28
+ :range="section.range"
29
+ :on-apply="section.onApply"
30
+ />
25
31
  <SDropdownSectionActions
26
- v-if="section.type === 'actions'"
32
+ v-else-if="section.type === 'actions'"
27
33
  :options="section.options"
28
34
  />
29
35
  <SDropdownSectionComponent
@@ -0,0 +1,126 @@
1
+ <script setup lang="ts">
2
+ import { type MaybeRefOrGetter, computed, shallowRef, toValue } from 'vue'
3
+ import { type DropdownSectionActionsOption } from '../composables/Dropdown'
4
+ import { useTrans } from '../composables/Lang'
5
+ import { useV } from '../composables/V'
6
+ import { DateRange, type DateRangePreset, type DateRangePresetType } from '../support/DateRange'
7
+ import SDropdownSectionActions from './SDropdownSectionActions.vue'
8
+ import SDropdownSectionDateRangeDateFromTo from './SDropdownSectionDateRangeDateFromTo.vue'
9
+ import SDropdownSectionDateRangeYear from './SDropdownSectionDateRangeYear.vue'
10
+ import SDropdownSectionDateRangeYearHalf from './SDropdownSectionDateRangeYearHalf.vue'
11
+ import SDropdownSectionDateRangeYearQuarter from './SDropdownSectionDateRangeYearQuarter.vue'
12
+ import SInputSelect from './SInputSelect.vue'
13
+
14
+ const props = defineProps<{
15
+ range: MaybeRefOrGetter<DateRange>
16
+ onApply: (range: DateRange) => void
17
+ }>()
18
+
19
+ const { t } = useTrans({
20
+ en: {
21
+ p_date_from_to: 'Custom period',
22
+ p_year: 'Year',
23
+ p_year_half: 'Year half',
24
+ p_year_quarter: 'Year quarter',
25
+ a_apply: 'Apply'
26
+ },
27
+ ja: {
28
+ p_date_from_to: '日付指定',
29
+ p_year: '年',
30
+ p_year_half: '半期',
31
+ p_year_quarter: '四半期',
32
+ a_apply: '適用する'
33
+ }
34
+ })
35
+
36
+ const _range = shallowRef(toValue(props.range))
37
+
38
+ const presetValue = computed(() => {
39
+ return _range.value.preset.type
40
+ })
41
+
42
+ const presetOptions = [
43
+ { label: t.p_date_from_to, value: 'date-from-to' },
44
+ { label: t.p_year, value: 'year' },
45
+ { label: t.p_year_half, value: 'year-half' },
46
+ { label: t.p_year_quarter, value: 'year-quarter' }
47
+ ]
48
+
49
+ const actions: DropdownSectionActionsOption[] = [
50
+ { label: t.a_apply, mode: 'info', onClick: onApply }
51
+ ]
52
+
53
+ const { validate } = useV()
54
+
55
+ function onPresetChange(value: any) {
56
+ _range.value = new DateRange().setPreset(value as DateRangePresetType)
57
+ }
58
+
59
+ function onValueChange(value: DateRangePreset) {
60
+ _range.value = new DateRange().setPreset(value)
61
+ }
62
+
63
+ async function onApply() {
64
+ if (await validate()) {
65
+ props.onApply(_range.value)
66
+ }
67
+ }
68
+ </script>
69
+
70
+ <template>
71
+ <div class="SDropdownSectionDateRange">
72
+ <div class="preset">
73
+ <SInputSelect
74
+ size="mini"
75
+ :options="presetOptions"
76
+ :value="presetValue"
77
+ @change="onPresetChange"
78
+ />
79
+ </div>
80
+ <div class="control">
81
+ <SDropdownSectionDateRangeDateFromTo
82
+ v-if="_range.preset.type === 'date-from-to'"
83
+ :preset="_range.preset"
84
+ @change="onValueChange"
85
+ />
86
+ <SDropdownSectionDateRangeYear
87
+ v-else-if="_range.preset.type === 'year'"
88
+ :preset="_range.preset"
89
+ @change="onValueChange"
90
+ />
91
+ <SDropdownSectionDateRangeYearHalf
92
+ v-else-if="_range.preset.type === 'year-half'"
93
+ :preset="_range.preset"
94
+ @change="onValueChange"
95
+ />
96
+ <SDropdownSectionDateRangeYearQuarter
97
+ v-else-if="_range.preset.type === 'year-quarter'"
98
+ :preset="_range.preset"
99
+ @change="onValueChange"
100
+ />
101
+ </div>
102
+ <div class="actions">
103
+ <SDropdownSectionActions
104
+ :options="actions"
105
+ />
106
+ </div>
107
+ </div>
108
+ </template>
109
+
110
+ <style scoped lang="postcss">
111
+ .SDropdownSectionDateRange {
112
+ display: flex;
113
+ flex-direction: column;
114
+ gap: 1px;
115
+ background-color: var(--c-bg-elv-1);
116
+ }
117
+
118
+ .preset {
119
+ padding: 8px;
120
+ background-color: var(--c-bg-elv-3);
121
+ }
122
+
123
+ .control {
124
+ background-color: var(--c-bg-elv-3);
125
+ }
126
+ </style>
@@ -0,0 +1,88 @@
1
+ <script setup lang="ts">
2
+ import { useV } from '../composables/V'
3
+ import { DateFromTo } from '../support/DateRange'
4
+ import { type Day } from '../support/Day'
5
+ import { required } from '../validation/rules'
6
+ import SInputDate from './SInputDate.vue'
7
+
8
+ const props = defineProps<{
9
+ preset: DateFromTo
10
+ }>()
11
+
12
+ const emit = defineEmits<{
13
+ (e: 'change', value: DateFromTo): void
14
+ }>()
15
+
16
+ const { validation } = useV(() => ({
17
+ from: props.preset.from,
18
+ to: props.preset.to
19
+ }), {
20
+ from: { required: required() },
21
+ to: { required: required() }
22
+ })
23
+
24
+ function onFromChange(value: Day | null) {
25
+ emit('change', new DateFromTo().setFrom(value).setTo(props.preset.to))
26
+ }
27
+
28
+ function onToChange(value: Day | null) {
29
+ emit('change', new DateFromTo().setFrom(props.preset.from).setTo(value))
30
+ }
31
+ </script>
32
+
33
+ <template>
34
+ <div class="SDropdownSectionDateRangeDateFromTo">
35
+ <div class="group">
36
+ <div class="label">From</div>
37
+ <div class="input">
38
+ <SInputDate
39
+ size="mini"
40
+ block
41
+ :model-value="preset.from"
42
+ :validation="validation.from"
43
+ hide-error
44
+ @change="onFromChange"
45
+ />
46
+ </div>
47
+ </div>
48
+ <div class="group">
49
+ <div class="label">To</div>
50
+ <div class="input">
51
+ <SInputDate
52
+ size="mini"
53
+ block
54
+ :model-value="preset.to"
55
+ :validation="validation.to"
56
+ hide-error
57
+ @change="onToChange"
58
+ />
59
+ </div>
60
+ </div>
61
+ </div>
62
+ </template>
63
+
64
+ <style scoped lang="postcss">
65
+ .SDropdownSectionDateRangeDateFromTo {
66
+ display: flex;
67
+ flex-direction: column;
68
+ gap: 8px;
69
+ padding: 8px 0;
70
+ }
71
+
72
+ .group {
73
+ display: flex;
74
+ align-items: center;
75
+ padding: 0 8px 0 16px;
76
+ }
77
+
78
+ .label {
79
+ flex-shrink: 0;
80
+ width: 64px;
81
+ font-size: 12px;
82
+ color: var(--c-text-2);
83
+ }
84
+
85
+ .input {
86
+ flex-grow: 1;
87
+ }
88
+ </style>
@@ -0,0 +1,73 @@
1
+ <script setup lang="ts">
2
+ import { useV } from '../composables/V'
3
+ import { Year } from '../support/DateRange'
4
+ import { maxValue, minValue, required } from '../validation/rules'
5
+ import SInputNumber from './SInputNumber.vue'
6
+
7
+ const props = defineProps<{
8
+ preset: Year
9
+ }>()
10
+
11
+ const emit = defineEmits<{
12
+ (e: 'change', value: Year): void
13
+ }>()
14
+
15
+ const { validation } = useV(() => ({
16
+ year: props.preset.year
17
+ }), {
18
+ year: {
19
+ required: required(),
20
+ minValue: minValue(1),
21
+ maxValue: maxValue(9999)
22
+ }
23
+ })
24
+
25
+ function onInput(value: number | null) {
26
+ emit('change', new Year().setYear(value))
27
+ }
28
+ </script>
29
+
30
+ <template>
31
+ <div class="SDropdownSectionDateRangeYear">
32
+ <div class="group">
33
+ <div class="label">Year</div>
34
+ <div class="input">
35
+ <SInputNumber
36
+ size="mini"
37
+ placeholder="YYYY"
38
+ block
39
+ :model-value="preset.year"
40
+ :validation="validation.year"
41
+ hide-error
42
+ @input="onInput"
43
+ />
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </template>
48
+
49
+ <style scoped lang="postcss">
50
+ .SDropdownSectionDateRangeYear {
51
+ display: flex;
52
+ flex-direction: column;
53
+ gap: 8px;
54
+ padding: 8px 0;
55
+ }
56
+
57
+ .group {
58
+ display: flex;
59
+ align-items: center;
60
+ padding: 0 8px 0 16px;
61
+ }
62
+
63
+ .label {
64
+ flex-shrink: 0;
65
+ width: 64px;
66
+ font-size: 12px;
67
+ color: var(--c-text-2);
68
+ }
69
+
70
+ .input {
71
+ flex-grow: 1;
72
+ }
73
+ </style>
@@ -0,0 +1,94 @@
1
+ <script setup lang="ts">
2
+ import { useV } from '../composables/V'
3
+ import { YearHalf } from '../support/DateRange'
4
+ import { maxValue, minValue, required } from '../validation/rules'
5
+ import SInputNumber from './SInputNumber.vue'
6
+ import SInputSelect from './SInputSelect.vue'
7
+
8
+ const props = defineProps<{
9
+ preset: YearHalf
10
+ }>()
11
+
12
+ const emit = defineEmits<{
13
+ (e: 'change', value: YearHalf): void
14
+ }>()
15
+
16
+ const { validation } = useV(() => ({
17
+ year: props.preset.year
18
+ }), {
19
+ year: {
20
+ required: required(),
21
+ minValue: minValue(1),
22
+ maxValue: maxValue(9999)
23
+ }
24
+ })
25
+
26
+ const halfOptions = [
27
+ { label: 'H1', value: 1 },
28
+ { label: 'H2', value: 2 }
29
+ ]
30
+
31
+ function onYearInput(value: number | null) {
32
+ emit('change', new YearHalf().setYear(value).setHalf(props.preset.half))
33
+ }
34
+
35
+ function onHalfChange(value: any) {
36
+ emit('change', new YearHalf().setYear(props.preset.year).setHalf(value))
37
+ }
38
+ </script>
39
+
40
+ <template>
41
+ <div class="SDropdownSectionDateRangeYearHalf">
42
+ <div class="group">
43
+ <div class="label">Year</div>
44
+ <div class="input">
45
+ <SInputNumber
46
+ size="mini"
47
+ placeholder="YYYY"
48
+ block
49
+ :model-value="preset.year"
50
+ :validation="validation.year"
51
+ hide-error
52
+ @input="onYearInput"
53
+ />
54
+ </div>
55
+ </div>
56
+ <div class="group">
57
+ <div class="label">Half</div>
58
+ <div class="input">
59
+ <SInputSelect
60
+ size="mini"
61
+ :options="halfOptions"
62
+ :model-value="preset.half"
63
+ @change="onHalfChange"
64
+ />
65
+ </div>
66
+ </div>
67
+ </div>
68
+ </template>
69
+
70
+ <style scoped lang="postcss">
71
+ .SDropdownSectionDateRangeYearHalf {
72
+ display: flex;
73
+ flex-direction: column;
74
+ gap: 8px;
75
+ padding: 8px 0;
76
+ }
77
+
78
+ .group {
79
+ display: flex;
80
+ align-items: center;
81
+ padding: 0 8px 0 16px;
82
+ }
83
+
84
+ .label {
85
+ flex-shrink: 0;
86
+ width: 64px;
87
+ font-size: 12px;
88
+ color: var(--c-text-2);
89
+ }
90
+
91
+ .input {
92
+ flex-grow: 1;
93
+ }
94
+ </style>
@@ -0,0 +1,96 @@
1
+ <script setup lang="ts">
2
+ import { useV } from '../composables/V'
3
+ import { YearQuarter } from '../support/DateRange'
4
+ import { maxValue, minValue, required } from '../validation/rules'
5
+ import SInputNumber from './SInputNumber.vue'
6
+ import SInputSelect from './SInputSelect.vue'
7
+
8
+ const props = defineProps<{
9
+ preset: YearQuarter
10
+ }>()
11
+
12
+ const emit = defineEmits<{
13
+ (e: 'change', value: YearQuarter): void
14
+ }>()
15
+
16
+ const { validation } = useV(() => ({
17
+ year: props.preset.year
18
+ }), {
19
+ year: {
20
+ required: required(),
21
+ minValue: minValue(1),
22
+ maxValue: maxValue(9999)
23
+ }
24
+ })
25
+
26
+ const quarterOptions = [
27
+ { label: 'Q1', value: 1 },
28
+ { label: 'Q2', value: 2 },
29
+ { label: 'Q3', value: 3 },
30
+ { label: 'Q4', value: 4 }
31
+ ]
32
+
33
+ function onYearInput(value: number | null) {
34
+ emit('change', new YearQuarter().setYear(value).setQuarter(props.preset.quarter))
35
+ }
36
+
37
+ function onQuarterChange(value: any) {
38
+ emit('change', new YearQuarter().setYear(props.preset.year).setQuarter(value))
39
+ }
40
+ </script>
41
+
42
+ <template>
43
+ <div class="SDropdownSectionDateRangeYearQuarter">
44
+ <div class="group">
45
+ <div class="label">Year</div>
46
+ <div class="input">
47
+ <SInputNumber
48
+ size="mini"
49
+ placeholder="YYYY"
50
+ block
51
+ :model-value="preset.year"
52
+ :validation="validation.year"
53
+ hide-error
54
+ @input="onYearInput"
55
+ />
56
+ </div>
57
+ </div>
58
+ <div class="group">
59
+ <div class="label">Quarter</div>
60
+ <div class="input">
61
+ <SInputSelect
62
+ size="mini"
63
+ :options="quarterOptions"
64
+ :model-value="preset.quarter"
65
+ @change="onQuarterChange"
66
+ />
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </template>
71
+
72
+ <style scoped lang="postcss">
73
+ .SDropdownSectionDateRangeYearQuarter {
74
+ display: flex;
75
+ flex-direction: column;
76
+ gap: 8px;
77
+ padding: 8px 0;
78
+ }
79
+
80
+ .group {
81
+ display: flex;
82
+ align-items: center;
83
+ padding: 0 8px 0 16px;
84
+ }
85
+
86
+ .label {
87
+ flex-shrink: 0;
88
+ width: 64px;
89
+ font-size: 12px;
90
+ color: var(--c-text-2);
91
+ }
92
+
93
+ .input {
94
+ flex-grow: 1;
95
+ }
96
+ </style>
@@ -1,23 +1,15 @@
1
1
  <script setup lang="ts">
2
- import { type DropdownSectionMenuOption } from '../composables/Dropdown'
2
+ import SActionList, { type ActionList } from './SActionList.vue'
3
3
 
4
4
  defineProps<{
5
- options: DropdownSectionMenuOption[]
5
+ options: ActionList
6
6
  }>()
7
7
  </script>
8
8
 
9
9
  <template>
10
- <ul class="SDropdownSectionMenu">
11
- <li v-for="option in options" :key="option.label" class="item">
12
- <button
13
- class="button"
14
- @click="option.onClick"
15
- :disabled="option.disabled"
16
- >
17
- {{ option.label }}
18
- </button>
19
- </li>
20
- </ul>
10
+ <div class="SDropdownSectionMenu">
11
+ <SActionList :list="options" />
12
+ </div>
21
13
  </template>
22
14
 
23
15
  <style scoped lang="postcss">
@@ -25,25 +17,4 @@ defineProps<{
25
17
  padding: 8px;
26
18
  background-color: var(--c-bg-elv-3);
27
19
  }
28
-
29
- .button {
30
- display: block;
31
- border-radius: 6px;
32
- padding: 0 8px;
33
- width: 100%;
34
- text-align: left;
35
- line-height: 32px;
36
- font-size: 14px;
37
- font-weight: 400;
38
- transition: color 0.25s, background-color 0.25s;
39
-
40
- &:hover:not(:disabled) {
41
- background-color: var(--c-bg-mute-1);
42
- }
43
-
44
- &:disabled {
45
- color: var(--c-text-3);
46
- cursor: not-allowed;
47
- }
48
- }
49
20
  </style>
@@ -1,13 +1,21 @@
1
1
  import { useElementBounding, useWindowSize } from '@vueuse/core'
2
- import { type Component, type MaybeRef, type Ref, ref, unref } from 'vue'
2
+ import { type Component, type MaybeRef, type MaybeRefOrGetter, type Ref, ref, unref } from 'vue'
3
+ import { type ActionList } from '../components/SActionList.vue'
4
+ import { type DateRange } from '../support/DateRange'
3
5
 
4
6
  export type DropdownSection =
5
7
  | DropdownSectionMenu
6
8
  | DropdownSectionFilter
9
+ | DropdownSectionDateRange
7
10
  | DropdownSectionComponent
8
11
  | DropdownSectionActions
9
12
 
10
- export type DropdownSectionType = 'menu' | 'filter' | 'actions' | 'component'
13
+ export type DropdownSectionType =
14
+ | 'menu'
15
+ | 'filter'
16
+ | 'date-range'
17
+ | 'actions'
18
+ | 'component'
11
19
 
12
20
  export interface DropdownSectionBase {
13
21
  type: DropdownSectionType
@@ -15,13 +23,7 @@ export interface DropdownSectionBase {
15
23
 
16
24
  export interface DropdownSectionMenu extends DropdownSectionBase {
17
25
  type: 'menu'
18
- options: DropdownSectionMenuOption[]
19
- }
20
-
21
- export interface DropdownSectionMenuOption {
22
- label: string
23
- disabled?: boolean
24
- onClick(): void
26
+ options: ActionList
25
27
  }
26
28
 
27
29
  export interface DropdownSectionFilter extends DropdownSectionBase {
@@ -60,6 +62,12 @@ export interface DropdownSectionFilterOptionAvatar extends DropdownSectionFilter
60
62
  image?: string | null
61
63
  }
62
64
 
65
+ export interface DropdownSectionDateRange extends DropdownSectionBase {
66
+ type: 'date-range'
67
+ range: MaybeRefOrGetter<DateRange>
68
+ onApply(range: DateRange): void
69
+ }
70
+
63
71
  export interface DropdownSectionActions extends DropdownSectionBase {
64
72
  type: 'actions'
65
73
  options: DropdownSectionActionsOption[]
@@ -87,6 +95,36 @@ export function createDropdown(section: DropdownSection[]): DropdownSection[] {
87
95
  return section
88
96
  }
89
97
 
98
+ export function createDropdownDateRange(
99
+ section: Omit<DropdownSectionDateRange, 'type'>
100
+ ): DropdownSectionDateRange {
101
+ return { type: 'date-range', ...section }
102
+ }
103
+
104
+ export function createDropdownMenu(
105
+ section: Omit<DropdownSectionMenu, 'type'>
106
+ ): DropdownSectionMenu {
107
+ return { type: 'menu', ...section }
108
+ }
109
+
110
+ export function createDropdownFilter(
111
+ section: Omit<DropdownSectionFilter, 'type'>
112
+ ): DropdownSectionFilter {
113
+ return { type: 'filter', ...section }
114
+ }
115
+
116
+ export function createDropdownActions(
117
+ section: Omit<DropdownSectionActions, 'type'>
118
+ ): DropdownSectionActions {
119
+ return { type: 'actions', ...section }
120
+ }
121
+
122
+ export function createDropdownComponent(
123
+ section: Omit<DropdownSectionComponent, 'type'>
124
+ ): DropdownSectionComponent {
125
+ return { type: 'component', ...section }
126
+ }
127
+
90
128
  export function useManualDropdownPosition(
91
129
  container?: Ref<any>,
92
130
  initPosition?: 'top' | 'bottom'
@@ -28,8 +28,8 @@ export function useV<
28
28
  Data extends { [key in keyof Rules]: any },
29
29
  Rules extends ValidationArgs = ValidationArgs
30
30
  >(
31
- data: MaybeRefOrGetter<Data>,
32
- rules: MaybeRefOrGetter<Rules>
31
+ data?: MaybeRefOrGetter<Data>,
32
+ rules?: MaybeRefOrGetter<Rules>
33
33
  ): V<Data, Rules> {
34
34
  const { t } = useTrans({
35
35
  en: { notify: 'Form contains errors. Please correct them and try again.' },
@@ -38,10 +38,10 @@ export function useV<
38
38
 
39
39
  const snackbars = useSnackbars()
40
40
 
41
- const d = computed(() => toValue(data))
42
- const r = computed(() => toValue(rules))
41
+ const d = computed(() => toValue(data) ?? {})
42
+ const r = computed(() => toValue(rules) ?? {})
43
43
 
44
- const validation = useVuelidate(r, d)
44
+ const validation = useVuelidate(r, d) as any
45
45
 
46
46
  function reset(): void {
47
47
  validation.value.$reset()
@@ -1,18 +1,22 @@
1
1
  import { type App } from 'vue'
2
2
  import SControl from '../components/SControl.vue'
3
+ import SControlActionMenu from '../components/SControlActionMenu.vue'
3
4
  import SControlButton from '../components/SControlButton.vue'
4
5
  import SControlCenter from '../components/SControlCenter.vue'
5
6
  import SControlInputSearch from '../components/SControlInputSearch.vue'
6
7
  import SControlLeft from '../components/SControlLeft.vue'
8
+ import SControlPagination from '../components/SControlPagination.vue'
7
9
  import SControlRight from '../components/SControlRight.vue'
8
10
  import SControlText from '../components/SControlText.vue'
9
11
 
10
12
  export function mixin(app: App): void {
11
13
  app.component('SControl', SControl)
14
+ app.component('SControlActionMenu', SControlActionMenu)
12
15
  app.component('SControlButton', SControlButton)
13
16
  app.component('SControlCenter', SControlCenter)
14
17
  app.component('SControlInputSearch', SControlInputSearch)
15
18
  app.component('SControlLeft', SControlLeft)
19
+ app.component('SControlPagination', SControlPagination)
16
20
  app.component('SControlRight', SControlRight)
17
21
  app.component('SControlText', SControlText)
18
22
  }
@@ -20,10 +24,12 @@ export function mixin(app: App): void {
20
24
  declare module 'vue' {
21
25
  export interface GlobalComponents {
22
26
  SControl: typeof SControl
27
+ SControlActionMenu: typeof SControlActionMenu
23
28
  SControlButton: typeof SControlButton
24
29
  SControlCenter: typeof SControlCenter
25
30
  SControlInputSearch: typeof SControlInputSearch
26
31
  SControlLeft: typeof SControlLeft
32
+ SControlPagination: typeof SControlPagination
27
33
  SControlRight: typeof SControlRight
28
34
  SControlText: typeof SControlText
29
35
  }
@@ -0,0 +1,227 @@
1
+ import { type Day, day } from './Day'
2
+
3
+ export type DateRangePreset =
4
+ | DateFromTo
5
+ | Year
6
+ | YearHalf
7
+ | YearQuarter
8
+
9
+ export type DateRangePresetType =
10
+ | 'date-from-to'
11
+ | 'year'
12
+ | 'year-half'
13
+ | 'year-quarter'
14
+
15
+ export type DateRangePresetDict = {
16
+ 'date-from-to': typeof DateFromTo
17
+ 'year': typeof Year
18
+ 'year-half': typeof YearHalf
19
+ 'year-quarter': typeof YearQuarter
20
+ }
21
+
22
+ export class DateRange {
23
+ preset: DateRangePreset
24
+
25
+ presetDict: DateRangePresetDict = {
26
+ 'date-from-to': DateFromTo,
27
+ 'year': Year,
28
+ 'year-half': YearHalf,
29
+ 'year-quarter': YearQuarter
30
+ }
31
+
32
+ constructor() {
33
+ this.preset = new DateFromTo()
34
+ }
35
+
36
+ setPreset(preset: DateRangePreset | DateRangePresetType): this {
37
+ preset = typeof preset === 'string' ? new this.presetDict[preset]() : preset
38
+
39
+ this.preset = preset
40
+
41
+ return this
42
+ }
43
+
44
+ getFrom(): Day | null {
45
+ return this.preset.getFrom()
46
+ }
47
+
48
+ getTo(): Day | null {
49
+ return this.preset.getTo()
50
+ }
51
+
52
+ getLabeltext(): string {
53
+ return this.preset.getLabeltext()
54
+ }
55
+ }
56
+
57
+ export abstract class DateRangePresetBase {
58
+ abstract getFrom(): Day | null
59
+ abstract getTo(): Day | null
60
+ abstract getLabeltext(): string
61
+
62
+ protected getYear(year: number | null): Day | null {
63
+ if (!year) {
64
+ return null
65
+ }
66
+
67
+ const d = day().year(year)
68
+
69
+ return d.isValid() ? d : null
70
+ }
71
+ }
72
+
73
+ export class DateFromTo extends DateRangePresetBase {
74
+ type = 'date-from-to' as const
75
+ from: Day | null
76
+ to: Day | null
77
+
78
+ constructor() {
79
+ super()
80
+ this.from = day().subtract(30, 'days')
81
+ this.to = day()
82
+ }
83
+
84
+ getFrom(): Day | null {
85
+ return this.from
86
+ }
87
+
88
+ getTo(): Day | null {
89
+ return this.to
90
+ }
91
+
92
+ getLabeltext(): string {
93
+ const f = this.getFrom()?.format('YYYY-MM-DD')
94
+ const t = this.getTo()?.format('YYYY-MM-DD')
95
+
96
+ return (f && t) ? `${f} – ${t}` : 'Error'
97
+ }
98
+
99
+ setFrom(from: Day | null): this {
100
+ this.from = from
101
+ return this
102
+ }
103
+
104
+ setTo(to: Day | null): this {
105
+ this.to = to
106
+ return this
107
+ }
108
+ }
109
+
110
+ export class Year extends DateRangePresetBase {
111
+ type = 'year' as const
112
+ year: number | null = null
113
+
114
+ constructor() {
115
+ super()
116
+ this.year = day().year()
117
+ }
118
+
119
+ getFrom(): Day | null {
120
+ return this.getYear(this.year)?.startOf('year') ?? null
121
+ }
122
+
123
+ getTo(): Day | null {
124
+ return this.getYear(this.year)?.endOf('year') ?? null
125
+ }
126
+
127
+ getLabeltext(): string {
128
+ return this.getYear(this.year)?.format('YYYY') ?? 'Error'
129
+ }
130
+
131
+ setYear(year: number | null): this {
132
+ this.year = year
133
+ return this
134
+ }
135
+ }
136
+
137
+ export class YearHalf extends DateRangePresetBase {
138
+ type = 'year-half' as const
139
+ year: number | null
140
+ half: 1 | 2
141
+
142
+ protected monthDict: Record<number, number> = {
143
+ 1: 0,
144
+ 2: 6
145
+ }
146
+
147
+ constructor() {
148
+ super()
149
+ this.year = day().year()
150
+ this.half = 1
151
+ }
152
+
153
+ getFrom(): Day | null {
154
+ return this.getYear(this.year)
155
+ ?.month(this.monthDict[this.half])
156
+ .startOf('month') ?? null
157
+ }
158
+
159
+ getTo(): Day | null {
160
+ return this.getYear(this.year)
161
+ ?.month(this.monthDict[this.half] + 5)
162
+ .endOf('month') ?? null
163
+ }
164
+
165
+ getLabeltext(): string {
166
+ const y = this.getYear(this.year)?.format('YYYY')
167
+
168
+ return y ? `${y}H${this.half}` : 'Error'
169
+ }
170
+
171
+ setYear(year: number | null): this {
172
+ this.year = year
173
+ return this
174
+ }
175
+
176
+ setHalf(half: 1 | 2): this {
177
+ this.half = half
178
+ return this
179
+ }
180
+ }
181
+
182
+ export class YearQuarter extends DateRangePresetBase {
183
+ type = 'year-quarter' as const
184
+ year: number | null
185
+ quarter: 1 | 2 | 3 | 4
186
+
187
+ protected monthDict: Record<number, number> = {
188
+ 1: 0,
189
+ 2: 3,
190
+ 3: 6,
191
+ 4: 9
192
+ }
193
+
194
+ constructor() {
195
+ super()
196
+ this.year = day().year()
197
+ this.quarter = 1
198
+ }
199
+
200
+ getFrom(): Day | null {
201
+ return this.getYear(this.year)
202
+ ?.month(this.monthDict[this.quarter])
203
+ .startOf('month') ?? null
204
+ }
205
+
206
+ getTo(): Day | null {
207
+ return this.getYear(this.year)
208
+ ?.month(this.monthDict[this.quarter] + 2)
209
+ .endOf('month') ?? null
210
+ }
211
+
212
+ getLabeltext(): string {
213
+ const y = this.getYear(this.year)?.format('YYYY')
214
+
215
+ return y ? `${y}Q${this.quarter}` : 'Error'
216
+ }
217
+
218
+ setYear(year: number | null): this {
219
+ this.year = year
220
+ return this
221
+ }
222
+
223
+ setQuarter(quarter: 1 | 2 | 3 | 4): this {
224
+ this.quarter = quarter
225
+ return this
226
+ }
227
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@globalbrain/sefirot",
3
- "version": "3.16.0",
3
+ "version": "3.17.1",
4
4
  "packageManager": "pnpm@8.12.1",
5
5
  "description": "Vue Components for Global Brain Design System.",
6
6
  "author": "Kia Ishii <ka.ishii@globalbrains.com>",
@@ -56,7 +56,7 @@
56
56
  "postcss": "^8.4.32",
57
57
  "postcss-nested": "^6.0.1",
58
58
  "v-calendar": "^3.1.2",
59
- "vue": "^3.3.11",
59
+ "vue": "^3.3.13",
60
60
  "vue-router": "^4.2.5"
61
61
  },
62
62
  "dependencies": {
@@ -79,16 +79,16 @@
79
79
  "@types/file-saver": "^2.0.7",
80
80
  "@types/lodash-es": "^4.17.12",
81
81
  "@types/markdown-it": "^13.0.7",
82
- "@types/node": "^20.10.4",
83
- "@types/qs": "^6.9.10",
82
+ "@types/node": "^20.10.5",
83
+ "@types/qs": "^6.9.11",
84
84
  "@vitejs/plugin-vue": "^4.5.2",
85
- "@vitest/coverage-v8": "^1.0.4",
85
+ "@vitest/coverage-v8": "^1.1.0",
86
86
  "@vue/test-utils": "^2.4.3",
87
87
  "@vuelidate/core": "^2.0.3",
88
88
  "@vuelidate/validators": "^2.0.4",
89
89
  "@vueuse/core": "^10.7.0",
90
90
  "body-scroll-lock": "4.0.0-beta.0",
91
- "eslint": "^8.55.0",
91
+ "eslint": "^8.56.0",
92
92
  "fuse.js": "^7.0.0",
93
93
  "happy-dom": "^12.10.3",
94
94
  "histoire": "^0.17.6",
@@ -102,11 +102,11 @@
102
102
  "release-it": "^17.0.1",
103
103
  "typescript": "~5.3.3",
104
104
  "v-calendar": "^3.1.2",
105
- "vite": "^5.0.9",
106
- "vitepress": "1.0.0-rc.31",
107
- "vitest": "^1.0.4",
108
- "vue": "^3.3.11",
105
+ "vite": "^5.0.10",
106
+ "vitepress": "1.0.0-rc.32",
107
+ "vitest": "^1.1.0",
108
+ "vue": "^3.3.13",
109
109
  "vue-router": "^4.2.5",
110
- "vue-tsc": "^1.8.25"
110
+ "vue-tsc": "^1.8.26"
111
111
  }
112
112
  }