Jelajahi Sumber

feat(新增组件): 新增RadioCard组件

xieyonghong 3 tahun lalu
induk
melakukan
f13952e61f

+ 1 - 1
package.json

@@ -76,7 +76,7 @@
     "ahooks": "^2.10.9",
     "antd": "^4.18.8",
     "braft-editor": "^2.3.9",
-    "classnames": "^2.2.6",
+    "classnames": "^2.3.1",
     "dexie": "^3.0.3",
     "event-source-polyfill": "^1.0.25",
     "isomorphic-form-data": "^2.0.0",

TEMPAT SAMPAH
public/images/avatar-1.png


TEMPAT SAMPAH
public/images/device-type-1.png


TEMPAT SAMPAH
public/images/device-type-2.png


TEMPAT SAMPAH
public/images/device-type-3.png


+ 71 - 0
src/components/RadioCard/index.less

@@ -0,0 +1,71 @@
+@import '~antd/lib/style/themes/variable';
+@border: 1px solid @border-color-base;
+
+.radio-card-items {
+  display: flex;
+
+  .radio-card-item {
+    display: flex;
+    align-items: center;
+    min-width: 180px;
+    padding: 16px 20px;
+    overflow: hidden;
+    font-size: 14px;
+    border: @border;
+    border-radius: @border-radius-base;
+
+    > img {
+      width: 32px;
+      height: 32px;
+      margin-right: 24px;
+    }
+
+    > span {
+      cursor: default;
+    }
+
+    &:not(:last-child) {
+      margin-right: 10px;
+    }
+
+    &:hover,
+    &:focus {
+      color: @primary-color-hover;
+      border-color: @primary-color-hover;
+    }
+
+    .checked-icon {
+      position: absolute;
+      right: -22px;
+      bottom: -22px;
+      z-index: 2;
+      display: none;
+      width: 44px;
+      height: 44px;
+      color: #fff;
+      background-color: @primary-color-active;
+      transform: rotate(-45deg);
+      > div {
+        position: relative;
+        height: 100%;
+        transform: rotate(45deg);
+        > span {
+          position: absolute;
+          top: 6px;
+          left: 6px;
+          font-size: 12px;
+        }
+      }
+    }
+
+    &.checked {
+      position: relative;
+      color: @primary-color-active;
+      border-color: @primary-color-active;
+
+      > .checked-icon {
+        display: block;
+      }
+    }
+  }
+}

+ 89 - 0
src/components/RadioCard/index.tsx

@@ -0,0 +1,89 @@
+import { useEffect, useState, useRef } from 'react';
+import classNames from 'classnames';
+import { isArray } from 'lodash';
+import './index.less';
+import { CheckOutlined } from '@ant-design/icons';
+
+type RadioCardModelType = 'multiple' | 'singular';
+
+interface RadioCardItem {
+  label: string;
+  value: string;
+  imgUrl: string;
+}
+
+export interface RadioCardProps {
+  value?: string | string[];
+  model?: RadioCardModelType;
+  options: RadioCardItem[];
+  onChange?: (keys: string | string[]) => void;
+  onSelect?: (key: string, selected: boolean, node: RadioCardItem[]) => void;
+}
+
+export default (props: RadioCardProps) => {
+  const { value, model, options, onChange, onSelect } = props;
+  const [keys, setKeys] = useState<string[]>([]);
+  const isMultiple = useRef<boolean>(true);
+
+  isMultiple.current = !(model && model === 'singular');
+
+  useEffect(() => {
+    // 初始化
+    setKeys(value ? (isArray(value) ? value : [value]) : []);
+  }, [props.value]);
+
+  const getNode = (_keys: string[]) =>
+    options.filter((item) => _keys.some((key) => key === item.value));
+
+  const toggleOption = (key: string) => {
+    const optionIndex = keys.indexOf(key);
+    const newKeys = [...keys];
+    if (optionIndex === -1) {
+      if (isMultiple.current) {
+        newKeys.push(key);
+      } else {
+        newKeys[0] = key;
+      }
+    } else {
+      newKeys.splice(optionIndex, 1);
+    }
+
+    if (!('value' in props)) {
+      setKeys(newKeys);
+    }
+
+    if (onChange) {
+      onChange(isMultiple.current ? newKeys : newKeys[0]);
+    }
+
+    if (onSelect) {
+      onSelect(key, optionIndex === -1, getNode(newKeys));
+    }
+  };
+
+  return (
+    <div className={'radio-card-items'}>
+      {options.map((item) => {
+        return (
+          <div
+            className={classNames('radio-card-item', {
+              checked: keys?.includes(item.value),
+            })}
+            key={item.value}
+            onClick={() => {
+              toggleOption(item.value);
+            }}
+          >
+            <img width={32} height={32} src={item.imgUrl} alt={''} />
+            <span>{item.label}</span>
+            <div className={'checked-icon'}>
+              <div>
+                <CheckOutlined />
+              </div>
+            </div>
+          </div>
+        );
+      })}
+    </div>
+  );
+};

+ 1 - 0
src/components/index.ts

@@ -0,0 +1 @@
+export { default as RadioCard } from './RadioCard';