index.tsx 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. import React, { useState, useEffect, useMemo, ReactElement } from 'react';
  2. import axios from 'axios';
  3. import { Tabs, Table } from '@douyinfe/semi-ui';
  4. import { useIntl } from 'react-intl';
  5. import { Link } from 'gatsby';
  6. import lodash from 'lodash-es';
  7. interface Props {
  8. componentName?: string;
  9. isColorPalette?: boolean;
  10. reg?: RegExp;
  11. isAnimation?: boolean
  12. }
  13. interface Token {
  14. key: string;
  15. value: string;
  16. category: string;
  17. raw: string;
  18. }
  19. interface DesignToken {
  20. // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  21. // @ts-ignore
  22. global: {
  23. global: {
  24. light: Token[];
  25. dark: Token[];
  26. };
  27. palette: {
  28. light: Token[];
  29. dark: Token[];
  30. };
  31. normal: Token[];
  32. animation: Token[];
  33. };
  34. [key: string]: Token[];
  35. }
  36. interface TokenMayWithColor extends Token {
  37. color?: string;
  38. }
  39. const JumpLink = ({ value, availableKeySet }: { value: string; availableKeySet: Set<string> }): ReactElement => {
  40. const { locale } = useIntl();
  41. const originValue = value;
  42. if (value.startsWith('var')) {
  43. const { length } = value;
  44. value = value.slice(4, length - 1);
  45. }
  46. const params = new URLSearchParams();
  47. params.append('token', lodash.trim(value, '$'));
  48. if (availableKeySet.has(value)) {
  49. return <Link to={`/${locale}/basic/tokens?${params.toString()}`} style={{ display: 'block' }}>{originValue}</Link>;
  50. } else {
  51. return <div>{originValue}</div>;
  52. }
  53. };
  54. const TokenTable = ({ tokenList, designToken, currentTab, mode = 'light' }: { mode?: 'light' | 'dark', tokenList: TokenMayWithColor[]; designToken: DesignToken; currentTab?: string; }): React.ReactElement => {
  55. const intl = useIntl();
  56. const globalTokenJumpAvailableSet = useMemo(() => {
  57. const global = designToken?.global;
  58. const set = new Set(global?.global?.light.map(token => token.key)
  59. .concat(global?.normal?.map(token => token.key)));
  60. return set;
  61. }, [designToken]);
  62. const columns = useMemo(() => [
  63. {
  64. title: intl.formatMessage({ id: 'designToken.variable' }),
  65. dataIndex: 'key',
  66. render: (text: string, record: TokenMayWithColor): React.ReactElement => {
  67. const { color } = record;
  68. if (color) {
  69. return (
  70. <div data-token={lodash.trim(text, '$')} style={{ fontWeight: 600 }}> <div style={{
  71. width: 16, height: 16, display: 'inline-block', borderRadius: 3, backgroundColor: color,
  72. transform: 'translateY(0.3rem)', marginRight: 8
  73. }} className={mode === 'dark' ? 'semi-always-dark' : 'semi-always-light'}
  74. /> {text}
  75. </div>
  76. );
  77. } else {
  78. return <div data-token={lodash.trim(text, '$')} style={{ fontWeight: 600 }}>{text}</div>;
  79. }
  80. }
  81. },
  82. {
  83. title: intl.formatMessage({ id: 'designToken.defaultValue' }),
  84. dataIndex: 'value',
  85. render: (text: string): React.ReactElement => <JumpLink availableKeySet={globalTokenJumpAvailableSet} value={text} />
  86. },
  87. {
  88. title: intl.formatMessage({ id: 'designToken.usage' }),
  89. dataIndex: 'comment',
  90. render: (text: string): React.ReactElement =>
  91. <div>{text || intl.formatMessage({ id: 'designToken.WIP' })}</div>
  92. },
  93. ], [intl.locale, mode]);
  94. if (!tokenList) {
  95. return null;
  96. }
  97. return <Table key={currentTab} columns={columns} dataSource={tokenList} />;
  98. };
  99. const TokenTab = ({ designToken, componentName }: { designToken: DesignToken; componentName: string }): React.ReactElement => {
  100. const componentDesignTokenList = useMemo(() => designToken[componentName], [designToken, componentName]);
  101. const tabList = useMemo(() => {
  102. const categorySet = new Set();
  103. componentDesignTokenList?.forEach(oneToken => categorySet.add(oneToken.category));
  104. return Array.from(categorySet).sort().map(category => ({ tab: category, itemKey: category }));
  105. }, [componentDesignTokenList]);
  106. const [currentTab, setCurrentTab] = useState(tabList[0]?.itemKey);
  107. const tokenList = useMemo(() =>
  108. designToken[componentName]?.filter((token: Token) => token.category === currentTab), [currentTab]);
  109. return (
  110. <div>
  111. <Tabs tabList={tabList} onChange={(key: string): void => setCurrentTab(key)}>
  112. <TokenTable designToken={designToken} tokenList={tokenList} currentTab={currentTab as string} />
  113. </Tabs>
  114. </div>
  115. );
  116. };
  117. const GlobalTokenTab = ({ designToken, isColorPalette = false, reg }: { designToken: DesignToken; isColorPalette?: boolean; reg: RegExp }): React.ReactElement => {
  118. const { global, palette, normal } = designToken.global;
  119. const [currentTab, setCurrentTab] = useState<'light' | 'dark'>('light');
  120. const [hasTab, setHasTab] = useState(true);
  121. const tokenList: TokenMayWithColor[] = useMemo(() => {
  122. if (!isColorPalette) {
  123. const modeFilteredTokenListOfGlobal: Token[] = global[currentTab].filter((token: Token) => reg.test(token.key));
  124. const normalTokenList: Token[] = normal.filter((token: Token) => reg.test(token.key));
  125. if ((modeFilteredTokenListOfGlobal.length !== 0) !== hasTab) {
  126. setHasTab(!hasTab);
  127. }
  128. return [...modeFilteredTokenListOfGlobal, ...normalTokenList].map((token: TokenMayWithColor): TokenMayWithColor => {
  129. if (token.key.startsWith('--semi-color')) {
  130. token.color = token.value;
  131. }
  132. return token;
  133. });
  134. } else {
  135. return [];
  136. }
  137. }, [currentTab]);
  138. return (
  139. <>
  140. {hasTab ? (
  141. <Tabs defaultActiveKey={'light'} tabList={[{ tab: 'Light', itemKey: 'light' }, { tab: 'Dark', itemKey: 'dark' }]} onChange={(key: typeof currentTab): void => setCurrentTab(key)}>
  142. <TokenTable designToken={designToken} tokenList={tokenList} mode={currentTab} />
  143. </Tabs>
  144. ) : <TokenTable designToken={designToken} tokenList={tokenList} mode={currentTab} />}
  145. </>
  146. );
  147. };
  148. const GlobalAnimationToken = ({ designToken }: { designToken: DesignToken }) => {
  149. const animationList = useMemo(() => designToken?.global?.animation ?? [], [designToken]);
  150. const tokenMap = useMemo(() => {
  151. const tokenMap = {};
  152. animationList.forEach(token => {
  153. const tab = token['key'].match(/--semi-transition_([\w\W]+)-/)?.[1] ?? "other";
  154. tokenMap[tab] = [...(tokenMap[tab] ?? []), token];
  155. })
  156. return tokenMap;
  157. }, [animationList]);
  158. return <>
  159. <Tabs defaultActiveKey={Object.keys(tokenMap)[0]} >
  160. {Object.entries(tokenMap).map(([category, tokenList]) => {
  161. return <Tabs.TabPane tab={category} itemKey={category} key={category}>
  162. <TokenTable designToken={designToken} tokenList={tokenList} />
  163. </Tabs.TabPane>
  164. })}
  165. </Tabs>
  166. </>
  167. }
  168. const DesignToken = (props: Props): React.ReactElement => {
  169. const [componentName, setComponentName] = useState(props.componentName?.toLowerCase());
  170. const [designToken, setDesignToken] = useState<DesignToken>(null);
  171. useEffect(() => {
  172. if (!componentName) {
  173. const componentNameFromUrl = lodash.nth(window.location.pathname.split('/'), -1);
  174. setComponentName(componentNameFromUrl.toLowerCase());
  175. }
  176. }, []);
  177. useEffect(() => {
  178. // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  179. // @ts-ignore
  180. if (window?.__semi__?.designToken) {
  181. // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  182. // @ts-ignore
  183. setDesignToken(window?.__semi__?.designToken);
  184. } else {
  185. (async (): Promise<void> => {
  186. const { data: designTokenFromServer } = await axios.get('/designToken.json');
  187. // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  188. // @ts-ignore
  189. window.__semi__ = {
  190. // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  191. // @ts-ignore
  192. ...window.__semi__,
  193. designToken: designTokenFromServer
  194. };
  195. setDesignToken(designTokenFromServer);
  196. })();
  197. }
  198. }, []);
  199. return (
  200. <div>
  201. {designToken && componentName && !props.isAnimation && (props.componentName === 'global' ?
  202. <GlobalTokenTab designToken={designToken} reg={props.reg} isColorPalette={props.isColorPalette} /> :
  203. <TokenTab designToken={designToken} componentName={componentName} />)}
  204. {
  205. props.isAnimation && <GlobalAnimationToken designToken={designToken} />
  206. }
  207. </div>
  208. );
  209. };
  210. export default DesignToken;