@synerise/ds-radio 1.1.16 → 1.1.17
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/CHANGELOG.md +4 -0
- package/dist/Radio.d.ts +7 -5
- package/dist/Radio.js +6 -26
- package/dist/Radio.styles.d.ts +25 -3
- package/dist/Radio.styles.js +46 -15
- package/dist/Radio.types.d.ts +69 -13
- package/dist/RadioContext.d.ts +17 -0
- package/dist/RadioContext.js +5 -0
- package/dist/RadioGroup.d.ts +9 -0
- package/dist/RadioGroup.js +61 -0
- package/dist/components/RadioBase.d.ts +3 -0
- package/dist/components/RadioBase.js +66 -0
- package/dist/components/RadioButton.d.ts +10 -0
- package/dist/components/RadioButton.js +49 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +6 -0
- package/dist/index.d.ts +1 -1
- package/package.json +4 -4
- package/dist/assets/style/index-tn0RQdqM.css +0 -0
- package/dist/style/index.css +0 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [1.1.17](https://github.com/synerise/synerise-design/compare/@synerise/ds-radio@1.1.16...@synerise/ds-radio@1.1.17) (2026-06-27)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @synerise/ds-radio
|
|
9
|
+
|
|
6
10
|
## [1.1.16](https://github.com/synerise/synerise-design/compare/@synerise/ds-radio@1.1.15...@synerise/ds-radio@1.1.16) (2026-06-17)
|
|
7
11
|
|
|
8
12
|
**Note:** Version bump only for package @synerise/ds-radio
|
package/dist/Radio.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
/**
|
|
2
|
+
* DS-native, antd-free radio. `Radio` (radio + label + description) with `Radio.Group` (single-select
|
|
3
|
+
* context) and `Radio.Button` (segmented control) attached as static members.
|
|
4
|
+
*/
|
|
5
|
+
declare const Radio: (({ value, checked, defaultChecked, disabled, name, id, autoFocus, tabIndex, onChange, onClick, label, description, children, className, style, ...rest }: import('./Radio.types').RadioProps) => import("react").JSX.Element) & {
|
|
6
|
+
Group: ({ value, defaultValue, onChange, options, optionType, buttonStyle, size, disabled, name, fullWidth, big, children, className, style, }: import('./Radio.types').RadioGroupProps) => import("react").JSX.Element;
|
|
7
|
+
Button: ({ value, disabled, children, className, checked, onChange, onClick, style, ...rest }: import('./Radio.types').RadioButtonProps) => import("react").JSX.Element;
|
|
6
8
|
};
|
|
7
9
|
export default Radio;
|
package/dist/Radio.js
CHANGED
|
@@ -1,29 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
fullWidth,
|
|
8
|
-
big,
|
|
9
|
-
...props
|
|
10
|
-
}) => {
|
|
11
|
-
return /* @__PURE__ */ jsx(AntRadioGroup, { fullWidth, big, ...props, children });
|
|
12
|
-
};
|
|
13
|
-
const RadioComponent = ({
|
|
14
|
-
description,
|
|
15
|
-
label,
|
|
16
|
-
children,
|
|
17
|
-
...antdRadioButtonProps
|
|
18
|
-
}) => {
|
|
19
|
-
return /* @__PURE__ */ jsxs(RadioWrapper, { children: [
|
|
20
|
-
/* @__PURE__ */ jsx(AntRadio, { ...antdRadioButtonProps, children: label ? /* @__PURE__ */ jsx(Label, { disabled: antdRadioButtonProps.disabled, children: label }) : children }),
|
|
21
|
-
description && /* @__PURE__ */ jsx(AdditionalData, { children: /* @__PURE__ */ jsx(Description, { disabled: antdRadioButtonProps.disabled, children: description }) })
|
|
22
|
-
] });
|
|
23
|
-
};
|
|
24
|
-
const Radio = Object.assign(RadioComponent, {
|
|
25
|
-
Group,
|
|
26
|
-
Button: Radio$1.Button
|
|
1
|
+
import RadioGroup from "./RadioGroup.js";
|
|
2
|
+
import { RadioBase } from "./components/RadioBase.js";
|
|
3
|
+
import { RadioButton } from "./components/RadioButton.js";
|
|
4
|
+
const Radio = Object.assign(RadioBase, {
|
|
5
|
+
Group: RadioGroup,
|
|
6
|
+
Button: RadioButton
|
|
27
7
|
});
|
|
28
8
|
export {
|
|
29
9
|
Radio as default
|
package/dist/Radio.styles.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { default as React } from 'react';
|
|
2
1
|
export declare const RadioWrapper: import('styled-components').StyledComponent<"div", any, {}, never>;
|
|
3
2
|
export declare const Description: import('styled-components').StyledComponent<"div", any, {
|
|
4
3
|
disabled?: boolean;
|
|
@@ -7,8 +6,31 @@ export declare const Label: import('styled-components').StyledComponent<({ id, l
|
|
|
7
6
|
disabled?: boolean;
|
|
8
7
|
}, never>;
|
|
9
8
|
export declare const AdditionalData: import('styled-components').StyledComponent<"div", any, {}, never>;
|
|
10
|
-
export declare const
|
|
11
|
-
export declare const
|
|
9
|
+
export declare const RadioInput: import('styled-components').StyledComponent<"input", any, {}, never>;
|
|
10
|
+
export declare const RadioInner: import('styled-components').StyledComponent<"span", any, {
|
|
11
|
+
$checked?: boolean;
|
|
12
|
+
$disabled?: boolean;
|
|
13
|
+
}, never>;
|
|
14
|
+
export declare const RadioText: import('styled-components').StyledComponent<"span", any, {
|
|
15
|
+
$checked?: boolean;
|
|
16
|
+
$disabled?: boolean;
|
|
17
|
+
}, never>;
|
|
18
|
+
export declare const RadioBox: import('styled-components').StyledComponent<"span", any, {}, never>;
|
|
19
|
+
export declare const RadioLabel: import('styled-components').StyledComponent<"label", any, {
|
|
20
|
+
$checked?: boolean;
|
|
21
|
+
$disabled?: boolean;
|
|
22
|
+
}, never>;
|
|
23
|
+
export declare const RadioButtonInput: import('styled-components').StyledComponent<"input", any, {}, never>;
|
|
24
|
+
export declare const RadioButtonLabel: import('styled-components').StyledComponent<"label", any, {
|
|
25
|
+
$checked?: boolean;
|
|
26
|
+
$disabled?: boolean;
|
|
27
|
+
$solid?: boolean;
|
|
28
|
+
$size?: "small" | "middle" | "large";
|
|
29
|
+
}, never>;
|
|
30
|
+
/**
|
|
31
|
+
* The `Radio.Group` container. Lays out the children and owns the `fullWidth`/`big` layout.
|
|
32
|
+
*/
|
|
33
|
+
export declare const RadioGroupWrapper: import('styled-components').StyledComponent<"div", any, {
|
|
12
34
|
fullWidth?: boolean;
|
|
13
35
|
big?: boolean;
|
|
14
36
|
}, never>;
|
package/dist/Radio.styles.js
CHANGED
|
@@ -1,39 +1,70 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Radio } from "antd";
|
|
3
1
|
import styled, { css } from "styled-components";
|
|
4
2
|
import { FormFieldLabel } from "@synerise/ds-form-field";
|
|
5
3
|
import { macro } from "@synerise/ds-typography";
|
|
6
4
|
const RadioWrapper = /* @__PURE__ */ styled.div.withConfig({
|
|
7
5
|
displayName: "Radiostyles__RadioWrapper",
|
|
8
6
|
componentId: "sc-137n5jf-0"
|
|
9
|
-
})(["
|
|
7
|
+
})(["display:block;margin-bottom:15px;"]);
|
|
10
8
|
const Description = /* @__PURE__ */ styled.div.withConfig({
|
|
11
9
|
displayName: "Radiostyles__Description",
|
|
12
10
|
componentId: "sc-137n5jf-1"
|
|
13
|
-
})(["color:", ";", " ", ""], (props) => props.theme.palette["grey-600"], (props) => props.disabled ?
|
|
11
|
+
})(["color:", ";", " ", ""], (props) => props.theme.palette["grey-600"], (props) => props.disabled ? "opacity: 0.4;" : "", macro.small);
|
|
14
12
|
const Label = /* @__PURE__ */ styled(FormFieldLabel).withConfig({
|
|
15
13
|
displayName: "Radiostyles__Label",
|
|
16
14
|
componentId: "sc-137n5jf-2"
|
|
17
|
-
})(["", ""], (props) => props.disabled ?
|
|
15
|
+
})(["", ""], (props) => props.disabled ? "opacity: 0.4;" : "");
|
|
18
16
|
const AdditionalData = /* @__PURE__ */ styled.div.withConfig({
|
|
19
17
|
displayName: "Radiostyles__AdditionalData",
|
|
20
18
|
componentId: "sc-137n5jf-3"
|
|
21
19
|
})(["margin:4px 8px 15px 28px;"]);
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
}) => /* @__PURE__ */ jsx(Radio, { ...rest })).withConfig({
|
|
25
|
-
displayName: "Radiostyles__AntRadio",
|
|
20
|
+
const RadioInput = /* @__PURE__ */ styled.input.withConfig({
|
|
21
|
+
displayName: "Radiostyles__RadioInput",
|
|
26
22
|
componentId: "sc-137n5jf-4"
|
|
27
|
-
})(["
|
|
28
|
-
const
|
|
29
|
-
displayName: "
|
|
23
|
+
})(["position:absolute;inset:0;width:16px;height:16px;margin:0;padding:0;opacity:0;cursor:pointer;z-index:1;"]);
|
|
24
|
+
const RadioInner = /* @__PURE__ */ styled.span.withConfig({
|
|
25
|
+
displayName: "Radiostyles__RadioInner",
|
|
30
26
|
componentId: "sc-137n5jf-5"
|
|
31
|
-
})(["", ""], (props) => props.
|
|
27
|
+
})(["position:relative;display:block;width:16px;height:16px;box-sizing:border-box;background-color:", ";border:1px solid ", ";border-radius:50%;&::after{content:'';position:absolute;inset:0;margin:auto;width:8px;height:8px;border-radius:50%;background-color:", ";transform:scale(0);transition:transform 0.2s ease;}", " ", ""], (props) => props.theme.palette.white, (props) => props.theme.palette["grey-300"], (props) => props.theme.palette["blue-600"], (props) => props.$checked && css(["border-color:", ";border-width:2px;&::after{transform:scale(1);}"], props.theme.palette["blue-600"]), (props) => props.$disabled && css(["border-color:", " !important;background-color:", " !important;&::after{background-color:", ";}"], props.theme.palette["grey-200"], props.theme.palette["grey-050"], props.theme.palette["grey-400"]));
|
|
28
|
+
const RadioText = /* @__PURE__ */ styled.span.withConfig({
|
|
29
|
+
displayName: "Radiostyles__RadioText",
|
|
30
|
+
componentId: "sc-137n5jf-6"
|
|
31
|
+
})(["flex-grow:1;margin:0 12px;padding:0;font-weight:500;color:", ";", ""], (props) => props.$checked ? props.theme.palette["grey-800"] : props.theme.palette["grey-700"], (props) => props.$disabled && css(["color:", ";opacity:0.4;"], props.theme.palette["grey-600"]));
|
|
32
|
+
const RadioBox = /* @__PURE__ */ styled.span.withConfig({
|
|
33
|
+
displayName: "Radiostyles__RadioBox",
|
|
34
|
+
componentId: "sc-137n5jf-7"
|
|
35
|
+
})(["position:relative;top:0;display:inline-flex;flex:none;height:16px;cursor:pointer;"]);
|
|
36
|
+
const RadioLabel = /* @__PURE__ */ styled.label.withConfig({
|
|
37
|
+
displayName: "Radiostyles__RadioLabel",
|
|
38
|
+
componentId: "sc-137n5jf-8"
|
|
39
|
+
})(["display:flex;align-items:flex-start;margin:0 8px 0 0;cursor:pointer;white-space:normal;", ":hover + ", "{border-color:", ";}", ":focus + ", "{border-color:", ";background-color:", ";border-width:2px;}", ""], RadioInput, RadioInner, (props) => props.theme.palette["grey-400"], RadioInput, RadioInner, (props) => props.theme.palette["blue-600"], (props) => props.$checked ? props.theme.palette.white : props.theme.palette["blue-050"], (props) => props.$disabled && css(["cursor:not-allowed;", ",", "{cursor:not-allowed;}"], RadioBox, RadioInput));
|
|
40
|
+
const RadioButtonInput = /* @__PURE__ */ styled.input.withConfig({
|
|
41
|
+
displayName: "Radiostyles__RadioButtonInput",
|
|
42
|
+
componentId: "sc-137n5jf-9"
|
|
43
|
+
})(["position:absolute;inset:0;opacity:0;cursor:pointer;"]);
|
|
44
|
+
const BUTTON_HEIGHT = {
|
|
45
|
+
small: "24px",
|
|
46
|
+
middle: "32px",
|
|
47
|
+
large: "40px"
|
|
48
|
+
};
|
|
49
|
+
const RadioButtonLabel = /* @__PURE__ */ styled.label.withConfig({
|
|
50
|
+
displayName: "Radiostyles__RadioButtonLabel",
|
|
51
|
+
componentId: "sc-137n5jf-10"
|
|
52
|
+
})(["position:relative;display:inline-flex;align-items:center;justify-content:center;height:", ";margin:0;padding:0 16px;color:", ";background-color:", ";border:1px solid ", ";border-left-width:0;cursor:pointer;&:first-child{border-left:1px solid ", ";border-radius:3px 0 0 3px;}&:last-child{border-radius:0 3px 3px 0;}&:hover{background-color:", ";}&:focus-within{border-color:", ";box-shadow:inset 0 0 0 1px ", ";z-index:2;}", " ", " ", ""], (props) => BUTTON_HEIGHT[props.$size ?? "middle"], (props) => props.theme.palette["grey-700"], (props) => props.theme.palette["grey-050"], (props) => props.theme.palette["grey-300"], (props) => props.theme.palette["grey-300"], (props) => props.theme.palette.white, (props) => props.theme.palette["blue-600"], (props) => props.theme.palette["blue-600"], (props) => props.$checked && css(["&&{color:", ";border-color:", ";box-shadow:-1px 0 0 0 ", ";z-index:1;}"], props.theme.palette["blue-600"], props.theme.palette["blue-600"], props.theme.palette["blue-600"]), (props) => props.$disabled && css(["color:", ";opacity:0.4;cursor:not-allowed;", "{cursor:not-allowed;}"], props.theme.palette["grey-700"], RadioButtonInput), (props) => props.$solid && props.$checked && !props.$disabled && css(["&&{color:", ";background-color:", ";border-color:", ";&:hover{background-color:", ";border-color:", ";box-shadow:-1px 0 0 0 ", ";}}"], props.theme.palette.white, props.theme.palette["blue-600"], props.theme.palette["blue-600"], props.theme.palette["blue-500"], props.theme.palette["blue-500"], props.theme.palette["blue-500"]));
|
|
53
|
+
const RadioGroupWrapper = /* @__PURE__ */ styled.div.withConfig({
|
|
54
|
+
displayName: "Radiostyles__RadioGroupWrapper",
|
|
55
|
+
componentId: "sc-137n5jf-11"
|
|
56
|
+
})(["display:inline-block;", ""], (props) => props.fullWidth && css(["&&{display:flex;width:100%;", ",", "{flex:1;height:", ";display:flex;align-items:center;justify-content:center;font-weight:500;}}"], RadioLabel, RadioButtonLabel, props.big ? "48px" : "32px"));
|
|
32
57
|
export {
|
|
33
58
|
AdditionalData,
|
|
34
|
-
AntRadio,
|
|
35
|
-
AntRadioGroup,
|
|
36
59
|
Description,
|
|
37
60
|
Label,
|
|
61
|
+
RadioBox,
|
|
62
|
+
RadioButtonInput,
|
|
63
|
+
RadioButtonLabel,
|
|
64
|
+
RadioGroupWrapper,
|
|
65
|
+
RadioInner,
|
|
66
|
+
RadioInput,
|
|
67
|
+
RadioLabel,
|
|
68
|
+
RadioText,
|
|
38
69
|
RadioWrapper
|
|
39
70
|
};
|
package/dist/Radio.types.d.ts
CHANGED
|
@@ -1,18 +1,74 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
export type
|
|
4
|
-
|
|
1
|
+
import { CSSProperties, MouseEventHandler, ReactNode } from 'react';
|
|
2
|
+
import { DataAttributes } from '@synerise/ds-utils';
|
|
3
|
+
export type RadioValueType = string | number | boolean;
|
|
4
|
+
/** antd-compatible radio change event. */
|
|
5
|
+
export type RadioChangeEventTarget = {
|
|
6
|
+
value?: RadioValueType;
|
|
7
|
+
checked: boolean;
|
|
8
|
+
name?: string;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
};
|
|
11
|
+
export type RadioChangeEvent = {
|
|
12
|
+
target: RadioChangeEventTarget;
|
|
13
|
+
stopPropagation: () => void;
|
|
14
|
+
preventDefault: () => void;
|
|
15
|
+
nativeEvent: Event;
|
|
16
|
+
};
|
|
17
|
+
export type RadioProps = {
|
|
18
|
+
value?: RadioValueType;
|
|
19
|
+
checked?: boolean;
|
|
20
|
+
defaultChecked?: boolean;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
name?: string;
|
|
23
|
+
id?: string;
|
|
24
|
+
autoFocus?: boolean;
|
|
25
|
+
tabIndex?: number;
|
|
26
|
+
onChange?: (event: RadioChangeEvent) => void;
|
|
27
|
+
/** Label text (rendered via `FormFieldLabel`). Takes precedence over `children`. */
|
|
5
28
|
label?: ReactNode;
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
29
|
+
/** Helper text rendered below the radio. */
|
|
30
|
+
description?: ReactNode;
|
|
31
|
+
/** @deprecated use `label` instead */
|
|
9
32
|
children?: ReactNode;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
33
|
+
className?: string;
|
|
34
|
+
style?: CSSProperties;
|
|
35
|
+
onClick?: MouseEventHandler<HTMLElement>;
|
|
36
|
+
} & DataAttributes;
|
|
37
|
+
/** @deprecated use `RadioProps` instead */
|
|
14
38
|
export type Props = RadioProps;
|
|
15
|
-
export type
|
|
39
|
+
export type RadioOptionType = {
|
|
40
|
+
label: ReactNode;
|
|
41
|
+
value: RadioValueType;
|
|
42
|
+
disabled?: boolean;
|
|
43
|
+
};
|
|
44
|
+
export type RadioGroupProps = {
|
|
45
|
+
value?: RadioValueType;
|
|
46
|
+
defaultValue?: RadioValueType;
|
|
47
|
+
onChange?: (event: RadioChangeEvent) => void;
|
|
48
|
+
options?: (RadioValueType | RadioOptionType)[];
|
|
49
|
+
optionType?: 'default' | 'button';
|
|
50
|
+
buttonStyle?: 'outline' | 'solid';
|
|
51
|
+
/** Segmented-button height: `small` 24px · `middle` (default) 32px · `large` 40px. */
|
|
52
|
+
size?: 'small' | 'middle' | 'large';
|
|
53
|
+
disabled?: boolean;
|
|
54
|
+
name?: string;
|
|
55
|
+
/** Group laid out full-width with equal-flex buttons. */
|
|
16
56
|
fullWidth?: boolean;
|
|
57
|
+
/** With `fullWidth`, taller (48px) buttons. */
|
|
17
58
|
big?: boolean;
|
|
18
|
-
|
|
59
|
+
children?: ReactNode;
|
|
60
|
+
className?: string;
|
|
61
|
+
style?: CSSProperties;
|
|
62
|
+
} & DataAttributes;
|
|
63
|
+
export type RadioButtonProps = {
|
|
64
|
+
value?: RadioValueType;
|
|
65
|
+
disabled?: boolean;
|
|
66
|
+
children?: ReactNode;
|
|
67
|
+
className?: string;
|
|
68
|
+
/** Override the group-derived checked state (e.g. when used standalone). */
|
|
69
|
+
checked?: boolean;
|
|
70
|
+
/** Fired on selection, in addition to the group's `onChange`. */
|
|
71
|
+
onChange?: (event: RadioChangeEvent) => void;
|
|
72
|
+
onClick?: MouseEventHandler<HTMLElement>;
|
|
73
|
+
style?: CSSProperties;
|
|
74
|
+
} & DataAttributes;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { RadioValueType } from './Radio.types';
|
|
2
|
+
export type RadioGroupContextValue = {
|
|
3
|
+
/** Currently selected value (single). */
|
|
4
|
+
value?: RadioValueType;
|
|
5
|
+
/** Select a value; the group builds the change event + calls the consumer onChange. */
|
|
6
|
+
onChange: (value: RadioValueType, nativeEvent: Event) => void;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
name?: string;
|
|
9
|
+
/** `'button'` makes the group render segmented `Radio.Button`s. */
|
|
10
|
+
optionType?: 'default' | 'button';
|
|
11
|
+
/** Button visual when `optionType === 'button'`. */
|
|
12
|
+
buttonStyle?: 'outline' | 'solid';
|
|
13
|
+
/** Segmented-button height (`small`/`middle`/`large`). */
|
|
14
|
+
size?: 'small' | 'middle' | 'large';
|
|
15
|
+
};
|
|
16
|
+
/** Provided by `Radio.Group`; consumed by child `Radio`/`Radio.Button`. `null` when standalone. */
|
|
17
|
+
export declare const RadioGroupContext: import('react').Context<RadioGroupContextValue | null>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { RadioGroupProps } from './Radio.types';
|
|
3
|
+
/**
|
|
4
|
+
* DS-native `Radio.Group`. Provides a single-value selection context that child `Radio`/`Radio.Button`s
|
|
5
|
+
* read, and emits `onChange(RadioChangeEvent)` (`target.value` = the selected value). Controlled via
|
|
6
|
+
* `value`, uncontrolled via `defaultValue`. `optionType="button"` renders segmented `Radio.Button`s.
|
|
7
|
+
*/
|
|
8
|
+
declare const RadioGroup: ({ value, defaultValue, onChange, options, optionType, buttonStyle, size, disabled, name, fullWidth, big, children, className, style, }: RadioGroupProps) => React.JSX.Element;
|
|
9
|
+
export default RadioGroup;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useCallback, useMemo } from "react";
|
|
3
|
+
import { RadioGroupWrapper } from "./Radio.styles.js";
|
|
4
|
+
import { RadioGroupContext } from "./RadioContext.js";
|
|
5
|
+
import { RadioBase } from "./components/RadioBase.js";
|
|
6
|
+
import { RadioButton } from "./components/RadioButton.js";
|
|
7
|
+
const cx = (...classes) => classes.filter(Boolean).join(" ");
|
|
8
|
+
const RadioGroup = ({
|
|
9
|
+
value,
|
|
10
|
+
defaultValue,
|
|
11
|
+
onChange,
|
|
12
|
+
options,
|
|
13
|
+
optionType = "default",
|
|
14
|
+
buttonStyle = "outline",
|
|
15
|
+
size,
|
|
16
|
+
disabled,
|
|
17
|
+
name,
|
|
18
|
+
fullWidth,
|
|
19
|
+
big,
|
|
20
|
+
children,
|
|
21
|
+
className,
|
|
22
|
+
style
|
|
23
|
+
}) => {
|
|
24
|
+
const isControlled = value !== void 0;
|
|
25
|
+
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
26
|
+
const resolvedValue = isControlled ? value : internalValue;
|
|
27
|
+
const handleChange = useCallback((newValue, nativeEvent) => {
|
|
28
|
+
if (!isControlled) {
|
|
29
|
+
setInternalValue(newValue);
|
|
30
|
+
}
|
|
31
|
+
const event = {
|
|
32
|
+
target: {
|
|
33
|
+
value: newValue,
|
|
34
|
+
checked: true,
|
|
35
|
+
name
|
|
36
|
+
},
|
|
37
|
+
stopPropagation: () => nativeEvent.stopPropagation?.(),
|
|
38
|
+
preventDefault: () => nativeEvent.preventDefault?.(),
|
|
39
|
+
nativeEvent
|
|
40
|
+
};
|
|
41
|
+
onChange?.(event);
|
|
42
|
+
}, [isControlled, name, onChange]);
|
|
43
|
+
const contextValue = useMemo(() => ({
|
|
44
|
+
value: resolvedValue,
|
|
45
|
+
onChange: handleChange,
|
|
46
|
+
disabled,
|
|
47
|
+
name,
|
|
48
|
+
optionType,
|
|
49
|
+
buttonStyle,
|
|
50
|
+
size
|
|
51
|
+
}), [resolvedValue, handleChange, disabled, name, optionType, buttonStyle, size]);
|
|
52
|
+
const normalizedOptions = options?.map((option) => typeof option === "object" ? option : {
|
|
53
|
+
label: String(option),
|
|
54
|
+
value: option
|
|
55
|
+
});
|
|
56
|
+
const renderOptions = () => normalizedOptions?.map((option) => optionType === "button" ? /* @__PURE__ */ jsx(RadioButton, { value: option.value, disabled: option.disabled, children: option.label }, String(option.value)) : /* @__PURE__ */ jsx(RadioBase, { value: option.value, disabled: option.disabled, label: option.label }, String(option.value)));
|
|
57
|
+
return /* @__PURE__ */ jsx(RadioGroupContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(RadioGroupWrapper, { className: cx("ant-radio-group", `ant-radio-group-${buttonStyle}`, "ds-radio-group", className), style, fullWidth, big, children: normalizedOptions ? renderOptions() : children }) });
|
|
58
|
+
};
|
|
59
|
+
export {
|
|
60
|
+
RadioGroup as default
|
|
61
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { RadioProps } from '../Radio.types';
|
|
3
|
+
export declare const RadioBase: ({ value, checked, defaultChecked, disabled, name, id, autoFocus, tabIndex, onChange, onClick, label, description, children, className, style, ...rest }: RadioProps) => React.JSX.Element;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useContext, useState } from "react";
|
|
3
|
+
import { RadioWrapper, RadioLabel, RadioBox, RadioInput, RadioInner, RadioText, Label, AdditionalData, Description } from "../Radio.styles.js";
|
|
4
|
+
import { RadioGroupContext } from "../RadioContext.js";
|
|
5
|
+
const cx = (...classes) => classes.filter(Boolean).join(" ");
|
|
6
|
+
const RadioBase = ({
|
|
7
|
+
value,
|
|
8
|
+
checked,
|
|
9
|
+
defaultChecked,
|
|
10
|
+
disabled,
|
|
11
|
+
name,
|
|
12
|
+
id,
|
|
13
|
+
autoFocus,
|
|
14
|
+
tabIndex,
|
|
15
|
+
onChange,
|
|
16
|
+
onClick,
|
|
17
|
+
label,
|
|
18
|
+
description,
|
|
19
|
+
children,
|
|
20
|
+
className,
|
|
21
|
+
style,
|
|
22
|
+
// forward data-*/aria-* (e.g. data-testid) to the input, as antd's rc-checkbox did
|
|
23
|
+
...rest
|
|
24
|
+
}) => {
|
|
25
|
+
const group = useContext(RadioGroupContext);
|
|
26
|
+
const isControlled = checked !== void 0;
|
|
27
|
+
const [internalChecked, setInternalChecked] = useState(Boolean(defaultChecked));
|
|
28
|
+
const isChecked = group ? group.value === value : isControlled ? Boolean(checked) : internalChecked;
|
|
29
|
+
const isDisabled = Boolean(disabled ?? group?.disabled);
|
|
30
|
+
const handleChange = (event) => {
|
|
31
|
+
if (isDisabled) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (group) {
|
|
35
|
+
group.onChange(value, event.nativeEvent);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (!isControlled) {
|
|
39
|
+
setInternalChecked(true);
|
|
40
|
+
}
|
|
41
|
+
const changeEvent = {
|
|
42
|
+
target: {
|
|
43
|
+
value,
|
|
44
|
+
checked: true,
|
|
45
|
+
name
|
|
46
|
+
},
|
|
47
|
+
stopPropagation: () => event.stopPropagation(),
|
|
48
|
+
preventDefault: () => event.preventDefault(),
|
|
49
|
+
nativeEvent: event.nativeEvent
|
|
50
|
+
};
|
|
51
|
+
onChange?.(changeEvent);
|
|
52
|
+
};
|
|
53
|
+
return /* @__PURE__ */ jsxs(RadioWrapper, { className: cx("ds-radio-field", className), style, children: [
|
|
54
|
+
/* @__PURE__ */ jsxs(RadioLabel, { className: cx("ant-radio-wrapper", "ds-radio", isChecked && "ant-radio-wrapper-checked", isDisabled && "ant-radio-wrapper-disabled"), $checked: isChecked, $disabled: isDisabled, onClick, children: [
|
|
55
|
+
/* @__PURE__ */ jsxs(RadioBox, { className: cx("ant-radio", "ds-radio-box", isChecked && "ant-radio-checked", isDisabled && "ant-radio-disabled"), children: [
|
|
56
|
+
/* @__PURE__ */ jsx(RadioInput, { type: "radio", className: "ant-radio-input ds-radio-input", checked: isChecked, disabled: isDisabled, name: name ?? group?.name, id, autoFocus, tabIndex, value: value === void 0 ? void 0 : String(value), onChange: handleChange, ...rest }),
|
|
57
|
+
/* @__PURE__ */ jsx(RadioInner, { className: "ant-radio-inner ds-radio-inner", $checked: isChecked, $disabled: isDisabled })
|
|
58
|
+
] }),
|
|
59
|
+
/* @__PURE__ */ jsx(RadioText, { $checked: isChecked, $disabled: isDisabled, children: label ? /* @__PURE__ */ jsx(Label, { disabled: isDisabled, children: label }) : children })
|
|
60
|
+
] }),
|
|
61
|
+
description && /* @__PURE__ */ jsx(AdditionalData, { children: /* @__PURE__ */ jsx(Description, { disabled: isDisabled, children: description }) })
|
|
62
|
+
] });
|
|
63
|
+
};
|
|
64
|
+
export {
|
|
65
|
+
RadioBase
|
|
66
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { RadioButtonProps } from '../Radio.types';
|
|
3
|
+
/**
|
|
4
|
+
* Segmented button-style radio (`Radio.Button`). Reproduces antd's `ant-radio-button-wrapper` DOM
|
|
5
|
+
* (class names kept as hooks only); the segment visual lives in the `RadioButtonLabel`
|
|
6
|
+
* styled-component, driven by checked/disabled/solid `$`-props read from the group context. When
|
|
7
|
+
* standalone (no group) `checked` applies directly; inside a group the group's value wins (antd
|
|
8
|
+
* parity). `onChange`/`onClick`/`style` always pass through.
|
|
9
|
+
*/
|
|
10
|
+
export declare const RadioButton: ({ value, disabled, children, className, checked, onChange, onClick, style, ...rest }: RadioButtonProps) => React.JSX.Element;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useContext } from "react";
|
|
3
|
+
import { RadioButtonLabel, RadioButtonInput } from "../Radio.styles.js";
|
|
4
|
+
import { RadioGroupContext } from "../RadioContext.js";
|
|
5
|
+
const cx = (...classes) => classes.filter(Boolean).join(" ");
|
|
6
|
+
const RadioButton = ({
|
|
7
|
+
value,
|
|
8
|
+
disabled,
|
|
9
|
+
children,
|
|
10
|
+
className,
|
|
11
|
+
checked,
|
|
12
|
+
onChange,
|
|
13
|
+
onClick,
|
|
14
|
+
style,
|
|
15
|
+
// forward data-*/aria-* (e.g. data-testid) to the input, as antd's rc-checkbox did
|
|
16
|
+
...rest
|
|
17
|
+
}) => {
|
|
18
|
+
const group = useContext(RadioGroupContext);
|
|
19
|
+
const isChecked = group ? group.value === value : Boolean(checked);
|
|
20
|
+
const isDisabled = Boolean(disabled ?? group?.disabled);
|
|
21
|
+
const handleChange = (event) => {
|
|
22
|
+
if (isDisabled) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (group) {
|
|
26
|
+
group.onChange(value, event.nativeEvent);
|
|
27
|
+
}
|
|
28
|
+
if (onChange) {
|
|
29
|
+
const changeEvent = {
|
|
30
|
+
target: {
|
|
31
|
+
value,
|
|
32
|
+
checked: true,
|
|
33
|
+
name: group?.name
|
|
34
|
+
},
|
|
35
|
+
stopPropagation: () => event.stopPropagation(),
|
|
36
|
+
preventDefault: () => event.preventDefault(),
|
|
37
|
+
nativeEvent: event.nativeEvent
|
|
38
|
+
};
|
|
39
|
+
onChange(changeEvent);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
return /* @__PURE__ */ jsxs(RadioButtonLabel, { className: cx("ant-radio-button-wrapper", "ds-radio-button", isChecked && "ant-radio-button-wrapper-checked", isDisabled && "ant-radio-button-wrapper-disabled", className), style, onClick, $checked: isChecked, $disabled: isDisabled, $solid: group?.buttonStyle === "solid", $size: group?.size, children: [
|
|
43
|
+
/* @__PURE__ */ jsx("span", { className: cx("ant-radio-button", isChecked && "ant-radio-button-checked", isDisabled && "ant-radio-button-disabled"), children: /* @__PURE__ */ jsx(RadioButtonInput, { type: "radio", className: "ant-radio-button-input ds-radio-button-input", checked: isChecked, disabled: isDisabled, name: group?.name, value: value === void 0 ? void 0 : String(value), onChange: handleChange, ...rest }) }),
|
|
44
|
+
/* @__PURE__ */ jsx("span", { children })
|
|
45
|
+
] });
|
|
46
|
+
};
|
|
47
|
+
export {
|
|
48
|
+
RadioButton
|
|
49
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { default } from './Radio';
|
|
2
|
-
export type { RadioProps, RadioGroupProps } from './Radio.types';
|
|
2
|
+
export type { RadioProps, RadioGroupProps, RadioButtonProps, RadioChangeEvent, RadioValueType, Props, } from './Radio.types';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synerise/ds-radio",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.17",
|
|
4
4
|
"description": "Radio UI Component for the Synerise Design System",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"repository": "synerise/synerise-design",
|
|
@@ -42,14 +42,14 @@
|
|
|
42
42
|
"types": "dist/index.d.ts",
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@synerise/ds-form-field": "^1.3.23",
|
|
45
|
-
"@synerise/ds-typography": "^1.1.26"
|
|
45
|
+
"@synerise/ds-typography": "^1.1.26",
|
|
46
|
+
"@synerise/ds-utils": "^1.10.1"
|
|
46
47
|
},
|
|
47
48
|
"peerDependencies": {
|
|
48
49
|
"@synerise/ds-core": "*",
|
|
49
|
-
"antd": "4.24.16",
|
|
50
50
|
"react": ">=16.9.0 <= 18.3.1",
|
|
51
51
|
"styled-components": "^5.3.3",
|
|
52
52
|
"vitest": "4"
|
|
53
53
|
},
|
|
54
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "dfd892acc1d02abebcf661db3fb32f0fc4b6c354"
|
|
55
55
|
}
|
|
File without changes
|
package/dist/style/index.css
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
.ant-radio-group{box-sizing:border-box;margin:0;padding:0;color:#6a7580;font-size:13px;font-variant:tabular-nums;line-height:1.38;list-style:none;font-feature-settings:'tnum';display:inline-block;font-size:0}.ant-radio-group .ant-badge-count{z-index:1}.ant-radio-group>.ant-badge:not(:first-child)>.ant-radio-button-wrapper{border-left:none}.ant-radio-wrapper{box-sizing:border-box;margin:0;padding:0;color:#6a7580;font-size:13px;font-variant:tabular-nums;line-height:1.38;list-style:none;font-feature-settings:'tnum';position:relative;display:inline-flex;align-items:baseline;margin-right:8px;cursor:pointer}.ant-radio-wrapper-disabled{cursor:not-allowed}.ant-radio-wrapper::after{display:inline-block;width:0;overflow:hidden;content:'\a0'}.ant-radio-wrapper.ant-radio-wrapper-in-form-item input[type=radio]{width:14px;height:14px}.ant-radio{box-sizing:border-box;margin:0;padding:0;color:#6a7580;font-size:13px;font-variant:tabular-nums;line-height:1.38;list-style:none;font-feature-settings:'tnum';position:relative;top:.2em;display:inline-block;outline:0;cursor:pointer}.ant-radio-input:focus+.ant-radio-inner,.ant-radio-wrapper:hover .ant-radio,.ant-radio:hover .ant-radio-inner{border-color:#0b68ff}.ant-radio-input:focus+.ant-radio-inner{box-shadow:0 0 0 3px #85b4ff}.ant-radio-checked::after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #0b68ff;border-radius:50%;visibility:hidden;animation:antRadioEffect .36s ease-in-out;animation-fill-mode:both;content:''}.ant-radio-wrapper:hover .ant-radio::after,.ant-radio:hover::after{visibility:visible}.ant-radio-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;background-color:#fff;border-color:#dbe0e3;border-style:solid;border-width:1px;border-radius:50%;transition:all .3s}.ant-radio-inner::after{position:absolute;top:50%;left:50%;display:block;width:16px;height:16px;margin-top:-8px;margin-left:-8px;background-color:#0b68ff;border-top:0;border-left:0;border-radius:16px;transform:scale(0);opacity:0;transition:all .3s cubic-bezier(.78, .14, .15, .86);content:' '}.ant-radio-input{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;cursor:pointer;opacity:0}.ant-radio.ant-radio-disabled .ant-radio-inner{border-color:#dbe0e3}.ant-radio-checked .ant-radio-inner{border-color:#0b68ff}.ant-radio-checked .ant-radio-inner::after{transform:scale(.5);opacity:1;transition:all .3s cubic-bezier(.78, .14, .15, .86)}.ant-radio-disabled{cursor:not-allowed}.ant-radio-disabled .ant-radio-inner{background-color:#f9fafb;cursor:not-allowed}.ant-radio-disabled .ant-radio-inner::after{background-color:rgba(0,0,0,.2)}.ant-radio-disabled .ant-radio-input{cursor:not-allowed}.ant-radio-disabled+span{color:#b5bdc3;cursor:not-allowed}span.ant-radio+*{padding-right:8px;padding-left:8px}.ant-radio-button-wrapper{position:relative;display:inline-block;height:32px;margin:0;padding:0 15px;color:#6a7580;font-size:13px;line-height:30px;background:#fff;border:1px solid #dbe0e3;border-top-width:1.02px;border-left-width:0;cursor:pointer;transition:color .3s,background .3s,border-color .3s,box-shadow .3s}.ant-radio-button-wrapper a{color:#6a7580}.ant-radio-button-wrapper>.ant-radio-button{position:absolute;top:0;left:0;z-index:-1;width:100%;height:100%}.ant-radio-group-large .ant-radio-button-wrapper{height:48px;font-size:13px;line-height:46px}.ant-radio-group-small .ant-radio-button-wrapper{height:24px;padding:0 7px;line-height:22px}.ant-radio-button-wrapper:not(:first-child)::before{position:absolute;top:-1px;left:-1px;display:block;box-sizing:content-box;width:1px;height:100%;padding:1px 0;background-color:#dbe0e3;transition:background-color .3s;content:''}.ant-radio-button-wrapper:first-child{border-left:1px solid #dbe0e3;border-radius:3px 0 0 3px}.ant-radio-button-wrapper:last-child{border-radius:0 3px 3px 0}.ant-radio-button-wrapper:first-child:last-child{border-radius:3px}.ant-radio-button-wrapper:hover{position:relative;color:#0b68ff}.ant-radio-button-wrapper:focus-within{box-shadow:0 0 0 3px #85b4ff}.ant-radio-button-wrapper .ant-radio-inner,.ant-radio-button-wrapper input[type=checkbox],.ant-radio-button-wrapper input[type=radio]{width:0;height:0;opacity:0;pointer-events:none}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled){z-index:1;color:#0b68ff;background:#f9fafb;border-color:#0b68ff}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)::before{background-color:#0b68ff}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):first-child{border-color:#0b68ff}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{color:#38f;border-color:#38f}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover::before{background-color:#38f}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active{color:#004cd9;border-color:#004cd9}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active::before{background-color:#004cd9}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within{box-shadow:0 0 0 3px #85b4ff}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled){color:#fff;background:#0b68ff;border-color:#0b68ff}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{color:#fff;background:#38f;border-color:#38f}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active{color:#fff;background:#004cd9;border-color:#004cd9}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within{box-shadow:0 0 0 3px #85b4ff}.ant-radio-button-wrapper-disabled{color:#b5bdc3;background-color:#f9fafb;border-color:#dbe0e3;cursor:not-allowed}.ant-radio-button-wrapper-disabled:first-child,.ant-radio-button-wrapper-disabled:hover{color:#b5bdc3;background-color:#f9fafb;border-color:#dbe0e3}.ant-radio-button-wrapper-disabled:first-child{border-left-color:#dbe0e3}.ant-radio-button-wrapper-disabled.ant-radio-button-wrapper-checked{color:#b5bdc3;background-color:#e6e6e6;border-color:#dbe0e3;box-shadow:none}@keyframes antRadioEffect{0%{transform:scale(1);opacity:.5}100%{transform:scale(1.6);opacity:0}}.ant-radio-group.ant-radio-group-rtl{direction:rtl}.ant-radio-wrapper.ant-radio-wrapper-rtl{margin-right:0;margin-left:8px;direction:rtl}.ant-radio-button-wrapper.ant-radio-button-wrapper-rtl{border-right-width:0;border-left-width:1px}.ant-radio-button-wrapper.ant-radio-button-wrapper-rtl.ant-radio-button-wrapper:not(:first-child)::before{right:-1px;left:0}.ant-radio-button-wrapper.ant-radio-button-wrapper-rtl.ant-radio-button-wrapper:first-child{border-right:1px solid #dbe0e3;border-radius:0 3px 3px 0}.ant-radio-button-wrapper-checked:not([class*=' ant-radio-button-wrapper-disabled']).ant-radio-button-wrapper:first-child{border-right-color:#38f}.ant-radio-button-wrapper.ant-radio-button-wrapper-rtl.ant-radio-button-wrapper:last-child{border-radius:3px 0 0 3px}.ant-radio-button-wrapper.ant-radio-button-wrapper-rtl.ant-radio-button-wrapper-disabled:first-child{border-right-color:#dbe0e3}.ant-radio-wrapper{display:flex;white-space:normal}.ant-radio+span{padding:0;color:#57616d;font-weight:500;margin:0 12px 0 12px;position:relative}.ant-radio-checked+span{color:#384350}.ant-radio-wrapper-disabled .ant-radio+span{color:#6a7580;opacity:.4}.ant-radio-wrapper{align-items:unset}.ant-radio-wrapper::after{display:none}.ant-radio-input:hover+.ant-radio-inner{border-color:#b5bdc3}.ant-radio-input:focus+.ant-radio-inner{border-color:#0b68ff;background-color:#f4faff;border-width:2px}.ant-radio-input:focus+.ant-radio-inner{box-shadow:none}.ant-radio-checked .ant-radio-inner{border-color:#0b68ff;border-width:2px}.ant-radio-button-wrapper{background-color:#f9fafb;color:#57616d}.ant-radio-button-wrapper:hover{background-color:#fff;color:#57616d}.ant-radio-button-wrapper:focus{border:2px solid #0b68ff;z-index:2;margin-left:-1px;top:1px}.ant-radio-button-wrapper:focus span+span{top:-2px}.ant-radio-button-wrapper:not(:focus){top:0}.ant-radio-button-wrapper:not(:first-child)::before{display:none}.ant-radio-button-wrapper:not(:first-child):not(:focus)::before{position:absolute;top:0;left:-1px;display:block;width:1px;height:100%;background-color:#dbe0e3;content:''}.ant-radio-button-wrapper-checked:not( .ant-radio-button-wrapper-disabled){box-shadow:-1px 0 0 0 #0b68ff}.ant-radio-button-wrapper span+span{position:relative;top:-1px}.ant-radio-button-wrapper-disabled{color:#57616d;opacity:.4}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(
|
|
2
|
-
.ant-radio-button-wrapper-disabled
|
|
3
|
-
):hover{color:#fff;background:#238afe;border-color:#238afe;box-shadow:-1px 0 0 0 #238afe}
|