@shipfox/react-ui 0.22.0 → 0.24.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/dist/components/dashboard/context/dashboard-context.d.ts +12 -8
- package/dist/components/dashboard/context/dashboard-context.js +42 -7
- package/dist/components/dashboard/context/index.d.ts +2 -2
- package/dist/components/dashboard/context/index.js +1 -1
- package/dist/components/dashboard/context/types.d.ts +9 -7
- package/dist/components/dashboard/index.d.ts +2 -4
- package/dist/components/dashboard/index.js +1 -2
- package/dist/components/dashboard/pages/analytics-page.js +2 -9
- package/dist/components/dashboard/pages/jobs-page.js +2 -4
- package/dist/components/dashboard/toolbar/filter-button.d.ts +6 -10
- package/dist/components/dashboard/toolbar/filter-button.js +109 -76
- package/dist/components/dashboard/toolbar/page-toolbar.d.ts +7 -19
- package/dist/components/dashboard/toolbar/page-toolbar.js +11 -99
- package/dist/components/dashboard/toolbar/toolbar-actions.js +3 -3
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/interval-selector/hooks/index.d.ts +4 -0
- package/dist/components/interval-selector/hooks/index.js +5 -0
- package/dist/components/interval-selector/hooks/use-interval-selector-input.d.ts +30 -0
- package/dist/components/interval-selector/hooks/use-interval-selector-input.js +125 -0
- package/dist/components/interval-selector/hooks/use-interval-selector-navigation.d.ts +21 -0
- package/dist/components/interval-selector/hooks/use-interval-selector-navigation.js +58 -0
- package/dist/components/interval-selector/hooks/use-interval-selector.d.ts +25 -0
- package/dist/components/interval-selector/hooks/use-interval-selector.js +75 -0
- package/dist/components/interval-selector/index.d.ts +3 -0
- package/dist/components/interval-selector/index.js +4 -0
- package/dist/components/interval-selector/interval-selector-calendar.d.ts +7 -0
- package/dist/components/interval-selector/interval-selector-calendar.js +47 -0
- package/dist/components/interval-selector/interval-selector-input.d.ts +8 -0
- package/dist/components/interval-selector/interval-selector-input.js +34 -0
- package/dist/components/interval-selector/interval-selector-suggestions.d.ts +11 -0
- package/dist/components/interval-selector/interval-selector-suggestions.js +107 -0
- package/dist/components/interval-selector/interval-selector.d.ts +12 -0
- package/dist/components/interval-selector/interval-selector.js +56 -0
- package/dist/components/interval-selector/interval-selector.stories.js +232 -0
- package/dist/components/interval-selector/types.d.ts +19 -0
- package/dist/components/interval-selector/types.js +3 -0
- package/dist/components/interval-selector/utils/constants.d.ts +24 -0
- package/dist/components/interval-selector/utils/constants.js +129 -0
- package/dist/components/interval-selector/utils/format.d.ts +16 -0
- package/dist/components/interval-selector/utils/format.js +23 -0
- package/dist/components/interval-selector/utils/index.d.ts +3 -0
- package/dist/components/interval-selector/utils/index.js +4 -0
- package/dist/components/modal/modal.d.ts +10 -3
- package/dist/components/modal/modal.js +30 -2
- package/dist/components/popover/popover.d.ts +3 -1
- package/dist/components/popover/popover.js +2 -1
- package/dist/styles.css +1 -1
- package/dist/utils/date.js +130 -22
- package/dist/utils/format/date.d.ts +1 -0
- package/dist/utils/format/date.js +11 -4
- package/package.json +3 -2
- package/dist/components/dashboard/filters/expression-filter-bar.d.ts +0 -42
- package/dist/components/dashboard/filters/expression-filter-bar.js +0 -80
- package/dist/components/dashboard/filters/index.d.ts +0 -6
- package/dist/components/dashboard/filters/index.js +0 -5
package/dist/utils/date.js
CHANGED
|
@@ -43,33 +43,141 @@ const DURATION_SHORTCUTS_REVERSED = Object.fromEntries(Object.entries(DURATION_S
|
|
|
43
43
|
value,
|
|
44
44
|
key
|
|
45
45
|
]));
|
|
46
|
-
const
|
|
46
|
+
const UNIT_NAMES = {
|
|
47
|
+
minutes: [
|
|
48
|
+
'm',
|
|
49
|
+
'min',
|
|
50
|
+
'minute',
|
|
51
|
+
'minutes'
|
|
52
|
+
],
|
|
53
|
+
hours: [
|
|
54
|
+
'h',
|
|
55
|
+
'hr',
|
|
56
|
+
'hour',
|
|
57
|
+
'hours'
|
|
58
|
+
],
|
|
59
|
+
days: [
|
|
60
|
+
'd',
|
|
61
|
+
'day',
|
|
62
|
+
'days'
|
|
63
|
+
],
|
|
64
|
+
weeks: [
|
|
65
|
+
'w',
|
|
66
|
+
'wk',
|
|
67
|
+
'week',
|
|
68
|
+
'weeks'
|
|
69
|
+
],
|
|
70
|
+
months: [
|
|
71
|
+
'mo',
|
|
72
|
+
'mon',
|
|
73
|
+
'month',
|
|
74
|
+
'months'
|
|
75
|
+
],
|
|
76
|
+
years: [
|
|
77
|
+
'y',
|
|
78
|
+
'yr',
|
|
79
|
+
'year',
|
|
80
|
+
'years'
|
|
81
|
+
],
|
|
82
|
+
seconds: [
|
|
83
|
+
's',
|
|
84
|
+
'sec',
|
|
85
|
+
'second',
|
|
86
|
+
'seconds'
|
|
87
|
+
]
|
|
88
|
+
};
|
|
89
|
+
const UNIT_NAME_TO_KEY = {};
|
|
90
|
+
for (const [key, names] of Object.entries(UNIT_NAMES)){
|
|
91
|
+
for (const name of names){
|
|
92
|
+
UNIT_NAME_TO_KEY[name.toLowerCase()] = key;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const SHORTCUT_PATTERN = Object.keys(DURATION_SHORTCUTS_REVERSED).join('|');
|
|
96
|
+
const FULL_NAME_PATTERN = Object.values(UNIT_NAMES).flat().sort((a, b)=>b.length - a.length).map((name)=>name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
|
|
97
|
+
const DURATION_SHORTCUT_REGEX = new RegExp(`^(\\d+)\\s*(${SHORTCUT_PATTERN})$`, 'i');
|
|
98
|
+
const DURATION_FULL_REGEX = new RegExp(`^(\\d+)\\s*(${FULL_NAME_PATTERN})\\s*$`, 'i');
|
|
47
99
|
export function generateDurationShortcut(duration) {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
100
|
+
for (const [key, shortcut] of Object.entries(DURATION_SHORTCUTS)){
|
|
101
|
+
const value = duration[key];
|
|
102
|
+
if (value) {
|
|
103
|
+
return `${value}${shortcut}`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return '';
|
|
53
107
|
}
|
|
54
108
|
export function parseTextDurationShortcut(text) {
|
|
55
|
-
const
|
|
56
|
-
if (!
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
109
|
+
const trimmed = text.trim();
|
|
110
|
+
if (!trimmed) return undefined;
|
|
111
|
+
const shortcutMatch = trimmed.match(DURATION_SHORTCUT_REGEX);
|
|
112
|
+
if (shortcutMatch) {
|
|
113
|
+
const [_, value, shortcut] = shortcutMatch;
|
|
114
|
+
const unit = DURATION_SHORTCUTS_REVERSED[shortcut.toLowerCase()];
|
|
115
|
+
if (unit) {
|
|
116
|
+
return {
|
|
117
|
+
[unit]: Number.parseInt(value, 10)
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const fullMatch = trimmed.match(DURATION_FULL_REGEX);
|
|
122
|
+
if (fullMatch) {
|
|
123
|
+
const [_, value, unitName] = fullMatch;
|
|
124
|
+
const normalizedUnitName = unitName.toLowerCase().trim();
|
|
125
|
+
const unit = UNIT_NAME_TO_KEY[normalizedUnitName];
|
|
126
|
+
if (unit) {
|
|
127
|
+
return {
|
|
128
|
+
[unit]: Number.parseInt(value, 10)
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
const DATE_SPLITTER_REGEX = /[-\u2013]/;
|
|
135
|
+
const YEAR_REGEX = /\d{4}/;
|
|
136
|
+
function hasYearInText(text) {
|
|
137
|
+
return YEAR_REGEX.test(text);
|
|
138
|
+
}
|
|
139
|
+
function parseDateString(dateText) {
|
|
140
|
+
const date = new Date(dateText);
|
|
141
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
142
|
+
}
|
|
143
|
+
function assignYearsToDates(start, end, startHasYear, endHasYear, currentYear) {
|
|
144
|
+
if (!startHasYear && !endHasYear) {
|
|
145
|
+
start.setFullYear(currentYear);
|
|
146
|
+
end.setFullYear(currentYear);
|
|
147
|
+
} else if (!startHasYear) {
|
|
148
|
+
start.setFullYear(end.getFullYear());
|
|
149
|
+
} else if (!endHasYear) {
|
|
150
|
+
end.setFullYear(currentYear);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function fixInvalidInterval(start, end, startHasYear, endHasYear) {
|
|
154
|
+
if (end < start) {
|
|
155
|
+
if (startHasYear || endHasYear) return false;
|
|
156
|
+
const endYear = end.getFullYear();
|
|
157
|
+
start.setFullYear(endYear - 1);
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
62
160
|
}
|
|
63
|
-
const dateSplitterRefex = /[-\u2013]/;
|
|
64
161
|
export function parseTextInterval(text) {
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
162
|
+
const textDates = text.split(DATE_SPLITTER_REGEX).map((token)=>token.trim());
|
|
163
|
+
if (textDates.length !== 2) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
const [startText, endText] = textDates;
|
|
167
|
+
const startHasYear = hasYearInText(startText);
|
|
168
|
+
const endHasYear = hasYearInText(endText);
|
|
169
|
+
const start = parseDateString(startText);
|
|
170
|
+
const end = parseDateString(endText);
|
|
171
|
+
if (!start || !end) {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
const now = new Date();
|
|
175
|
+
const currentYear = now.getFullYear();
|
|
176
|
+
assignYearsToDates(start, end, startHasYear, endHasYear, currentYear);
|
|
177
|
+
const isValid = fixInvalidInterval(start, end, startHasYear, endHasYear);
|
|
178
|
+
if (!isValid) {
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
73
181
|
return {
|
|
74
182
|
start,
|
|
75
183
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type NormalizedInterval } from 'date-fns';
|
|
2
2
|
interface DateTimeFormatOptions extends Intl.DateTimeFormatOptions {
|
|
3
3
|
locale?: string;
|
|
4
|
+
forceShowTime?: boolean;
|
|
4
5
|
}
|
|
5
6
|
export declare function formatDateTime(date: Date, options?: DateTimeFormatOptions): string;
|
|
6
7
|
export declare function formatDateTimeRelativeToInterval(date: Date, interval: NormalizedInterval, options?: DateTimeFormatOptions): string;
|
|
@@ -30,14 +30,21 @@ export function formatDateTimeRelativeToInterval(date, interval, options) {
|
|
|
30
30
|
}
|
|
31
31
|
export function formatDateTimeRange(interval, options) {
|
|
32
32
|
const { start, end } = interval;
|
|
33
|
+
const { forceShowTime, ...formatOptions } = options || {};
|
|
33
34
|
const areFullDays = isStartOfDay(start) && isEndOfDay(end);
|
|
35
|
+
const shouldShowTime = forceShowTime || !areFullDays;
|
|
34
36
|
const formatter = getDateTimeFormatter({
|
|
35
37
|
year: areCurrentYear(interval) ? undefined : defaultOptions.year,
|
|
36
|
-
hour:
|
|
37
|
-
minute:
|
|
38
|
-
...
|
|
38
|
+
hour: shouldShowTime ? defaultOptions.hour : undefined,
|
|
39
|
+
minute: shouldShowTime ? defaultOptions.minute : undefined,
|
|
40
|
+
...formatOptions
|
|
39
41
|
});
|
|
40
|
-
|
|
42
|
+
if (areFullDays && !forceShowTime) {
|
|
43
|
+
return formatter.formatRange(start, end);
|
|
44
|
+
}
|
|
45
|
+
const startFormatted = formatter.format(start);
|
|
46
|
+
const endFormatted = formatter.format(end);
|
|
47
|
+
return `${startFormatted} – ${endFormatted}`;
|
|
41
48
|
}
|
|
42
49
|
export function formatTimeSeriesTick(date, { locale, ...options } = {}) {
|
|
43
50
|
const tickOptions = isStartOfDay(date) ? {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/react-ui",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.24.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"@radix-ui/react-checkbox": "^1.3.3",
|
|
22
22
|
"@radix-ui/react-collapsible": "^1.1.12",
|
|
23
23
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
24
|
+
"@radix-ui/react-dismissable-layer": "^1.1.11",
|
|
24
25
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
25
26
|
"@radix-ui/react-label": "^2.1.7",
|
|
26
27
|
"@radix-ui/react-popover": "^1.1.15",
|
|
@@ -86,8 +87,8 @@
|
|
|
86
87
|
"zod": "^4.1.12",
|
|
87
88
|
"@shipfox/biome": "1.6.0",
|
|
88
89
|
"@shipfox/swc": "1.2.2",
|
|
89
|
-
"@shipfox/typescript": "1.1.2",
|
|
90
90
|
"@shipfox/ts-config": "1.3.5",
|
|
91
|
+
"@shipfox/typescript": "1.1.2",
|
|
91
92
|
"@shipfox/vite": "1.2.2",
|
|
92
93
|
"@shipfox/vitest": "1.2.0"
|
|
93
94
|
},
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Expression Filter Bar Component
|
|
3
|
-
*
|
|
4
|
-
* A horizontal button group for filtering by resource type.
|
|
5
|
-
*/
|
|
6
|
-
import type { ComponentProps } from 'react';
|
|
7
|
-
import type { ResourceType } from '../context';
|
|
8
|
-
export interface ResourceTypeOption {
|
|
9
|
-
id: ResourceType;
|
|
10
|
-
label: string;
|
|
11
|
-
disabled?: boolean;
|
|
12
|
-
}
|
|
13
|
-
export interface ExpressionFilterBarProps extends Omit<ComponentProps<'div'>, 'children'> {
|
|
14
|
-
/**
|
|
15
|
-
* Available resource type options
|
|
16
|
-
*/
|
|
17
|
-
options?: ResourceTypeOption[];
|
|
18
|
-
/**
|
|
19
|
-
* Currently selected resource type
|
|
20
|
-
*/
|
|
21
|
-
value?: ResourceType;
|
|
22
|
-
/**
|
|
23
|
-
* Callback when resource type changes
|
|
24
|
-
*/
|
|
25
|
-
onValueChange?: (value: ResourceType) => void;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Expression Filter Bar
|
|
29
|
-
*
|
|
30
|
-
* Displays a horizontal button group for selecting resource types.
|
|
31
|
-
* Integrates with the dashboard context for state management.
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```tsx
|
|
35
|
-
* <ExpressionFilterBar
|
|
36
|
-
* value="ci-pipeline"
|
|
37
|
-
* onValueChange={setResourceType}
|
|
38
|
-
* />
|
|
39
|
-
* ```
|
|
40
|
-
*/
|
|
41
|
-
export declare function ExpressionFilterBar({ options, value, onValueChange, className, ...props }: ExpressionFilterBarProps): import("react/jsx-runtime").JSX.Element;
|
|
42
|
-
//# sourceMappingURL=expression-filter-bar.d.ts.map
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* Expression Filter Bar Component
|
|
4
|
-
*
|
|
5
|
-
* A horizontal button group for filtering by resource type.
|
|
6
|
-
*/ import { Button } from '../../../components/button/index.js';
|
|
7
|
-
import { cn } from '../../../utils/cn.js';
|
|
8
|
-
/**
|
|
9
|
-
* Default resource type options
|
|
10
|
-
*/ const DEFAULT_OPTIONS = [
|
|
11
|
-
{
|
|
12
|
-
id: 'ci-pipeline',
|
|
13
|
-
label: 'CI Pipeline'
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
id: 'ci-jobs',
|
|
17
|
-
label: 'CI Jobs'
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
id: 'ci-steps',
|
|
21
|
-
label: 'CI Steps'
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
id: 'runners',
|
|
25
|
-
label: 'Runners',
|
|
26
|
-
disabled: true
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
id: 'suite',
|
|
30
|
-
label: 'Suite',
|
|
31
|
-
disabled: true
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
id: 'cases',
|
|
35
|
-
label: 'Cases',
|
|
36
|
-
disabled: true
|
|
37
|
-
}
|
|
38
|
-
];
|
|
39
|
-
/**
|
|
40
|
-
* Expression Filter Bar
|
|
41
|
-
*
|
|
42
|
-
* Displays a horizontal button group for selecting resource types.
|
|
43
|
-
* Integrates with the dashboard context for state management.
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
|
-
* ```tsx
|
|
47
|
-
* <ExpressionFilterBar
|
|
48
|
-
* value="ci-pipeline"
|
|
49
|
-
* onValueChange={setResourceType}
|
|
50
|
-
* />
|
|
51
|
-
* ```
|
|
52
|
-
*/ export function ExpressionFilterBar({ options = DEFAULT_OPTIONS, value = 'ci-pipeline', onValueChange, className, ...props }) {
|
|
53
|
-
return /*#__PURE__*/ _jsx("div", {
|
|
54
|
-
className: cn(// Desktop: Normal flex layout
|
|
55
|
-
'md:flex md:gap-4 md:items-start', // Mobile: Swipeable with scroll-snap
|
|
56
|
-
'overflow-x-auto scrollbar-none', // Scroll snap for smooth swiping
|
|
57
|
-
'snap-x snap-mandatory', // Hide scrollbar but allow scrolling
|
|
58
|
-
'[&::-webkit-scrollbar]:hidden', className),
|
|
59
|
-
...props,
|
|
60
|
-
children: /*#__PURE__*/ _jsx("div", {
|
|
61
|
-
className: "flex gap-4 items-start px-0",
|
|
62
|
-
children: options.map((option)=>{
|
|
63
|
-
const isActive = value === option.id;
|
|
64
|
-
return /*#__PURE__*/ _jsx(Button, {
|
|
65
|
-
variant: isActive ? 'secondary' : 'transparent',
|
|
66
|
-
size: "md",
|
|
67
|
-
disabled: option.disabled,
|
|
68
|
-
onClick: ()=>!option.disabled && onValueChange?.(option.id),
|
|
69
|
-
className: cn('flex items-center justify-center gap-8 px-10 py-6 rounded-6', 'text-sm font-medium leading-20 tracking-0', 'transition-colors', // Mobile: Prevent shrinking, snap alignment
|
|
70
|
-
'shrink-0 snap-start', // Active state
|
|
71
|
-
isActive && 'shadow-none bg-background-button-neutral-pressed', // Inactive state
|
|
72
|
-
!isActive && !option.disabled && 'bg-transparent text-foreground-neutral-subtle hover:text-foreground-neutral-base'),
|
|
73
|
-
children: option.label
|
|
74
|
-
}, option.id);
|
|
75
|
-
})
|
|
76
|
-
})
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
//# sourceMappingURL=expression-filter-bar.js.map
|