@base-ripple/react 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +159 -0
- package/dist/assets/styles-DuOh9mGm.css +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/base-ripple.d.ts +17 -0
- package/dist/lib/base-ripple.d.ts.map +1 -0
- package/dist/lib/base-ripple.js +56 -0
- package/package.json +41 -0
- package/src/styles.css +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# @base-ripple/react
|
|
2
|
+
|
|
3
|
+
React wrapper for Base Ripple. Use this package when you want a declarative
|
|
4
|
+
component that attaches the ripple behavior from `@base-ripple/core`.
|
|
5
|
+
|
|
6
|
+
Demo: <https://base-ripple.vercel.app/>
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm i @base-ripple/react
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick start (React)
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
"use client";
|
|
18
|
+
|
|
19
|
+
import "@base-ripple/react/styles.css";
|
|
20
|
+
import { BaseRipple } from "@base-ripple/react";
|
|
21
|
+
|
|
22
|
+
export function Example() {
|
|
23
|
+
return <BaseRipple className="base-ripple-container">Click me</BaseRipple>;
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
You still need to define a fill color and opacity for the ripple:
|
|
28
|
+
|
|
29
|
+
```css
|
|
30
|
+
.base-ripple {
|
|
31
|
+
background: currentColor;
|
|
32
|
+
opacity: 0.12;
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Styles (required)
|
|
37
|
+
|
|
38
|
+
The ripple uses two class names:
|
|
39
|
+
|
|
40
|
+
- `.base-ripple-container` on the target element
|
|
41
|
+
- `.base-ripple` on the injected `<span>`
|
|
42
|
+
|
|
43
|
+
Option A: Import the default styles (recommended)
|
|
44
|
+
|
|
45
|
+
```css
|
|
46
|
+
@import "@base-ripple/react/styles.css";
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Option B: Provide your own styles (advanced)
|
|
50
|
+
|
|
51
|
+
If you do not import `styles.css`, create your own CSS using the same class
|
|
52
|
+
names. Make sure the container is positioned and clips overflow. Here is the
|
|
53
|
+
default `styles.css` you can copy and customize:
|
|
54
|
+
|
|
55
|
+
```css
|
|
56
|
+
.base-ripple-container {
|
|
57
|
+
position: relative;
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
touch-action: manipulation;
|
|
60
|
+
-webkit-tap-highlight-color: transparent;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.base-ripple {
|
|
64
|
+
position: absolute;
|
|
65
|
+
top: 0;
|
|
66
|
+
left: 0;
|
|
67
|
+
border-radius: 50%;
|
|
68
|
+
pointer-events: none;
|
|
69
|
+
animation: baseRipple 600ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
|
70
|
+
transition: opacity 600ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
71
|
+
will-change: transform, opacity;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@keyframes baseRipple {
|
|
75
|
+
from {
|
|
76
|
+
transform: translate3d(
|
|
77
|
+
calc(var(--base-ripple-keyframes-from-x) - 50%),
|
|
78
|
+
calc(var(--base-ripple-keyframes-from-y) - 50%),
|
|
79
|
+
0
|
|
80
|
+
)
|
|
81
|
+
scale(0);
|
|
82
|
+
}
|
|
83
|
+
to {
|
|
84
|
+
transform: translate3d(
|
|
85
|
+
calc(var(--base-ripple-keyframes-to-x) - 50%),
|
|
86
|
+
calc(var(--base-ripple-keyframes-to-y) - 50%),
|
|
87
|
+
0
|
|
88
|
+
)
|
|
89
|
+
scale(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
IMPORTANT: If you write your own `.base-ripple` styles, it MUST include a
|
|
95
|
+
`transition` for `opacity`. Cleanup relies on the `transitionend` event for
|
|
96
|
+
`opacity`. Without it, ripple elements never remove themselves. If you want no
|
|
97
|
+
transition, set a `transition` with 0 duration, for example:
|
|
98
|
+
|
|
99
|
+
```css
|
|
100
|
+
.base-ripple {
|
|
101
|
+
transition: opacity 0ms linear;
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## API
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
import { BaseRipple } from "@base-ripple/react";
|
|
109
|
+
|
|
110
|
+
<BaseRipple
|
|
111
|
+
as="button"
|
|
112
|
+
className="base-ripple-container"
|
|
113
|
+
rippleOptions={{ origin: "pointer", sizeOffset: 96 }}
|
|
114
|
+
>
|
|
115
|
+
Click me
|
|
116
|
+
</BaseRipple>;
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Props
|
|
120
|
+
|
|
121
|
+
| Prop | Type | Default | Description |
|
|
122
|
+
| --------------- | ------------------------- | -------- | ----------------------------------------------------- |
|
|
123
|
+
| `as` | `ElementType` | `button` | Underlying element to render. |
|
|
124
|
+
| `rippleOptions` | `AttachBaseRippleOptions` | `{}` | Options passed to the core `attachBaseRipple` helper. |
|
|
125
|
+
|
|
126
|
+
All other props are passed through to the underlying element, and the `ref` is
|
|
127
|
+
forwarded to the rendered element.
|
|
128
|
+
|
|
129
|
+
### `rippleOptions`
|
|
130
|
+
|
|
131
|
+
| Prop | Type | Default | Description |
|
|
132
|
+
| ------------ | ------------------------ | ----------- | ------------------------------------------------------------------------------------------------------ |
|
|
133
|
+
| `origin` | `"pointer" \| "center"` | `pointer` | Where the ripple starts. `pointer` uses the pointer position; `center` starts from the element center. |
|
|
134
|
+
| `sizeOffset` | `number` | `0` | Extra pixels added to the ripple diameter, useful when adding blur or glow. |
|
|
135
|
+
| `attributes` | `Record<string, string>` | `undefined` | Extra attributes added to each ripple `<span>`. |
|
|
136
|
+
|
|
137
|
+
## Behavior notes
|
|
138
|
+
|
|
139
|
+
- Uses pointer and keyboard interactions (Space, Enter, NumpadEnter).
|
|
140
|
+
- Respects `prefers-reduced-motion` by disabling ripples and clearing existing.
|
|
141
|
+
- Skips ripples for disabled elements (`disabled` or `aria-disabled="true"`).
|
|
142
|
+
- Adds `aria-hidden="true"` to the ripple spans for accessibility.
|
|
143
|
+
- Browser-only API; use inside client components.
|
|
144
|
+
|
|
145
|
+
## Core package
|
|
146
|
+
|
|
147
|
+
If you need the DOM API or want to build your own wrapper, use the core package:
|
|
148
|
+
|
|
149
|
+
- `@base-ripple/core`
|
|
150
|
+
|
|
151
|
+
## Planned framework wrappers (not implemented yet)
|
|
152
|
+
|
|
153
|
+
- Preact
|
|
154
|
+
- Vue
|
|
155
|
+
- Svelte
|
|
156
|
+
- Solid
|
|
157
|
+
- Angular
|
|
158
|
+
- Lit
|
|
159
|
+
- Qwik
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.base-ripple-container{touch-action:manipulation;-webkit-tap-highlight-color:transparent;position:relative;overflow:hidden}.base-ripple{pointer-events:none;will-change:transform,opacity;border-radius:50%;transition:opacity .6s cubic-bezier(.4,0,.2,1);animation:.6s cubic-bezier(.4,0,.2,1) forwards baseRipple;position:absolute;top:0;left:0}@keyframes baseRipple{0%{transform:translate3d(calc(var(--base-ripple-keyframes-from-x) - 50%),calc(var(--base-ripple-keyframes-from-y) - 50%),0)scale(0)}to{transform:translate3d(calc(var(--base-ripple-keyframes-to-x) - 50%),calc(var(--base-ripple-keyframes-to-y) - 50%),0)scale(1)}}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./lib/base-ripple.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AttachBaseRippleOptions } from "@base-ripple/core";
|
|
2
|
+
import { ComponentPropsWithoutRef, ComponentPropsWithRef, ElementType, ReactElement } from "react";
|
|
3
|
+
type PolymorphicAs<E extends ElementType> = {
|
|
4
|
+
as?: E;
|
|
5
|
+
};
|
|
6
|
+
type PolymorphicOmit<E extends ElementType, P> = keyof (PolymorphicAs<E> & P);
|
|
7
|
+
type PolymorphicComponentProps<E extends ElementType, P> = P & PolymorphicAs<E> & Omit<ComponentPropsWithoutRef<E>, PolymorphicOmit<E, P>>;
|
|
8
|
+
type PolymorphicComponentRef<E extends ElementType> = ComponentPropsWithRef<E>["ref"];
|
|
9
|
+
type RippleProps = {
|
|
10
|
+
rippleOptions?: AttachBaseRippleOptions;
|
|
11
|
+
};
|
|
12
|
+
type RippleComponent = <E extends ElementType = "button">(props: PolymorphicComponentProps<E, RippleProps> & {
|
|
13
|
+
ref?: PolymorphicComponentRef<E>;
|
|
14
|
+
}) => ReactElement | null;
|
|
15
|
+
export declare const BaseRipple: RippleComponent;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=base-ripple.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-ripple.d.ts","sourceRoot":"","sources":["../../src/lib/base-ripple.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAoB,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAC9E,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,WAAW,EAGX,YAAY,EAMb,MAAM,OAAO,CAAC;AAIf,KAAK,aAAa,CAAC,CAAC,SAAS,WAAW,IAAI;IAAE,EAAE,CAAC,EAAE,CAAC,CAAA;CAAE,CAAC;AAEvD,KAAK,eAAe,CAAC,CAAC,SAAS,WAAW,EAAE,CAAC,IAAI,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAE9E,KAAK,yBAAyB,CAAC,CAAC,SAAS,WAAW,EAAE,CAAC,IAAI,CAAC,GAC1D,aAAa,CAAC,CAAC,CAAC,GAChB,IAAI,CAAC,wBAAwB,CAAC,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAE3D,KAAK,uBAAuB,CAAC,CAAC,SAAS,WAAW,IAChD,qBAAqB,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;AAElC,KAAK,WAAW,GAAG;IACjB,aAAa,CAAC,EAAE,uBAAuB,CAAC;CACzC,CAAC;AAEF,KAAK,eAAe,GAAG,CAAC,CAAC,SAAS,WAAW,GAAG,QAAQ,EACtD,KAAK,EAAE,yBAAyB,CAAC,CAAC,EAAE,WAAW,CAAC,GAAG;IACjD,GAAG,CAAC,EAAE,uBAAuB,CAAC,CAAC,CAAC,CAAC;CAClC,KACE,YAAY,GAAG,IAAI,CAAC;AA0BzB,eAAO,MAAM,UAAU,EAAyB,eAAe,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { attachBaseRipple } from "@base-ripple/core";
|
|
4
|
+
import { forwardRef, useCallback, useEffect, useRef, } from "react";
|
|
5
|
+
// endregion TYPES
|
|
6
|
+
// region MAIN
|
|
7
|
+
const DEFAULT_RIPPLE_OPTIONS = {};
|
|
8
|
+
function Ripple({ rippleOptions = DEFAULT_RIPPLE_OPTIONS, as: Component = "button", ...restProps }, ref) {
|
|
9
|
+
const localRef = useRef(null);
|
|
10
|
+
const mergedRef = useMergedRefs(ref, localRef);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (!localRef.current)
|
|
13
|
+
return;
|
|
14
|
+
return attachBaseRipple(localRef.current, rippleOptions);
|
|
15
|
+
}, [serializeOptions(rippleOptions)]);
|
|
16
|
+
return _jsx(Component, { ref: mergedRef, ...restProps });
|
|
17
|
+
}
|
|
18
|
+
export const BaseRipple = forwardRef(Ripple);
|
|
19
|
+
function setRef(ref, value) {
|
|
20
|
+
if (!ref)
|
|
21
|
+
return;
|
|
22
|
+
if (typeof ref === "function") {
|
|
23
|
+
ref(value);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
ref.current = value;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// in extremely rare cases (e.g. read-only refs), ignore.
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function useMergedRefs(...refs) {
|
|
34
|
+
const refsRef = useRef(refs);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
refsRef.current = refs;
|
|
37
|
+
}, refs);
|
|
38
|
+
return useCallback((node) => {
|
|
39
|
+
for (const ref of refsRef.current)
|
|
40
|
+
setRef(ref, node);
|
|
41
|
+
}, []);
|
|
42
|
+
}
|
|
43
|
+
// endregion USE MERGED REFS
|
|
44
|
+
// region OTHER UTILITIES
|
|
45
|
+
function serializeOptions(options) {
|
|
46
|
+
if (!options)
|
|
47
|
+
return "";
|
|
48
|
+
const attributes = options.attributes;
|
|
49
|
+
const serializedAttributes = attributes
|
|
50
|
+
? Object.keys(attributes)
|
|
51
|
+
.sort()
|
|
52
|
+
.map((key) => `${encodeURIComponent(key)}:${encodeURIComponent(attributes[key])}`)
|
|
53
|
+
.join("|")
|
|
54
|
+
: "";
|
|
55
|
+
return `${options.origin ?? ""}__${options.sizeOffset ?? ""}__${serializedAttributes}`;
|
|
56
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@base-ripple/react",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
"./package.json": "./package.json",
|
|
10
|
+
".": {
|
|
11
|
+
"@base-ripple/source": "./src/index.ts",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./styles.css": "./src/styles.css"
|
|
17
|
+
},
|
|
18
|
+
"sideEffects": [
|
|
19
|
+
"./src/styles.css"
|
|
20
|
+
],
|
|
21
|
+
"files": [
|
|
22
|
+
"src/styles.css",
|
|
23
|
+
"dist",
|
|
24
|
+
"!**/*.tsbuildinfo"
|
|
25
|
+
],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@base-ripple/core": "1.0.0",
|
|
28
|
+
"tslib": "^2.3.0"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"react": ">=16.8 <20"
|
|
32
|
+
},
|
|
33
|
+
"nx": {
|
|
34
|
+
"tags": [
|
|
35
|
+
"scope:react"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/styles.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "@base-ripple/core/styles.css";
|