lupine.components 1.1.10 → 1.1.11
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lupine.components",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.11",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "uuware.com",
|
|
6
6
|
"homepage": "https://github.com/uuware/lupine.js",
|
|
@@ -39,4 +39,4 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"lupine.web": "^1.0.0"
|
|
41
41
|
}
|
|
42
|
-
}
|
|
42
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -24,10 +24,14 @@ export * from './paging-link';
|
|
|
24
24
|
export * from './panel';
|
|
25
25
|
export * from './popup-menu';
|
|
26
26
|
export * from './progress';
|
|
27
|
+
export * from './radio-label-component';
|
|
27
28
|
export * from './redirect';
|
|
28
29
|
export * from './resizable-splitter';
|
|
30
|
+
export * from './select-angle-component';
|
|
29
31
|
export * from './select-with-title';
|
|
32
|
+
export * from './slide-tab-component';
|
|
30
33
|
export * from './spinner';
|
|
34
|
+
export * from './stars-component';
|
|
31
35
|
export * from './svg';
|
|
32
36
|
export * from './tabs';
|
|
33
37
|
export * from './text-glow';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { bindGlobalStyle, CssProps } from 'lupine.components';
|
|
2
|
+
|
|
3
|
+
export const RadioLabelComponent = (props: {
|
|
4
|
+
label: string;
|
|
5
|
+
name: string;
|
|
6
|
+
checked?: boolean;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
onChange?: (checked: boolean) => void;
|
|
9
|
+
className?: string;
|
|
10
|
+
radioClassname?: string;
|
|
11
|
+
}) => {
|
|
12
|
+
const css: CssProps = {
|
|
13
|
+
display: 'flex',
|
|
14
|
+
'& > label': {
|
|
15
|
+
display: 'flex',
|
|
16
|
+
alignItems: 'center',
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
bindGlobalStyle('radio-label-component', css);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div class={'radio-label-component' + (props.className ? ' ' + props.className : '')}>
|
|
23
|
+
<label>
|
|
24
|
+
<input
|
|
25
|
+
type='radio'
|
|
26
|
+
name={props.name}
|
|
27
|
+
class={'input-base input-s' + (props.radioClassname ? ' ' + props.radioClassname : '')}
|
|
28
|
+
checked={props.checked}
|
|
29
|
+
disabled={props.disabled}
|
|
30
|
+
onChange={(event) => props.onChange?.((event.target as HTMLInputElement).checked)}
|
|
31
|
+
/>
|
|
32
|
+
<span class='ml-ss'>{props.label}</span>
|
|
33
|
+
</label>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { CssProps, RefProps } from 'lupine.components';
|
|
2
|
+
|
|
3
|
+
export type SelectAngleComponentHookProps = {
|
|
4
|
+
setAngle?: (angle: number) => void;
|
|
5
|
+
};
|
|
6
|
+
export type SelectAngleComponentProps = {
|
|
7
|
+
size?: string;
|
|
8
|
+
angle: number;
|
|
9
|
+
onChange: (angle: number) => void;
|
|
10
|
+
hook?: SelectAngleComponentHookProps;
|
|
11
|
+
};
|
|
12
|
+
export const SelectAngleComponent = (props: SelectAngleComponentProps) => {
|
|
13
|
+
const css: CssProps = {
|
|
14
|
+
width: props.size || '80px',
|
|
15
|
+
height: props.size || '80px',
|
|
16
|
+
'&circle': {
|
|
17
|
+
width: '100%',
|
|
18
|
+
height: '100%',
|
|
19
|
+
borderRadius: '50%',
|
|
20
|
+
border: '2px solid #aaa',
|
|
21
|
+
position: 'relative',
|
|
22
|
+
backgroundColor: 'var(--primary-bg-color)',
|
|
23
|
+
cursor: 'pointer',
|
|
24
|
+
},
|
|
25
|
+
'&needle': {
|
|
26
|
+
width: '2px',
|
|
27
|
+
height: '50%',
|
|
28
|
+
backgroundColor: 'red',
|
|
29
|
+
position: 'absolute',
|
|
30
|
+
top: '0',
|
|
31
|
+
left: '50%',
|
|
32
|
+
transformOrigin: 'bottom center',
|
|
33
|
+
transform: 'rotate(90deg)',
|
|
34
|
+
},
|
|
35
|
+
'&tips': {
|
|
36
|
+
position: 'absolute',
|
|
37
|
+
top: '50%',
|
|
38
|
+
left: '50%',
|
|
39
|
+
transform: 'translate(-50%, -50%)',
|
|
40
|
+
fontSize: '12px',
|
|
41
|
+
color: 'var(--primary-color)',
|
|
42
|
+
fontWeight: '600',
|
|
43
|
+
zIndex: '10',
|
|
44
|
+
},
|
|
45
|
+
'&a0, &a90, &a180, &a270': {
|
|
46
|
+
width: '6px',
|
|
47
|
+
height: '6px',
|
|
48
|
+
borderRadius: '50%',
|
|
49
|
+
backgroundColor: '#333',
|
|
50
|
+
position: 'absolute',
|
|
51
|
+
top: '0',
|
|
52
|
+
left: '50%',
|
|
53
|
+
transform: 'translate(-50%, -50%)',
|
|
54
|
+
fontSize: '12px',
|
|
55
|
+
color: '#333',
|
|
56
|
+
},
|
|
57
|
+
'&a90': {
|
|
58
|
+
top: '50%',
|
|
59
|
+
left: '100%',
|
|
60
|
+
},
|
|
61
|
+
'&a180': {
|
|
62
|
+
top: '100%',
|
|
63
|
+
left: '50%',
|
|
64
|
+
},
|
|
65
|
+
'&a270': {
|
|
66
|
+
top: '50%',
|
|
67
|
+
left: '0',
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
let cx: number = 0;
|
|
72
|
+
let cy: number = 0;
|
|
73
|
+
let mv = false;
|
|
74
|
+
if (props.hook) {
|
|
75
|
+
props.hook.setAngle = (angle) => {
|
|
76
|
+
updateAngleSub(angle);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const updateAngle = (ev: MouseEvent) => {
|
|
80
|
+
const dx = ev.clientX - cx;
|
|
81
|
+
const dy = ev.clientY - cy;
|
|
82
|
+
// atan2 返回弧度,顺时针0°为右侧
|
|
83
|
+
let deg = Math.atan2(dy, dx) * (180 / Math.PI);
|
|
84
|
+
deg = (deg + 450) % 360; // 让上方为0°
|
|
85
|
+
updateAngleSub(deg);
|
|
86
|
+
};
|
|
87
|
+
const updateAngleSub = (deg: number) => {
|
|
88
|
+
const needle = ref.$('&needle');
|
|
89
|
+
const text = ref.$('&tips');
|
|
90
|
+
needle.style.transform = `rotate(${deg}deg)`;
|
|
91
|
+
text.textContent = `${deg.toFixed(0)}°`;
|
|
92
|
+
props.onChange(deg);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const pointerdown = (ev: MouseEvent) => {
|
|
96
|
+
const picker = ref.$('&circle');
|
|
97
|
+
const rect = picker.getBoundingClientRect();
|
|
98
|
+
cx = rect.left + rect.width / 2;
|
|
99
|
+
cy = rect.top + rect.height / 2;
|
|
100
|
+
|
|
101
|
+
updateAngle(ev);
|
|
102
|
+
mv = true;
|
|
103
|
+
};
|
|
104
|
+
const pointermove = (ev: MouseEvent) => {
|
|
105
|
+
if (!mv) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
updateAngle(ev);
|
|
109
|
+
};
|
|
110
|
+
const pointerup = () => {
|
|
111
|
+
mv = false;
|
|
112
|
+
};
|
|
113
|
+
const ref: RefProps = {};
|
|
114
|
+
return (
|
|
115
|
+
<div ref={ref} css={css}>
|
|
116
|
+
<div class='&circle' onPointerDown={pointerdown} onPointerMove={pointermove} onPointerUp={pointerup}>
|
|
117
|
+
<div class='&needle'></div>
|
|
118
|
+
<div class='&tips'>90°</div>
|
|
119
|
+
|
|
120
|
+
<div class='&a0' onClick={() => updateAngleSub(0)}></div>
|
|
121
|
+
<div class='&a90' onClick={() => updateAngleSub(90)}></div>
|
|
122
|
+
<div class='&a180' onClick={() => updateAngleSub(180)}></div>
|
|
123
|
+
<div class='&a270' onClick={() => updateAngleSub(270)}></div>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { CssProps, RefProps, VNode, bindGlobalStyle } from 'lupine.components';
|
|
2
|
+
|
|
3
|
+
export interface SlideTabProps {
|
|
4
|
+
pages: { title: string; content: VNode<any> }[];
|
|
5
|
+
};
|
|
6
|
+
export const SlideTabComponent = (props: SlideTabProps) => {
|
|
7
|
+
const css: CssProps = {
|
|
8
|
+
display: 'flex',
|
|
9
|
+
flexDirection: 'column',
|
|
10
|
+
flex: 1,
|
|
11
|
+
fontSize: '12px',
|
|
12
|
+
borderRadius: '6px',
|
|
13
|
+
padding: '0px 8px 4px 8px',
|
|
14
|
+
// marginBottom: '8px',
|
|
15
|
+
height: '100%',
|
|
16
|
+
'.slide-tab-c-list': {
|
|
17
|
+
flex: 1,
|
|
18
|
+
borderRadius: '6px',
|
|
19
|
+
display: 'flex',
|
|
20
|
+
overflowX: 'auto',
|
|
21
|
+
width: '100%',
|
|
22
|
+
scrollSnapType: 'x mandatory',
|
|
23
|
+
gap: '8px',
|
|
24
|
+
paddingBottom: '10px',
|
|
25
|
+
scrollBehavior: 'smooth',
|
|
26
|
+
WebkitOverflowScrolling: 'touch',
|
|
27
|
+
},
|
|
28
|
+
'.slide-tab-c-slide': {
|
|
29
|
+
width: '100%',
|
|
30
|
+
overflow: 'hidden',
|
|
31
|
+
position: 'relative',
|
|
32
|
+
minWidth: '100%',
|
|
33
|
+
flexShrink: 0,
|
|
34
|
+
scrollSnapAlign: 'start',
|
|
35
|
+
height: '100%',
|
|
36
|
+
overflowY: 'auto',
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
'.slide-tab-c-nav': {
|
|
40
|
+
display: 'flex',
|
|
41
|
+
flexDirection: 'row',
|
|
42
|
+
justifyContent: 'center',
|
|
43
|
+
backgroundColor: 'var(--primary-bg-color)',
|
|
44
|
+
position: 'sticky',
|
|
45
|
+
top: 0,
|
|
46
|
+
zIndex: 1,
|
|
47
|
+
},
|
|
48
|
+
'.slide-tab-c-nav-wrap': {
|
|
49
|
+
display: 'flex',
|
|
50
|
+
flexDirection: 'row',
|
|
51
|
+
justifyContent: 'center',
|
|
52
|
+
padding: '2px 4px',
|
|
53
|
+
borderRadius: '4px',
|
|
54
|
+
backgroundColor: 'var(--secondary-bg-color)',
|
|
55
|
+
},
|
|
56
|
+
'.slide-tab-c-nav-item': {
|
|
57
|
+
cursor: 'pointer',
|
|
58
|
+
padding: '4px 8px',
|
|
59
|
+
borderRadius: '4px',
|
|
60
|
+
marginRight: '8px',
|
|
61
|
+
},
|
|
62
|
+
'.slide-tab-c-nav-item.active': {
|
|
63
|
+
backgroundColor: 'var(--primary-accent-color)',
|
|
64
|
+
color: 'white',
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
bindGlobalStyle('slide-tab-c-box', css);
|
|
68
|
+
|
|
69
|
+
const ref: RefProps = {};
|
|
70
|
+
let slideIndex = 0;
|
|
71
|
+
let manualScroll = false;
|
|
72
|
+
let scrollEndTimer: NodeJS.Timeout | null = null;
|
|
73
|
+
const drawerScroll = () => {
|
|
74
|
+
if (manualScroll) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (scrollEndTimer) {
|
|
78
|
+
clearTimeout(scrollEndTimer);
|
|
79
|
+
}
|
|
80
|
+
scrollEndTimer = setTimeout(() => {
|
|
81
|
+
drawerScrollStop();
|
|
82
|
+
}, 100);
|
|
83
|
+
};
|
|
84
|
+
const resetSlides = (index: number) => {
|
|
85
|
+
const dots = ref.$all('.slide-tab-c-nav-item');
|
|
86
|
+
for (let i = 0; i < dots.length; i++) {
|
|
87
|
+
dots[i].classList.toggle('active', i === index);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const drawerScrollStop = () => {
|
|
91
|
+
const drawer = ref.$('.slide-tab-c-list');
|
|
92
|
+
const width = drawer.clientWidth;
|
|
93
|
+
const currentScrollIndex = Math.round(drawer.scrollLeft / width);
|
|
94
|
+
slideIndex = currentScrollIndex;
|
|
95
|
+
resetSlides(slideIndex);
|
|
96
|
+
};
|
|
97
|
+
const moveSlide = (slideIndex: number) => {
|
|
98
|
+
const drawer = ref.$('.slide-tab-c-list');
|
|
99
|
+
const children = ref.$all('.slide-tab-c-slide');
|
|
100
|
+
if (!drawer || !children || children.length === 0) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const target = children[slideIndex];
|
|
104
|
+
if (!target) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const offsetLeft = target.offsetLeft;
|
|
108
|
+
manualScroll = true;
|
|
109
|
+
drawer.scrollTo({
|
|
110
|
+
left: offsetLeft,
|
|
111
|
+
behavior: 'smooth',
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
resetSlides(slideIndex);
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
manualScroll = false;
|
|
117
|
+
}, 300);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<section class='slide-tab-c-box' ref={ref}>
|
|
122
|
+
<div class='slide-tab-c-nav'>
|
|
123
|
+
<div class='slide-tab-c-nav-wrap'>
|
|
124
|
+
{props.pages.map((page, index) => (
|
|
125
|
+
<div
|
|
126
|
+
class={`slide-tab-c-nav-item ${index === 0 ? 'active' : ''}`}
|
|
127
|
+
onClick={(event) => {
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
moveSlide(index);
|
|
130
|
+
}}
|
|
131
|
+
>
|
|
132
|
+
{page.title}
|
|
133
|
+
</div>
|
|
134
|
+
))}
|
|
135
|
+
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
<div class='slide-tab-c-list no-scrollbar-container' onScroll={drawerScroll}>
|
|
139
|
+
|
|
140
|
+
{props.pages.map((page) => (
|
|
141
|
+
<div class='slide-tab-c-slide no-scrollbar-container'>
|
|
142
|
+
{page.content}
|
|
143
|
+
</div>
|
|
144
|
+
))}
|
|
145
|
+
|
|
146
|
+
</div>
|
|
147
|
+
</section>
|
|
148
|
+
);
|
|
149
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { bindGlobalStyle, CssProps, RefProps } from 'lupine.components';
|
|
2
|
+
|
|
3
|
+
export type StarsHookComponentProps = {
|
|
4
|
+
setValue: (value: number) => void;
|
|
5
|
+
getValue: () => number;
|
|
6
|
+
};
|
|
7
|
+
export type StarsComponentProps = {
|
|
8
|
+
maxLength: number;
|
|
9
|
+
value: number;
|
|
10
|
+
onChange?: (value: number) => void;
|
|
11
|
+
hook?: StarsHookComponentProps;
|
|
12
|
+
fontSize?: string;
|
|
13
|
+
};
|
|
14
|
+
export const StarsComponent = (props: StarsComponentProps) => {
|
|
15
|
+
const css: CssProps = {
|
|
16
|
+
display: 'flex',
|
|
17
|
+
flexDirection: 'row',
|
|
18
|
+
'.stars-label': {
|
|
19
|
+
color: '#9d9d9d',
|
|
20
|
+
cursor: 'pointer',
|
|
21
|
+
display: 'flex',
|
|
22
|
+
alignItems: 'center',
|
|
23
|
+
},
|
|
24
|
+
'.stars-label.active': {
|
|
25
|
+
color: 'blue',
|
|
26
|
+
},
|
|
27
|
+
'.stars-label .full, .stars-label.active .outline': {
|
|
28
|
+
display: 'none',
|
|
29
|
+
},
|
|
30
|
+
'.stars-label.active .full, .stars-label .outline': {
|
|
31
|
+
display: 'inline',
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
bindGlobalStyle('stars-box', css);
|
|
35
|
+
|
|
36
|
+
const setValue = (value: number) => {
|
|
37
|
+
props.value = value;
|
|
38
|
+
const stars = ref.$all('.stars-label') as NodeListOf<Element>;
|
|
39
|
+
stars.forEach((star, index) => {
|
|
40
|
+
star.classList.toggle('active', index < value);
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
if (props.hook) {
|
|
44
|
+
props.hook.setValue = (value) => {
|
|
45
|
+
setValue(value);
|
|
46
|
+
};
|
|
47
|
+
props.hook.getValue = () => props.value;
|
|
48
|
+
}
|
|
49
|
+
const ref: RefProps = {};
|
|
50
|
+
return (
|
|
51
|
+
<div style={{ fontSize: props.fontSize || '20px' }} ref={ref} class='stars-box'>
|
|
52
|
+
{Array.from({ length: props.maxLength }).map((value, index) => (
|
|
53
|
+
<label
|
|
54
|
+
class={'stars-label' + (index < props.value ? ' active' : '')}
|
|
55
|
+
onClick={() => {
|
|
56
|
+
setValue(index + 1);
|
|
57
|
+
props.onChange?.(index + 1);
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
<i class='ifc-icon ma-cards-heart full'></i>
|
|
61
|
+
<i class='ifc-icon ma-cards-heart-outline outline'></i>
|
|
62
|
+
</label>
|
|
63
|
+
))}
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
};
|