Skip to content

Commit e4d4c17

Browse files
author
Daniel Pandyan
committed
S2 SelectBox initial implementation
1 parent fe5e1b6 commit e4d4c17

File tree

4 files changed

+651
-0
lines changed

4 files changed

+651
-0
lines changed
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
/*
2+
* Copyright 2025 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
9+
* governing permissions and limitations under the License.
10+
*/
11+
12+
import {Radio as AriaRadio, Checkbox as AriaCheckbox, ContextValue, RadioProps, CheckboxProps} from 'react-aria-components';
13+
import {FocusableRef, FocusableRefValue, SpectrumLabelableProps, HelpTextProps} from '@react-types/shared';
14+
import {Checkbox} from './Checkbox';
15+
import {forwardRef, ReactNode, useContext, useRef, createContext} from 'react';
16+
import {useFocusableRef} from '@react-spectrum/utils';
17+
import {SelectBoxContext} from './SelectBoxGroup';
18+
import {style, focusRing, baseColor} from '../style' with {type: 'macro'};
19+
import {controlFont, getAllowedOverrides, StyleProps} from './style-utils' with {type: 'macro'};
20+
import {useSpectrumContextProps} from './useSpectrumContextProps';
21+
import React from 'react';
22+
23+
export interface SelectBoxProps extends
24+
Omit<CheckboxProps & RadioProps, 'className' | 'style' | 'children'>, StyleProps {
25+
/**
26+
* The value of the SelectBox.
27+
*/
28+
value: string,
29+
/**
30+
* The label for the element.
31+
*/
32+
children?: ReactNode,
33+
/**
34+
* Whether the SelectBox is disabled.
35+
*/
36+
isDisabled?: boolean
37+
}
38+
39+
export const SelectBoxItemContext = createContext<ContextValue<Partial<SelectBoxProps>, FocusableRefValue<HTMLLabelElement>>>(null);
40+
41+
// Simple basic styling with proper dark mode support
42+
const selectBoxStyles = style({
43+
...focusRing(),
44+
display: 'flex',
45+
flexDirection: 'column',
46+
lineHeight: 'title',
47+
justifyContent: 'center',
48+
flexShrink: 0,
49+
alignItems: 'center',
50+
fontFamily: 'sans',
51+
font: 'ui',
52+
//vertical orientation
53+
size: {
54+
default: {
55+
size: {
56+
S: 120,
57+
M: 170,
58+
L: 220,
59+
XL: 270
60+
}
61+
},
62+
//WIP horizontal orientation
63+
orientation: {
64+
horizontal: {
65+
size: {
66+
S: 280,
67+
M: 368,
68+
L: 420,
69+
XL: 480
70+
}
71+
}
72+
}
73+
},
74+
minWidth: {
75+
default: {
76+
size: {
77+
S: 100,
78+
M: 144,
79+
L: 180,
80+
XL: 220
81+
}
82+
},
83+
orientation: {
84+
horizontal: {
85+
size: {
86+
S: 160,
87+
M: 188,
88+
L: 220,
89+
XL: 250
90+
}
91+
}
92+
}
93+
},
94+
maxWidth: {
95+
default: {
96+
size: {
97+
S: 140,
98+
M: 200,
99+
L: 260,
100+
XL: 320
101+
}
102+
},
103+
orientation: {
104+
horizontal: {
105+
size: {
106+
S: 360,
107+
M: 420,
108+
L: 480,
109+
XL: 540
110+
}
111+
}
112+
}
113+
},
114+
minHeight: {
115+
default: {
116+
size: {
117+
S: 100,
118+
M: 144,
119+
L: 180,
120+
XL: 220
121+
}
122+
},
123+
orientation: {
124+
horizontal: 80
125+
}
126+
},
127+
maxHeight: {
128+
default: {
129+
size: {
130+
S: 140,
131+
M: 200,
132+
L: 260,
133+
XL: 320
134+
}
135+
},
136+
orientation: {
137+
horizontal: 240
138+
}
139+
},
140+
padding: {
141+
size: {
142+
S: 16,
143+
M: 24,
144+
L: 32,
145+
XL: 40
146+
}
147+
},
148+
borderRadius: 'lg',
149+
backgroundColor: 'layer-2',
150+
boxShadow: {
151+
default: 'emphasized',
152+
isHovered: 'elevated',
153+
isSelected: 'elevated',
154+
forcedColors: 'none'
155+
},
156+
position: 'relative',
157+
borderWidth: 2,
158+
borderStyle: {
159+
default: 'solid',
160+
isSelected: 'solid'
161+
},
162+
borderColor: {
163+
default: 'transparent',
164+
isSelected: 'gray-900',
165+
isFocusVisible: 'transparent'
166+
},
167+
transition: 'default'
168+
}, getAllowedOverrides());
169+
170+
const checkboxContainer = style({
171+
position: 'absolute',
172+
top: 16,
173+
left: 16
174+
}, getAllowedOverrides());
175+
176+
/**
177+
* SelectBox components allow users to select options from a list.
178+
* They can behave as radio buttons (single selection) or checkboxes (multiple selection).
179+
*/
180+
export const SelectBox = /*#__PURE__*/ forwardRef(function SelectBox(props: SelectBoxProps, ref: FocusableRef<HTMLLabelElement>) {
181+
[props, ref] = useSpectrumContextProps(props, ref, SelectBoxItemContext);
182+
let {children, value, isDisabled = false, UNSAFE_className = '', UNSAFE_style} = props;
183+
let inputRef = useRef<HTMLInputElement | null>(null);
184+
let domRef = useFocusableRef(ref, inputRef);
185+
186+
let groupContext = useContext(SelectBoxContext);
187+
let {
188+
allowMultiSelect = false,
189+
size = 'M',
190+
orientation = 'vertical'
191+
} = groupContext || {};
192+
193+
const Selector = allowMultiSelect ? AriaCheckbox : AriaRadio;
194+
195+
return (
196+
<Selector
197+
value={value}
198+
isDisabled={isDisabled}
199+
ref={domRef}
200+
inputRef={inputRef}
201+
style={UNSAFE_style}
202+
className={renderProps => UNSAFE_className + selectBoxStyles({...renderProps, size, orientation}, props.styles)}>
203+
{renderProps => (
204+
<>
205+
{(renderProps.isSelected || renderProps.isHovered) && (
206+
<div className={checkboxContainer({...renderProps, size}, props.styles)}>
207+
<Checkbox
208+
value={value}
209+
isSelected={renderProps.isSelected}
210+
isDisabled={isDisabled}
211+
size={size}
212+
/>
213+
</div>
214+
)}
215+
{children}
216+
</>
217+
)}
218+
</Selector>
219+
);
220+
});

0 commit comments

Comments
 (0)