Skip to content

Commit 24537f6

Browse files
committed
[refactor] Toggle Field & Input Group components
[add] isToggleField() utility [remove] Toggle Group & Group Label components [optimize] replace Float Label with official classes
1 parent e948398 commit 24537f6

10 files changed

Lines changed: 239 additions & 366 deletions

File tree

.husky/pre-push

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
. "$(dirname "$0")/_/husky.sh"
44

5-
npm run build
5+
# npm run build

source/Content/Collapse.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,8 @@ export class CollapseBox extends mixin<CollapseProps>() {
4545

4646
this.resizer = new ResizeObserver(
4747
([{ target }]: ResizeObserverEntry[]) => {
48-
(target as HTMLElement).style.display = self.getComputedStyle(
49-
this
50-
).display;
48+
(target as HTMLElement).style.display =
49+
self.getComputedStyle(this).display;
5150

5251
if (this.open)
5352
this.style.height = self.getComputedStyle(target).height;
@@ -60,7 +59,7 @@ export class CollapseBox extends mixin<CollapseProps>() {
6059
this.resizer.disconnect();
6160
}
6261

63-
async toggle(open = !this.open) {
62+
async toggle(open?: boolean) {
6463
const end = watchMotion('transition', this),
6564
box = this.shadowRoot.lastElementChild as HTMLElement;
6665

source/Form/FormField/index.less

Lines changed: 0 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// [Origin] https://getbootstrap.com/docs/4.5/examples/floating-labels/floating-labels.css
2-
31
:global {
42
.form-inline {
53
label:not(.input-group-text) {
@@ -12,89 +10,4 @@
1210
.form-file-label::before {
1311
content: attr(data-file);
1412
}
15-
.form-label-group {
16-
position: relative;
17-
margin-bottom: 1rem;
18-
19-
& > input,
20-
& > select,
21-
& > label {
22-
height: 3.125rem;
23-
padding: 0.75rem;
24-
}
25-
& > textarea {
26-
padding: 0.75rem;
27-
}
28-
29-
& > label {
30-
position: absolute;
31-
top: 0;
32-
left: 0;
33-
display: block;
34-
width: 100%;
35-
margin-bottom: 0; /* Override default `<label>` margin */
36-
line-height: 1.5;
37-
color: #495057;
38-
pointer-events: none;
39-
cursor: text; /* Match the input under the label */
40-
border: 1px solid transparent;
41-
border-radius: 0.25rem;
42-
transition: all 0.1s ease-in-out;
43-
}
44-
.label-float {
45-
padding-top: 1.25rem;
46-
padding-bottom: 0.25rem;
47-
& ~ label {
48-
padding-top: 0.25rem;
49-
padding-bottom: 0.25rem;
50-
font-size: 12px;
51-
color: #777;
52-
}
53-
}
54-
.form-control:not([type='date']):not([type='datetime-local']):not([type='month']):not([type='week']) {
55-
&::placeholder {
56-
color: transparent;
57-
}
58-
&:not(:placeholder-shown) {
59-
.label-float();
60-
}
61-
}
62-
& > [type='date'],
63-
& > [type='datetime-local'],
64-
& > [type='month'],
65-
& > [type='week'] {
66-
color: transparent;
67-
&:valid {
68-
color: inherit;
69-
.label-float();
70-
}
71-
}
72-
& > select:valid {
73-
.label-float();
74-
}
75-
}
76-
/* Fallback for Edge
77-
-------------------------------------------------- */
78-
@supports (-ms-ime-align: auto) {
79-
.form-label-group {
80-
& > label {
81-
display: none;
82-
}
83-
.form-control::-ms-input-placeholder {
84-
color: #777;
85-
}
86-
}
87-
}
88-
/* Fallback for IE
89-
-------------------------------------------------- */
90-
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
91-
.form-label-group {
92-
& > label {
93-
display: none;
94-
}
95-
.form-control:-ms-input-placeholder {
96-
color: #777;
97-
}
98-
}
99-
}
10013
}

source/Form/FormField/index.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,18 +67,20 @@ export function FormField({
6767
describedBy.push(messageID);
6868
}
6969

70-
const labelClass =
71-
labelColumn &&
72-
classNames(
73-
'col-form-label',
74-
typeof size === 'string' && `col-form-label-${size}`,
75-
`col-sm-${labelColumn}`,
76-
'text-nowrap'
77-
);
70+
const labelClass = labelColumn
71+
? classNames(
72+
'col-form-label',
73+
typeof size === 'string' && `col-form-label-${size}`,
74+
`col-sm-${labelColumn}`,
75+
'text-nowrap'
76+
)
77+
: labelFloat
78+
? undefined
79+
: 'form-label';
7880

7981
defaultSlot = [
8082
label && (
81-
<label htmlFor={id} className={labelClass || ''}>
83+
<label htmlFor={id} className={labelClass}>
8284
{label}
8385
</label>
8486
),
@@ -108,7 +110,7 @@ export function FormField({
108110
return (
109111
<div
110112
className={classNames(
111-
labelFloat ? 'form-label-group' : 'form-group',
113+
labelFloat ? 'form-floating' : 'form-group',
112114
labelColumn && 'row',
113115
className
114116
)}

source/Form/InputGroup.tsx

Lines changed: 46 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,39 @@
1-
import { WebCellProps, VNodeChildElement, VNode, createCell } from 'web-cell';
2-
import type { HTMLContainerProps } from 'web-utility';
3-
import { uniqueID } from 'web-utility/source/data';
1+
import { VNode, VNodeChildElement, WebCellProps, createCell } from 'web-cell';
2+
import { HTMLContainerProps, uniqueID } from 'web-utility';
43
import classNames from 'classnames';
54

65
import { isButton } from './Button';
76
import { DropMenu } from '../Navigator/DropMenu';
8-
import { isField } from './Field';
7+
import { isToggleField } from './ToggleField';
98
import { ValidMessage, ValidableFieldProps } from './Form';
109

11-
export interface GroupLabelProps extends WebCellProps {
12-
htmlFor?: string;
13-
type: 'prepend' | 'append';
14-
list: VNodeChildElement[];
15-
}
16-
17-
export function GroupLabel({
18-
className,
19-
type,
20-
list,
21-
id = uniqueID(),
22-
htmlFor
23-
}: GroupLabelProps) {
24-
return (
25-
<div className={classNames(`input-group-${type}`, className)}>
26-
{list.map((item, index) => {
27-
const ID = `${id}-${type}-${index}`;
28-
29-
if (isButton(item as VNode) || DropMenu.is(item as VNode)) {
30-
(item as VNode).data.props.id = ID;
31-
32-
return item;
33-
}
34-
35-
return typeof item !== 'object' ? (
36-
<label
37-
className="input-group-text"
38-
id={ID}
39-
htmlFor={htmlFor}
40-
>
41-
{item}
42-
</label>
43-
) : (
44-
<div className="input-group-text" id={ID}>
45-
{item}
46-
</div>
47-
);
48-
})}
49-
</div>
50-
);
51-
}
52-
5310
export interface InputGroupProps
5411
extends WebCellProps,
5512
HTMLContainerProps,
5613
ValidableFieldProps {
5714
size?: 'sm' | 'lg';
5815
}
5916

17+
function toLabelNode(node: VNodeChildElement) {
18+
if (isToggleField(node))
19+
node.data.class = {
20+
...node.data.class,
21+
'input-group-text': true
22+
};
23+
else if (
24+
typeof node === 'string' ||
25+
typeof node === 'number' ||
26+
!(
27+
/^(input|textarea|select|label)/.test((node as VNode).sel) ||
28+
isButton(node) ||
29+
DropMenu.is(node)
30+
)
31+
)
32+
node = <label className="input-group-text">{node}</label>;
33+
34+
return node as VNode;
35+
}
36+
6037
export function InputGroup({
6138
className,
6239
id = uniqueID(),
@@ -67,37 +44,31 @@ export function InputGroup({
6744
invalidMessage,
6845
...rest
6946
}: InputGroupProps) {
70-
var field_id = `${id}-field-0`;
71-
72-
const [fields, prepends, appends] = (
73-
defaultSlot as VNodeChildElement[]
74-
).reduce(
75-
([fields, prepends, appends], node) => {
76-
if (isField(node)) {
77-
fields.push(node);
47+
var lastID = '',
48+
count = 0;
7849

79-
if (fields.length === 1) {
80-
if (node.data.props?.id) field_id = node.data.props.id;
81-
else
82-
(node.data.props = node.data.props || {}).id = field_id;
83-
}
84-
(node.data.attrs = node.data.attrs || {})[
85-
'aria-describedby'
86-
] = `${id}-label-${prepends[0] ? 'prepend' : 'append'}-0`;
87-
} else if (fields[0]) appends.push(node);
88-
else prepends.push(node);
50+
const nodes = (defaultSlot as VNodeChildElement[])
51+
.flat(Infinity)
52+
.reverse()
53+
.map(node => {
54+
node = toLabelNode(node);
8955

90-
return [fields, prepends, appends];
91-
},
92-
[[], [], []] as [VNode[], VNodeChildElement[], VNodeChildElement[]]
93-
);
94-
95-
if (!appends[0]) {
96-
const [last_field] = fields.slice(-1);
56+
const [tag] = node.sel.split(/[^\w-]/);
9757

98-
(last_field.data.class = last_field.data.class || {})['rounded-end'] =
99-
true;
100-
}
58+
switch (tag) {
59+
case 'input':
60+
case 'textarea':
61+
case 'select':
62+
lastID = (node.data.props ||= {}).id =
63+
node.data.props?.id || `${id}-${count++}`;
64+
break;
65+
case 'label':
66+
if (node.data.class?.['input-group-text'])
67+
(node.data.props ||= {}).htmlFor = lastID;
68+
}
69+
return node;
70+
})
71+
.reverse();
10172

10273
return (
10374
<div
@@ -108,25 +79,7 @@ export function InputGroup({
10879
)}
10980
{...{ id, ...rest }}
11081
>
111-
{prepends[0] && (
112-
<GroupLabel
113-
type="prepend"
114-
id={`${id}-label`}
115-
htmlFor={field_id}
116-
list={prepends}
117-
/>
118-
)}
119-
{fields}
120-
121-
{appends[0] && (
122-
<GroupLabel
123-
className="rounded-end"
124-
type="append"
125-
id={`${id}-label`}
126-
htmlFor={field_id}
127-
list={appends}
128-
/>
129-
)}
82+
{nodes}
13083
<ValidMessage {...{ validMode, validMessage, invalidMessage }} />
13184
</div>
13285
);

0 commit comments

Comments
 (0)