@diskette/use-render 0.3.0 → 0.4.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 CHANGED
@@ -39,7 +39,7 @@ function Button(props: ButtonProps) {
39
39
  const state: ButtonState = { isPressed, isHovered }
40
40
 
41
41
  return useRender('button', state, {
42
- defaultProps: {
42
+ baseProps: {
43
43
  className: 'btn',
44
44
  onMouseDown: () => setIsPressed(true),
45
45
  onMouseUp: () => setIsPressed(false),
@@ -83,8 +83,8 @@ Pass a function to resolve className based on component state:
83
83
 
84
84
  ```tsx
85
85
  <Button
86
- className={(state, defaultClassName) =>
87
- `${defaultClassName} ${state.isPressed ? 'pressed' : ''}`
86
+ className={(state, baseClassName) =>
87
+ `${baseClassName} ${state.isPressed ? 'pressed' : ''}`
88
88
  }
89
89
  >
90
90
  Press me
@@ -141,17 +141,17 @@ function useRender<T extends ElementType, S>(
141
141
 
142
142
  ```ts
143
143
  interface UseRenderOptions<T extends ElementType, S> {
144
- defaultProps?: React.ComponentProps<T>
144
+ baseProps?: React.ComponentProps<T>
145
145
  props?: ComponentProps<T, S>
146
146
  ref?: React.Ref<any> | (React.Ref<any> | undefined)[]
147
147
  }
148
148
  ```
149
149
 
150
- | Option | Description |
151
- | -------------- | ------------------------------------------------------------------ |
152
- | `defaultProps` | Default props applied to the element |
153
- | `props` | Consumer-provided props (typically forwarded from component props) |
154
- | `ref` | Ref(s) to merge with the consumer's ref |
150
+ | Option | Description |
151
+ | ----------- | ------------------------------------------------------------------ |
152
+ | `baseProps` | Base props applied to the element |
153
+ | `props` | Consumer-provided props (typically forwarded from component props) |
154
+ | `ref` | Ref(s) to merge with the consumer's ref |
155
155
 
156
156
  ### `ComponentProps<T, S>`
157
157
 
@@ -170,7 +170,7 @@ type ComponentProps<T extends ElementType, S> = BaseComponentProps<T> & {
170
170
 
171
171
  ```ts
172
172
  type ClassNameResolver<S> =
173
- | ((state: S, defaultClassName?: string) => string | undefined)
173
+ | ((state: S, baseClassName?: string) => string | undefined)
174
174
  | string
175
175
  | undefined
176
176
  ```
@@ -179,7 +179,7 @@ type ClassNameResolver<S> =
179
179
 
180
180
  ```ts
181
181
  type StyleResolver<S> =
182
- | ((state: S, defaultStyle?: CSSProperties) => CSSProperties | undefined)
182
+ | ((state: S, baseStyle?: CSSProperties) => CSSProperties | undefined)
183
183
  | CSSProperties
184
184
  | undefined
185
185
  ```
package/dist/types.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { ComponentPropsWithRef, CSSProperties, ElementType, ReactNode } from 'react';
2
- export type ClassNameResolver<State> = ((state: State, defaultClassName?: string) => string | undefined) | string | undefined;
3
- export type StyleResolver<State> = ((state: State, defaultStyle?: CSSProperties) => CSSProperties | undefined) | CSSProperties | undefined;
2
+ export type ClassNameResolver<State> = ((state: State, baseClassName?: string) => string | undefined) | string | undefined;
3
+ export type StyleResolver<State> = ((state: State, baseStyle?: CSSProperties) => CSSProperties | undefined) | CSSProperties | undefined;
4
4
  export type Renderer<S> = (state: S, props: React.HTMLAttributes<any> & {
5
5
  ref?: React.Ref<any> | undefined;
6
6
  }) => ReactNode;
@@ -7,12 +7,28 @@ export type ComponentProps<T extends ElementType, S> = BaseComponentProps<T> & {
7
7
  render?: Renderer<S> | JSX.Element;
8
8
  };
9
9
  export interface UseRenderOptions<T extends ElementType, S> {
10
- defaultProps?: React.ComponentProps<T> & DataAttributes;
10
+ baseProps?: React.ComponentProps<T> & DataAttributes;
11
11
  props?: ComponentProps<T, S> & DataAttributes;
12
12
  ref?: React.Ref<any> | (React.Ref<any> | undefined)[];
13
13
  }
14
14
  /**
15
- * Hook for enabling a render prop in custom components. Designed to be used by component libraries as an implementation detail
16
- * in providing a way to override a component's default rendered element.
15
+ * Hook for rendering elements with render prop support, prop merging, and state-driven className/style resolution.
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * import { useRender, ComponentProps } from '@diskette/use-render'
20
+ *
21
+ * type State = { disabled: boolean; loading: boolean }
22
+ * type ButtonProps = ComponentProps<'button', State>
23
+ *
24
+ * function Button(props: ButtonProps) {
25
+ * const state: State = { disabled: props.disabled ?? false, loading: false }
26
+ * return useRender('button', state, { props })
27
+ * }
28
+ *
29
+ * // Usage:
30
+ * <Button className={(state) => state.disabled && 'opacity-50'} />
31
+ * <Button render={<a href="#" />} />
32
+ * ```
17
33
  */
18
34
  export declare function useRender<T extends ElementType, S>(tag: T, state: S, options: UseRenderOptions<T, S>): ReactNode;
@@ -2,24 +2,40 @@ import { cloneElement, createElement, isValidElement } from 'react';
2
2
  import { useComposedRef } from "./use-composed-ref.js";
3
3
  import { isFunction, isString, mergeProps, resolveClassName, resolveStyle, } from "./utils.js";
4
4
  /**
5
- * Hook for enabling a render prop in custom components. Designed to be used by component libraries as an implementation detail
6
- * in providing a way to override a component's default rendered element.
5
+ * Hook for rendering elements with render prop support, prop merging, and state-driven className/style resolution.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * import { useRender, ComponentProps } from '@diskette/use-render'
10
+ *
11
+ * type State = { disabled: boolean; loading: boolean }
12
+ * type ButtonProps = ComponentProps<'button', State>
13
+ *
14
+ * function Button(props: ButtonProps) {
15
+ * const state: State = { disabled: props.disabled ?? false, loading: false }
16
+ * return useRender('button', state, { props })
17
+ * }
18
+ *
19
+ * // Usage:
20
+ * <Button className={(state) => state.disabled && 'opacity-50'} />
21
+ * <Button render={<a href="#" />} />
22
+ * ```
7
23
  */
8
24
  export function useRender(tag, state, options) {
9
25
  // Workarounds for getting the prop objects to be typed. But should still be ok as the properties we need is common to all elements
10
- const defaultProps = options.defaultProps ?? {};
26
+ const baseProps = options.baseProps ?? {};
11
27
  const props = (options.props ?? {});
12
- const { className: defaultClassName, style: defaultStyle, children: defaultChildren, ...defaults } = defaultProps;
28
+ const { className: baseClassName, style: baseStyle, children: baseChildren, ...base } = baseProps;
13
29
  const { className, style, children, ref, render, ...rest } = props ?? {};
14
- const resolvedClassName = resolveClassName(defaultClassName, className, state);
15
- const resolvedStyle = resolveStyle(defaultStyle, style, state);
30
+ const resolvedClassName = resolveClassName(baseClassName, className, state);
31
+ const resolvedStyle = resolveStyle(baseStyle, style, state);
16
32
  const refs = Array.isArray(options.ref)
17
33
  ? [ref, ...options.ref]
18
34
  : [ref, options.ref];
19
35
  const mergedRef = useComposedRef(refs);
20
36
  // Another workaround for getting component props typed
21
37
  const resolvedProps = {
22
- ...mergeProps(defaults, rest),
38
+ ...mergeProps(base, rest),
23
39
  ref: mergedRef,
24
40
  };
25
41
  if (isString(resolvedClassName)) {
@@ -40,5 +56,5 @@ export function useRender(tag, state, options) {
40
56
  children: resolvedChildren,
41
57
  });
42
58
  }
43
- return createElement(tag, resolvedProps, resolvedChildren ?? defaultChildren);
59
+ return createElement(tag, resolvedProps, resolvedChildren ?? baseChildren);
44
60
  }
@@ -8,7 +8,7 @@ function createBtn(defaultProps) {
8
8
  const state = { isActive };
9
9
  return useRender('button', state, {
10
10
  props,
11
- defaultProps: {
11
+ baseProps: {
12
12
  onClick: () => setIsActive((cur) => !cur),
13
13
  ...defaultProps,
14
14
  },
@@ -33,9 +33,9 @@ describe('useRender', () => {
33
33
  await button.click();
34
34
  await expect.element(button).toHaveClass('active');
35
35
  });
36
- test('className function receives defaultClassName as second argument', async () => {
36
+ test('className function receives baseClassName as second argument', async () => {
37
37
  const Button = createBtn({ className: 'btn-default' });
38
- const { getByRole } = await render(<Button className={(_state, defaultClassName) => `custom ${defaultClassName ?? ''}`}>
38
+ const { getByRole } = await render(<Button className={(_state, baseClassName) => `custom ${baseClassName ?? ''}`}>
39
39
  Click
40
40
  </Button>);
41
41
  const button = getByRole('button');
@@ -69,10 +69,10 @@ describe('useRender', () => {
69
69
  await button.click();
70
70
  await expect.element(button).toHaveStyle({ backgroundColor: 'green' });
71
71
  });
72
- test('style function receives defaultStyle as second argument', async () => {
72
+ test('style function receives baseStyle as second argument', async () => {
73
73
  const Button = createBtn({ style: { padding: '10px' } });
74
- const { getByRole } = await render(<Button style={(_state, defaultStyle) => ({
75
- ...defaultStyle,
74
+ const { getByRole } = await render(<Button style={(_state, baseStyle) => ({
75
+ ...baseStyle,
76
76
  color: 'blue',
77
77
  })}>
78
78
  Click
@@ -188,7 +188,7 @@ describe('useRender', () => {
188
188
  const [isActive, setIsActive] = useState(false);
189
189
  return useRender('button', { isActive }, {
190
190
  props,
191
- defaultProps: {
191
+ baseProps: {
192
192
  onClick: (_e) => {
193
193
  setIsActive((cur) => !cur);
194
194
  return defaultHandler();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@diskette/use-render",
3
3
  "type": "module",
4
- "version": "0.3.0",
4
+ "version": "0.4.0",
5
5
  "exports": "./dist/index.js",
6
6
  "files": [
7
7
  "dist"