diff --git a/components/index.tsx b/components/index.tsx index 4d07b20d3..0e1436e64 100644 --- a/components/index.tsx +++ b/components/index.tsx @@ -50,6 +50,7 @@ export { default as TextareaItem } from './textarea-item/index' export { default as Toast } from './toast/index' export { default as Tooltip } from './tooltip/index' export { default as View } from './view/index' +export { default as Watermark } from './watermark/index' export { default as WhiteSpace } from './white-space/index' export { default as WingBlank } from './wing-blank/index' /** diff --git a/components/types.ts b/components/types.ts index 82dcdf182..4d103fe05 100644 --- a/components/types.ts +++ b/components/types.ts @@ -63,5 +63,6 @@ export type { TextareaItemProps } from './textarea-item/index' export type { ToastProps } from './toast/index' export type { TooltipProps } from './tooltip/PropsType' export type { ViewInterface as ViewProps } from './view/index' +export type { WatermarkProps } from './watermark/PropsType' export type { WhiteSpaceProps } from './white-space/index' export type { WingBlankProps } from './wing-blank/index' diff --git a/components/watermark/PropsType.tsx b/components/watermark/PropsType.tsx new file mode 100644 index 000000000..87c66254e --- /dev/null +++ b/components/watermark/PropsType.tsx @@ -0,0 +1,19 @@ +import React from 'react' +import { ImageStyle, StyleProp, TextStyle, ViewStyle } from 'react-native' +import { WatermarkStyle } from './style' + +export interface WatermarkProps { + content?: string | string[] + contentStyle?: StyleProp + image?: string | React.ReactNode + imageStyle?: StyleProp + width?: number + height?: number + gapX?: number + gapY?: number + rotate?: number + foreground?: boolean + children?: React.ReactNode + style?: ViewStyle + styles?: Partial +} diff --git a/components/watermark/__tests__/__snapshots__/demo.test.js.snap b/components/watermark/__tests__/__snapshots__/demo.test.js.snap new file mode 100644 index 000000000..79798efd5 --- /dev/null +++ b/components/watermark/__tests__/__snapshots__/demo.test.js.snap @@ -0,0 +1,450 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders ./components/watermark/demo/basic.tsx correctly 1`] = ` + + + + + + + + + 单行 + + + + + + + + + + + 多行 + + + + + + + + + + + 图片 + + + + + + + + + + + 背景层 + + + + + + + + +`; diff --git a/components/watermark/__tests__/demo.test.js b/components/watermark/__tests__/demo.test.js new file mode 100644 index 000000000..d013b2c4c --- /dev/null +++ b/components/watermark/__tests__/demo.test.js @@ -0,0 +1,3 @@ +import rnDemoTest from '../../../tests/shared/demoTest' + +rnDemoTest('watermark') diff --git a/components/watermark/demo/basic.md b/components/watermark/demo/basic.md new file mode 100644 index 000000000..0646aad3c --- /dev/null +++ b/components/watermark/demo/basic.md @@ -0,0 +1,104 @@ +--- +order: 0 +title: + zh-CN: 基本 + en-US: Basic +--- + +[Demo Source Code](https://github.com/ant-design/ant-design-mobile-rn/blob/master/components/watermark/demo/basic.tsx) + +```jsx +import React from 'react' +import { Dimensions, StyleSheet, View } from 'react-native' +import { Button, Watermark, WhiteSpace } from '@ant-design/react-native' + +const { height: windowHeight } = Dimensions.get('window') + +export default class WatermarkExample extends React.Component { + constructor(props: any) { + super(props) + this.state = { + props: {}, + foreground: false, + } + } + render() { + const textProps = { + content: ['AntD Mobile'], + width: 100, + height: 40, + } + + const rowsTextProps = { + content: ['AntD Mobile', 'AntD'], + width: 100, + height: 40, + gapY: 10, + } + + const imageProps = { + image: + 'https://gw.alipayobjects.com/zos/rmsportal/BGcxWbIWmgBlIChNOpqp.png', + gapX: 10, + gapY: 10, + imageStyle: { + width: 132, + height: 40, + }, + } + return ( + + + + + + + + + + + + + + + + + ) + } +} + +const styles = StyleSheet.create({ + root: { + width: 200, + marginLeft: 60, + height: windowHeight, + }, +}) + +``` diff --git a/components/watermark/demo/basic.tsx b/components/watermark/demo/basic.tsx new file mode 100644 index 000000000..fb12a681e --- /dev/null +++ b/components/watermark/demo/basic.tsx @@ -0,0 +1,90 @@ +import React from 'react' +import { Dimensions, StyleSheet, View } from 'react-native' +import { Button, Watermark, WhiteSpace } from '../..' + +const { height: windowHeight } = Dimensions.get('window') + +export default class WatermarkExample extends React.Component { + constructor(props: any) { + super(props) + this.state = { + props: {}, + foreground: false, + } + } + render() { + const textProps = { + content: ['AntD Mobile'], + width: 100, + height: 40, + } + + const rowsTextProps = { + content: ['AntD Mobile', 'AntD'], + width: 100, + height: 40, + gapY: 10, + } + + const imageProps = { + image: + 'https://gw.alipayobjects.com/zos/rmsportal/BGcxWbIWmgBlIChNOpqp.png', + gapX: 10, + gapY: 10, + imageStyle: { + width: 132, + height: 40, + }, + } + return ( + + + + + + + + + + + + + + + + + ) + } +} + +const styles = StyleSheet.create({ + root: { + width: 200, + marginLeft: 60, + height: windowHeight, + }, +}) diff --git a/components/watermark/index.en-US.md b/components/watermark/index.en-US.md new file mode 100644 index 000000000..6331106fa --- /dev/null +++ b/components/watermark/index.en-US.md @@ -0,0 +1,43 @@ +--- +category: Components +type: Data Display +title: Watermark +--- + +Add watermark to pages or components for copyright identification or information protection. + +### Rule + +- Useful for scenarios that need to protect content copyright. +- Support both text and image watermark forms. +- Watermark will automatically tile to fill the entire container. + +## API + +| Name | Description | Type | Default | +| ------------- | ------------------------------------------------------------------------------- | ----------------------- | ------- | +| content | Watermark text content, supports string or string array | `string \| string[]` | - | +| contentStyle | Watermark text style | `StyleProp` | - | +| image | Watermark image | `string \| React.ReactNode` | - | +| imageStyle | Watermark image style | `StyleProp` | - | +| width | Width of a single watermark | `number` | `120` | +| height | Height of a single watermark | `number` | `64` | +| gapX | Horizontal spacing between watermarks | `number` | `0` | +| gapY | Vertical spacing between watermarks | `number` | `0` | +| rotate | Rotation angle of watermark (unit: degrees) | `number` | `-22` | +| foreground | Whether to display watermark in foreground layer (`true` means above content) | `boolean` | `true` | +| children | Content that needs watermark | `React.ReactNode` | - | +| style | Container style | `ViewStyle` | - | +| styles | Internal component style set | [Partial\](#watermarkstyle-semantic-styles) | - | + + +### WatermarkStyle Semantic Styles +```jsx +interface WatermarkStyle { + container: ViewStyle + wmContainer: ViewStyle + wmItem: ViewStyle + wmImage: ImageStyle + wmText: TextStyle +} +``` \ No newline at end of file diff --git a/components/watermark/index.tsx b/components/watermark/index.tsx new file mode 100644 index 000000000..2158e07ab --- /dev/null +++ b/components/watermark/index.tsx @@ -0,0 +1,123 @@ +import React, { useCallback, useMemo, useState } from 'react' +import { Dimensions, Image, LayoutChangeEvent, Text, View } from 'react-native' +import { useTheme } from '../style' + +import WatermarkStyle from './style' + +import { WatermarkProps } from './PropsType' + +const { width: windowWidth, height: windowHeight } = Dimensions.get('window') + +const Watermark: React.FC = ({ + content, + contentStyle, + image, + imageStyle, + height = 64, + width = 120, + gapX = 0, + gapY = 0, + rotate = -22, + foreground = true, + children, + style, + ...rest +}) => { + const styles = useTheme({ + styles: rest.styles, + themeStyles: WatermarkStyle, + }) + const [layout, setLayout] = useState<{ width: number; height: number }>({ + width: windowWidth, + height: windowHeight, + }) + const onChildrenLayout = useCallback((event: LayoutChangeEvent) => { + setLayout(event.nativeEvent.layout) + }, []) + + const _width = width + gapX * 2 + const _height = height + gapY * 2 + const count = + parseInt(String(layout.width / Math.max(_width, 1)), 10) * + parseInt(String(layout.height / Math.max(_height, 1)), 10) + + // ========== watermarkDom ============ + const watermarkDom = useMemo(() => { + let innerDom = null + const _content = typeof content === 'string' ? [content] : content + if (Array.isArray(_content) && _content.length > 0) { + innerDom = ( + <> + {_content.map((item, index) => { + return ( + + {item} + + ) + })} + + ) + } else { + if (typeof image === 'string') { + innerDom = ( + + ) + } else if (React.isValidElement(image)) { + innerDom = image + } + } + return innerDom + }, [content, contentStyle, image, imageStyle, styles.wmImage, styles.wmText]) + + // ========== renderWatermark ============ + const renderWatermark = useCallback(() => { + if (!watermarkDom || isNaN(count) || count <= 0) { + return null + } + const _zIndex = foreground ? 1 : -1 + return ( + + {Array.from({ length: count }).map((_, index) => { + return ( + + {watermarkDom} + + ) + })} + + ) + }, [ + watermarkDom, + count, + foreground, + styles.wmContainer, + styles.wmItem, + _height, + _width, + rotate, + ]) + + return ( + + {renderWatermark()} + + {children} + + + ) +} + +export default Watermark diff --git a/components/watermark/index.zh-CN.md b/components/watermark/index.zh-CN.md new file mode 100644 index 000000000..ba4622084 --- /dev/null +++ b/components/watermark/index.zh-CN.md @@ -0,0 +1,43 @@ +--- +category: Components +type: Data Display +title: Watermark +subtitle: 水印 +--- + +给页面或组件添加水印,用于标识版权或防止信息泄露。 + +### 规则 + +- 适用于需要保护内容版权的场景。 +- 支持文字和图片两种水印形式。 +- 水印会自动平铺填充整个容器。 + +## API + +| 属性 | 说明 | 类型 | 默认值 | +| ------------- | --------------------------------------------------------- | ----------------------- | ------- | +| content | 水印文字内容,支持字符串或字符串数组 | `string \| string[]` | - | +| contentStyle | 水印文字样式 | `StyleProp` | - | +| image | 水印图片 | `string \| React.ReactNode` | - | +| imageStyle | 水印图片样式 | `StyleProp` | - | +| width | 单个水印的宽度 | `number` | `120` | +| height | 单个水印的高度 | `number` | `64` | +| gapX | 水印之间的水平间距 | `number` | `0` | +| gapY | 水印之间的垂直间距 | `number` | `0` | +| rotate | 水印的旋转角度(单位:度) | `number` | `-22` | +| foreground | 是否将水印显示在前景层(`true` 时水印在内容上方) | `boolean` | `true` | +| children | 需要添加水印的内容 | `React.ReactNode` | - | +| style | 容器样式 | `ViewStyle` | - | +| styles | 内部组件样式集 | [Partial\](#watermarkstyle-语义化样式) | - | + +### WatermarkStyle 语义化样式 +```jsx +interface WatermarkStyle { + container: ViewStyle + wmContainer: ViewStyle + wmItem: ViewStyle + wmImage: ImageStyle + wmText: TextStyle +} +``` \ No newline at end of file diff --git a/components/watermark/style/index.tsx b/components/watermark/style/index.tsx new file mode 100644 index 000000000..5be60c18e --- /dev/null +++ b/components/watermark/style/index.tsx @@ -0,0 +1,43 @@ +import { ImageStyle, StyleSheet, TextStyle, ViewStyle } from 'react-native' + +export interface WatermarkStyle { + container: ViewStyle + wmContainer: ViewStyle + wmItem: ViewStyle + wmImage: ImageStyle + wmText: TextStyle +} + +export default () => + StyleSheet.create({ + container: { + flex: 1, + position: 'relative', + }, + wmContainer: { + position: 'absolute', + left: 0, + right: 0, + top: 0, + bottom: 0, + backgroundColor: 'rgba(0,0,0,0)', + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + alignContent: 'center', + flexWrap: 'wrap', + }, + wmItem: { + backgroundColor: 'rgba(0,0,0,0)', + justifyContent: 'center', + alignItems: 'center', + }, + wmImage: { + height: 32, + width: 60, + }, + wmText: { + color: 'rgba(0,0,0,0.15)', + fontSize: 14, + }, + }) diff --git a/rn-kitchen-sink/demoList.js b/rn-kitchen-sink/demoList.js index d692fdb40..d014f2f97 100644 --- a/rn-kitchen-sink/demoList.js +++ b/rn-kitchen-sink/demoList.js @@ -265,6 +265,12 @@ module.exports = { icon: 'https://os.alipayobjects.com/rmsportal/DUkfOYZVcLctGot.png', module: require('../components/view/demo/basic'), }, + { + title: 'Watermark', + description: '水印', + icon: 'https://os.alipayobjects.com/rmsportal/daARhPjKcxlSuuZ.png', + module: require('../components/watermark/demo/basic'), + }, { title: 'WhiteSpace', description: '上下留白', diff --git a/tests/__snapshots__/index.test.js.snap b/tests/__snapshots__/index.test.js.snap index 123cab0c2..2a8378491 100644 --- a/tests/__snapshots__/index.test.js.snap +++ b/tests/__snapshots__/index.test.js.snap @@ -48,6 +48,7 @@ Array [ "Toast", "Tooltip", "View", + "Watermark", "WhiteSpace", "WingBlank", "ImagePicker",