`来声明组件,更符合 React 的开发习惯。
29 | 2. **属性**:在 React 中,我们使用`props`而不是`attribute`,所以你可以传递任何类型的参数给组件,而不用关心烦人的[转换规则](/guide/quick-start#attribute-和-property)。
30 | 3. **事件**:我们对组件事件做了适配,事件的名称会被转换为符合 [React 事件命名规范](https://react.dev/learn/responding-to-events#naming-event-handler-props)的形式。如`afterHide`会被转换为`onAfterHide`。
31 | 4. **类型支持**:使用 TypeScript 的用户会得到更好的类型支持。
32 |
33 | ## 怎么实现的?
34 |
35 | 我们使用了[@lit/react](https://github.com/lit/lit/blob/HEAD/packages/react/README.md)。
36 |
--------------------------------------------------------------------------------
/docs/doc-components/features/index.css:
--------------------------------------------------------------------------------
1 | .features-wrapper {
2 | display: flex;
3 | align-items: stretch;
4 | justify-content: space-between;
5 | gap: 40px;
6 | margin-bottom: 80px;
7 | }
8 |
9 | .feature-item {
10 | box-sizing: border-box;
11 | padding: 20px;
12 | border-radius: 10px;
13 | flex: 1;
14 | color: #333;
15 | border: 1px solid #f0f0f0;
16 | box-shadow: 0 0 10px rgba(0, 0, 0, 10%);
17 | transition: all 0.3s;
18 | }
19 |
20 | .feature-item:hover {
21 | box-shadow: 0 0 20px rgba(0, 0, 0, 10%);
22 | background: linear-gradient(45deg, #f0f0f0, #f9f9f9);
23 | }
24 |
25 | .feature-title {
26 | font-size: 24px;
27 | font-weight: 700;
28 | }
29 |
30 | .feature-description {
31 | font-size: 14px;
32 | color: #666;
33 | }
34 |
35 | @media (max-width: 575px) {
36 | .features-wrapper {
37 | flex-direction: column;
38 | gap: 20px;
39 | }
40 |
41 | .feature-item {
42 | padding: 10px 20px;
43 | }
44 |
45 | .feature-title {
46 | margin: 10px 0;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/docs/example/Progress/demos/customColor.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: 自定义进度条和背景颜色
3 | */
4 |
5 | import { Progress } from '@banana-ui/react';
6 | import React from 'react';
7 |
8 | export default function CustomColor() {
9 | return (
10 |
11 | 你可以使用
color 参数和
backgroundColor 参数来自定义进度条和背景颜色。
12 |
13 | 当然你也可以实现一个渐变的进度条:
14 |
19 | 你还可以使用
css变量 来自定义进度条和背景颜色。
20 |
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/public/Select/Multiple.html:
--------------------------------------------------------------------------------
1 |
2 | 🍎 AppleAppleAppleAppleAppleAppleAppleAppleAppleAppleAppleAppleAppleAppleAppleApple
5 | 🍌 Banana
6 | 🍊 Orange
7 | 🍐 Pear
8 | 🍇 Grape
9 | 🍓 Strawberry
10 | 🍍 Pineapple
11 | 🍒 Cherry
12 | 🚫 Disabled
13 |
14 |
23 |
--------------------------------------------------------------------------------
/docs/example/Modal/demos/overflow.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: 溢出内容
3 | * description: 为了保证Modal的Header和Footer能始终显示在视窗内,当Modal内容过多时,Modal会自动出现滚动条。
4 | */
5 |
6 | import { Button, Message, Modal } from '@banana-ui/react';
7 | import { useState } from 'react';
8 |
9 | export default function Overflow() {
10 | const [visible, setVisible] = useState(false);
11 |
12 | return (
13 | <>
14 | setVisible(true)}>
15 | Open Modal
16 |
17 | setVisible(false)}
20 | onOk={() => {
21 | setVisible(false);
22 | Message.success({
23 | content: 'OK~',
24 | });
25 | }}
26 | title="I am a Modal~"
27 | >
28 |
29 |
往下滚动试试~👇🏻👇👇🏻👇👇🏻👇
30 |
Hi~ o(* ̄▽ ̄*)ブ
31 |
32 |
33 | >
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/packages/banana/src/tab-item/index.ts:
--------------------------------------------------------------------------------
1 | import { CSSResultGroup, html, LitElement } from 'lit';
2 | import { customElement, property } from 'lit/decorators.js';
3 | import { classMap } from 'lit/directives/class-map.js';
4 | import styles from './index.styles';
5 |
6 | @customElement('b-tab-item')
7 | export default class BTabItem extends LitElement {
8 | static styles?: CSSResultGroup = styles;
9 |
10 | connectedCallback() {
11 | super.connectedCallback();
12 | }
13 |
14 | disconnectedCallback() {
15 | super.disconnectedCallback();
16 | }
17 |
18 | @property({ type: Boolean, reflect: true })
19 | active = false;
20 |
21 | @property({ type: Boolean, reflect: true })
22 | disabled = false;
23 |
24 | @property({ type: String, reflect: true })
25 | panel = '';
26 |
27 | render() {
28 | return html`
29 |
33 |
34 |
35 | `;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/todo.md:
--------------------------------------------------------------------------------
1 | # 文档站点
2 |
3 | - HTML/Vue 版本的 preview 代码
4 |
5 | # 组件
6 |
7 | ## 新组件
8 |
9 | - Tabs
10 | - Image
11 | - Space
12 |
13 | ## 已有组件
14 |
15 | ### Button
16 |
17 | - 链接按钮
18 | - icon 按钮
19 | - css part / css 变量的文档说明
20 | - slot 的文档说明
21 |
22 | ### Carousel
23 |
24 | - 暴露出 emit 事件
25 | - css part / css 变量的文档说明
26 | - 事件的文档说明
27 | - slot 的文档说明
28 | - 受控模式
29 | - 从性能角度上考虑,可以给 resize 处理加一个防抖
30 |
31 | ### Collapse
32 |
33 | - group 模式
34 |
35 | ### Overlay
36 |
37 | - 用 requestAnimationFrame()代替 css 实现消失的动画,这样我们就可以得到“消失”这一动作完成的时机,从而在消失完成而非开始消失时就让浏览器出现滚动条。
38 |
39 | ### Popup
40 |
41 | - round 模式(圆角)
42 | - 关闭按钮
43 | - 最大高度、最大宽度参数
44 | - 禁止遮罩层点击关闭的参数
45 |
46 | ### Menu
47 |
48 | - 文档
49 | - checkbox type
50 | - 事件的类型
51 | - suffix、prefix
52 | - menu label(可选)
53 | - menu 的键盘事件,a11y 支持
54 |
55 | ### Dropdown
56 |
57 | - 暴露事件
58 | - 参数补全(floating-ui 中间件相关)
59 | - 移动端禁用 hover 触发
60 | - autoUpdate
61 |
62 | ### Rating
63 |
64 | - 键盘事件,a11y 支持
65 |
66 | # 构建流程
67 |
68 | - umd 模式 d.ts 文件问题
69 | - 使用 unbuild 代替现有的 rollup(可选)
70 |
71 | # 其他
72 |
73 | - 自定义 commitizen 配置
74 |
--------------------------------------------------------------------------------
/docs/example/Carousel/demos/vertical.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: 垂直展示
3 | * description: 当需要垂直展示时,需要手动指定容器高度(设置对应css变量`--banana-carousel-vertical-height`)
4 | */
5 |
6 | import { Carousel } from '@banana-ui/react';
7 |
8 | export default function Vertical() {
9 | const style = `
10 | .demo-slide--vertical {
11 | display: flex;
12 | align-items: center;
13 | justify-content: center;
14 | background: linear-gradient(to bottom right, #5193e9, #0e61cd);
15 | color: #fff;
16 | font-size: 48px;
17 | }
18 | .container {
19 | --banana-carousel-vertical-height: 300px;
20 | }
21 | `;
22 |
23 | return (
24 | <>
25 |
26 |
27 | 1
28 | 2
29 | 3
30 | 4
31 | 5
32 |
33 | >
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/docs/example/Message/demos/type.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: 不同的提示类型
3 | */
4 |
5 | import { Button, Message } from '@banana-ui/react';
6 |
7 | export default function Type() {
8 | return (
9 |
10 | {
12 | Message.info({ content: '一个普通提示' });
13 | }}
14 | type="primary"
15 | >
16 | 普通提示
17 |
18 |
19 | {
21 | Message.success({ content: '一个成功提示' });
22 | }}
23 | type="success"
24 | >
25 | 成功提示
26 |
27 |
28 | {
30 | Message.error({ content: '一个错误提示' });
31 | }}
32 | type="danger"
33 | >
34 | 错误提示
35 |
36 |
37 | {
39 | Message.warning({ content: '一个警告提示' });
40 | }}
41 | type="warning"
42 | >
43 | 警告提示
44 |
45 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/public/Message/type.html:
--------------------------------------------------------------------------------
1 |
2 | 普通提示
3 | 成功提示
4 | 错误提示
5 | 警告提示
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/public/Tooltip/backgroundColor.html:
--------------------------------------------------------------------------------
1 | 你可以通过 background-color 参数设置背景色。
2 |
3 |
4 | maroon
5 |
6 |
7 | #015AE0
8 |
9 |
10 | rgb(16, 145, 11)
11 |
12 |
13 | 也可以通过css变量来实现这一点
14 |
15 |
16 | marron
17 |
18 |
19 | #015AE0
20 |
21 |
22 | rgb(16, 145, 11)
23 |
24 |
25 |
--------------------------------------------------------------------------------
/docs/example/Carousel/demos/disableFill.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: 填充行为
3 | * description: 当轮播图中内容的数量比`slidesPerView`参数所设置的还要少,会默认使用已有的内容充满整个轮播图。使用`disableFill`参数可以禁用这种行为。
4 | */
5 |
6 | import { Carousel, Divider } from '@banana-ui/react';
7 |
8 | export default function disableFill() {
9 | const style = `
10 | .demo-slide {
11 | height: 300px;
12 | display: flex;
13 | align-items: center;
14 | justify-content: center;
15 | background: linear-gradient(to bottom right, #5193e9, #0e61cd);
16 | color: #fff;
17 | font-size: 48px;
18 | }
19 | `;
20 |
21 | return (
22 | <>
23 |
24 |
25 | 1
26 | 2
27 |
28 |
29 |
30 |
31 |
32 | 1
33 | 2
34 |
35 | >
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/public/Select/basicUsage.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | 🍎 Apple
4 | 🍌 Banana
5 | 🍊 Orange
6 | 🍐 Pear
7 | 🍇 Grape
8 | 🚫 Disabled
9 |
10 |
11 | 🍎 Apple
12 | 🍌 Banana
13 | 🍊 Orange
14 | 🍐 Pear
15 | 🍇 Grape
16 | 🚫 Disabled
17 |
18 |
19 |
--------------------------------------------------------------------------------
/public/Select/Clearable.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | 🍎 Apple
4 | 🍌 Banana
5 | 🍊 Orange
6 | 🍐 Pear
7 | 🍇 Grape
8 | 🚫 Disabled
9 |
10 |
11 | 🍎 Apple
12 | 🍌 Banana
13 | 🍊 Orange
14 | 🍐 Pear
15 | 🍇 Grape
16 | 🚫 Disabled
17 |
18 |
19 |
--------------------------------------------------------------------------------
/rollup.config.dev.mjs:
--------------------------------------------------------------------------------
1 | import commonjs from '@rollup/plugin-commonjs';
2 | import nodeResolve from '@rollup/plugin-node-resolve';
3 | import typescript from '@rollup/plugin-typescript';
4 | import fs from 'fs';
5 | import runTSC from './scripts/rollup-plugin-copy-types.js'
6 |
7 | const componentNames = fs
8 | // 获取所有文件夹及文件
9 | .readdirSync('./src', { withFileTypes: true })
10 | // 筛选出所有文件夹
11 | .filter((p) => p.isDirectory())
12 | // 数据预处理
13 | .map((p) => ({
14 | path: `${p.name}/index`,
15 | name: p.name,
16 | }))
17 | // 带上package/src/index.ts
18 | .concat({ path: 'index', name: 'index' });
19 |
20 | export default [
21 | {
22 | input: componentNames.reduce((result, p) => {
23 | result[p.path] = `./src/${p.name}`;
24 | return result;
25 | }, {}),
26 | output: {
27 | dir: 'dist',
28 | format: 'es',
29 | entryFileNames: '[name].js',
30 | },
31 | plugins: [
32 | typescript(),
33 | nodeResolve({
34 | extensions: ['.ts', '.js'],
35 | }),
36 | commonjs(),
37 | runTSC(),
38 | ],
39 | },
40 | ];
41 |
--------------------------------------------------------------------------------
/docs/example/Modal/demos/customFooter.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: 自定义Footer
3 | * description: 可以通过插槽(`slot`)自定义Footer。
4 | */
5 |
6 | import { Button, Message, Modal } from '@banana-ui/react';
7 | import { useState } from 'react';
8 |
9 | export default function CustomFooter() {
10 | const [visible, setVisible] = useState(false);
11 |
12 | return (
13 | <>
14 | setVisible(true)}>
15 | 自定义Footer
16 |
17 | setVisible(false)}>
18 | 是否删除数据?
19 |
20 | setVisible(false)} style={{ marginRight: '10px' }}>
21 | 取消
22 |
23 | {
26 | setVisible(false);
27 | Message.success({
28 | content: '已删除数据',
29 | });
30 | }}
31 | >
32 | 删除
33 |
34 |
35 |
36 | >
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/docs/guide/SSR.md:
--------------------------------------------------------------------------------
1 | ---
2 | group: 指南
3 | order: 5
4 | ---
5 |
6 | # SSR(服务端渲染)支持
7 |
8 | 我们为 Banana UI 提供了服务端渲染(SSR)支持,这意味着你可以在服务端渲染的环境/框架中使用 Banana UI。
9 |
10 | ## 支持的环境
11 |
12 | - [node](https://nodejs.org/en)
13 | - [worker](https://developers.cloudflare.com/workers/)
14 |
15 | ## 我们做了什么?
16 |
17 | Banana UI 打包了支持服务端渲染的构建产物,并配置在了 package.json 的`exports`字段中,在 node 或 worker 环境中,你可以直接使用 Banana UI,并不需要额外的配置。
18 |
19 | ```json
20 | {
21 | "exports": {
22 | ".": {
23 | "worker": "./node/index.js",
24 | "node": "./node/index.js",
25 | "default": "./dist/index.js"
26 | }
27 | }
28 | }
29 | ```
30 |
31 | 如果它在服务端渲染报错了,请检查你运行环境中的[conditions](https://nodejs.org/api/packages.html#resolving-user-conditions)配置。
32 |
33 | 相关文档:https://nodejs.org/api/packages.html#conditional-exports
34 |
35 | ## 已验证过的 SSR 框架
36 |
37 | - ✅ [Remix](https://remix.run/)
38 | - ✅ [Hydrogen](https://hydrogen.shopify.dev/)
39 | - ❓ [Next.js](https://nextjs.org/)
40 |
41 | :::warning
42 | 经验证,Banana 在Next.js@12.3.4中正常运作,但是在 13/14 版本中会出现问题。
43 | 相关 issue:https://github.com/vercel/next.js/discussions/57428
44 | :::
45 |
--------------------------------------------------------------------------------
/public/Dropdown/arrow.html:
--------------------------------------------------------------------------------
1 |
2 | Hover me!
3 |
4 | Option 1
5 | Option 2
6 |
7 | Option A
8 | Option B
9 |
10 |
12 |
13 |
14 |
15 | topRight
16 |
17 | Option 1
18 | Option 2
19 |
20 | Option A
21 | Option B
22 |
23 |
25 |
--------------------------------------------------------------------------------
/packages/banana/src/overlay/index.styles.ts:
--------------------------------------------------------------------------------
1 | import { css, unsafeCSS } from 'lit';
2 | import componentStyles from '../../styles/components.styles';
3 | import { Variables as Var } from '../../styles/global-variables';
4 |
5 | export default [
6 | componentStyles,
7 | css`
8 | :host {
9 | position: fixed;
10 | display: flex;
11 | left: 0;
12 | top: 0;
13 | right: 0;
14 | bottom: 0;
15 | background: var(--banana-overlay-background, rgba(0, 0, 0, 0.5));
16 | visibility: hidden;
17 | opacity: 0;
18 | transition: all ${unsafeCSS(Var.TransitionNormal)};
19 | }
20 |
21 | :host([open]) {
22 | opacity: 1;
23 | visibility: visible;
24 | }
25 |
26 | .overlay__mask {
27 | position: fixed;
28 | left: 0;
29 | top: 0;
30 | right: 0;
31 | bottom: 0;
32 | background: transparent;
33 | z-index: -1;
34 | }
35 |
36 | .overlay__container {
37 | position: relative;
38 | margin: auto;
39 | display: flex;
40 | align-items: center;
41 | justify-content: center;
42 | }
43 | `,
44 | ];
45 |
--------------------------------------------------------------------------------
/packages/banana/src/radio-group/index.styles.ts:
--------------------------------------------------------------------------------
1 | import { css, unsafeCSS } from 'lit';
2 | import componentStyles from '../../styles/components.styles';
3 | import { Variables as Var } from '../../styles/global-variables';
4 |
5 | export default [
6 | componentStyles,
7 | css`
8 | :host {
9 | display: flex;
10 | }
11 |
12 | :host([disabled]) {
13 | opacity: 0.5;
14 | cursor: not-allowed;
15 | }
16 |
17 | :host([disabled]) ::slotted(b-radio),
18 | :host([readonly]) ::slotted(b-radio) {
19 | pointer-events: none;
20 | }
21 |
22 | .radio-group {
23 | position: relative;
24 | }
25 |
26 | .radio-group--vertical {
27 | display: flex;
28 | flex-direction: column;
29 | gap: var(--banana-radio-group-vertical-gap, ${unsafeCSS(Var.RadioGroupVerticalGap)});
30 | }
31 |
32 | .radio-group__validation-input {
33 | position: absolute;
34 | visibility: hidden;
35 | top: 50%;
36 | left: 50%;
37 | transform: translate(-50%, -50%);
38 | z-index: -1;
39 | width: 0;
40 | height: 0;
41 | }
42 | `,
43 | ];
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) hjw741@foxmail.com
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/banana/src/divider/index.styles.ts:
--------------------------------------------------------------------------------
1 | import { css, unsafeCSS } from 'lit';
2 | import componentStyles from '../../styles/components.styles';
3 | import { Spacings, Variables as Var } from '../../styles/global-variables';
4 |
5 | export default [
6 | componentStyles,
7 | css`
8 | :host(:not([vertical])) {
9 | display: block;
10 | border-top: solid var(--banana-divider-width, ${unsafeCSS(Var.PanelBorderWidth)}) var(--banana-divider-color, ${unsafeCSS(Var.PanelBorderColor)});
11 | margin: var(--banana-divider-spacing, ${unsafeCSS(Spacings.SpacingMedium)}) 0;
12 | }
13 |
14 | :host([vertical]) {
15 | display: inline-block;
16 | height: 100%;
17 | border-left: solid var(--banana-divider-width, ${unsafeCSS(Var.PanelBorderWidth)}) var(--banana-divider-color, ${unsafeCSS(Var.PanelBorderColor)});
18 | margin: 0 var(--banana-divider-spacing, ${unsafeCSS(Spacings.SpacingMedium)});
19 | }
20 |
21 | :host([dashed]:not([vertical])) {
22 | border-top-style: dashed;
23 | }
24 |
25 | :host([dashed][vertical]) {
26 | border-left-style: dashed;
27 | }
28 | `,
29 | ];
30 |
--------------------------------------------------------------------------------
/packages/banana/src/menu/index.ts:
--------------------------------------------------------------------------------
1 | import { CSSResultGroup, html, LitElement } from 'lit';
2 | import { customElement } from 'lit/decorators.js';
3 | import type BMenuItem from '../menu-item';
4 | import styles from './index.styles';
5 |
6 | @customElement('b-menu')
7 | export default class BMenu extends LitElement {
8 | connectedCallback() {
9 | super.connectedCallback();
10 |
11 | this.setAttribute('role', 'menu');
12 | }
13 |
14 | disconnectedCallback() {
15 | super.disconnectedCallback();
16 | }
17 |
18 | static styles: CSSResultGroup = styles;
19 |
20 | private handleClick(event: MouseEvent) {
21 | const target = event.target as HTMLElement;
22 | const item = target.closest('b-menu-item') as BMenuItem;
23 |
24 | if (!item || item.disabled) {
25 | return;
26 | }
27 |
28 | this.dispatchEvent(new CustomEvent('select', { detail: { item } }));
29 | }
30 |
31 | // todo...
32 | private handleKeydown(event: KeyboardEvent) {
33 | return event;
34 | }
35 |
36 | render() {
37 | return html` `;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/public/Select/Disabled.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | 🍎 Apple
4 | 🍌 Banana
5 | 🍊 Orange
6 | 🍐 Pear
7 | 🍇 Grape
8 | 🚫 Disabled
9 |
10 |
11 | 🍎 Apple
12 | 🍌 Banana
13 | 🍊 Orange
14 | 🍐 Pear
15 | 🍇 Grape
16 | 🚫 Disabled
17 |
18 |
19 |
--------------------------------------------------------------------------------
/docs/example/Carousel/demos/autoHeight.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: 自动调整高度
3 | * description: 使用`autoHeight`参数开启自动调整高度。
4 | */
5 |
6 | import { Carousel } from '@banana-ui/react';
7 |
8 | export default function autoHeight() {
9 | const style = `
10 | .autoheight .demo-slide {
11 | height: 300px;
12 | display: flex;
13 | align-items: center;
14 | justify-content: center;
15 | background: linear-gradient(to bottom right, #5193e9, #0e61cd);
16 | color: #fff;
17 | font-size: 48px;
18 | }
19 |
20 | .autoheight .demo-slide:nth-child(2n) {
21 | background: linear-gradient(to bottom right, #0e61cd, #5193e9);
22 | height: 500px;
23 | }
24 | `;
25 |
26 | return (
27 |
28 |
29 |
30 | 1
31 | 2
32 | 3
33 | 4
34 | 5
35 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/public/Modal/customFooter.html:
--------------------------------------------------------------------------------
1 | 自定义Footer
2 |
3 | 是否删除数据?
4 |
5 | 取消
6 | 删除
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/packages/banana-react/copyTypesToDist.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 |
6 | const srcDir = path.join(__dirname, './src');
7 | const typesDir = path.join(__dirname, './types');
8 | const distDir = path.join(__dirname, './dist');
9 | const nodeDir = path.join(__dirname, './node');
10 |
11 | const componentNames = fs
12 | .readdirSync(srcDir, { withFileTypes: true })
13 | .filter((item) => item.isDirectory())
14 | .map((item) => item.name);
15 |
16 | // components
17 | for (const component of componentNames) {
18 | const typePath = `${typesDir}/${component}/index.d.ts`;
19 | const distPath = `${distDir}/${component}/index.d.ts`;
20 | const nodePath = `${nodeDir}/${component}/index.d.ts`;
21 | try {
22 | fs.copyFileSync(typePath, distPath);
23 | fs.copyFileSync(typePath, nodePath);
24 | } catch (error) {
25 | console.log('copy failed', error);
26 | }
27 | }
28 |
29 | // index
30 | try {
31 | fs.copyFileSync(`${typesDir}/index.d.ts`, `${distDir}/index.d.ts`);
32 | fs.copyFileSync(`${typesDir}/index.d.ts`, `${nodeDir}/index.d.ts`);
33 | } catch (error) {
34 | console.log('copy failed', error);
35 | }
36 |
--------------------------------------------------------------------------------
/packages/banana/copyTypesToDist.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 |
6 | const srcDir = path.join(__dirname, './src');
7 | const typesDir = path.join(__dirname, './types');
8 | const distDir = path.join(__dirname, './dist');
9 | const nodeDir = path.join(__dirname, './node');
10 |
11 | const componentNames = fs
12 | .readdirSync(srcDir, { withFileTypes: true })
13 | .filter((item) => item.isDirectory())
14 | .map((item) => item.name);
15 |
16 | // components
17 | for (const component of componentNames) {
18 | const typePath = `${typesDir}/src/${component}/index.d.ts`;
19 | const distPath = `${distDir}/${component}/index.d.ts`;
20 | const nodePath = `${nodeDir}/${component}/index.d.ts`;
21 | try {
22 | fs.copyFileSync(typePath, distPath);
23 | fs.copyFileSync(typePath, nodePath);
24 | } catch (error) {
25 | console.log('copy failed', error);
26 | }
27 | }
28 |
29 | // index
30 | try {
31 | fs.copyFileSync(`${typesDir}/src/index.d.ts`, `${distDir}/index.d.ts`);
32 | fs.copyFileSync(`${typesDir}/src/index.d.ts`, `${nodeDir}/index.d.ts`);
33 | } catch (error) {
34 | console.log('copy failed', error);
35 | }
36 |
--------------------------------------------------------------------------------
/docs/example/Modal/demos/buttonStatus.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: 按钮状态
3 | * description: 可以设置默认的Footer按钮为Loading或者Disabled状态。
4 | */
5 |
6 | import { Button, Message, Modal } from '@banana-ui/react';
7 | import { useState } from 'react';
8 |
9 | export default function ButtonStatus() {
10 | const [visible, setVisible] = useState(false);
11 | const [loading, setLoading] = useState(false);
12 |
13 | return (
14 | <>
15 | setVisible(true)}>
16 | 自定义按钮状态
17 |
18 | setVisible(false)}
21 | onOk={() => {
22 | if (loading) return;
23 | setLoading(true);
24 | setTimeout(() => {
25 | setLoading(false);
26 | setVisible(false);
27 | Message.success({
28 | content: 'OK~',
29 | });
30 | }, 3000);
31 | }}
32 | title="I am a Modal~"
33 | okButtonLoading={loading}
34 | cancelButtonDisabled={loading}
35 | >
36 | Some contents...
37 | Some contents...
38 | Some contents...
39 |
40 | >
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/packages/banana/src/progress/index.styles.ts:
--------------------------------------------------------------------------------
1 | import { css, unsafeCSS } from 'lit';
2 | import componentStyles from '../../styles/components.styles';
3 | import { Colors, Variables as Var } from '../../styles/global-variables';
4 |
5 | export default [
6 | componentStyles,
7 | css`
8 | :host {
9 | margin: 10px 0;
10 | display: block;
11 | width: 100%;
12 | }
13 |
14 | .progress {
15 | position: relative;
16 | height: var(--banana-progress-height, 10px);
17 | background: var(--banana-progress-background-color, rgb(${unsafeCSS(Colors.Gray1)}));
18 | border-radius: var(--banana-progress-border-radius, calc(var(--banana-progress-height, 10px) / 2));
19 | overflow: hidden;
20 | }
21 |
22 | .progress-bar {
23 | position: absolute;
24 | top: 0;
25 | left: 0;
26 | width: var(--banana-progress-percent, 0%);
27 | height: 100%;
28 | border-radius: var(--banana-progress-border-radius, calc(var(--banana-progress-height, 10px) / 2));
29 | background: var(--banana-progress-color, ${unsafeCSS(Var.ColorPrimary)});
30 | transition: width var(--banana-progress-transition-duration, ${unsafeCSS(Var.TransitionSlow)}) ease;
31 | }
32 | `,
33 | ];
34 |
--------------------------------------------------------------------------------
/packages/banana/web-test-runner.config.js:
--------------------------------------------------------------------------------
1 | import { esbuildPlugin } from '@web/dev-server-esbuild';
2 | import { playwrightLauncher } from '@web/test-runner-playwright';
3 | import process from 'node:process';
4 |
5 | const browsers = process.env.CI
6 | ? [playwrightLauncher({ product: 'chromium' })]
7 | : [playwrightLauncher({ product: 'chromium' }), playwrightLauncher({ product: 'firefox' })];
8 |
9 | export default {
10 | rootDir: '.',
11 | files: 'src/**/*.test.ts',
12 | nodeResolve: true,
13 | concurrentBrowsers: 3,
14 | plugins: [
15 | esbuildPlugin({
16 | ts: true,
17 | target: 'auto',
18 | }),
19 | ],
20 | coverage: true,
21 | coverageConfig: {
22 | include: ['dist/**/*'],
23 | report: true,
24 | reportDir: 'coverage',
25 | reporters: ['cobertura', 'lcov'],
26 | },
27 | testFramework: {
28 | config: {
29 | timeout: 3000,
30 | retries: 1,
31 | },
32 | },
33 | browsers,
34 | testRunnerHtml: (testFramework) => `
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | `,
43 | };
44 |
--------------------------------------------------------------------------------
/docs/example/Divider/demos/customSpacing.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: 自定义间距
3 | */
4 |
5 | import { Divider } from '@banana-ui/react';
6 | import React from 'react';
7 |
8 | export default function customSpacing() {
9 | const style = `
10 | .surrounding {
11 | text-align: center;
12 | color: gray;
13 | font-size: 12px;
14 | }
15 | `;
16 |
17 | return (
18 | <>
19 |
20 |
21 | 你可以使用spacing参数自定义分割线的间距:
22 |
23 | 上面的元素
24 |
25 | 下面的元素
26 |
27 |
28 | 上面的元素
29 |
30 | 下面的元素
31 |
32 |
33 | 上面的元素
34 |
35 | 下面的元素
36 |
37 |
38 | 也可以通过css变量的方式自定义分割线的粗细:
39 | 上面的元素
40 |
41 | 下面的元素
42 | >
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | concurrency: ${{ github.workflow }}-${{ github.ref }}
9 |
10 | jobs:
11 | release:
12 | name: Release
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout Repo
16 | uses: actions/checkout@v3
17 |
18 | - name: Setup Node.js 20
19 | uses: actions/setup-node@v3
20 | with:
21 | node-version: 20
22 |
23 | # 安装 pnpm
24 | - uses: pnpm/action-setup@v2
25 | with:
26 | version: 9.3.0
27 |
28 | # 安装依赖
29 | - name: Install dependencies
30 | run: pnpm install
31 |
32 | # 构建
33 | - name: Build
34 | run: pnpm build
35 |
36 | - name: Before Test
37 | run: cd packages/banana && pnpm beforeTest
38 |
39 | # 测试
40 | - name: Test
41 | run: pnpm test
42 |
43 | # 版本变更,创建 PR 或发版
44 | - name: Create Release Pull Request or Publish
45 | uses: changesets/action@v1
46 | with:
47 | commit: 'chore(release): release by changesets'
48 | publish: pnpm changeset publish
49 | env:
50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
51 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
52 |
--------------------------------------------------------------------------------
/public/Collapse/accordion.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
4 | aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
5 |
6 |
7 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
8 | aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
9 |
10 |
11 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
12 | aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/public/Checkbox/indeterminate.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Check All
4 |
5 |
6 |
7 |
8 |
9 | Apple🍎
10 |
11 |
12 | Banana🍌
13 |
14 |
15 | Cherry🍒
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/docs/example/Marquee/demos/customStyle.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: 自定义样式
3 | * description: 你可以使用组件暴露出来的`CSS变量`和`part`属性来实现更细粒度的自定义样式。
4 | */
5 |
6 | import { Marquee } from '@banana-ui/react';
7 | import React from 'react';
8 |
9 | export default function CustomStyle() {
10 | const content =
11 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.';
12 |
13 | const style = `
14 | .foo::part(base) {
15 | background-color: rgba(255, 0, 0, 0.8 );
16 | padding: 10px 0;
17 | color: #fff;
18 | font-family: "Gill Sans", sans-serif;
19 | }
20 | `;
21 |
22 | return (
23 | <>
24 |
25 |
26 |
37 |
38 |
39 | >
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/packages/banana/src/select-option/index.styles.ts:
--------------------------------------------------------------------------------
1 | import { css, unsafeCSS } from 'lit';
2 | import componentStyles from '../../styles/components.styles';
3 | import { Variables as Var } from '../../styles/global-variables';
4 |
5 | export default [
6 | componentStyles,
7 | css`
8 | :host {
9 | cursor: pointer;
10 | display: block;
11 | padding: var(--banana-select-option-padding, ${unsafeCSS(Var.SelectOptionPadding)});
12 | transition: background ${unsafeCSS(Var.TransitionNormal)} ease;
13 | user-select: none;
14 | }
15 |
16 | :host([active]) {
17 | background-color: var(
18 | --banana-select-option-hover-background-color,
19 | ${unsafeCSS(Var.SelectOptionHoverBackgroundColor)}
20 | );
21 | }
22 |
23 | :host([selected]) {
24 | background-color: var(
25 | --banana-select-option-selected-background-color,
26 | ${unsafeCSS(Var.SelectOptionSelectedBackgroundColor)}
27 | );
28 | }
29 |
30 | :host([disabled]) {
31 | opacity: 0.5;
32 | cursor: not-allowed;
33 | }
34 |
35 | /* data-filter-hidden */
36 | :host([data-filter-hidden]) {
37 | display: none;
38 | }
39 |
40 | .option {
41 | overflow: hidden;
42 | text-overflow: ellipsis;
43 | white-space: nowrap;
44 | }
45 | `,
46 | ];
47 |
--------------------------------------------------------------------------------
/packages/banana/src/tab-item/index.styles.ts:
--------------------------------------------------------------------------------
1 | import { css, unsafeCSS } from 'lit';
2 | import componentStyles from '../../styles/components.styles';
3 | import { Colors, Variables as Var } from '../../styles/global-variables';
4 |
5 | export default [
6 | componentStyles,
7 | css`
8 | :host {
9 | line-height: ${unsafeCSS(Var.LineHeightDense)};
10 | cursor: pointer;
11 | --banana-tab-nav-color: rgba(${unsafeCSS(Colors.Gray9)});
12 | --banana-tab-nav-color-hover: rgba(${unsafeCSS(Colors.Blue3)});
13 | --banana-tab-nav-color-active: rgba(${unsafeCSS(Colors.Blue3)});
14 | --banana-tab-item-vertical-padding: 16px;
15 | --banana-tab-item-horizontal-padding: 20px;
16 | }
17 | .tab-item {
18 | display: inline-flex;
19 | align-items: center;
20 | padding: var(--banana-tab-item-vertical-padding) var(--banana-tab-item-horizontal-padding);
21 | color: var(--banana-tab-nav-color);
22 | transition: all ease-in-out ${unsafeCSS(Var.TransitionNormal)};
23 | }
24 | .tab-item:hover:not(.tab-item--disabled) {
25 | color: var(--banana-tab-nav-color-hover);
26 | }
27 | .tab-item--active {
28 | color: var(--banana-tab-nav-color-active);
29 | }
30 | .tab-item--disabled {
31 | opacity: 0.5;
32 | cursor: not-allowed;
33 | }
34 | `,
35 | ];
36 |
--------------------------------------------------------------------------------
/docs/example/Select/demos/basicUsage.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: 基本使用
3 | */
4 |
5 | import { Select, SelectOption } from '@banana-ui/react';
6 |
7 | export default function BasicUsage() {
8 | return (
9 |
10 |
11 | 🍎 Apple
12 | 🍌 Banana
13 | 🍊 Orange
14 | 🍐 Pear
15 | 🍇 Grape
16 |
17 | 🚫 Disabled
18 |
19 |
20 |
21 |
22 | 🍎 Apple
23 | 🍌 Banana
24 | 🍊 Orange
25 | 🍐 Pear
26 | 🍇 Grape
27 |
28 | 🚫 Disabled
29 |
30 |
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/docs/example/Select/demos/filter.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: 开启搜索功能
3 | */
4 |
5 | import { Select, SelectOption } from '@banana-ui/react';
6 |
7 | export default function Filter() {
8 | return (
9 |
10 |
11 | 🍎 Apple
12 | 🍌 Banana
13 | 🍊 Orange
14 | 🍐 Pear
15 | 🍇 Grape
16 |
17 | 🚫 Disabled
18 |
19 |
20 | {/*
21 | 🍎 Apple
22 | 🍌 Banana
23 | 🍊 Orange
24 | 🍐 Pear
25 | 🍇 Grape
26 |
27 | 🚫 Disabled
28 |
29 | */}
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/packages/banana/src/menu-item/index.styles.ts:
--------------------------------------------------------------------------------
1 | import { css, unsafeCSS } from 'lit';
2 | import componentStyles from '../../styles/components.styles';
3 | import { Colors, Fonts, Spacings, Variables as Var } from '../../styles/global-variables';
4 |
5 | export default [
6 | componentStyles,
7 | css`
8 | :host {
9 | display: block;
10 | }
11 |
12 | :host(:hover:not([aria-disabled='true'])) .menu-item {
13 | background-color: var(--banana-menu-item-hover-background, rgba(${unsafeCSS(Colors.Gray1)}, 0.75));
14 | }
15 |
16 | .menu-item {
17 | position: relative;
18 | display: flex;
19 | align-items: stretch;
20 | font-size: var(--banana-menu-item-font-size, ${unsafeCSS(Fonts.FontSizeMedium)});
21 | line-height: ${unsafeCSS(Var.LineHeightNormal)};
22 | padding: var(--banana-menu-item-padding, ${unsafeCSS(Spacings.Spacing2XS)} ${unsafeCSS(Spacings.SpacingXL)});
23 | color: var(--banana-menu-item-color, rgba(${unsafeCSS(Colors.Gray9)}, 1));
24 | user-select: none;
25 | white-space: nowrap;
26 | cursor: pointer;
27 | }
28 |
29 | .menu-item.menu-item--disabled {
30 | outline: none;
31 | opacity: 0.5;
32 | cursor: not-allowed;
33 | }
34 |
35 | .menu-item .menu-item__label {
36 | flex: 1 1 auto;
37 | display: inline-block;
38 | }
39 | `,
40 | ];
41 |
--------------------------------------------------------------------------------
/public/Rating/character.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
26 |
--------------------------------------------------------------------------------
/docs/example/Divider/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | group: 组件
3 | demo:
4 | cols: 2
5 | ---
6 |
7 | # Divider 分割线
8 |
9 | ```
10 | | Divider
11 | ```
12 |
13 | ## 介绍
14 |
15 | 区隔内容的分割线。
16 |
17 | ## 代码演示
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | ## 属性 - Attributes & Properties
27 |
28 | | 属性 | 说明 | 类型 | 默认值 |
29 | | -------- | ------------ | -------------------- | -------------------- |
30 | | width | 分割线的粗细 | `number` \| `string` | '1px' |
31 | | color | 分割线的颜色 | `string` | 'rgb(229, 231, 233)' |
32 | | spacing | 分割线的间距 | `number` \| `string` | '16px' |
33 | | vertical | 垂直分割线 | `boolean` | false |
34 | | dashed | 虚线分割线 | `boolean` | false |
35 |
36 | ## 样式变量
37 |
38 | | 变量 | 说明 | 默认值 |
39 | | ------------------------ | ------------ | ------------------ |
40 | | --banana-divider-width | 分割线的粗细 | 1px |
41 | | --banana-divider-color | 分割线的颜色 | rgb(229, 231, 233) |
42 | | --banana-divider-spacing | 分割线的间距 | 16px |
43 |
--------------------------------------------------------------------------------
/docs/example/Select/demos/Multiple.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: 多选
3 | * description: 设置 `multiple` 属性可以开启多选模式。
4 | */
5 |
6 | import { Select, SelectOption } from '@banana-ui/react';
7 | import React from 'react';
8 |
9 | export default function Multiple() {
10 | const handleChange = (e: any) => {
11 | // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
12 | console.log(e.detail.value);
13 | };
14 | return (
15 |
16 |
17 |
18 | 🍎 AppleAppleAppleAppleAppleAppleAppleAppleAppleAppleAppleAppleAppleAppleAppleApple
19 |
20 | 🍌 Banana
21 | 🍊 Orange
22 | 🍐 Pear
23 | 🍇 Grape
24 | 🍓 Strawberry
25 | 🍍 Pineapple
26 | 🍒 Cherry
27 |
28 | 🚫 Disabled
29 |
30 |
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/docs/example/Select/demos/Clearable.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: 可清空的选择器
3 | * description: 设置 `clearable` 属性可以让选择器可清空。
4 | */
5 |
6 | import { Select, SelectOption } from '@banana-ui/react';
7 | import React from 'react';
8 |
9 | export default function Clearable() {
10 | return (
11 |
12 |
13 | 🍎 Apple
14 | 🍌 Banana
15 | 🍊 Orange
16 | 🍐 Pear
17 | 🍇 Grape
18 |
19 | 🚫 Disabled
20 |
21 |
22 |
23 |
24 | 🍎 Apple
25 | 🍌 Banana
26 | 🍊 Orange
27 | 🍐 Pear
28 | 🍇 Grape
29 |
30 | 🚫 Disabled
31 |
32 |
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/packages/banana/src/stepper/utils.ts:
--------------------------------------------------------------------------------
1 | export const isInteger = (value: string) => {
2 | return /^[0-9]+$/.test(value);
3 | };
4 |
5 | export class MathUtils {
6 | static add(a: number, b: number) {
7 | if (isInteger(a.toString()) && isInteger(b.toString())) {
8 | return a + b;
9 | }
10 |
11 | try {
12 | const aTransformIntMultiple = String(a).split('.')?.[1]?.length ?? 0;
13 | const bTransformIntMultiple = String(b).split('.')?.[1]?.length ?? 0;
14 |
15 | const maxMultiple = Math.pow(10, Math.max(aTransformIntMultiple, bTransformIntMultiple));
16 |
17 | return (a * maxMultiple + b * maxMultiple) / maxMultiple;
18 | } catch (error) {
19 | return a + b;
20 | }
21 | }
22 |
23 | static minus(a: number, b: number) {
24 | if (isInteger(a.toString()) && isInteger(b.toString())) {
25 | return a - b;
26 | }
27 | try {
28 | const aTransformIntMultiple = String(a).split('.')?.[1]?.length ?? 0;
29 | const bTransformIntMultiple = String(b).split('.')?.[1]?.length ?? 0;
30 |
31 | const maxMultiple = Math.pow(10, Math.max(aTransformIntMultiple, bTransformIntMultiple));
32 |
33 | const resPrecision =
34 | aTransformIntMultiple >= bTransformIntMultiple ? aTransformIntMultiple : bTransformIntMultiple;
35 |
36 | return Number(((a * maxMultiple - b * maxMultiple) / maxMultiple).toFixed(resPrecision));
37 | } catch (error) {
38 | return a - b;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/packages/banana/src/popup/index.styles.ts:
--------------------------------------------------------------------------------
1 | import { css, unsafeCSS } from 'lit';
2 | import componentStyles from '../../styles/components.styles';
3 | import { Variables as Var } from '../../styles/global-variables';
4 |
5 | export default [
6 | componentStyles,
7 | css`
8 | .inside-overlay::part(container) {
9 | position: static;
10 | margin: 0;
11 | display: block;
12 | }
13 |
14 | .popup__container {
15 | position: absolute;
16 | background-color: #fff;
17 | transition: transform ${unsafeCSS(Var.TransitionNormal)};
18 | }
19 |
20 | .popup__container.popup--open {
21 | transform: translate3d(0, 0, 0) !important;
22 | }
23 |
24 | .popup__container.popup--top {
25 | top: 0;
26 | right: 0;
27 | left: 0;
28 | transform: translate3d(0, -100%, 0);
29 | overflow-y: scroll;
30 | }
31 |
32 | .popup__container.popup--right {
33 | top: 0;
34 | right: 0;
35 | bottom: 0;
36 | transform: translate3d(100%, 0, 0);
37 | overflow-x: scroll;
38 | }
39 |
40 | .popup__container.popup--bottom {
41 | right: 0;
42 | bottom: 0;
43 | left: 0;
44 | transform: translate3d(0, 100%, 0);
45 | overflow-y: scroll;
46 | }
47 |
48 | .popup__container.popup--left {
49 | top: 0;
50 | bottom: 0;
51 | left: 0;
52 | transform: translate3d(-100%, 0, 0);
53 | overflow-x: scroll;
54 | }
55 | `,
56 | ];
57 |
--------------------------------------------------------------------------------
/docs/example/Rating/demos/formTest.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: 表单测试
3 | * debug: true
4 | */
5 |
6 | import { Button, Rating } from '@banana-ui/react';
7 | import React from 'react';
8 |
9 | export default function FormTest() {
10 | const [controlledValue, setControlledValue] = React.useState(3);
11 |
12 | return (
13 |
14 |
42 |
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/.github/workflows/gh-pages.yml:
--------------------------------------------------------------------------------
1 | name: github pages
2 |
3 | on:
4 | push:
5 | branches:
6 | - master # default branch
7 |
8 | jobs:
9 | deploy:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v3
14 | with:
15 | # 如果配置 themeConfig.lastUpdated 为 false,则不需要添加该参数以加快检出速度
16 | fetch-depth: 0
17 | # 安装 pnpm
18 | - uses: pnpm/action-setup@v2
19 | with:
20 | version: 9.3.0
21 | # 安装依赖
22 | - name: Install dependencies
23 | run: pnpm install
24 | # 组件库打包
25 | - name: Build packages
26 | run: pnpm build
27 |
28 | - name: Before Test
29 | run: cd packages/banana && pnpm beforeTest
30 |
31 | # 测试
32 | - name: Test
33 | run: pnpm test
34 |
35 | # 生成覆盖率报告
36 | - name: Upload coverage reports to Codecov
37 | uses: codecov/codecov-action@v4.0.1
38 | with:
39 | file: ./packages/banana/coverage/cobertura-coverage.xml
40 | token: ${{ secrets.CODECOV_TOKEN }}
41 | slug: FriedRiceNoodles/banana-ui
42 |
43 | # 构建文档
44 | - name: Build with dumi
45 | run: pnpm docs:build
46 | # 部署文档
47 | - name: Deploy
48 | uses: peaceiris/actions-gh-pages@v3
49 | with:
50 | deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY_0219 }}
51 | # 文档目录,如果是 react 模板需要修改为 docs-dist
52 | publish_dir: ./docs-dist
53 | cname: banana-ui.com
54 |
--------------------------------------------------------------------------------
/packages/banana/src/index.ts:
--------------------------------------------------------------------------------
1 | import BButton from './button';
2 | import BCarousel from './carousel';
3 | import BCheckbox from './checkbox';
4 | import BCollapse from './collapse';
5 | import BCountdown from './countdown';
6 | import BDivider from './divider';
7 | import BDropdown from './dropdown';
8 | import BInput from './input';
9 | import BMarquee from './marquee';
10 | import BMenu from './menu';
11 | import BMenuItem from './menu-item';
12 | import BMessage from './message';
13 | import BModal from './modal';
14 | import BOverlay from './overlay';
15 | import BPopup from './popup';
16 | import BProgress from './progress';
17 | import BRadio from './radio';
18 | import BRadioGroup from './radio-group';
19 | import BRating from './rating';
20 | import BSelect from './select';
21 | import BSelectOption from './select-option';
22 | import BStepper from './stepper';
23 | import BTabItem from './tab-item';
24 | import BTabPanel from './tab-panel';
25 | import BTabs from './tabs';
26 | import BTextarea from './textarea';
27 | import BTooltip from './tooltip';
28 |
29 | export {
30 | BButton,
31 | BCarousel,
32 | BCheckbox,
33 | BCollapse,
34 | BCountdown,
35 | BDivider,
36 | BDropdown,
37 | BInput,
38 | BMarquee,
39 | BMenu,
40 | BMenuItem,
41 | BMessage,
42 | BModal,
43 | BOverlay,
44 | BPopup,
45 | BProgress,
46 | BRadio,
47 | BRadioGroup,
48 | BRating,
49 | BSelect,
50 | BSelectOption,
51 | BStepper,
52 | BTabItem,
53 | BTabPanel,
54 | BTabs,
55 | BTextarea,
56 | BTooltip,
57 | };
58 |
--------------------------------------------------------------------------------
/packages/banana/src/tooltip/index.styles.ts:
--------------------------------------------------------------------------------
1 | import { css, unsafeCSS } from 'lit';
2 | import componentStyles from '../../styles/components.styles';
3 | import { Variables as Var } from '../../styles/global-variables';
4 |
5 | export default [
6 | componentStyles,
7 | css`
8 | :host {
9 | display: contents;
10 | }
11 |
12 | .tooltip {
13 | display: contents;
14 | }
15 |
16 | .tooltip__content {
17 | width: max-content;
18 | position: absolute;
19 | top: 0;
20 | left: 0;
21 | opacity: 0;
22 | z-index: 100;
23 | }
24 |
25 | .tooltip__empty-content {
26 | display: none;
27 | }
28 |
29 | .tooltip__content-body {
30 | padding: var(--banana-tooltip-padding, ${unsafeCSS(Var.TooltipPadding)});
31 | background-color: var(--banana-tooltip-background-color, ${unsafeCSS(Var.TooltipBackgroundColor)});
32 | color: var(--banana-tooltip-color, #fff);
33 | font-size: var(--banana-tooltip-font-size, ${unsafeCSS(Var.TooltipFontSize)});
34 | border-radius: var(--banana-tooltip-border-radius, ${unsafeCSS(Var.TooltipBorderRadius)});
35 | width: max-content;
36 | max-width: var(--banana-tooltip-max-width, ${unsafeCSS(Var.TooltipMaxWidth)});
37 | }
38 |
39 | .tooltip__default-arrow {
40 | position: absolute;
41 | width: 0;
42 | height: 0;
43 | border: 4px solid var(--banana-tooltip-background-color, ${unsafeCSS(Var.TooltipBackgroundColor)});
44 | rotate: 45deg;
45 | }
46 | `,
47 | ];
48 |
--------------------------------------------------------------------------------
/docs/example/Carousel/demos/useRef.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: 使用useRef手动改变currentIndex
3 | * debug: true
4 | */
5 |
6 | /* eslint-disable @typescript-eslint/no-unsafe-return */
7 | /* eslint-disable @typescript-eslint/no-unsafe-call */
8 |
9 | import { Button, Carousel } from '@banana-ui/react';
10 | import { useRef } from 'react';
11 |
12 | export default function CarouselDemo0() {
13 | const carousel = useRef(null);
14 |
15 | const style = `
16 | .demo-slide {
17 | height: 300px;
18 | display: flex;
19 | align-items: center;
20 | justify-content: center;
21 | background: linear-gradient(to bottom right, #5193e9, #0e61cd);
22 | color: #fff;
23 | font-size: 48px;
24 | }
25 | `;
26 |
27 | return (
28 | <>
29 |
30 |
31 | 1
32 | 2
33 | 3
34 | 4
35 | 5
36 | 6
37 | 7
38 | 8
39 | 9
40 | 10
41 |
42 |
43 | carousel.current?.goto(0)}>go 1
44 | carousel.current?.goto(9)}>go 10
45 | >
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/public/Collapse/customIcon.html:
--------------------------------------------------------------------------------
1 |
2 |
9 |
16 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
17 | aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
18 |
--------------------------------------------------------------------------------
/packages/banana-react/src/index.ts:
--------------------------------------------------------------------------------
1 | import { Button } from './button';
2 | import { Carousel } from './carousel';
3 | import { Checkbox } from './checkbox';
4 | import { Collapse } from './collapse';
5 | import { Countdown } from './countdown';
6 | import { Divider } from './divider';
7 | import { Dropdown } from './dropdown';
8 | import { Input } from './input';
9 | import { Marquee } from './marquee';
10 | import { Menu } from './menu';
11 | import { MenuItem } from './menu-item';
12 | import { Message } from './message';
13 | import { Modal } from './modal';
14 | import { Overlay } from './overlay';
15 | import { Popup } from './popup';
16 | import { Progress } from './progress';
17 | import { Radio } from './radio';
18 | import { RadioGroup } from './radio-group';
19 | import { Rating } from './rating';
20 | import { Select } from './select';
21 | import { SelectOption } from './select-option';
22 | import { Stepper } from './stepper';
23 | import { TabItem } from './tab-item';
24 | import { TabPanel } from './tab-panel';
25 | import { Tabs } from './tabs';
26 | import { Textarea } from './textarea';
27 | import { Tooltip } from './tooltip';
28 |
29 | export {
30 | Button,
31 | Carousel,
32 | Checkbox,
33 | Collapse,
34 | Countdown,
35 | Divider,
36 | Dropdown,
37 | Input,
38 | Marquee,
39 | Menu,
40 | MenuItem,
41 | Message,
42 | Modal,
43 | Overlay,
44 | Popup,
45 | Progress,
46 | Radio,
47 | RadioGroup,
48 | Rating,
49 | Select,
50 | SelectOption,
51 | Stepper,
52 | TabItem,
53 | TabPanel,
54 | Tabs,
55 | Textarea,
56 | Tooltip,
57 | };
58 |
--------------------------------------------------------------------------------
/packages/banana/src/radio/index.ts:
--------------------------------------------------------------------------------
1 | import { CSSResultGroup, html, LitElement } from 'lit';
2 | import { customElement, property } from 'lit/decorators.js';
3 | import { classMap } from 'lit/directives/class-map.js';
4 | import styles from './index.styles';
5 |
6 | @customElement('b-radio')
7 | export default class BRadio extends LitElement {
8 | static styles?: CSSResultGroup = styles;
9 |
10 | @property({ reflect: true })
11 | value = '';
12 |
13 | @property({ type: Boolean, reflect: true })
14 | checked = false;
15 |
16 | @property({ type: Boolean, reflect: true })
17 | disabled = false;
18 |
19 | @property({ reflect: true })
20 | size: 'small' | 'medium' | 'large' = 'medium';
21 |
22 | connectedCallback() {
23 | super.connectedCallback();
24 | }
25 |
26 | disconnectedCallback() {
27 | super.disconnectedCallback();
28 | }
29 |
30 | render() {
31 | return html`
32 |
53 | `;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/public/Select/Size.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | 🍎 Apple
4 | 🍌 Banana
5 | 🍊 Orange
6 | 🍐 Pear
7 | 🍇 Grape
8 | 🚫 Disabled
9 |
10 |
11 | 🍎 Apple
12 | 🍌 Banana
13 | 🍊 Orange
14 | 🍐 Pear
15 | 🍇 Grape
16 | 🚫 Disabled
17 |
18 |
19 | 🍎 Apple
20 | 🍌 Banana
21 | 🍊 Orange
22 | 🍐 Pear
23 | 🍇 Grape
24 | 🚫 Disabled
25 |
26 |
27 |
--------------------------------------------------------------------------------
/docs/example/Select/demos/Disabled.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: 禁用的选择器
3 | * description: 设置 `disabled` 属性可以让选择器禁用。
4 | */
5 |
6 | import { Select, SelectOption } from '@banana-ui/react';
7 | import React from 'react';
8 |
9 | export default function Disabled() {
10 | return (
11 |
12 |
13 | 🍎 Apple
14 | 🍌 Banana
15 | 🍊 Orange
16 | 🍐 Pear
17 | 🍇 Grape
18 |
19 | 🚫 Disabled
20 |
21 |
22 |
23 |
31 | 🍎 Apple
32 | 🍌 Banana
33 | 🍊 Orange
34 | 🍐 Pear
35 | 🍇 Grape
36 |
37 | 🚫 Disabled
38 |
39 |
40 |
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/packages/banana/src/collapse/index.styles.ts:
--------------------------------------------------------------------------------
1 | import { css, unsafeCSS } from 'lit';
2 | import componentStyles from '../../styles/components.styles';
3 | import { Colors, Variables as Var } from '../../styles/global-variables';
4 |
5 | export default [
6 | componentStyles,
7 | css`
8 | .collapse {
9 | border: 1px solid rgb(${unsafeCSS(Colors.Gray2)});
10 | border-radius: ${unsafeCSS(Var.BorderRadiusMedium)};
11 | transition: all ${unsafeCSS(Var.TransitionFast)};
12 | overflow: hidden;
13 | overflow-anchor: none;
14 | }
15 |
16 | .collapse__header {
17 | cursor: pointer;
18 | display: flex;
19 | justify-content: space-between;
20 | align-items: center;
21 | padding: var(--banana-collapse-title-padding, 16px);
22 | }
23 |
24 | .collapse__body {
25 | height: 0;
26 | overflow-y: hidden;
27 | }
28 |
29 | .collapse__content {
30 | display: block;
31 | padding: var(--banana-collapse-content-padding, 16px);
32 | }
33 |
34 | .collapse--open .collapse__body {
35 | height: auto;
36 | }
37 |
38 | .collapse__icon {
39 | flex-shrink: 0;
40 | }
41 |
42 | .default-expand-icon {
43 | transition: all ${unsafeCSS(Var.TransitionFast)};
44 | }
45 |
46 | .collapse--open .default-expand-icon {
47 | transform: rotate(90deg);
48 | }
49 |
50 | /* Disabled */
51 | .collapse--disabled {
52 | cursor: not-allowed;
53 | opacity: 0.5;
54 | }
55 |
56 | .collapse--disabled .collapse__body {
57 | height: 0 !important;
58 | }
59 | `,
60 | ];
61 |
--------------------------------------------------------------------------------