From 57940eaff988b518ef50405a15617fcf1eb5e09a Mon Sep 17 00:00:00 2001 From: Alexandre Asselin Date: Fri, 6 Dec 2024 10:08:49 -0500 Subject: [PATCH 1/9] temp update to slots.mdx --- .../content/components/concepts/slots.mdx | 133 +++++++++++++++++- 1 file changed, 131 insertions(+), 2 deletions(-) diff --git a/apps/docs/content/components/concepts/slots.mdx b/apps/docs/content/components/concepts/slots.mdx index 4581760fa..5045c29be 100644 --- a/apps/docs/content/components/concepts/slots.mdx +++ b/apps/docs/content/components/concepts/slots.mdx @@ -2,26 +2,155 @@ title: Slots description: This page describes how Hopper components include predefined layouts that you can insert elements into via slots. Slots are named areas in a component that receive children and provide style and layout for them. order: 7 -status: WIP --- -This page will be inspired by https://react-spectrum.adobe.com/react-aria/advanced.html#slots but the focus of the page should be slots, not the context like this page. +_Since Hopper components are designed on top of React Aria, this article is heavily inspired by the [Advanced Customization article](https://react-spectrum.adobe.com/react-aria/advanced.html#slots) in React-Aria's documentation._ ## Introduction +The Hopper component API is designed around composition. Components are reused between patterns to build larger composite components. For example, there is no dedicated NumberFieldIncrementButton or SelectPopover component. Instead, the standalone Button and Popover components are reused within NumberField and Select. This reduces the amount of duplicate styling code you need to write and maintain, and provides powerful composition capabilities you can use in your own components. + + + + + + + + +Slots in Hopper are named areas within a component where developers can insert content. They make it easier to create flexible and reusable components while keeping layouts accessible and consistent. Instead of using only children for content, slots act as specific placeholders that clearly define where each piece of content goes. + +Hopper builds on React Aria's context-based design to make working with slots simple and efficient. This approach gives developers more control over how components are customized and ensures they follow accessibility best practices. This guide explains how slots work in Hopper, how they use contexts, and how to create or extend components with them. + ## Contexts +All Hopper Components (and React Aria Components) export a corresponding context that can be used to send props to them from a parent element. This enables you to build your own compositional APIs similar to those found in Hopper itself +You can send any prop or ref via context that you could pass to the corresponding component. The local props and ref on the component are merged with the ones passed via context, with the local props taking precedence. + + + + + + + + + + + + + + + + + + + + + + + + + + + +Here is an example of how contexts can be used to send props to a Button component: + +// TODO extract in a file +```tsx +import { ButtonContext, Inline } from "@hopper-ui/components"; +import { ReactNode } from "react"; + +interface CustomButtonGroupProps { + children?: ReactNode, + isDisabled?: boolean +} + +function CustomButtonGroup({ children, isDisabled }: CustomButtonGroupProps) { + return ( + + + {children} + + + ); +} + + + + + +``` + +Using the Context Provider, you can also target specific slots within a component. This allows you to send different props to different slots within the same component. + +The following example demonstrates how to target specific slots within a component: + +// TODO extract in a file +```tsx +import { ButtonContext, Inline } from "@hopper-ui/components"; +import { ReactNode, useState } from "react"; + +interface StepperProps { + children?: ReactNode +} + +function Stepper({ children }: StepperProps) { + let [value, setValue] = useState(0); + + return ( + setValue(value + 1) + }, + decrement: { + onPress: () => setValue(value - 1) + } + } + }}> + {children} + + ); +} + + + + + +``` + ### Default slot +TODO: TBD + ### Consuming contexts +Contexts play a critical role in the interaction between the parent component and its slots. Hopper uses two main mechanisms for consuming contexts: `useContextProps` and `useSlottedContext`. + #### useContextProps +`useContextProps` is a utility for passing context values (e.g., event handlers, accessibility attributes) to child elements within slots. This ensures that the children behave appropriately based on the parent component's state. + +TODO : Add example + #### useSlottedContext +`useSlottedContext` helps distinguish between multiple slots in a single component. It works by identifying and passing the correct attributes to child components based on their assigned `slot` prop. + +TODO : Add example + ## Creating Custom Components +Hopper's flexibility allows developers to create custom components that either extend existing contexts or define new ones. + #### using existing context +You can reuse Hopper's built-in contexts to create custom components. This approach ensures that your custom components remain consistent with Hopper's design and accessibility standards. + +TODO : Add example + #### using new contexts +For more advanced use cases, you can define new contexts and provide your own slots. This allows you to build entirely custom components while leveraging Hopper's core principles. + +TODO : Add example From 777805eb666464f6dd9a38797e0fbfb33632785b Mon Sep 17 00:00:00 2001 From: Alexandre Asselin Date: Tue, 10 Dec 2024 14:33:15 -0500 Subject: [PATCH 2/9] add examples to the slots.mdx --- .../content/components/concepts/slots.mdx | 181 ++++++++---------- apps/docs/next.config.js | 1 + packages/components/.eslintrc.json | 3 +- .../docs/slots/custom-patterns.tsx | 15 ++ .../docs/slots/default-slot.tsx | 21 ++ .../src/HopperProvider/docs/slots/slots.tsx | 23 +++ packages/components/src/index.ts | 2 +- 7 files changed, 141 insertions(+), 105 deletions(-) create mode 100644 packages/components/src/HopperProvider/docs/slots/custom-patterns.tsx create mode 100644 packages/components/src/HopperProvider/docs/slots/default-slot.tsx create mode 100644 packages/components/src/HopperProvider/docs/slots/slots.tsx diff --git a/apps/docs/content/components/concepts/slots.mdx b/apps/docs/content/components/concepts/slots.mdx index 5045c29be..35a6cf9b7 100644 --- a/apps/docs/content/components/concepts/slots.mdx +++ b/apps/docs/content/components/concepts/slots.mdx @@ -8,149 +8,124 @@ _Since Hopper components are designed on top of React Aria, this article is heav ## Introduction -The Hopper component API is designed around composition. Components are reused between patterns to build larger composite components. For example, there is no dedicated NumberFieldIncrementButton or SelectPopover component. Instead, the standalone Button and Popover components are reused within NumberField and Select. This reduces the amount of duplicate styling code you need to write and maintain, and provides powerful composition capabilities you can use in your own components. - - - - - - +The Hopper component API is designed around composition. Components are reused between patterns to build larger composite components. For example, there is no dedicated `NumberFieldIncrementButton` or `SelectPopover` component. Instead, the standalone [Button](../buttons/Button) and [Popover](../overlays/Popover) components are reused within [NumberField](../forms/NumberField) and [Select](../pickers/Select). This reduces the amount of duplicate styling code you need to write and maintain, and provides powerful composition capabilities you can use in your own components. +```tsx + + + + +``` Slots in Hopper are named areas within a component where developers can insert content. They make it easier to create flexible and reusable components while keeping layouts accessible and consistent. Instead of using only children for content, slots act as specific placeholders that clearly define where each piece of content goes. Hopper builds on React Aria's context-based design to make working with slots simple and efficient. This approach gives developers more control over how components are customized and ensures they follow accessibility best practices. This guide explains how slots work in Hopper, how they use contexts, and how to create or extend components with them. -## Contexts - -All Hopper Components (and React Aria Components) export a corresponding context that can be used to send props to them from a parent element. This enables you to build your own compositional APIs similar to those found in Hopper itself -You can send any prop or ref via context that you could pass to the corresponding component. The local props and ref on the component are merged with the ones passed via context, with the local props taking precedence. - - - - - - +## Custom patterns +Each Hopper exports a corresponding context that you can use to build your own compositional APIs similar to the built-in components. You can send any prop or ref via context that you could pass to the corresponding component. The local props and ref on the component are merged with the ones passed via context, with the local props taking precedence (following the rules documented in [mergeProps](https://react-spectrum.adobe.com/react-aria/mergeProps.html)). +This example shows a `FieldGroup` component that renders a group of text fields. The entire group can be marked as disabled via the isDisabled prop, which is passed to all child text fields via the TextFieldContext provider. + +Any `TextField` component you place inside a `FieldGroup` will automatically receive the `isDisabled` prop from the group, including those that are deeply nested inside other components. +```tsx + + + + +``` +## Slots +Some patterns include multiple instances of the same component. These use the `slot` prop to distinguish each instance. Slots are named children within a component that can receive separate behaviors and [styles](./styling.mdx). Separate props can be sent to slots by providing an object with keys for each slot name to the component's context provider. +This example shows a `Stepper` component with slots for its increment and decrement buttons. + +```tsx + + + + +``` +{/* TODO: uncomment this when the anatomy section is done */} +{/* The slots provided by each built-in Hopper component are shown in the Anatomy section of their documentation. */} +### Default slot +The default slot is used to provide props to a component without specifying a slot name. This approach allows you to assign a default slot to a component for its default use case and enables you to specify a slot name for a specific use case. +This example shows a custom component that passes a specific class name to a standard button child and to a button child with a slot named "end". + +```tsx + + {/* Consumes the props passed to the default slot */} + + + + + {/* Consumes the props passed to the "end" slot */} + + +``` +## Consuming contexts +You can also consume from contexts provided by Hopper components in your own custom components. This allows you to replace a component used as part of a larger pattern with a custom implementation. For example, you could consume from `LabelContext` in an existing styled label component to make it compatible with Hopper Components. +### useContextProps -Here is an example of how contexts can be used to send props to a Button component: +The `useContextProps` hook merges the local props and ref with the ones provided via context by a parent component. The local props always take precedence over the context values (following the rules documented in [mergeProps](https://react-spectrum.adobe.com/react-aria/mergeProps.html)). `useContextProps` supports the slot prop to indicate which value to consume from context. -// TODO extract in a file ```tsx -import { ButtonContext, Inline } from "@hopper-ui/components"; -import { ReactNode } from "react"; - -interface CustomButtonGroupProps { - children?: ReactNode, - isDisabled?: boolean -} - -function CustomButtonGroup({ children, isDisabled }: CustomButtonGroupProps) { - return ( - - - {children} - - - ); -} - - - - - +import { type LabelProps, LabelContext, useContextProps } from "@hopper-ui/components"; +import { forwardRef } from "react"; + +export const MyCustomLabel = forwardRef( + (props: LabelProps, ref: React.ForwardedRef) => { + // Merge the local props and ref with the ones provided via context. + [props, ref] = useContextProps(props, ref, LabelContext); + + // ... your existing Label component + return