├── .dumirc.ts
├── .editorconfig
├── .eslintrc.js
├── .fatherrc.ts
├── .github
├── dependabot.yml
└── workflows
│ └── test.yml
├── .gitignore
├── .husky
└── pre-commit
├── .npmignore
├── .prettierignore
├── .prettierrc
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── assets
└── index.less
├── docs
├── changelog.md
├── demo
│ ├── rows.md
│ ├── simple.md
│ └── theme.md
├── examples
│ ├── rows.tsx
│ ├── simple.tsx
│ └── theme.tsx
└── index.md
├── index.js
├── jest.config.ts
├── package.json
├── pnpm-lock.yaml
├── script
└── update-content.js
├── src
├── column.tsx
└── index.tsx
├── tests
├── __snapshots__
│ └── index.test.tsx.snap
├── index.test.tsx
└── setupFilesAfterEnv.ts
├── tsconfig.json
└── type.d.ts
/.dumirc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'dumi';
2 |
3 | const basePath = process.env.GH_PAGES ? '/footer/' : '/';
4 | const publicPath = process.env.GH_PAGES ? '/footer/' : '/';
5 |
6 | export default defineConfig({
7 | favicons: ['https://avatars0.githubusercontent.com/u/9441414?s=200&v=4'],
8 | themeConfig: {
9 | name: 'Footer',
10 | logo: 'https://avatars0.githubusercontent.com/u/9441414?s=200&v=4',
11 | },
12 | outputPath: '.doc',
13 | exportStatic: {},
14 | base: basePath,
15 | publicPath,
16 | });
17 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [Makefile]
16 | indent_style = tab
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | const base = require('@umijs/fabric/dist/eslint');
2 |
3 | module.exports = {
4 | ...base,
5 | rules: {
6 | ...base.rules,
7 | 'no-template-curly-in-string': 0,
8 | 'prefer-promise-reject-errors': 0,
9 | 'react/no-array-index-key': 0,
10 | 'react/sort-comp': 0,
11 | '@typescript-eslint/no-explicit-any': 0,
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/.fatherrc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'father';
2 |
3 | export default defineConfig({
4 | plugins: ['@rc-component/father-plugin'],
5 | });
6 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: "21:00"
8 | open-pull-requests-limit: 10
9 | ignore:
10 | - dependency-name: husky
11 | versions:
12 | - 5.0.9
13 | - 5.1.1
14 | - 5.1.2
15 | - 5.1.3
16 | - 5.2.0
17 | - dependency-name: "@types/react-dom"
18 | versions:
19 | - 17.0.0
20 | - 17.0.1
21 | - 17.0.2
22 | - dependency-name: "@types/react"
23 | versions:
24 | - 17.0.0
25 | - 17.0.1
26 | - 17.0.2
27 | - 17.0.3
28 | - dependency-name: react-dom
29 | versions:
30 | - 17.0.1
31 | - dependency-name: less
32 | versions:
33 | - 4.1.0
34 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: ✅ test
2 | on: [push, pull_request]
3 | jobs:
4 | CI:
5 | uses: react-component/rc-test/.github/workflows/test.yml@main
6 | secrets: inherit
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | *.log
3 | .idea/
4 | .ipr
5 | .iws
6 | *~
7 | ~*
8 | *.diff
9 | *.patch
10 | *.bak
11 | .DS_Store
12 | Thumbs.db
13 | .project
14 | .*proj
15 | .svn/
16 | *.swp
17 | *.swo
18 | *.pyc
19 | *.pyo
20 | .build
21 | node_modules
22 | .cache
23 | assets/**/*.css
24 | build
25 | lib
26 | es
27 | yarn.lock
28 | package-lock.json
29 | coverage/
30 | .doc
31 |
32 | # dumi
33 | .dumi/tmp
34 | .dumi/tmp-test
35 | .dumi/tmp-production
36 | .env.local
37 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx pretty-quick --staged
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | build/
2 | *.cfg
3 | nohup.out
4 | *.iml
5 | .idea/
6 | .ipr
7 | .iws
8 | *~
9 | ~*
10 | *.diff
11 | *.log
12 | *.patch
13 | *.bak
14 | .DS_Store
15 | Thumbs.db
16 | .project
17 | .*proj
18 | .svn/
19 | *.swp
20 | out/
21 | .build
22 | node_modules
23 | .cache
24 | examples
25 | tests
26 | src
27 | /index.js
28 | .*
29 | assets/**/*.less
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .storybook
2 | node_modules
3 | lib
4 | es
5 | .cache
6 | package.json
7 | package-lock.json
8 | public
9 | .site
10 | _site
11 | .umi
12 | .doc
13 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "endOfLine": "lf",
3 | "semi": true,
4 | "singleQuote": true,
5 | "tabWidth": 2,
6 | "trailingComma": "all"
7 | }
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 0.6.0
4 |
5 | - support `maxColumnsPerRow`.
6 |
7 | ## 0.5.0
8 |
9 | - support `theme="dark|light"`.
10 |
11 | ## 0.4.0
12 |
13 | - support `columnLayout`.
14 | - support `backgroundColor`.
15 |
16 | ## 0.3.0
17 |
18 | - support `style` and `className` for footer column and footer item.
19 | - support `LinkComponent` for footer item.
20 |
21 | ## 0.2.0
22 |
23 | - Fix `lib` and `es` folders missing.
24 |
25 | ## 0.1.0
26 |
27 | - First release.
28 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019-present afc163
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rc-footer 🐾
2 |
3 | [![NPM version][npm-image]][npm-url]
4 | [![npm download][download-image]][download-url]
5 | [![build status][github-actions-image]][github-actions-url]
6 | [![Codecov][codecov-image]][codecov-url]
7 | [![bundle size][bundlephobia-image]][bundlephobia-url]
8 | [![dumi][dumi-image]][dumi-url]
9 |
10 | [npm-image]: http://img.shields.io/npm/v/rc-footer.svg?style=flat-square
11 | [npm-url]: http://npmjs.org/package/rc-footer
12 | [github-actions-image]: https://github.com/react-component/footer/actions/workflows/main.yml/badge.svg
13 | [github-actions-url]: https://github.com/react-component/footer/actions/workflows/main.yml
14 | [codecov-image]: https://img.shields.io/codecov/c/github/react-component/footer/main.svg?style=flat-square
15 | [codecov-url]: https://codecov.io/gh/react-component/footer/
16 | [david-url]: https://david-dm.org/react-component/footer
17 | [david-image]: https://david-dm.org/react-component/footer/status.svg?style=flat-square
18 | [david-dev-url]: https://david-dm.org/react-component/footer?type=dev
19 | [david-dev-image]: https://david-dm.org/react-component/footer/dev-status.svg?style=flat-square
20 | [download-image]: https://img.shields.io/npm/dm/rc-footer.svg?style=flat-square
21 | [download-url]: https://npmjs.org/package/rc-footer
22 | [bundlephobia-url]: https://bundlephobia.com/result?p=rc-footer
23 | [bundlephobia-image]: https://badgen.net/bundlephobia/minzip/rc-footer
24 | [dumi-url]: https://github.com/umijs/dumi
25 | [dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
26 |
27 | Pretty Footer react component used in [ant.design](https://ant.design) and [antv.vision](https://antv.vision).
28 |
29 | 
30 |
31 | ## Live Demo
32 |
33 | https://react-component.github.io/footer/
34 |
35 | ## Install
36 |
37 | [](https://npmjs.org/package/rc-footer)
38 |
39 | ## Usage
40 |
41 | ```js
42 | import Footer from 'rc-footer';
43 | import 'rc-footer/assets/index.css'; // import 'rc-footer/asssets/index.less';
44 | import { render } from 'react-dom';
45 |
46 | render(
47 |
52 | ),
53 | title: '语雀',
54 | url: 'https://yuque.com',
55 | description: '知识创作与分享工具',
56 | openExternal: true,
57 | },
58 | ]}
59 | bottom="Made with ❤️ by AFX"
60 | />,
61 | mountNode,
62 | );
63 | ```
64 |
65 | ## API
66 |
67 | | Property | Type | Default | Description |
68 | | ---------------- | --------------------------------- | -------------- | ---------------------------------------- |
69 | | prefixCls | string | rc-footer | |
70 | | className | string | '' | additional class name of footer |
71 | | style | React.CSSProperties | | style properties of footer |
72 | | columns | [Column](#Column) Array | [] | columns data inside footer |
73 | | bottom | ReactNode | | extra bottom area beneath footer columns |
74 | | theme | 'light' \| 'dark' | 'dark' | preset theme of footer |
75 | | backgroundColor | string | '#000' | background color of footer |
76 | | columnLayout | 'space-around' \| 'space-between' | 'space-around' | justify-content value of columns element |
77 | | maxColumnsPerRow | number | - | max count of columns for each row |
78 |
79 | ### Column
80 |
81 | | Property | Type | Default | Description |
82 | | --------- | -------------------------- | ------- | ------------------------------- |
83 | | icon | ReactNode | | icon that before column title |
84 | | title | ReactNode | | title of column |
85 | | items | [Item](#Column-Item) Array | [] | items data inside each column |
86 | | className | string | '' | additional class name of footer |
87 | | style | React.CSSProperties | | style properties of footer |
88 |
89 | ### Column Item
90 |
91 | | Property | Type | Default | Description |
92 | | ------------- | ------------------- | ------- | ------------------------------------------------------- |
93 | | icon | ReactNode | | icon that before column title |
94 | | title | ReactNode | | title of column |
95 | | description | ReactNode | | description of column, come after title |
96 | | url | string | | link url of item title |
97 | | openExternal | boolean | false | link target would be `_blank` if `openExternal` is ture |
98 | | className | string | '' | additional class name of footer |
99 | | style | React.CSSProperties | | style properties of footer |
100 | | LinkComponent | React.ReactType | 'a' | the link element to render item |
101 |
102 | ## Development
103 |
104 | ```
105 | npm install
106 | npm start
107 | ```
108 |
109 | ## License
110 |
111 | rc-footer is released under the MIT license.
112 |
--------------------------------------------------------------------------------
/assets/index.less:
--------------------------------------------------------------------------------
1 | @footer-prefix-cls: rc-footer;
2 |
3 | .@{footer-prefix-cls} {
4 | position: relative;
5 | clear: both;
6 | color: rgba(255, 255, 255, 0.4);
7 | font-size: 14px;
8 | line-height: 1.5;
9 | background-color: #000;
10 |
11 | a {
12 | color: rgba(255, 255, 255, 0.9);
13 | text-decoration: none;
14 | transition: all 0.3s;
15 |
16 | &:hover {
17 | color: #40a9ff;
18 | }
19 | }
20 |
21 | &-container {
22 | width: 100%;
23 | max-width: 1200px;
24 | margin: auto;
25 | padding: 80px 0 20px;
26 | }
27 |
28 | &-columns {
29 | display: flex;
30 | justify-content: space-around;
31 | }
32 |
33 | &-column {
34 | margin-bottom: 60px;
35 |
36 | h2 {
37 | position: relative;
38 | margin: 0 auto;
39 | color: #fff;
40 | font-weight: 500;
41 | font-size: 16px;
42 | }
43 |
44 | &-icon {
45 | position: relative;
46 | top: -1px;
47 | display: inline-block;
48 | width: 22px;
49 | text-align: center;
50 | vertical-align: middle;
51 | margin-inline-end: 0.5em;
52 |
53 | > span,
54 | > svg,
55 | img {
56 | display: block;
57 | width: 100%;
58 | }
59 | }
60 | }
61 |
62 | &-item {
63 | margin: 12px 0;
64 |
65 | &-icon {
66 | position: relative;
67 | top: -1px;
68 | display: inline-block;
69 | width: 16px;
70 | text-align: center;
71 | vertical-align: middle;
72 | margin-inline-end: 0.4em;
73 |
74 | > span,
75 | > svg,
76 | img {
77 | display: block;
78 | width: 100%;
79 | }
80 | }
81 |
82 | &-separator {
83 | margin: 0 0.3em;
84 | }
85 | }
86 |
87 | &-bottom {
88 | &-container {
89 | width: 100%;
90 | max-width: 1200px;
91 | margin: 0 auto;
92 | padding: 16px 0;
93 | font-size: 16px;
94 | line-height: 32px;
95 | text-align: center;
96 | border-top: 1px solid rgba(255, 255, 255, 0.25);
97 | }
98 | }
99 |
100 | &-light {
101 | color: rgba(0, 0, 0, 0.85);
102 | background-color: transparent;
103 |
104 | h2,
105 | a {
106 | color: rgba(0, 0, 0, 0.85);
107 | }
108 | }
109 |
110 | &-light &-bottom-container {
111 | border-top-color: #e8e8e8;
112 | }
113 |
114 | &-light &-item-separator,
115 | &-light &-item-description {
116 | color: rgba(0, 0, 0, 0.45);
117 | }
118 | }
119 |
120 | @media only screen and (max-width: 767.99px) {
121 | .@{footer-prefix-cls} {
122 | text-align: center;
123 |
124 | &-container {
125 | padding: 40px 0;
126 | }
127 |
128 | &-columns {
129 | display: block;
130 | }
131 |
132 | &-column {
133 | display: block;
134 | margin-bottom: 40px;
135 |
136 | &:last-child {
137 | margin-bottom: 0;
138 | }
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/docs/changelog.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/demo/rows.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: rows
3 | nav:
4 | title: Demo
5 | path: /demo
6 | ---
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/demo/simple.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: simple
3 | nav:
4 | title: Demo
5 | path: /demo
6 | ---
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/demo/theme.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: theme
3 | nav:
4 | title: Demo
5 | path: /demo
6 | ---
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/examples/rows.tsx:
--------------------------------------------------------------------------------
1 | import '../../assets/index.less';
2 | import React from 'react';
3 | import Footer from 'rc-footer';
4 |
5 | export default function App() {
6 | return (
7 |
8 |
77 | ),
78 | title: '更多产品',
79 | items: [
80 | {
81 | icon: (
82 |

86 | ),
87 | title: '语雀',
88 | url: 'https://yuque.com',
89 | description: '知识创作与分享工具',
90 | openExternal: true,
91 | },
92 | {
93 | icon: (
94 |

98 | ),
99 | title: '云凤蝶',
100 | url: 'https://yunfengdie.com',
101 | description: '中台建站平台',
102 | openExternal: true,
103 | },
104 | ],
105 | },
106 | {
107 | title: '相关资源',
108 | items: [
109 | {
110 | title: 'Ant Design Pro',
111 | url: 'https://pro.ant.design/',
112 | openExternal: true,
113 | },
114 | {
115 | title: 'Ant Design Mobile',
116 | url: 'https://mobile.ant.design/',
117 | openExternal: true,
118 | },
119 | {
120 | title: 'Kitchen',
121 | url: 'https://kitchen.alipay.com/',
122 | description: 'Sketch 工具集',
123 | },
124 | ],
125 | },
126 | {
127 | title: '社区',
128 | items: [
129 | {
130 | title: 'Ant Design Pro',
131 | url: 'https://pro.ant.design/',
132 | openExternal: true,
133 | },
134 | {
135 | title: 'Ant Design Mobile',
136 | url: 'https://mobile.ant.design/',
137 | openExternal: true,
138 | },
139 | {
140 | title: 'Kitchen',
141 | url: 'https://kitchen.alipay.com/',
142 | description: 'Sketch 工具集',
143 | },
144 | ],
145 | },
146 | {
147 | title: '帮助',
148 | items: [
149 | {
150 | title: 'Ant Design Pro',
151 | url: 'https://pro.ant.design/',
152 | openExternal: true,
153 | },
154 | {
155 | title: 'Ant Design Mobile',
156 | url: 'https://mobile.ant.design/',
157 | openExternal: true,
158 | },
159 | {
160 | title: 'Kitchen',
161 | url: 'https://kitchen.alipay.com/',
162 | description: 'Sketch 工具集',
163 | },
164 | ],
165 | },
166 | {
167 | icon: (
168 |

172 | ),
173 | title: '更多产品',
174 | items: [
175 | {
176 | icon: (
177 |

181 | ),
182 | title: '语雀',
183 | url: 'https://yuque.com',
184 | description: '知识创作与分享工具',
185 | openExternal: true,
186 | },
187 | {
188 | icon: (
189 |

193 | ),
194 | title: '云凤蝶',
195 | url: 'https://yunfengdie.com',
196 | description: '中台建站平台',
197 | openExternal: true,
198 | },
199 | ],
200 | },
201 | ]}
202 | bottom="Made with ❤️ by AFX"
203 | />
204 |
205 | );
206 | }
207 |
--------------------------------------------------------------------------------
/docs/examples/simple.tsx:
--------------------------------------------------------------------------------
1 | import '../../assets/index.less';
2 | import React from 'react';
3 | import Footer from 'rc-footer';
4 |
5 | export default function App() {
6 | return (
7 |
8 |
76 | ),
77 | title: '更多产品',
78 | items: [
79 | {
80 | icon: (
81 |

85 | ),
86 | title: '语雀',
87 | url: 'https://yuque.com',
88 | description: '知识创作与分享工具',
89 | openExternal: true,
90 | },
91 | {
92 | icon: (
93 |

97 | ),
98 | title: '云凤蝶',
99 | url: 'https://yunfengdie.com',
100 | description: '中台建站平台',
101 | openExternal: true,
102 | },
103 | ],
104 | },
105 | ]}
106 | bottom="Made with ❤️ by AFX"
107 | />
108 |
109 | );
110 | }
111 |
--------------------------------------------------------------------------------
/docs/examples/theme.tsx:
--------------------------------------------------------------------------------
1 | import '../../assets/index.less';
2 | import React, { useState } from 'react';
3 | import Footer, { type FooterProps } from 'rc-footer';
4 |
5 | export default function App() {
6 | const [theme, setTheme] = useState('light');
7 | return (
8 |
9 |
78 | ),
79 | title: '更多产品',
80 | items: [
81 | {
82 | icon: (
83 |

87 | ),
88 | title: '语雀',
89 | url: 'https://yuque.com',
90 | description: '知识创作与分享工具',
91 | openExternal: true,
92 | },
93 | {
94 | icon: (
95 |

99 | ),
100 | title: '云凤蝶',
101 | url: 'https://yunfengdie.com',
102 | description: '中台建站平台',
103 | openExternal: true,
104 | },
105 | {
106 | title: (
107 |
115 | ),
116 | },
117 | ],
118 | },
119 | ]}
120 | bottom="Made with ❤️ by AFX"
121 | />
122 |
123 | );
124 | }
125 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | hero:
3 | title: rc-footer
4 | description: React Footer Component
5 | ---
6 |
7 |
8 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./src/');
2 |
--------------------------------------------------------------------------------
/jest.config.ts:
--------------------------------------------------------------------------------
1 | import { createConfig, type Config } from '@umijs/test';
2 |
3 | const defaultConfig = createConfig({
4 | target: 'browser',
5 | });
6 |
7 | const config: Config.InitialOptions = {
8 | ...defaultConfig,
9 | setupFilesAfterEnv: [
10 | ...(defaultConfig.setupFilesAfterEnv || []),
11 | './tests/setupFilesAfterEnv.ts',
12 | ],
13 | };
14 |
15 | export default config;
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rc-footer",
3 | "version": "0.6.8",
4 | "description": "Pretty Footer react component used in ant.design",
5 | "keywords": [
6 | "react",
7 | "react-component",
8 | "react-footer",
9 | "footer",
10 | "antd",
11 | "ant-design"
12 | ],
13 | "main": "./lib/index",
14 | "module": "./es/index",
15 | "files": [
16 | "assets/*.css",
17 | "assets/*.less",
18 | "es",
19 | "lib",
20 | "dist"
21 | ],
22 | "homepage": "https://react-component.github.io/footer",
23 | "repository": {
24 | "type": "git",
25 | "url": "git@github.com:react-component/footer.git"
26 | },
27 | "bugs": {
28 | "url": "http://github.com/react-component/footer/issues"
29 | },
30 | "license": "MIT",
31 | "scripts": {
32 | "start": "dumi dev",
33 | "type:check": "tsc --noEmit",
34 | "docs:build": "dumi build",
35 | "docs:deploy": "gh-pages -d .doc",
36 | "compile": "father build && lessc assets/index.less assets/index.css",
37 | "gh-pages": "GH_PAGES=1 npm run docs:build && npm run docs:deploy",
38 | "prepublishOnly": "npm run compile && np --yolo --no-publish && npm run gh-pages",
39 | "lint": "eslint src/ --ext .ts,.tsx,.jsx,.js,.md",
40 | "prettier": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
41 | "pretty-quick": "pretty-quick",
42 | "test": "jest",
43 | "coverage": "jest --coverage",
44 | "prepare": "husky install"
45 | },
46 | "dependencies": {
47 | "@babel/runtime": "^7.25.0",
48 | "classnames": "^2.5.1"
49 | },
50 | "devDependencies": {
51 | "@rc-component/father-plugin": "^1.0.3",
52 | "@testing-library/jest-dom": "^5.17.0",
53 | "@testing-library/react": "^14.3.1",
54 | "@types/classnames": "^2.3.1",
55 | "@types/jest": "^29.5.12",
56 | "@types/node": "^22.2.0",
57 | "@types/react": "^17.0.80",
58 | "@types/react-dom": "^18.3.1",
59 | "@umijs/fabric": "^4.0.0",
60 | "@umijs/test": "^4.3.12",
61 | "dumi": "^2.4.7",
62 | "eslint": "^8.57.0",
63 | "father": "^4.5.0",
64 | "gh-pages": "^3.2.3",
65 | "husky": "^8.0.3",
66 | "jest": "^29.7.0",
67 | "jest-environment-jsdom": "^29.7.0",
68 | "less": "^4.2.0",
69 | "np": "^7.7.0",
70 | "prettier": "^3.3.3",
71 | "pretty-quick": "^4.0.0",
72 | "react": "^18.3.1",
73 | "react-dom": "^18.3.1",
74 | "ts-node": "^10.9.2",
75 | "typescript": "^5.5.4"
76 | },
77 | "husky": {
78 | "hooks": {
79 | "pre-commit": "pretty-quick --staged"
80 | }
81 | },
82 | "cnpm": {
83 | "mode": "npm"
84 | },
85 | "tnpm": {
86 | "mode": "npm"
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/script/update-content.js:
--------------------------------------------------------------------------------
1 | /*
2 | 用于 dumi 改造使用,
3 | 可用于将 examples 的文件批量修改为 demo 引入形式,
4 | 其他项目根据具体情况使用。
5 | */
6 |
7 | const fs = require('fs');
8 | const glob = require('glob');
9 |
10 | const paths = glob.sync('./docs/examples/*.tsx');
11 |
12 | paths.forEach((path) => {
13 | const name = path.split('/').pop().split('.')[0];
14 | fs.writeFile(
15 | `./docs/demo/${name}.md`,
16 | `---
17 | title: ${name}
18 | nav:
19 | title: Demo
20 | path: /demo
21 | ---
22 |
23 |
24 | `,
25 | 'utf8',
26 | function (error) {
27 | if (error) {
28 | console.log(error);
29 | return false;
30 | }
31 | console.log(`${name} 更新成功~`);
32 | },
33 | );
34 | });
35 |
--------------------------------------------------------------------------------
/src/column.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classNames from 'classnames';
3 |
4 | export interface FooterColumnItem {
5 | title: React.ReactNode;
6 | url?: string;
7 | openExternal?: boolean;
8 | icon?: React.ReactNode;
9 | description?: React.ReactNode;
10 | className?: string;
11 | style?: React.CSSProperties;
12 | LinkComponent?: React.ReactType;
13 | }
14 |
15 | export interface FooterColumn {
16 | prefixCls?: string;
17 | title?: React.ReactNode;
18 | icon?: React.ReactNode;
19 | items?: FooterColumnItem[];
20 | className?: string;
21 | style?: React.CSSProperties;
22 | }
23 |
24 | const Column: React.FC = ({
25 | prefixCls,
26 | icon,
27 | title,
28 | items = [],
29 | style,
30 | className,
31 | }) => (
32 |
33 | {(title || icon) && (
34 |
35 | {icon && {icon}}
36 | {title}
37 |
38 | )}
39 | {items.map((item, i) => {
40 | const LinkComponent = item.LinkComponent || 'a';
41 | return (
42 |
47 |
53 | {item.icon && (
54 | {item.icon}
55 | )}
56 | {item.title}
57 |
58 | {item.description && (
59 | <>
60 | -
61 |
62 | {item.description}
63 |
64 | >
65 | )}
66 |
67 | );
68 | })}
69 |
70 | );
71 |
72 | export default Column;
73 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classNames from 'classnames';
3 | import Column from './column';
4 | import type { FooterColumn } from './column';
5 |
6 | export interface FooterProps {
7 | prefixCls?: string;
8 | bottom?: React.ReactNode;
9 | maxColumnsPerRow?: number;
10 | columns?: FooterColumn[];
11 | theme?: 'dark' | 'light';
12 | className?: string;
13 | style?: React.CSSProperties;
14 | backgroundColor?: string;
15 | columnLayout?: 'space-around' | 'space-between';
16 | }
17 |
18 | const Footer: React.FC = ({
19 | prefixCls = 'rc-footer',
20 | className,
21 | style,
22 | bottom,
23 | columns,
24 | maxColumnsPerRow,
25 | backgroundColor,
26 | columnLayout,
27 | theme = 'dark',
28 | ...restProps
29 | }) => {
30 | const footerClassName = classNames(`${prefixCls}`, className, {
31 | [`${prefixCls}-${theme}`]: !!theme,
32 | });
33 | const shouldWrap =
34 | typeof maxColumnsPerRow === 'number' && maxColumnsPerRow > 0;
35 | return (
36 |
92 | );
93 | };
94 |
95 | export default Footer;
96 |
--------------------------------------------------------------------------------
/tests/__snapshots__/index.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`rc-footer render Footer 1`] = `
4 | {
5 | "asFragment": [Function],
6 | "baseElement":
7 |
8 |
248 |
249 | ,
250 | "container":
251 |
491 |
,
492 | "debug": [Function],
493 | "findAllByAltText": [Function],
494 | "findAllByDisplayValue": [Function],
495 | "findAllByLabelText": [Function],
496 | "findAllByPlaceholderText": [Function],
497 | "findAllByRole": [Function],
498 | "findAllByTestId": [Function],
499 | "findAllByText": [Function],
500 | "findAllByTitle": [Function],
501 | "findByAltText": [Function],
502 | "findByDisplayValue": [Function],
503 | "findByLabelText": [Function],
504 | "findByPlaceholderText": [Function],
505 | "findByRole": [Function],
506 | "findByTestId": [Function],
507 | "findByText": [Function],
508 | "findByTitle": [Function],
509 | "getAllByAltText": [Function],
510 | "getAllByDisplayValue": [Function],
511 | "getAllByLabelText": [Function],
512 | "getAllByPlaceholderText": [Function],
513 | "getAllByRole": [Function],
514 | "getAllByTestId": [Function],
515 | "getAllByText": [Function],
516 | "getAllByTitle": [Function],
517 | "getByAltText": [Function],
518 | "getByDisplayValue": [Function],
519 | "getByLabelText": [Function],
520 | "getByPlaceholderText": [Function],
521 | "getByRole": [Function],
522 | "getByTestId": [Function],
523 | "getByText": [Function],
524 | "getByTitle": [Function],
525 | "queryAllByAltText": [Function],
526 | "queryAllByDisplayValue": [Function],
527 | "queryAllByLabelText": [Function],
528 | "queryAllByPlaceholderText": [Function],
529 | "queryAllByRole": [Function],
530 | "queryAllByTestId": [Function],
531 | "queryAllByText": [Function],
532 | "queryAllByTitle": [Function],
533 | "queryByAltText": [Function],
534 | "queryByDisplayValue": [Function],
535 | "queryByLabelText": [Function],
536 | "queryByPlaceholderText": [Function],
537 | "queryByRole": [Function],
538 | "queryByTestId": [Function],
539 | "queryByText": [Function],
540 | "queryByTitle": [Function],
541 | "rerender": [Function],
542 | "unmount": [Function],
543 | }
544 | `;
545 |
546 | exports[`rc-footer render Footer with columnLayout and backgroundColor 1`] = `
547 | {
548 | "asFragment": [Function],
549 | "baseElement":
550 |
551 |
579 |
580 | ,
581 | "container":
582 |
610 |
,
611 | "debug": [Function],
612 | "findAllByAltText": [Function],
613 | "findAllByDisplayValue": [Function],
614 | "findAllByLabelText": [Function],
615 | "findAllByPlaceholderText": [Function],
616 | "findAllByRole": [Function],
617 | "findAllByTestId": [Function],
618 | "findAllByText": [Function],
619 | "findAllByTitle": [Function],
620 | "findByAltText": [Function],
621 | "findByDisplayValue": [Function],
622 | "findByLabelText": [Function],
623 | "findByPlaceholderText": [Function],
624 | "findByRole": [Function],
625 | "findByTestId": [Function],
626 | "findByText": [Function],
627 | "findByTitle": [Function],
628 | "getAllByAltText": [Function],
629 | "getAllByDisplayValue": [Function],
630 | "getAllByLabelText": [Function],
631 | "getAllByPlaceholderText": [Function],
632 | "getAllByRole": [Function],
633 | "getAllByTestId": [Function],
634 | "getAllByText": [Function],
635 | "getAllByTitle": [Function],
636 | "getByAltText": [Function],
637 | "getByDisplayValue": [Function],
638 | "getByLabelText": [Function],
639 | "getByPlaceholderText": [Function],
640 | "getByRole": [Function],
641 | "getByTestId": [Function],
642 | "getByText": [Function],
643 | "getByTitle": [Function],
644 | "queryAllByAltText": [Function],
645 | "queryAllByDisplayValue": [Function],
646 | "queryAllByLabelText": [Function],
647 | "queryAllByPlaceholderText": [Function],
648 | "queryAllByRole": [Function],
649 | "queryAllByTestId": [Function],
650 | "queryAllByText": [Function],
651 | "queryAllByTitle": [Function],
652 | "queryByAltText": [Function],
653 | "queryByDisplayValue": [Function],
654 | "queryByLabelText": [Function],
655 | "queryByPlaceholderText": [Function],
656 | "queryByRole": [Function],
657 | "queryByTestId": [Function],
658 | "queryByText": [Function],
659 | "queryByTitle": [Function],
660 | "rerender": [Function],
661 | "unmount": [Function],
662 | }
663 | `;
664 |
665 | exports[`rc-footer render Footer with maxColumnsPerRow 1`] = `
666 | {
667 | "asFragment": [Function],
668 | "baseElement":
669 |
670 |
914 |
915 | ,
916 | "container":
917 |
1161 |
,
1162 | "debug": [Function],
1163 | "findAllByAltText": [Function],
1164 | "findAllByDisplayValue": [Function],
1165 | "findAllByLabelText": [Function],
1166 | "findAllByPlaceholderText": [Function],
1167 | "findAllByRole": [Function],
1168 | "findAllByTestId": [Function],
1169 | "findAllByText": [Function],
1170 | "findAllByTitle": [Function],
1171 | "findByAltText": [Function],
1172 | "findByDisplayValue": [Function],
1173 | "findByLabelText": [Function],
1174 | "findByPlaceholderText": [Function],
1175 | "findByRole": [Function],
1176 | "findByTestId": [Function],
1177 | "findByText": [Function],
1178 | "findByTitle": [Function],
1179 | "getAllByAltText": [Function],
1180 | "getAllByDisplayValue": [Function],
1181 | "getAllByLabelText": [Function],
1182 | "getAllByPlaceholderText": [Function],
1183 | "getAllByRole": [Function],
1184 | "getAllByTestId": [Function],
1185 | "getAllByText": [Function],
1186 | "getAllByTitle": [Function],
1187 | "getByAltText": [Function],
1188 | "getByDisplayValue": [Function],
1189 | "getByLabelText": [Function],
1190 | "getByPlaceholderText": [Function],
1191 | "getByRole": [Function],
1192 | "getByTestId": [Function],
1193 | "getByText": [Function],
1194 | "getByTitle": [Function],
1195 | "queryAllByAltText": [Function],
1196 | "queryAllByDisplayValue": [Function],
1197 | "queryAllByLabelText": [Function],
1198 | "queryAllByPlaceholderText": [Function],
1199 | "queryAllByRole": [Function],
1200 | "queryAllByTestId": [Function],
1201 | "queryAllByText": [Function],
1202 | "queryAllByTitle": [Function],
1203 | "queryByAltText": [Function],
1204 | "queryByDisplayValue": [Function],
1205 | "queryByLabelText": [Function],
1206 | "queryByPlaceholderText": [Function],
1207 | "queryByRole": [Function],
1208 | "queryByTestId": [Function],
1209 | "queryByText": [Function],
1210 | "queryByTitle": [Function],
1211 | "rerender": [Function],
1212 | "unmount": [Function],
1213 | }
1214 | `;
1215 |
1216 | exports[`rc-footer render empty Footer 1`] = `
1217 | {
1218 | "asFragment": [Function],
1219 | "baseElement":
1220 |
1221 |
1228 |
1229 | ,
1230 | "container":
1231 |
1238 |
,
1239 | "debug": [Function],
1240 | "findAllByAltText": [Function],
1241 | "findAllByDisplayValue": [Function],
1242 | "findAllByLabelText": [Function],
1243 | "findAllByPlaceholderText": [Function],
1244 | "findAllByRole": [Function],
1245 | "findAllByTestId": [Function],
1246 | "findAllByText": [Function],
1247 | "findAllByTitle": [Function],
1248 | "findByAltText": [Function],
1249 | "findByDisplayValue": [Function],
1250 | "findByLabelText": [Function],
1251 | "findByPlaceholderText": [Function],
1252 | "findByRole": [Function],
1253 | "findByTestId": [Function],
1254 | "findByText": [Function],
1255 | "findByTitle": [Function],
1256 | "getAllByAltText": [Function],
1257 | "getAllByDisplayValue": [Function],
1258 | "getAllByLabelText": [Function],
1259 | "getAllByPlaceholderText": [Function],
1260 | "getAllByRole": [Function],
1261 | "getAllByTestId": [Function],
1262 | "getAllByText": [Function],
1263 | "getAllByTitle": [Function],
1264 | "getByAltText": [Function],
1265 | "getByDisplayValue": [Function],
1266 | "getByLabelText": [Function],
1267 | "getByPlaceholderText": [Function],
1268 | "getByRole": [Function],
1269 | "getByTestId": [Function],
1270 | "getByText": [Function],
1271 | "getByTitle": [Function],
1272 | "queryAllByAltText": [Function],
1273 | "queryAllByDisplayValue": [Function],
1274 | "queryAllByLabelText": [Function],
1275 | "queryAllByPlaceholderText": [Function],
1276 | "queryAllByRole": [Function],
1277 | "queryAllByTestId": [Function],
1278 | "queryAllByText": [Function],
1279 | "queryAllByTitle": [Function],
1280 | "queryByAltText": [Function],
1281 | "queryByDisplayValue": [Function],
1282 | "queryByLabelText": [Function],
1283 | "queryByPlaceholderText": [Function],
1284 | "queryByRole": [Function],
1285 | "queryByTestId": [Function],
1286 | "queryByText": [Function],
1287 | "queryByTitle": [Function],
1288 | "rerender": [Function],
1289 | "unmount": [Function],
1290 | }
1291 | `;
1292 |
1293 | exports[`rc-footer render light theme Footer 1`] = `
1294 | {
1295 | "asFragment": [Function],
1296 | "baseElement":
1297 |
1298 |
1325 |
1326 | ,
1327 | "container":
1328 |
1355 |
,
1356 | "debug": [Function],
1357 | "findAllByAltText": [Function],
1358 | "findAllByDisplayValue": [Function],
1359 | "findAllByLabelText": [Function],
1360 | "findAllByPlaceholderText": [Function],
1361 | "findAllByRole": [Function],
1362 | "findAllByTestId": [Function],
1363 | "findAllByText": [Function],
1364 | "findAllByTitle": [Function],
1365 | "findByAltText": [Function],
1366 | "findByDisplayValue": [Function],
1367 | "findByLabelText": [Function],
1368 | "findByPlaceholderText": [Function],
1369 | "findByRole": [Function],
1370 | "findByTestId": [Function],
1371 | "findByText": [Function],
1372 | "findByTitle": [Function],
1373 | "getAllByAltText": [Function],
1374 | "getAllByDisplayValue": [Function],
1375 | "getAllByLabelText": [Function],
1376 | "getAllByPlaceholderText": [Function],
1377 | "getAllByRole": [Function],
1378 | "getAllByTestId": [Function],
1379 | "getAllByText": [Function],
1380 | "getAllByTitle": [Function],
1381 | "getByAltText": [Function],
1382 | "getByDisplayValue": [Function],
1383 | "getByLabelText": [Function],
1384 | "getByPlaceholderText": [Function],
1385 | "getByRole": [Function],
1386 | "getByTestId": [Function],
1387 | "getByText": [Function],
1388 | "getByTitle": [Function],
1389 | "queryAllByAltText": [Function],
1390 | "queryAllByDisplayValue": [Function],
1391 | "queryAllByLabelText": [Function],
1392 | "queryAllByPlaceholderText": [Function],
1393 | "queryAllByRole": [Function],
1394 | "queryAllByTestId": [Function],
1395 | "queryAllByText": [Function],
1396 | "queryAllByTitle": [Function],
1397 | "queryByAltText": [Function],
1398 | "queryByDisplayValue": [Function],
1399 | "queryByLabelText": [Function],
1400 | "queryByPlaceholderText": [Function],
1401 | "queryByRole": [Function],
1402 | "queryByTestId": [Function],
1403 | "queryByText": [Function],
1404 | "queryByTitle": [Function],
1405 | "rerender": [Function],
1406 | "unmount": [Function],
1407 | }
1408 | `;
1409 |
--------------------------------------------------------------------------------
/tests/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import Footer from '../src';
4 |
5 | describe('rc-footer', () => {
6 | it('render empty Footer', () => {
7 | const wrapper = render();
8 | expect(wrapper).toMatchSnapshot();
9 | });
10 |
11 | it('render Footer', () => {
12 | const wrapper = render(
13 |
90 | ),
91 | title: '更多产品',
92 | items: [
93 | {
94 | icon: (
95 |
99 | ),
100 | title: '语雀',
101 | url: 'https://yuque.com',
102 | description: '知识创作与分享工具',
103 | openExternal: true,
104 | },
105 | {
106 | icon: (
107 |
111 | ),
112 | title: '云凤蝶',
113 | url: 'https://yunfengdie.com',
114 | description: '中台建站平台',
115 | openExternal: true,
116 | },
117 | ],
118 | },
119 | ]}
120 | bottom="Made with ❤️ by AFX"
121 | />,
122 | );
123 | expect(wrapper).toMatchSnapshot();
124 | });
125 |
126 | it('render Footer with columnLayout and backgroundColor', () => {
127 | const wrapper = render(
128 | ,
134 | );
135 | expect(wrapper).toMatchSnapshot();
136 | });
137 |
138 | it('render light theme Footer', () => {
139 | const wrapper = render(
140 | ,
145 | );
146 | expect(wrapper).toMatchSnapshot();
147 | });
148 |
149 | it('render Footer with maxColumnsPerRow', () => {
150 | const wrapper = render(
151 |
229 | ),
230 | title: '更多产品',
231 | items: [
232 | {
233 | icon: (
234 |
238 | ),
239 | title: '语雀',
240 | url: 'https://yuque.com',
241 | description: '知识创作与分享工具',
242 | openExternal: true,
243 | },
244 | {
245 | icon: (
246 |
250 | ),
251 | title: '云凤蝶',
252 | url: 'https://yunfengdie.com',
253 | description: '中台建站平台',
254 | openExternal: true,
255 | },
256 | ],
257 | },
258 | ]}
259 | bottom="Made with ❤️ by AFX"
260 | />,
261 | );
262 | expect(wrapper).toMatchSnapshot();
263 | });
264 | });
265 |
--------------------------------------------------------------------------------
/tests/setupFilesAfterEnv.ts:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom';
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "ESNext",
5 | "moduleResolution": "node",
6 | "baseUrl": "./",
7 | "lib": ["dom", "es2017"],
8 | "jsx": "react",
9 | "strict": true,
10 | "esModuleInterop": true,
11 | "experimentalDecorators": true,
12 | "emitDecoratorMetadata": true,
13 | "skipLibCheck": true,
14 | "declaration": true,
15 | "types": ["jest", "node"],
16 | "paths": {
17 | "@/*": ["src/*"],
18 | "@@/*": [".dumi/tmp/*"],
19 | "rc-footer": ["src/index.tsx"]
20 | }
21 | },
22 | "include": [".dumi/**/*", ".dumirc.ts", "src", "tests", "docs/examples"]
23 | }
24 |
--------------------------------------------------------------------------------
/type.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.css';
2 |
3 | declare module '*.less';
4 |
--------------------------------------------------------------------------------