diff --git a/docs/blocks/attributes/_alignment.md b/docs/blocks/attributes/_alignment.md deleted file mode 100644 index f0a3838d..00000000 --- a/docs/blocks/attributes/_alignment.md +++ /dev/null @@ -1,19 +0,0 @@ -A `string` for defining the text alignment within the text block, both vertically and horizontally. Can be 1 or 2 values, separated by a space. e.g. `top` or `top start`. - -![Showing all the alignment options](../../assets/blocks/docs-blocks-stacks-alignment.png) - -#### Vertical alignment - -- `top`: The default if the attribute is not used. -- `middle` -- `bottom` - -Note: If vertical alignment is not used in a `hstack`, then the content will stretch vertically. - -#### Horizontal alignment - -- `start`: The default if the attribute is not used. -- `center` -- `end` - -Note: If horizontal alignment is not used in a `vstack`, then the content will stretch horizontally. diff --git a/docs/blocks/attributes/_color.md b/docs/blocks/attributes/_color.md deleted file mode 100644 index 6624d328..00000000 --- a/docs/blocks/attributes/_color.md +++ /dev/null @@ -1,7 +0,0 @@ -A `string` for setting the color to any [Blocks-compatible color value](../colors.mdx). Possible values: - -- HTML color names: `red` -- HEX: `#ff4500` -- RGB: `rgb(255, 69, 0)` -- RGBA: `rgba(255, 69, 0, 0.5)` -- RPL Token: `neutral-content` diff --git a/docs/blocks/attributes/_grow.md b/docs/blocks/attributes/_grow.md deleted file mode 100644 index b947dddf..00000000 --- a/docs/blocks/attributes/_grow.md +++ /dev/null @@ -1,6 +0,0 @@ -A `boolean` that allows the block to expand to fill all available space along the axis of its parent container if set to true. Possible values: - -- `true` -- `false`: The default if the attribute is not used. - -Has no effect inside a ``. diff --git a/docs/blocks/attributes/_height.mdx b/docs/blocks/attributes/_height.mdx deleted file mode 100644 index 93e00b32..00000000 --- a/docs/blocks/attributes/_height.mdx +++ /dev/null @@ -1,8 +0,0 @@ -import PercentDimensionWarning from './_percent_dimension_warning.mdx'; - -A `string` that sets a height of the block. Possible values: - -- Absolute: `100px` -- Relative: `50%` (The default if the unit is not specified, i.e. `height="50"`) - - diff --git a/docs/blocks/attributes/_maxHeight.md b/docs/blocks/attributes/_maxHeight.md deleted file mode 100644 index f2fadf20..00000000 --- a/docs/blocks/attributes/_maxHeight.md +++ /dev/null @@ -1 +0,0 @@ -Prevents the used value of `height` from becoming larger than the maximum height if set. diff --git a/docs/blocks/attributes/_maxWidth.md b/docs/blocks/attributes/_maxWidth.md deleted file mode 100644 index 9720e519..00000000 --- a/docs/blocks/attributes/_maxWidth.md +++ /dev/null @@ -1 +0,0 @@ -Prevents the used value of `width` from becoming larger than the maximum width if set. diff --git a/docs/blocks/attributes/_minHeight.md b/docs/blocks/attributes/_minHeight.md deleted file mode 100644 index c091cdd2..00000000 --- a/docs/blocks/attributes/_minHeight.md +++ /dev/null @@ -1 +0,0 @@ -Prevents the used value of `height` from becoming smaller than the minimum height if set. diff --git a/docs/blocks/attributes/_minWidth.md b/docs/blocks/attributes/_minWidth.md deleted file mode 100644 index 52d1c55e..00000000 --- a/docs/blocks/attributes/_minWidth.md +++ /dev/null @@ -1 +0,0 @@ -Prevents the used value of `width` from becoming smaller than the minimum width if set. diff --git a/docs/blocks/attributes/_onPress.md b/docs/blocks/attributes/_onPress.md deleted file mode 100644 index 64207407..00000000 --- a/docs/blocks/attributes/_onPress.md +++ /dev/null @@ -1 +0,0 @@ -Attaches an event handler for press events on the block. diff --git a/docs/blocks/attributes/_percent_dimension_examples.mdx b/docs/blocks/attributes/_percent_dimension_examples.mdx deleted file mode 100644 index dc00b58a..00000000 --- a/docs/blocks/attributes/_percent_dimension_examples.mdx +++ /dev/null @@ -1,95 +0,0 @@ -**Valid dimensions** - -```tsx - - - -``` - -Z stack dimensions are valid because it satisfies the following rules: - -- Height: Parent block must have the height defined. - -- Width: Parent block must have the width defined. - ---- - -```tsx - - - - - -``` - -Z stack dimensions are valid because it satisfies the following rules: - -- Height: Parent block must have the height defined. - -- Width: Parent block must be stretching horizontally in a `vstack`. - The parent `hstack` is stretching horizontally in the `vstack`, because the `vstack` does not have any alignment indicated on - the cross axis. (See [alignment](../blocks/stacks#alignment)) - ---- - -```tsx - - - - - -``` - -Z stack dimensions are valid because it satisfies the following rules: - -- Height: Parent block must be growing vertically in a `vstack`. - The parent `hstack` is growing vertically along the main axis of the `vstack` because it has grow indicated. - (See [grow](../blocks/stacks#grow)) - -- Width: Parent block must have the width defined. - ---- - -**Invalid dimensions** - -```tsx - - - - - -``` - -Z stack dimensions are invalid because it does not satisfy any of the rules: - -- Height: `hstack` is not growing vertically because it does not indicate `grow`. - Therefore, there is no parent height for the `zstack` to base its `height=100%` on. -- Width: `hstack` is not stretching horizontally because because the `vstack` indicates an alignment. - Therefore, there is no parent width for the `zstack` to based its `width=100%` on. Width will be omitted, because the relative width is invalid and the parent is an `hstack`. - ---- - -**Inferred dimensions** - -```tsx - - - - - -``` - -In this example, the `hstack` infers its height definition from the `vstack` (height = 300px), so -the `zstack` dimensions are **valid**. - ---- - -```tsx - - - - - -``` - -In this example, the `vstack` does not specify width, so there’s nothing for the `hstack` to infer. The width of the `zstack` is **invalid**. Therefore, the width will be omitted. diff --git a/docs/blocks/attributes/_percent_dimension_warning.mdx b/docs/blocks/attributes/_percent_dimension_warning.mdx deleted file mode 100644 index 551f7394..00000000 --- a/docs/blocks/attributes/_percent_dimension_warning.mdx +++ /dev/null @@ -1,26 +0,0 @@ -import PercentDimensionExamples from './_percent_dimension_examples.mdx'; - -export const widthStretch = 'horizontally in a vstack'; -export const heightStretch = 'vertically in a hstack'; -export const widthGrow = 'horizontally in a hstack'; -export const heightGrow = 'vertically in a vstack'; - -A block's {props.dimension} will be omitted if the relative {props.dimension} is invalid and the parent is a {(props.dimension == "width") && ("hstack")}{(props.dimension != "width") && ("vstack")}. -If the parent is a `zstack`, then an invalid {props.dimension} will always be omitted. - -A relative {props.dimension} is valid if it meets one of the following rules: - -- Parent block must have a defined {props.dimension} or infer its {props.dimension} from an ancestor. -- Parent block must stretch {(props.dimension == "width") && (widthStretch)}{(props.dimension != "width") && (heightStretch)} - (see [alignment](../blocks/stacks#alignment)). -- Parent block must [grow](../blocks/stacks#grow) {(props.dimension == "width") && (widthGrow)}{(props.dimension != "width") && (heightGrow)}. - -:::note -Relative {props.dimension} can only grow or stretch in a constrained dimension. The nearest ancestor without a definition will break -the chain, and the block's {props.dimension} will be omitted. -::: - -
- Relative (Percent) Examples - -
diff --git a/docs/blocks/attributes/_width.mdx b/docs/blocks/attributes/_width.mdx deleted file mode 100644 index e41bff80..00000000 --- a/docs/blocks/attributes/_width.mdx +++ /dev/null @@ -1,8 +0,0 @@ -import PercentDimensionWarning from './_percent_dimension_warning.mdx'; - -A `string` that sets a width of the block. Possible values: - -- Absolute: `100px` -- Relative: `50%` (The default if the unit is not specified, i.e. `width="50"`) - - diff --git a/docs/blocks/button.mdx b/docs/blocks/button.mdx deleted file mode 100644 index ee521f55..00000000 --- a/docs/blocks/button.mdx +++ /dev/null @@ -1,183 +0,0 @@ -import AttributeMinHeight from './attributes/_minHeight.md'; -import AttributeMaxHeight from './attributes/_maxHeight.md'; -import AttributeMinWidth from './attributes/_minWidth.md'; -import AttributeMaxWidth from './attributes/_maxWidth.md'; -import AttributeGrow from './attributes/_grow.md'; -import AttributeOnPress from './attributes/_onPress.md'; -import AttributeWidth from './attributes/_width.mdx'; -import AttributeHeight from './attributes/_height.mdx'; - -# Button - -The ` -``` - -A button with no icon - -```tsx - -``` - -### `size` - -Determines the button size. Possible values: - -- `small`: 32px -- `medium`: 40px. The default if the attribute is not used. -- `large`: 48px - -![Button sizes](../assets/blocks/docs-blocks-button-sizes.png) - -#### Example - -A small button - -```tsx - -``` - -### `appearance` - -Sets the button style. Possible values: - -- `primary` -- `secondary`: The default if the attribute is not used. -- `plain` -- `bordered` -- `media` -- `destructive` -- `caution` -- `success` - -![Button appearance](../assets/blocks/docs-blocks-button-appearance.png) - -#### Example - -A primary button - -```tsx - -``` - -### `disabled` - -Disables the button if set to true, preventing interactions. - -![Disabled buttons](../assets/blocks/docs-blocks-button-disabled.png) - -#### Examples - -A disabled button button - -```tsx - -``` - -A disabled button button - -```tsx - -``` - -### `grow` - - - -#### Example - -A growing button - -```tsx - -``` - -### `width` - - - -### `minWidth` - - - -### `maxWidth` - - - -### `height` - - - -### `minHeight` - - - -### `maxHeight` - - - -## Functions - -### `onPress` - - - -#### Examples - -```tsx - -``` - -## Notes - - - -## Examples - -A button that increments a counter - -```tsx -import { Devvit, useState } from '@devvit/public-api'; - -// addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. View the announcement below this example. -Devvit.addCustomPostType({ - name: 'Say Hello', - render: (context) => { - const [votes, setCounter] = useState(0); - return ( - - - - - - ); - }, -}); -``` -[View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) \ No newline at end of file diff --git a/docs/blocks/colors.mdx b/docs/blocks/colors.mdx deleted file mode 100644 index 26c53c55..00000000 --- a/docs/blocks/colors.mdx +++ /dev/null @@ -1,297 +0,0 @@ -# Colors - -Colors in Blocks can be defined by any of the following formats. - -## Supported color formats - -### HEX - -Specify a color in hexadecimal format using `#RRGGBB`, where RR represents red, GG represents green, and BB represents blue. Ensure that the values for each component fall within the range of 00 to FF. - -- Example: `#ff4500` - -### RGB - -Specify an RGB color using the `rgb()` function with the syntax `rgb(red, green, blue)`. Each parameter (red, green, and blue) determines color intensity and can be an integer ranging from 0 to 255. For instance, `rgb(0,0,255)` renders as blue, as the blue parameter is set to its maximum value (255) while the others are set to 0. - -- Example: `rgb(255, 69, 0)` - -### RGBA - -Extend RGB color values with transparency using RGBA with the `rgba()` function with the syntax `rgba(red, green, blue, alpha)`. The alpha parameter, ranging from 0.0 (fully transparent) to 1.0 (fully opaque), determines the object's opacity. - -- Example: `rgba(255, 69, 0, 0.5)` - -### HTML Color Names - -There are ~150 standard color names defined. These include common names like "red", "blue', and "green". You can see the full list in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/CSS/named-color). - -Examples: - -- `red` -- `white` -- `blue` - -### RPL Tokens - -A set of color tokens from the Reddit Product Language (RPL). - -#### RPL Semantic Tokens - -- A semantic color token that changes based on light and dark mode. An alias to 2 primive colors. -- Example: `neutral-background` - -#### Primitives - -- A static color that does not change between light and dark mode. -- Example: `orangered-500` - ---- - -## RPL Semantic Tokens - -Semantic color tokens and their light/dark mode color aliases. - -### Global - -| Token | Light mode primitive | Dark mode primitive | -| ------------------ | -------------------- | ------------------- | -| `global-black` | Global-Black | Global-Black | -| `global-white` | Global-White | Global-White | -| `global-orangered` | Global-Orangered | Global-Orangered | -| `global-online` | KiwiGreen-400 | KiwiGreen-400 | -| `global-offline` | PureGray-500 | PureGray-500 | -| `global-admin` | Orangered-500 | Orangered-500 | -| `global-moderator` | KiwiGreen-500 | KiwiGreen-500 | -| `global-self ` | MintGreen-500 | MintGreen-500 | -| `global-coins` | Yellow-400 | Yellow-400 | -| `global-stars` | Yellow-500 | Yellow-400 | -| `global-live` | Global-Orangered | Global-Orangered | -| `global-nsfw` | SakuraPink-500 | SakuraPink-500 | - -### Neutral - -| Token | Light mode primitive | Dark mode primitive | -| ------------------------------ | -------------------- | ------------------- | -| `neutral-content-strong` | PureGray-850 | PureGray-100 | -| `neutral-content` | PureGray-650 | PureGray-350 | -| `neutral-content-weak ` | PureGray-525 | PureGray-450 | -| `neutral-background-strong` | Global-White | PureGray-850 | -| `neutral-background` | Global-White | PureGray-900 | -| `neutral-background-weak` | PureGray-50 | PureGray-950 | -| `neutral-background-hovered` | CoolGray-50 | CoolGray-850 | -| `neutral-background-selected` | PureGray-150 | PureGray-750 | -| `neutral-background-pinned` | Global-White | CoolGray-900 | -| `neutral-background-container` | CoolGray-50 | CoolGray-850 | -| `neutral-border-strong` | PureGray-850 | PureGray-100 | -| `neutral-border-medium` | Black 50% | White 50% | -| `neutral-border` | Black 20% | White 20% | -| `neutral-border-weak` | Black 10% | White 10% | - -### Primary - -| Token | Light mode primitive | Dark mode primitive | -| ------------------------------- | -------------------- | ------------------- | -| `primary-plain` | AlienBlue-600 | AlienBlue-400 | -| `primary-plain-hovered` | AlienBlue-700 | AlienBlue-300 | -| `primary-plain-visited` | BerryPurple-600 | BerryPurple-400 | -| `primary-onBackground` | Global-White | Global-White | -| `primary-onBackground-selected` | Global-White | Global-Black | -| `primary-background` | AlienBlue-600 | AlienBlue-600 | -| `primary-background-hovered` | AlienBlue-700 | AlienBlue-500 | -| `primary-background-selected` | AlienBlue-800 | AlienBlue-400 | -| `primary-border` | AlienBlue-600 | AlienBlue-400 | -| `primary-border-hovered` | AlienBlue-700 | AlienBlue-300 | - -### Secondary - -| Token | Light mode primitive | Dark mode primitive | -| ------------------------------- | -------------------- | ------------------- | -| `secondary-plain` | PureGray-850 | PureGray-100 | -| `secondary-plain-weak` | PureGray-525 | PureGray-450 | -| `secondary-plain-hovered` | Global-Black | Global-White | -| `secondary-onBackground` | Global-Black | Global-White | -| `secondary-background` | PureGray-150 | PureGray-750 | -| `secondary-background-hovered` | PureGray-200 | PureGray-700 | -| `secondary-background-selected` | PureGray-300 | PureGray-600 | - -### Media - -| Token | Light mode primitive | Dark mode primitive | -| ----------------------------- | -------------------- | ------------------- | -| `media-onBackground` | Global-White | Global-White | -| `media-onBackground-disabled` | White 25% | White 25% | -| `media-onBackground-weak` | CoolGray-200 | CoolGray-200 | -| `media-background` | Black 54% | Black 54% | -| `media-background-hovered` | Black 75% | Black 75% | -| `media-background-selected` | Black 75% | Black 75% | -| `media-border-selected` | Global-White | Global-White | - -### Danger - -| Token | Light mode primitive | Dark mode primitive | -| --------------------------- | -------------------- | ------------------- | -| `danger-plain` | Red-600 | Red-400 | -| `danger-plain-hovered` | Red-700 | Red-300 | -| `danger-onBackground` | Global-White | Global-White | -| `danger-background` | Red-500 | Red-600 | -| `danger-background-hovered` | Red-600 | Red-500 | -| `danger-background-weaker` | Red-200 | Red-800 | - -### Success - -| Token | Light mode primitive | Dark mode primitive | -| ---------------------------- | -------------------- | ------------------- | -| `success-plain` | KiwiGreen-600 | KiwiGreen-400 | -| `success-plain-hovered` | KiwiGreen-700 | KiwiGreen-300 | -| `success-onBackground` | Global-White | Global-White | -| `success-background` | KiwiGreen-500 | KiwiGreen-600 | -| `success-background-hovered` | KiwiGreen-600 | KiwiGreen-500 | - -### Caution - -| Token | Light mode primitive | Dark mode primitive | -| ---------------------------- | -------------------- | ------------------- | -| `caution-plain` | Yellow-600 | Yellow-400 | -| `caution-plain-hovered` | Yellow-700 | Yellow-300 | -| `caution-onBackground` | Global-White | Global-White | -| `caution-background` | Yellow-500 | Yellow-600 | -| `caution-background-hovered` | Yellow-500 | Yellow-300 | - -### Brand - -| Token | Light mode primitive | Dark mode primitive | -| -------------------------- | -------------------- | ------------------- | -| `brand-onBackground` | Global-White | Global-White | -| `brand-background` | Orangered-500 | Orangered-500 | -| `brand-background-hovered` | Orangered-600 | Orangered-600 | - -### Scrim - -| Token | Light mode primitive | Dark mode primitive | -| ------------------------- | -------------------- | ------------------- | -| `scrim-background-strong` | Black 80% | Black 80% | -| `scrim-background` | Black 60% | Black 60% | - -### Interactive - -| Token | Light mode primitive | Dark mode primitive | -| --------------------------------- | -------------------- | ------------------- | -| `interactive-content-disabled` | Black 25% | White 25% | -| `interactive-pressed` | Black 16% | White 16% | -| `interactive-background-disabled` | Global-Black 5% | Global-White 5% | -| `interactive-focused` | LightBlue-500 | LightBlue-500 | - -### Upvote - -| Token | Light mode primitive | Dark mode primitive | -| ------------------------------- | -------------------- | ------------------- | -| `upvote-plain` | Orangered-600 | Orangered-400 | -| `upvote-plain-weaker` | Brand-Orangered | Brand-Orangered | -| `upvote-plain-disabled` | Orangered-600 30% | Orangered-400 30% | -| `upvote-onBackground` | Global-White | Global-White | -| `upvote-background` | Orangered-500 | Orangered-500 | -| `upvote-background-hovered` | Orangered-600 | Orangered-600 | -| `upvote-background-disabled` | Orangered-500 30% | Orangered-500 30% | -| `upvote-onStrongScrim` | Orangered-400 | Orangered-400 | -| `upvote-onStrongScrim-weaker` | Brand-Orangered | Brand-Orangered | -| `upvote-onStrongScrim-disabled` | Orangered-400 30% | Orangered-400 30% | - -### Downvote - -| Token | Light mode primitive | Dark mode primitive | -| --------------------------------- | -------------------- | ------------------- | -| `downvote-plain` | Periwinkle-600 | Periwinkle-400 | -| `downvote-plain-weaker` | Periwinkle-500 | Periwinkle-500 | -| `downvote-plain-disabled` | Periwinkle-600 30% | Periwinkle-400 30% | -| `downvote-onBackground` | Global-White | Global-White | -| `downvote-background` | Periwinkle-500 | Periwinkle-600 | -| `downvote-background-hovered` | Periwinkle-600 | Periwinkle-600 | -| `downvote-background-disabled` | Periwinkle-500 30% | Periwinkle-500 30% | -| `downvote-onStrongScrim` | Periwinkle-400 | Periwinkle-400 | -| `downvote-onStrongScrim-weaker` | Periwinkle-500 | Periwinkle-500 | -| `downvote-onStrongScrim-disabled` | Periwinkle-400 30% | Periwinkle-400 30% | - ---- - -## RPL Color Primitives - -### CoolGray - -![CoolGray Colors](../assets/blocks/docs-blocks-colors-coolgray.png) - -`CoolGray-50`, `CoolGray-100`, `CoolGray-150`, `CoolGray-200`, `CoolGray-250`, `CoolGray-300`, `CoolGray-350`, `CoolGray-400`, `CoolGray-450`, `CoolGray-500`, `CoolGray-525`, `CoolGray-550`, `CoolGray-600`, `CoolGray-650`, `CoolGray-700`, `CoolGray-750`, `CoolGray-800`, `CoolGray-850`, `CoolGray-900`, `CoolGray-950` - -### PureGray - -![PureGray Colors](../assets/blocks/docs-blocks-colors-puregray.png) - -`PureGray-50`, `PureGray-100`, `PureGray-150`, `PureGray-200`, `PureGray-250`, `PureGray-300`, `PureGray-350`, `PureGray-400`, `PureGray-450`, `PureGray-500`, `PureGray-525`, `PureGray-550`, `PureGray-600`, `PureGray-650`, `PureGray-700`, `PureGray-750`, `PureGray-800`, `PureGray-850`, `PureGray-900`, `PureGray-950` - -### Orangered - -![Orangered Colors](../assets/blocks/docs-blocks-colors-orangered.png) - -`Orangered-50`, `Orangered-100`, `Orangered-200`, `Orangered-300`, `Orangered-400`, `Orangered-500`, `Orangered-600`, `Orangered-700`, `Orangered-800`, `Orangered-900`, `Orangered-950` - -### Red - -![Red Colors](../assets/blocks/docs-blocks-colors-red.png) - -`Red-50`, `Red-100`, `Red-200`, `Red-300`, `Red-400`, `Red-500`, `Red-600`, `Red-700`, `Red-800`, `Red-900`, `Red-950` - -### SakuraPink - -![SakuraPink Colors](../assets/blocks/docs-blocks-colors-sakurapink.png) - -`SakuraPink-50`, `SakuraPink-100`, `SakuraPink-200`, `SakuraPink-300`, `SakuraPink-400`, `SakuraPink-500`, `SakuraPink-600`, `SakuraPink-700`, `SakuraPink-800`, `SakuraPink-900`, `SakuraPink-950` - -### BerryPurple - -![BerryPurple Colors](../assets/blocks/docs-blocks-colors-berrypurple.png) -`BerryPurple-50`, `BerryPurple-100`, `BerryPurple-200`, `BerryPurple-300`, `BerryPurple-400`, `BerryPurple-500`, `BerryPurple-600`, `BerryPurple-700`, `BerryPurple-800`, `BerryPurple-900`, `BerryPurple-950` - -### Periwinkle - -![Periwinkle Colors](../assets/blocks/docs-blocks-colors-periwinkle.png) -`Periwinkle-50`, `Periwinkle-100`, `Periwinkle-200`, `Periwinkle-300`, `Periwinkle-400`, `Periwinkle-500`, `Periwinkle-600`, `Periwinkle-700`, `Periwinkle-800`, `Periwinkle-900`, `Periwinkle-950` - -### AlienBlue - -![AlienBlue Colors](../assets/blocks/docs-blocks-colors-alienblue.png) -`AlienBlue-50`, `AlienBlue-100`, `AlienBlue-200`, `AlienBlue-300`, `AlienBlue-400`, `AlienBlue-500`, `AlienBlue-600`, `AlienBlue-700`, `AlienBlue-800`, `AlienBlue-900`, `AlienBlue-950` - -### LightBlue - -![LightBlue Colors](../assets/blocks/docs-blocks-colors-lightblue.png) -`LightBlue-50`, `LightBlue-100`, `LightBlue-200`, `LightBlue-300`, `LightBlue-400`, `LightBlue-500`, `LightBlue-600`, `LightBlue-700`, `LightBlue-800`, `LightBlue-900`, `LightBlue-950` - -### MintGreen - -![MintGreen Colors](../assets/blocks/docs-blocks-colors-mintgreen.png) -`MintGreen-50`, `MintGreen-100`, `MintGreen-200`, `MintGreen-300`, `MintGreen-400`, `MintGreen-500`, `MintGreen-600`, `MintGreen-700`, `MintGreen-800`, `MintGreen-900`, `MintGreen-950` - -### KiwiGreen - -![KiwiGreen Colors](../assets/blocks/docs-blocks-colors-kiwigreen.png) -`KiwiGreen-50`, `KiwiGreen-100`, `KiwiGreen-200`, `KiwiGreen-300`, `KiwiGreen-400`, `KiwiGreen-500`, `KiwiGreen-600`, `KiwiGreen-700`, `KiwiGreen-800`, `KiwiGreen-900`, `KiwiGreen-950` - -### Lime - -![Lime Colors](../assets/blocks/docs-blocks-colors-lime.png) -`Lime-50`, `Lime-100`, `Lime-200`, `Lime-300`, `Lime-400`, `Lime-500`, `Lime-600`, `Lime-700`, `Lime-800`, `Lime-900`, `Lime-950` - -### Yellow - -![Yellow Colors](../assets/blocks/docs-blocks-colors-yellow.png) -`Yellow-50`, `Yellow-100`, `Yellow-200`, `Yellow-300`, `Yellow-400`, `Yellow-500`, `Yellow-600`, `Yellow-700`, `Yellow-800`, `Yellow-900`, `Yellow-950` - -### YellowOrange - -![YellowOrange Colors](../assets/blocks/docs-blocks-colors-yelloworange.png) -`YellowOrange-50`, `YellowOrange-100`, `YellowOrange-200`, `YellowOrange-300`, `YellowOrange-400`, `YellowOrange-500`, `YellowOrange-600`, `YellowOrange-700`, `YellowOrange-800`, `YellowOrange-900`, `YellowOrange-950` - -### Brown - -![Brown Colors](../assets/blocks/docs-blocks-colors-brown.png) -`Brown-50`, `Brown-100`, `Brown-200`, `Brown-300`, `Brown-400`, `Brown-500`, `Brown-600`, `Brown-700`, `Brown-800`, `Brown-900`, `Brown-950` diff --git a/docs/blocks/icon.mdx b/docs/blocks/icon.mdx deleted file mode 100644 index 63637ea7..00000000 --- a/docs/blocks/icon.mdx +++ /dev/null @@ -1,1426 +0,0 @@ -# Icons - -import IconTile from '@site/src/components/IconTile'; -import IconTileGrid from '@site/src/components/IconTileGrid'; - -import Icon3rdPartyFill from '../assets/icons/icon-3rd-party-fill.svg'; -import Icon3rdPartyOutline from '../assets/icons/icon-3rd-party-outline.svg'; -import IconActivityFill from '../assets/icons/icon-activity-fill.svg'; -import IconActivityOutline from '../assets/icons/icon-activity-outline.svg'; -import IconAddEmojiFill from '../assets/icons/icon-add-emoji-fill.svg'; -import IconAddEmojiOutline from '../assets/icons/icon-add-emoji-outline.svg'; -import IconAddFill24 from '../assets/icons/icon-add-fill-24.svg'; -import IconAddFill from '../assets/icons/icon-add-fill.svg'; -import IconAddMediaFill from '../assets/icons/icon-add-media-fill.svg'; -import IconAddMediaOutline from '../assets/icons/icon-add-media-outline.svg'; -import IconAddOutline24 from '../assets/icons/icon-add-outline-24.svg'; -import IconAddOutline from '../assets/icons/icon-add-outline.svg'; -import IconAddToFeedFill from '../assets/icons/icon-add-to-feed-fill.svg'; -import IconAddToFeedOutline from '../assets/icons/icon-add-to-feed-outline.svg'; -import IconAdminFill from '../assets/icons/icon-admin-fill.svg'; -import IconAdminOutline from '../assets/icons/icon-admin-outline.svg'; -import IconAdsFill from '../assets/icons/icon-ads-fill.svg'; -import IconAdsOutline from '../assets/icons/icon-ads-outline.svg'; -import IconAlignCenterFill from '../assets/icons/icon-align-center-fill.svg'; -import IconAlignCenterOutline from '../assets/icons/icon-align-center-outline.svg'; -import IconAlignLeftFill from '../assets/icons/icon-align-left-fill.svg'; -import IconAlignLeftOutline from '../assets/icons/icon-align-left-outline.svg'; -import IconAlignRightFill from '../assets/icons/icon-align-right-fill.svg'; -import IconAlignRightOutline from '../assets/icons/icon-align-right-outline.svg'; -import IconAllFill from '../assets/icons/icon-all-fill.svg'; -import IconAllOutline from '../assets/icons/icon-all-outline.svg'; -import IconAppearanceFill from '../assets/icons/icon-appearance-fill.svg'; -import IconAppearanceOutline from '../assets/icons/icon-appearance-outline.svg'; -import IconApproveFill from '../assets/icons/icon-approve-fill.svg'; -import IconApproveOutline from '../assets/icons/icon-approve-outline.svg'; -import IconArchivedFill from '../assets/icons/icon-archived-fill.svg'; -import IconArchivedOutline from '../assets/icons/icon-archived-outline.svg'; -import IconAspectRatioFill from '../assets/icons/icon-aspect-ratio-fill.svg'; -import IconAspectRatioOutline from '../assets/icons/icon-aspect-ratio-outline.svg'; -import IconAspectRectangleFill from '../assets/icons/icon-aspect-rectangle-fill.svg'; -import IconAspectRectangleOutline from '../assets/icons/icon-aspect-rectangle-outline.svg'; -import IconAttachFill from '../assets/icons/icon-attach-fill.svg'; -import IconAttachOutline from '../assets/icons/icon-attach-outline.svg'; -import IconAudienceFill from '../assets/icons/icon-audience-fill.svg'; -import IconAudienceOutline from '../assets/icons/icon-audience-outline.svg'; -import IconAudioFill from '../assets/icons/icon-audio-fill.svg'; -import IconAudioOutline from '../assets/icons/icon-audio-outline.svg'; -import IconAuthorFill from '../assets/icons/icon-author-fill.svg'; -import IconAuthorOutline from '../assets/icons/icon-author-outline.svg'; -import IconAutomodFill from '../assets/icons/icon-automod-fill.svg'; -import IconAutomodOutline from '../assets/icons/icon-automod-outline.svg'; -import IconAvatarStyleFill from '../assets/icons/icon-avatar-style-fill.svg'; -import IconAvatarStyleOutline from '../assets/icons/icon-avatar-style-outline.svg'; -import IconAwardFill from '../assets/icons/icon-award-fill.svg'; -import IconAwardOutline from '../assets/icons/icon-award-outline.svg'; -import IconBackFill24 from '../assets/icons/icon-back-fill-24.svg'; -import IconBackFill from '../assets/icons/icon-back-fill.svg'; -import IconBackOutline24 from '../assets/icons/icon-back-outline-24.svg'; -import IconBackOutline from '../assets/icons/icon-back-outline.svg'; -import IconBackupFill from '../assets/icons/icon-backup-fill.svg'; -import IconBackupOutline from '../assets/icons/icon-backup-outline.svg'; -import IconBanFill from '../assets/icons/icon-ban-fill.svg'; -import IconBanOutline from '../assets/icons/icon-ban-outline.svg'; -import IconBasketballColor24 from '../assets/icons/icon-basketball-color-24.svg'; -import IconBasketballFill24 from '../assets/icons/icon-basketball-fill-24.svg'; -import IconBasketballOutline24 from '../assets/icons/icon-basketball-outline-24.svg'; -import IconBestFill from '../assets/icons/icon-best-fill.svg'; -import IconBestOutline from '../assets/icons/icon-best-outline.svg'; -import IconBetaBinocularsFill from '../assets/icons/icon-beta-binoculars-fill.svg'; -import IconBetaBinocularsOutline from '../assets/icons/icon-beta-binoculars-outline.svg'; -import IconBetaCaretUpdownFill from '../assets/icons/icon-beta-caret-updown-fill.svg'; -import IconBetaCaretUpdownOutline from '../assets/icons/icon-beta-caret-updown-outline.svg'; -import IconBetaLatestFill from '../assets/icons/icon-beta-latest-fill.svg'; -import IconBetaLatestOutline from '../assets/icons/icon-beta-latest-outline.svg'; -import IconBetaPlanetFill from '../assets/icons/icon-beta-planet-fill.svg'; -import IconBetaPlanetOutline from '../assets/icons/icon-beta-planet-outline.svg'; -import IconBetaTalk01Outline from '../assets/icons/icon-beta-talk-01-outline.svg'; -import IconBetaTalk02Outline from '../assets/icons/icon-beta-talk-02-outline.svg'; -import IconBetaTalkAddFill from '../assets/icons/icon-beta-talk-add-fill.svg'; -import IconBetaTalkAddOutline from '../assets/icons/icon-beta-talk-add-outline.svg'; -import IconBetaTelescopeFill from '../assets/icons/icon-beta-telescope-fill.svg'; -import IconBetaTelescopeOutline from '../assets/icons/icon-beta-telescope-outline.svg'; -import IconBlockFill from '../assets/icons/icon-block-fill.svg'; -import IconBlockOutline from '../assets/icons/icon-block-outline.svg'; -import IconBlockchainFill from '../assets/icons/icon-blockchain-fill.svg'; -import IconBlockchainOutline from '../assets/icons/icon-blockchain-outline.svg'; -import IconBoldFill from '../assets/icons/icon-bold-fill.svg'; -import IconBoldOutline from '../assets/icons/icon-bold-outline.svg'; -import IconBotFill from '../assets/icons/icon-bot-fill.svg'; -import IconBotOutline from '../assets/icons/icon-bot-outline.svg'; -import IconBounceFill from '../assets/icons/icon-bounce-fill.svg'; -import IconBounceOutline from '../assets/icons/icon-bounce-outline.svg'; -import IconBrandAwarenessFill from '../assets/icons/icon-brand-awareness-fill.svg'; -import IconBrandAwarenessOutline from '../assets/icons/icon-brand-awareness-outline.svg'; -import IconBrowseFill from '../assets/icons/icon-browse-fill.svg'; -import IconBrowseOutline from '../assets/icons/icon-browse-outline.svg'; -import IconBrowserFill from '../assets/icons/icon-browser-fill.svg'; -import IconBrowserOutline from '../assets/icons/icon-browser-outline.svg'; -import IconCakeFill from '../assets/icons/icon-cake-fill.svg'; -import IconCakeOutline from '../assets/icons/icon-cake-outline.svg'; -import IconCalendarFill from '../assets/icons/icon-calendar-fill.svg'; -import IconCalendarOutline from '../assets/icons/icon-calendar-outline.svg'; -import IconCameraFill24 from '../assets/icons/icon-camera-fill-24.svg'; -import IconCameraFill from '../assets/icons/icon-camera-fill.svg'; -import IconCameraOutline24 from '../assets/icons/icon-camera-outline-24.svg'; -import IconCameraOutline from '../assets/icons/icon-camera-outline.svg'; -import IconCampaignFill from '../assets/icons/icon-campaign-fill.svg'; -import IconCampaignOutline from '../assets/icons/icon-campaign-outline.svg'; -import IconCaretDownFill from '../assets/icons/icon-caret-down-fill.svg'; -import IconCaretDownOutline from '../assets/icons/icon-caret-down-outline.svg'; -import IconCaretLeftFill from '../assets/icons/icon-caret-left-fill.svg'; -import IconCaretLeftOutline from '../assets/icons/icon-caret-left-outline.svg'; -import IconCaretRightFill from '../assets/icons/icon-caret-right-fill.svg'; -import IconCaretRightOutline from '../assets/icons/icon-caret-right-outline.svg'; -import IconCaretUpFill from '../assets/icons/icon-caret-up-fill.svg'; -import IconCaretUpOutline from '../assets/icons/icon-caret-up-outline.svg'; -import IconChatFill24 from '../assets/icons/icon-chat-fill-24.svg'; -import IconChatFill from '../assets/icons/icon-chat-fill.svg'; -import IconChatGroupFill from '../assets/icons/icon-chat-group-fill.svg'; -import IconChatGroupOutline from '../assets/icons/icon-chat-group-outline.svg'; -import IconChatNewFill from '../assets/icons/icon-chat-new-fill.svg'; -import IconChatNewOutline from '../assets/icons/icon-chat-new-outline.svg'; -import IconChatOutline24 from '../assets/icons/icon-chat-outline-24.svg'; -import IconChatOutline from '../assets/icons/icon-chat-outline.svg'; -import IconChatPrivateFill from '../assets/icons/icon-chat-private-fill.svg'; -import IconChatPrivateOutline from '../assets/icons/icon-chat-private-outline.svg'; -import IconCheckboxDismissFill from '../assets/icons/icon-checkbox-dismiss-fill.svg'; -import IconCheckboxDismissOutline from '../assets/icons/icon-checkbox-dismiss-outline.svg'; -import IconCheckboxFill from '../assets/icons/icon-checkbox-fill.svg'; -import IconCheckboxOutline from '../assets/icons/icon-checkbox-outline.svg'; -import IconCheckmarkFill from '../assets/icons/icon-checkmark-fill.svg'; -import IconCheckmarkOutline from '../assets/icons/icon-checkmark-outline.svg'; -import IconChromeFill from '../assets/icons/icon-chrome-fill.svg'; -import IconChromeOutline from '../assets/icons/icon-chrome-outline.svg'; -import IconClearFill from '../assets/icons/icon-clear-fill.svg'; -import IconClearOutline from '../assets/icons/icon-clear-outline.svg'; -import IconClientListFill from '../assets/icons/icon-client-list-fill.svg'; -import IconClientListOutline from '../assets/icons/icon-client-list-outline.svg'; -import IconCloseFill from '../assets/icons/icon-close-fill.svg'; -import IconCloseOutline from '../assets/icons/icon-close-outline.svg'; -import IconClosedCaptioningFill from '../assets/icons/icon-closed-captioning-fill.svg'; -import IconClosedCaptioningOutline from '../assets/icons/icon-closed-captioning-outline.svg'; -import IconCodeBlockFill from '../assets/icons/icon-code-block-fill.svg'; -import IconCodeBlockOutline from '../assets/icons/icon-code-block-outline.svg'; -import IconCodeInlineFill from '../assets/icons/icon-code-inline-fill.svg'; -import IconCodeInlineOutline from '../assets/icons/icon-code-inline-outline.svg'; -import IconCoinsColorOld from '../assets/icons/icon-coins-color-old.svg'; -import IconCoinsColor from '../assets/icons/icon-coins-color.svg'; -import IconCoinsFill from '../assets/icons/icon-coins-fill.svg'; -import IconCoinsOutline from '../assets/icons/icon-coins-outline.svg'; -import IconCollapseLeftFill from '../assets/icons/icon-collapse-left-fill.svg'; -import IconCollapseLeftOutline from '../assets/icons/icon-collapse-left-outline.svg'; -import IconCollapseRightFill from '../assets/icons/icon-collapse-right-fill.svg'; -import IconCollapseRightOutline from '../assets/icons/icon-collapse-right-outline.svg'; -import IconCollectibleExpressionsFill from '../assets/icons/icon-collectible-expressions-fill.svg'; -import IconCollectibleExpressionsOutline from '../assets/icons/icon-collectible-expressions-outline.svg'; -import IconCollectionFill from '../assets/icons/icon-collection-fill.svg'; -import IconCollectionOutline from '../assets/icons/icon-collection-outline.svg'; -import IconCommentFill from '../assets/icons/icon-comment-fill.svg'; -import IconCommentOutline from '../assets/icons/icon-comment-outline.svg'; -import IconCommentsFill from '../assets/icons/icon-comments-fill.svg'; -import IconCommentsOutline from '../assets/icons/icon-comments-outline.svg'; -import IconCommunitiesFill from '../assets/icons/icon-communities-fill.svg'; -import IconCommunitiesOutline from '../assets/icons/icon-communities-outline.svg'; -import IconCommunityFill from '../assets/icons/icon-community-fill.svg'; -import IconCommunityOutline from '../assets/icons/icon-community-outline.svg'; -import IconConfidenceFill from '../assets/icons/icon-confidence-fill.svg'; -import IconConfidenceOutline from '../assets/icons/icon-confidence-outline.svg'; -import IconContestFill from '../assets/icons/icon-contest-fill.svg'; -import IconContestOutline from '../assets/icons/icon-contest-outline.svg'; -import IconControversialFill from '../assets/icons/icon-controversial-fill.svg'; -import IconControversialOutline from '../assets/icons/icon-controversial-outline.svg'; -import IconConversionFill from '../assets/icons/icon-conversion-fill.svg'; -import IconConversionOutline from '../assets/icons/icon-conversion-outline.svg'; -import IconCopyClipboardFill from '../assets/icons/icon-copy-clipboard-fill.svg'; -import IconCopyClipboardOutline from '../assets/icons/icon-copy-clipboard-outline.svg'; -import IconCricketFillOutline24 from '../assets/icons/icon-cricket-fill-outline-24.svg'; -import IconCricketOutline24 from '../assets/icons/icon-cricket-outline-24.svg'; -import IconCropFill from '../assets/icons/icon-crop-fill.svg'; -import IconCropOutline from '../assets/icons/icon-crop-outline.svg'; -import IconCrosspostFill from '../assets/icons/icon-crosspost-fill.svg'; -import IconCrosspostOutline from '../assets/icons/icon-crosspost-outline.svg'; -import IconCrowdControlFill from '../assets/icons/icon-crowd-control-fill.svg'; -import IconCrowdControlOutline from '../assets/icons/icon-crowd-control-outline.svg'; -import IconCustomFeedFill from '../assets/icons/icon-custom-feed-fill.svg'; -import IconCustomFeedOutline from '../assets/icons/icon-custom-feed-outline.svg'; -import IconCustomizeFill from '../assets/icons/icon-customize-fill.svg'; -import IconCustomizeOutline from '../assets/icons/icon-customize-outline.svg'; -import IconDashboardFill from '../assets/icons/icon-dashboard-fill.svg'; -import IconDashboardOutline from '../assets/icons/icon-dashboard-outline.svg'; -import IconDayFill from '../assets/icons/icon-day-fill.svg'; -import IconDayOutline from '../assets/icons/icon-day-outline.svg'; -import IconDeleteFill from '../assets/icons/icon-delete-fill.svg'; -import IconDeleteOutline from '../assets/icons/icon-delete-outline.svg'; -import IconDiscoverFill24 from '../assets/icons/icon-discover-fill-24.svg'; -import IconDiscoverFill from '../assets/icons/icon-discover-fill.svg'; -import IconDiscoverOutline24 from '../assets/icons/icon-discover-outline-24.svg'; -import IconDiscoverOutline from '../assets/icons/icon-discover-outline.svg'; -import IconDismissAllFill from '../assets/icons/icon-dismiss-all-fill.svg'; -import IconDismissAllOutline from '../assets/icons/icon-dismiss-all-outline.svg'; -import IconDistinguishFill from '../assets/icons/icon-distinguish-fill.svg'; -import IconDistinguishOutline from '../assets/icons/icon-distinguish-outline.svg'; -import IconDownArrowOutline from '../assets/icons/icon-down-arrow-outline.svg'; -import IconDownFill from '../assets/icons/icon-down-fill.svg'; -import IconDownOutline from '../assets/icons/icon-down-outline.svg'; -import IconDownloadFill from '../assets/icons/icon-download-fill.svg'; -import IconDownloadOutline from '../assets/icons/icon-download-outline.svg'; -import IconDownvoteFillMask from '../assets/icons/icon-downvote-fill-mask.svg'; -import IconDownvoteFill from '../assets/icons/icon-downvote-fill.svg'; -import IconDownvoteOffsetmask from '../assets/icons/icon-downvote-offsetmask.svg'; -import IconDownvoteOutline from '../assets/icons/icon-downvote-outline.svg'; -import IconDownvotesFill from '../assets/icons/icon-downvotes-fill.svg'; -import IconDownvotesOutline from '../assets/icons/icon-downvotes-outline.svg'; -import IconDragOutline from '../assets/icons/icon-drag-outline.svg'; -import IconDrugsFill from '../assets/icons/icon-drugs-fill.svg'; -import IconDrugsOutline from '../assets/icons/icon-drugs-outline.svg'; -import IconDuplicateFill from '../assets/icons/icon-duplicate-fill.svg'; -import IconDuplicateOutline from '../assets/icons/icon-duplicate-outline.svg'; -import IconEditFill from '../assets/icons/icon-edit-fill.svg'; -import IconEditOutline from '../assets/icons/icon-edit-outline.svg'; -import IconEffectFill from '../assets/icons/icon-effect-fill.svg'; -import IconEffectOutline from '../assets/icons/icon-effect-outline.svg'; -import IconEmbedFill from '../assets/icons/icon-embed-fill.svg'; -import IconEmbedOutline from '../assets/icons/icon-embed-outline.svg'; -import IconEmojiFill from '../assets/icons/icon-emoji-fill.svg'; -import IconEmojiOutline from '../assets/icons/icon-emoji-outline.svg'; -import IconEndLiveChatFill from '../assets/icons/icon-end-live-chat-fill.svg'; -import IconEndLiveChatOutline from '../assets/icons/icon-end-live-chat-outline.svg'; -import IconErrorFill from '../assets/icons/icon-error-fill.svg'; -import IconErrorOutline from '../assets/icons/icon-error-outline.svg'; -import IconExpandLeftFill from '../assets/icons/icon-expand-left-fill.svg'; -import IconExpandLeftOutline from '../assets/icons/icon-expand-left-outline.svg'; -import IconExpandRightFill from '../assets/icons/icon-expand-right-fill.svg'; -import IconExpandRightOutline from '../assets/icons/icon-expand-right-outline.svg'; -import IconExternalFill from '../assets/icons/icon-external-fill.svg'; -import IconExternalOutline from '../assets/icons/icon-external-outline.svg'; -import IconFeedVideoFill from '../assets/icons/icon-feed-video-fill.svg'; -import IconFeedVideoOutline from '../assets/icons/icon-feed-video-outline.svg'; -import IconFilterFill24 from '../assets/icons/icon-filter-fill-24.svg'; -import IconFilterFill from '../assets/icons/icon-filter-fill.svg'; -import IconFilterOutline24 from '../assets/icons/icon-filter-outline-24.svg'; -import IconFilterOutline from '../assets/icons/icon-filter-outline.svg'; -import IconFootballFill24 from '../assets/icons/icon-football-fill-24.svg'; -import IconFootballOutline24 from '../assets/icons/icon-football-outline-24.svg'; -import IconFormatFill from '../assets/icons/icon-format-fill.svg'; -import IconFormatOutline from '../assets/icons/icon-format-outline.svg'; -import IconForwardFill from '../assets/icons/icon-forward-fill.svg'; -import IconForwardOutline from '../assets/icons/icon-forward-outline.svg'; -import IconFunnelFill from '../assets/icons/icon-funnel-fill.svg'; -import IconFunnelOutline from '../assets/icons/icon-funnel-outline.svg'; -import IconGifPostFill from '../assets/icons/icon-gif-post-fill.svg'; -import IconGifPostOutline from '../assets/icons/icon-gif-post-outline.svg'; -import IconHashtagFill from '../assets/icons/icon-hashtag-fill.svg'; -import IconHashtagOutline from '../assets/icons/icon-hashtag-outline.svg'; -import IconHeartFill from '../assets/icons/icon-heart-fill.svg'; -import IconHeartOutline from '../assets/icons/icon-heart-outline.svg'; -import IconHelpFill from '../assets/icons/icon-help-fill.svg'; -import IconHelpOutline from '../assets/icons/icon-help-outline.svg'; -import IconHideFill from '../assets/icons/icon-hide-fill.svg'; -import IconHideOutline from '../assets/icons/icon-hide-outline.svg'; -import IconHistoryFill from '../assets/icons/icon-history-fill.svg'; -import IconHistoryOutline from '../assets/icons/icon-history-outline.svg'; -import IconHockeyFill24 from '../assets/icons/icon-hockey-fill-24.svg'; -import IconHockeyOutline24 from '../assets/icons/icon-hockey-outline-24.svg'; -import IconHomeFill24 from '../assets/icons/icon-home-fill-24.svg'; -import IconHomeFill from '../assets/icons/icon-home-fill.svg'; -import IconHomeOutline24 from '../assets/icons/icon-home-outline-24.svg'; -import IconHomeOutline from '../assets/icons/icon-home-outline.svg'; -import IconHotFill from '../assets/icons/icon-hot-fill.svg'; -import IconHotOutline from '../assets/icons/icon-hot-outline.svg'; -import IconIgnoreReportsFill from '../assets/icons/icon-ignore-reports-fill.svg'; -import IconIgnoreReportsOutline from '../assets/icons/icon-ignore-reports-outline.svg'; -import IconImagePostFill from '../assets/icons/icon-image-post-fill.svg'; -import IconImagePostOutline from '../assets/icons/icon-image-post-outline.svg'; -import IconInboxFill from '../assets/icons/icon-inbox-fill.svg'; -import IconInboxOutline from '../assets/icons/icon-inbox-outline.svg'; -import IconIndiaIndependence24Color from '../assets/icons/icon-india-independence-24-color.svg'; -import IconIndiaIndependenceColor from '../assets/icons/icon-india-independence-color.svg'; -import IconIndiaIndependenceOutline24 from '../assets/icons/icon-india-independence-outline-24.svg'; -import IconInfoFill from '../assets/icons/icon-info-fill.svg'; -import IconInfoOutline from '../assets/icons/icon-info-outline.svg'; -import IconInviteFill from '../assets/icons/icon-invite-fill.svg'; -import IconInviteOutline from '../assets/icons/icon-invite-outline.svg'; -import IconItalicFill from '../assets/icons/icon-italic-fill.svg'; -import IconItalicOutline from '../assets/icons/icon-italic-outline.svg'; -import IconJoinFill from '../assets/icons/icon-join-fill.svg'; -import IconJoinOutline from '../assets/icons/icon-join-outline.svg'; -import IconJoinedFill from '../assets/icons/icon-joined-fill.svg'; -import IconJoinedOutline from '../assets/icons/icon-joined-outline.svg'; -import IconJumpDownFill from '../assets/icons/icon-jump-down-fill.svg'; -import IconJumpDownOutline from '../assets/icons/icon-jump-down-outline.svg'; -import IconJumpUpFill from '../assets/icons/icon-jump-up-fill.svg'; -import IconJumpUpOutline from '../assets/icons/icon-jump-up-outline.svg'; -import IconKarmaFill from '../assets/icons/icon-karma-fill.svg'; -import IconKarmaOutline from '../assets/icons/icon-karma-outline.svg'; -import IconKeyboardFill from '../assets/icons/icon-keyboard-fill.svg'; -import IconKeyboardOutline from '../assets/icons/icon-keyboard-outline.svg'; -import IconKickFill from '../assets/icons/icon-kick-fill.svg'; -import IconKickOutline from '../assets/icons/icon-kick-outline.svg'; -import IconLanguageFill from '../assets/icons/icon-language-fill.svg'; -import IconLanguageOutline from '../assets/icons/icon-language-outline.svg'; -import IconLeaveFill from '../assets/icons/icon-leave-fill.svg'; -import IconLeaveOutline from '../assets/icons/icon-leave-outline.svg'; -import IconLeftFill24 from '../assets/icons/icon-left-fill-24.svg'; -import IconLeftFill from '../assets/icons/icon-left-fill.svg'; -import IconLeftOutline24 from '../assets/icons/icon-left-outline-24.svg'; -import IconLeftOutline from '../assets/icons/icon-left-outline.svg'; -import IconLinkFill from '../assets/icons/icon-link-fill.svg'; -import IconLinkOutline from '../assets/icons/icon-link-outline.svg'; -import IconLinkPostFill from '../assets/icons/icon-link-post-fill.svg'; -import IconLinkPostOutline from '../assets/icons/icon-link-post-outline.svg'; -import IconListBulletedFill from '../assets/icons/icon-list-bulleted-fill.svg'; -import IconListBulletedOutline from '../assets/icons/icon-list-bulleted-outline.svg'; -import IconListNumberedFill from '../assets/icons/icon-list-numbered-fill.svg'; -import IconListNumberedOutline from '../assets/icons/icon-list-numbered-outline.svg'; -import IconLiveChatFill from '../assets/icons/icon-live-chat-fill.svg'; -import IconLiveChatOutline from '../assets/icons/icon-live-chat-outline.svg'; -import IconLiveFill from '../assets/icons/icon-live-fill.svg'; -import IconLiveOutline from '../assets/icons/icon-live-outline.svg'; -import IconLoadOutline from '../assets/icons/icon-load-outline.svg'; -import IconLocationFill from '../assets/icons/icon-location-fill.svg'; -import IconLocationOutline from '../assets/icons/icon-location-outline.svg'; -import IconLockFill from '../assets/icons/icon-lock-fill.svg'; -import IconLockOutline from '../assets/icons/icon-lock-outline.svg'; -import IconLogoutFill from '../assets/icons/icon-logout-fill.svg'; -import IconLogoutOutline from '../assets/icons/icon-logout-outline.svg'; -import IconLoopFill from '../assets/icons/icon-loop-fill.svg'; -import IconLoopOutline from '../assets/icons/icon-loop-outline.svg'; -import IconMacroFill from '../assets/icons/icon-macro-fill.svg'; -import IconMacroOutline from '../assets/icons/icon-macro-outline.svg'; -import IconMarkReadFill from '../assets/icons/icon-mark-read-fill.svg'; -import IconMarkReadOutline from '../assets/icons/icon-mark-read-outline.svg'; -import IconMarketplaceFill from '../assets/icons/icon-marketplace-fill.svg'; -import IconMarketplaceOutline from '../assets/icons/icon-marketplace-outline.svg'; -import IconMaskFill from '../assets/icons/icon-mask-fill.svg'; -import IconMaskOutline from '../assets/icons/icon-mask-outline.svg'; -import IconMediaGalleryFill from '../assets/icons/icon-media-gallery-fill.svg'; -import IconMediaGalleryOutline from '../assets/icons/icon-media-gallery-outline.svg'; -import IconMemeFill from '../assets/icons/icon-meme-fill.svg'; -import IconMemeOutline from '../assets/icons/icon-meme-outline.svg'; -import IconMenuFill24 from '../assets/icons/icon-menu-fill-24.svg'; -import IconMenuFill from '../assets/icons/icon-menu-fill.svg'; -import IconMenuOutline24 from '../assets/icons/icon-menu-outline-24.svg'; -import IconMenuOutline from '../assets/icons/icon-menu-outline.svg'; -import IconMessageFill from '../assets/icons/icon-message-fill.svg'; -import IconMessageOutline from '../assets/icons/icon-message-outline.svg'; -import IconMicFill from '../assets/icons/icon-mic-fill.svg'; -import IconMicMuteFill from '../assets/icons/icon-mic-mute-fill.svg'; -import IconMicMuteOutline from '../assets/icons/icon-mic-mute-outline.svg'; -import IconMicOutline from '../assets/icons/icon-mic-outline.svg'; -import IconModFill from '../assets/icons/icon-mod-fill.svg'; -import IconModMailFill from '../assets/icons/icon-mod-mail-fill.svg'; -import IconModMailOutline from '../assets/icons/icon-mod-mail-outline.svg'; -import IconModModeFill from '../assets/icons/icon-mod-mode-fill.svg'; -import IconModModeOutline from '../assets/icons/icon-mod-mode-outline.svg'; -import IconModMuteFill from '../assets/icons/icon-mod-mute-fill.svg'; -import IconModMuteOutline from '../assets/icons/icon-mod-mute-outline.svg'; -import IconModOutline from '../assets/icons/icon-mod-outline.svg'; -import IconModOverflowFill from '../assets/icons/icon-mod-overflow-fill.svg'; -import IconModOverflowOutline from '../assets/icons/icon-mod-overflow-outline.svg'; -import IconModQueueFill from '../assets/icons/icon-mod-queue-fill.svg'; -import IconModQueueOutline from '../assets/icons/icon-mod-queue-outline.svg'; -import IconModUnmuteFill from '../assets/icons/icon-mod-unmute-fill.svg'; -import IconModUnmuteOutline from '../assets/icons/icon-mod-unmute-outline.svg'; -import IconMusicFill from '../assets/icons/icon-music-fill.svg'; -import IconMusicOutline from '../assets/icons/icon-music-outline.svg'; -import IconMuteFill from '../assets/icons/icon-mute-fill.svg'; -import IconMuteOutline from '../assets/icons/icon-mute-outline.svg'; -import IconNewFill from '../assets/icons/icon-new-fill.svg'; -import IconNewOutline from '../assets/icons/icon-new-outline.svg'; -import IconNightFill from '../assets/icons/icon-night-fill.svg'; -import IconNightOutline from '../assets/icons/icon-night-outline.svg'; -import IconNotificationFill24 from '../assets/icons/icon-notification-fill-24.svg'; -import IconNotificationFill from '../assets/icons/icon-notification-fill.svg'; -import IconNotificationFrequentFill from '../assets/icons/icon-notification-frequent-fill.svg'; -import IconNotificationFrequentOutline from '../assets/icons/icon-notification-frequent-outline.svg'; -import IconNotificationOffFill from '../assets/icons/icon-notification-off-fill.svg'; -import IconNotificationOffOutline from '../assets/icons/icon-notification-off-outline.svg'; -import IconNotificationOutline24 from '../assets/icons/icon-notification-outline-24.svg'; -import IconNotificationOutline from '../assets/icons/icon-notification-outline.svg'; -import IconNsfwFill from '../assets/icons/icon-nsfw-fill.svg'; -import IconNsfwLanguageFill from '../assets/icons/icon-nsfw-language-fill.svg'; -import IconNsfwLanguageOutline from '../assets/icons/icon-nsfw-language-outline.svg'; -import IconNsfwOutline from '../assets/icons/icon-nsfw-outline.svg'; -import IconNsfwViolenceFill from '../assets/icons/icon-nsfw-violence-fill.svg'; -import IconNsfwViolenceOutline from '../assets/icons/icon-nsfw-violence-outline.svg'; -import IconOfficialFill from '../assets/icons/icon-official-fill.svg'; -import IconOfficialOutline from '../assets/icons/icon-official-outline.svg'; -import IconOriginalFill from '../assets/icons/icon-original-fill.svg'; -import IconOriginalOutline from '../assets/icons/icon-original-outline.svg'; -import IconOverflowCaretFill from '../assets/icons/icon-overflow-caret-fill.svg'; -import IconOverflowCaretOutline from '../assets/icons/icon-overflow-caret-outline.svg'; -import IconOverflowHorizontalFill24 from '../assets/icons/icon-overflow-horizontal-fill-24.svg'; -import IconOverflowHorizontalFill from '../assets/icons/icon-overflow-horizontal-fill.svg'; -import IconOverflowHorizontalOutline24 from '../assets/icons/icon-overflow-horizontal-outline-24.svg'; -import IconOverflowHorizontalOutline from '../assets/icons/icon-overflow-horizontal-outline.svg'; -import IconOverflowVerticalFill from '../assets/icons/icon-overflow-vertical-fill.svg'; -import IconOverflowVerticalOutline from '../assets/icons/icon-overflow-vertical-outline.svg'; -import IconPauseFill from '../assets/icons/icon-pause-fill.svg'; -import IconPauseOutline from '../assets/icons/icon-pause-outline.svg'; -import IconPaymentFill from '../assets/icons/icon-payment-fill.svg'; -import IconPaymentOutline from '../assets/icons/icon-payment-outline.svg'; -import IconPeaceFill from '../assets/icons/icon-peace-fill.svg'; -import IconPeaceOutline from '../assets/icons/icon-peace-outline.svg'; -import IconPendingPostsFill from '../assets/icons/icon-pending-posts-fill.svg'; -import IconPendingPostsOutline from '../assets/icons/icon-pending-posts-outline.svg'; -import IconPhoneFill from '../assets/icons/icon-phone-fill.svg'; -import IconPhoneOutline from '../assets/icons/icon-phone-outline.svg'; -import IconPinFill from '../assets/icons/icon-pin-fill.svg'; -import IconPinOutline from '../assets/icons/icon-pin-outline.svg'; -import IconPlayFill from '../assets/icons/icon-play-fill.svg'; -import IconPlayOutline from '../assets/icons/icon-play-outline.svg'; -import IconPollPostFill from '../assets/icons/icon-poll-post-fill.svg'; -import IconPollPostOutline from '../assets/icons/icon-poll-post-outline.svg'; -import IconPopularFill from '../assets/icons/icon-popular-fill.svg'; -import IconPopularOutline from '../assets/icons/icon-popular-outline.svg'; -import IconPostsFill from '../assets/icons/icon-posts-fill.svg'; -import IconPostsOutline from '../assets/icons/icon-posts-outline.svg'; -import IconPowerupColor from '../assets/icons/icon-powerup-color.svg'; -import IconPowerupFillColor from '../assets/icons/icon-powerup-fill-color.svg'; -import IconPowerupFill from '../assets/icons/icon-powerup-fill.svg'; -import IconPowerupOutline from '../assets/icons/icon-powerup-outline.svg'; -import IconPredictionsFill from '../assets/icons/icon-predictions-fill.svg'; -import IconPredictionsOutline from '../assets/icons/icon-predictions-outline.svg'; -import IconPremiumFill from '../assets/icons/icon-premium-fill.svg'; -import IconPremiumOutline from '../assets/icons/icon-premium-outline.svg'; -import IconPrivacyFill from '../assets/icons/icon-privacy-fill.svg'; -import IconPrivacyOutline from '../assets/icons/icon-privacy-outline.svg'; -import IconProfileFill from '../assets/icons/icon-profile-fill.svg'; -import IconProfileOutline from '../assets/icons/icon-profile-outline.svg'; -import IconQaFill from '../assets/icons/icon-qa-fill.svg'; -import IconQaOutline from '../assets/icons/icon-qa-outline.svg'; -import IconQrCodeFill from '../assets/icons/icon-qr-code-fill.svg'; -import IconQrCodeOutline from '../assets/icons/icon-qr-code-outline.svg'; -import IconQuarantinedFill from '../assets/icons/icon-quarantined-fill.svg'; -import IconQuarantinedOutline from '../assets/icons/icon-quarantined-outline.svg'; -import IconQuoteFill from '../assets/icons/icon-quote-fill.svg'; -import IconQuoteOutline from '../assets/icons/icon-quote-outline.svg'; -import IconRSlashFill from '../assets/icons/icon-r-slash-fill.svg'; -import IconRSlashOutline from '../assets/icons/icon-r-slash-outline.svg'; -import IconRadioButtonFill from '../assets/icons/icon-radio-button-fill.svg'; -import IconRadioButtonOutline from '../assets/icons/icon-radio-button-outline.svg'; -import IconRaiseHandFill from '../assets/icons/icon-raise-hand-fill.svg'; -import IconRaiseHandOutline from '../assets/icons/icon-raise-hand-outline.svg'; -import IconRandomFill from '../assets/icons/icon-random-fill.svg'; -import IconRandomOutline from '../assets/icons/icon-random-outline.svg'; -import IconRatingsEveryoneFill from '../assets/icons/icon-ratings-everyone-fill.svg'; -import IconRatingsEveryoneOutline from '../assets/icons/icon-ratings-everyone-outline.svg'; -import IconRatingsMatureFill from '../assets/icons/icon-ratings-mature-fill.svg'; -import IconRatingsMatureOutline from '../assets/icons/icon-ratings-mature-outline.svg'; -import IconRatingsNsfwFill from '../assets/icons/icon-ratings-nsfw-fill.svg'; -import IconRatingsNsfwOutline from '../assets/icons/icon-ratings-nsfw-outline.svg'; -import IconRatingsViolenceFill from '../assets/icons/icon-ratings-violence-fill.svg'; -import IconRatingsViolenceOutline from '../assets/icons/icon-ratings-violence-outline.svg'; -import IconRecoveryPhraseFill from '../assets/icons/icon-recovery-phrase-fill.svg'; -import IconRecoveryPhraseOutline from '../assets/icons/icon-recovery-phrase-outline.svg'; -import IconRefreshFill from '../assets/icons/icon-refresh-fill.svg'; -import IconRefreshOutline from '../assets/icons/icon-refresh-outline.svg'; -import IconRemovalReasonsFill from '../assets/icons/icon-removal-reasons-fill.svg'; -import IconRemovalReasonsOutline from '../assets/icons/icon-removal-reasons-outline.svg'; -import IconRemoveFill from '../assets/icons/icon-remove-fill.svg'; -import IconRemoveOutline from '../assets/icons/icon-remove-outline.svg'; -import IconReplyFill from '../assets/icons/icon-reply-fill.svg'; -import IconReplyOutline from '../assets/icons/icon-reply-outline.svg'; -import IconReportFill from '../assets/icons/icon-report-fill.svg'; -import IconReportOutline from '../assets/icons/icon-report-outline.svg'; -import IconReverseFill from '../assets/icons/icon-reverse-fill.svg'; -import IconReverseOutline from '../assets/icons/icon-reverse-outline.svg'; -import IconRichTextFill from '../assets/icons/icon-rich-text-fill.svg'; -import IconRichTextOutline from '../assets/icons/icon-rich-text-outline.svg'; -import IconRightFill from '../assets/icons/icon-right-fill.svg'; -import IconRightOutline from '../assets/icons/icon-right-outline.svg'; -import IconRisingFill from '../assets/icons/icon-rising-fill.svg'; -import IconRisingOutline from '../assets/icons/icon-rising-outline.svg'; -import IconRotateFill from '../assets/icons/icon-rotate-fill.svg'; -import IconRotateImageFill from '../assets/icons/icon-rotate-image-fill.svg'; -import IconRotateImageOutline from '../assets/icons/icon-rotate-image-outline.svg'; -import IconRotateOutline from '../assets/icons/icon-rotate-outline.svg'; -import IconRpanFill from '../assets/icons/icon-rpan-fill.svg'; -import IconRpanOutline from '../assets/icons/icon-rpan-outline.svg'; -import IconRulesFill from '../assets/icons/icon-rules-fill.svg'; -import IconRulesOutline from '../assets/icons/icon-rules-outline.svg'; -import IconSafariFill from '../assets/icons/icon-safari-fill.svg'; -import IconSafariOutline from '../assets/icons/icon-safari-outline.svg'; -import IconSaveFill from '../assets/icons/icon-save-fill.svg'; -import IconSaveOutline from '../assets/icons/icon-save-outline.svg'; -import IconSaveViewFill from '../assets/icons/icon-save-view-fill.svg'; -import IconSaveViewOutline from '../assets/icons/icon-save-view-outline.svg'; -import IconSavedFill from '../assets/icons/icon-saved-fill.svg'; -import IconSavedOutline from '../assets/icons/icon-saved-outline.svg'; -import IconSearchFill24 from '../assets/icons/icon-search-fill-24.svg'; -import IconSearchFill from '../assets/icons/icon-search-fill.svg'; -import IconSearchOutline24 from '../assets/icons/icon-search-outline-24.svg'; -import IconSearchOutline from '../assets/icons/icon-search-outline.svg'; -import IconSelfFill from '../assets/icons/icon-self-fill.svg'; -import IconSelfOutline from '../assets/icons/icon-self-outline.svg'; -import IconSendFill from '../assets/icons/icon-send-fill.svg'; -import IconSendOutline from '../assets/icons/icon-send-outline.svg'; -import IconSettingsFill from '../assets/icons/icon-settings-fill.svg'; -import IconSettingsOutline from '../assets/icons/icon-settings-outline.svg'; -import IconSeverityFill from '../assets/icons/icon-severity-fill.svg'; -import IconSeverityOutline from '../assets/icons/icon-severity-outline.svg'; -import IconShareAndroidFill from '../assets/icons/icon-share-android-fill.svg'; -import IconShareAndroidOutline from '../assets/icons/icon-share-android-outline.svg'; -import IconShareFill from '../assets/icons/icon-share-fill.svg'; -import IconShareIosFill from '../assets/icons/icon-share-ios-fill.svg'; -import IconShareIosOutline from '../assets/icons/icon-share-ios-outline.svg'; -import IconShareNewFill from '../assets/icons/icon-share-new-fill.svg'; -import IconShareNewOutline from '../assets/icons/icon-share-new-outline.svg'; -import IconShareOutline from '../assets/icons/icon-share-outline.svg'; -import IconShowFill from '../assets/icons/icon-show-fill.svg'; -import IconShowOutline from '../assets/icons/icon-show-outline.svg'; -import IconSideMenuFill from '../assets/icons/icon-side-menu-fill.svg'; -import IconSideMenuOutline from '../assets/icons/icon-side-menu-outline.svg'; -import IconSkipback10Fill from '../assets/icons/icon-skipback10-fill.svg'; -import IconSkipback10Outline from '../assets/icons/icon-skipback10-outline.svg'; -import IconSkipforward10Fill from '../assets/icons/icon-skipforward10-fill.svg'; -import IconSkipforward10Outline from '../assets/icons/icon-skipforward10-outline.svg'; -import IconSortAzFill from '../assets/icons/icon-sort-az-fill.svg'; -import IconSortAzOutline from '../assets/icons/icon-sort-az-outline.svg'; -import IconSortFill from '../assets/icons/icon-sort-fill.svg'; -import IconSortOutline from '../assets/icons/icon-sort-outline.svg'; -import IconSortPriceFill from '../assets/icons/icon-sort-price-fill.svg'; -import IconSortPriceOutline from '../assets/icons/icon-sort-price-outline.svg'; -import IconSortZaFill from '../assets/icons/icon-sort-za-fill.svg'; -import IconSortZaOutline from '../assets/icons/icon-sort-za-outline.svg'; -import IconSpamFill from '../assets/icons/icon-spam-fill.svg'; -import IconSpamOutline from '../assets/icons/icon-spam-outline.svg'; -import IconSpoilerFill from '../assets/icons/icon-spoiler-fill.svg'; -import IconSpoilerOutline from '../assets/icons/icon-spoiler-outline.svg'; -import IconSponsoredFill from '../assets/icons/icon-sponsored-fill.svg'; -import IconSponsoredOutline from '../assets/icons/icon-sponsored-outline.svg'; -import IconSpreadsheetFill from '../assets/icons/icon-spreadsheet-fill.svg'; -import IconSpreadsheetOutline from '../assets/icons/icon-spreadsheet-outline.svg'; -import IconStarFill from '../assets/icons/icon-star-fill.svg'; -import IconStarOutline from '../assets/icons/icon-star-outline.svg'; -import IconStatisticsFill from '../assets/icons/icon-statistics-fill.svg'; -import IconStatisticsOutline from '../assets/icons/icon-statistics-outline.svg'; -import IconStatusLiveFill from '../assets/icons/icon-status-live-fill.svg'; -import IconStatusLiveOutline from '../assets/icons/icon-status-live-outline.svg'; -import IconStickerFill from '../assets/icons/icon-sticker-fill.svg'; -import IconStickerOutline from '../assets/icons/icon-sticker-outline.svg'; -import IconStrikethroughFill from '../assets/icons/icon-strikethrough-fill.svg'; -import IconStrikethroughOutline from '../assets/icons/icon-strikethrough-outline.svg'; -import IconSubtractFill from '../assets/icons/icon-subtract-fill.svg'; -import IconSubtractOutline from '../assets/icons/icon-subtract-outline.svg'; -import IconSuperscriptFill from '../assets/icons/icon-superscript-fill.svg'; -import IconSuperscriptOutline from '../assets/icons/icon-superscript-outline.svg'; -import IconSwapCameraFill from '../assets/icons/icon-swap-camera-fill.svg'; -import IconSwapCameraOutline from '../assets/icons/icon-swap-camera-outline.svg'; -import IconSwipeBackFill from '../assets/icons/icon-swipe-back-fill.svg'; -import IconSwipeBackOutline from '../assets/icons/icon-swipe-back-outline.svg'; -import IconSwipeDownFill from '../assets/icons/icon-swipe-down-fill.svg'; -import IconSwipeDownOutline from '../assets/icons/icon-swipe-down-outline.svg'; -import IconSwipeFill from '../assets/icons/icon-swipe-fill.svg'; -import IconSwipeOutline from '../assets/icons/icon-swipe-outline.svg'; -import IconSwipeUpFill from '../assets/icons/icon-swipe-up-fill.svg'; -import IconSwipeUpOutline from '../assets/icons/icon-swipe-up-outline.svg'; -import IconTableFill from '../assets/icons/icon-table-fill.svg'; -import IconTableOutline from '../assets/icons/icon-table-outline.svg'; -import IconTagFill from '../assets/icons/icon-tag-fill.svg'; -import IconTagOutline from '../assets/icons/icon-tag-outline.svg'; -import IconTapFill from '../assets/icons/icon-tap-fill.svg'; -import IconTapOutline from '../assets/icons/icon-tap-outline.svg'; -import IconTextFill from '../assets/icons/icon-text-fill.svg'; -import IconTextOutline from '../assets/icons/icon-text-outline.svg'; -import IconTextPostFill from '../assets/icons/icon-text-post-fill.svg'; -import IconTextPostOutline from '../assets/icons/icon-text-post-outline.svg'; -import IconTextSizeFill from '../assets/icons/icon-text-size-fill.svg'; -import IconTextSizeOutline from '../assets/icons/icon-text-size-outline.svg'; -import IconToggleFill from '../assets/icons/icon-toggle-fill.svg'; -import IconToggleOutline from '../assets/icons/icon-toggle-outline.svg'; -import IconToolsFill from '../assets/icons/icon-tools-fill.svg'; -import IconToolsOutline from '../assets/icons/icon-tools-outline.svg'; -import IconTopFill from '../assets/icons/icon-top-fill.svg'; -import IconTopOutline from '../assets/icons/icon-top-outline.svg'; -import IconTopicActivismFill from '../assets/icons/icon-topic-activism-fill.svg'; -import IconTopicActivismOutline from '../assets/icons/icon-topic-activism-outline.svg'; -import IconTopicAddictionsupportFill from '../assets/icons/icon-topic-addictionsupport-fill.svg'; -import IconTopicAddictionsupportOutline from '../assets/icons/icon-topic-addictionsupport-outline.svg'; -import IconTopicAdviceFill from '../assets/icons/icon-topic-advice-fill.svg'; -import IconTopicAdviceOutline from '../assets/icons/icon-topic-advice-outline.svg'; -import IconTopicAnimalsFill from '../assets/icons/icon-topic-animals-fill.svg'; -import IconTopicAnimalsOutline from '../assets/icons/icon-topic-animals-outline.svg'; -import IconTopicAnimeFill from '../assets/icons/icon-topic-anime-fill.svg'; -import IconTopicAnimeOutline from '../assets/icons/icon-topic-anime-outline.svg'; -import IconTopicArtFill from '../assets/icons/icon-topic-art-fill.svg'; -import IconTopicArtOutline from '../assets/icons/icon-topic-art-outline.svg'; -import IconTopicBeautyFill from '../assets/icons/icon-topic-beauty-fill.svg'; -import IconTopicBeautyOutline from '../assets/icons/icon-topic-beauty-outline.svg'; -import IconTopicBusinessFill from '../assets/icons/icon-topic-business-fill.svg'; -import IconTopicBusinessOutline from '../assets/icons/icon-topic-business-outline.svg'; -import IconTopicCareersFill from '../assets/icons/icon-topic-careers-fill.svg'; -import IconTopicCareersOutline from '../assets/icons/icon-topic-careers-outline.svg'; -import IconTopicCarsFill from '../assets/icons/icon-topic-cars-fill.svg'; -import IconTopicCarsOutline from '../assets/icons/icon-topic-cars-outline.svg'; -import IconTopicCelebrityFill from '../assets/icons/icon-topic-celebrity-fill.svg'; -import IconTopicCelebrityOutline from '../assets/icons/icon-topic-celebrity-outline.svg'; -import IconTopicCraftsdiyFill from '../assets/icons/icon-topic-craftsdiy-fill.svg'; -import IconTopicCraftsdiyOutline from '../assets/icons/icon-topic-craftsdiy-outline.svg'; -import IconTopicCryptoFill from '../assets/icons/icon-topic-crypto-fill.svg'; -import IconTopicCryptoOutline from '../assets/icons/icon-topic-crypto-outline.svg'; -import IconTopicCultureFill from '../assets/icons/icon-topic-culture-fill.svg'; -import IconTopicCultureOutline from '../assets/icons/icon-topic-culture-outline.svg'; -import IconTopicDiyFill from '../assets/icons/icon-topic-diy-fill.svg'; -import IconTopicDiyOutline from '../assets/icons/icon-topic-diy-outline.svg'; -import IconTopicEntertainmentFill from '../assets/icons/icon-topic-entertainment-fill.svg'; -import IconTopicEntertainmentOutline from '../assets/icons/icon-topic-entertainment-outline.svg'; -import IconTopicEthicsFill from '../assets/icons/icon-topic-ethics-fill.svg'; -import IconTopicEthicsOutline from '../assets/icons/icon-topic-ethics-outline.svg'; -import IconTopicFamilyFill from '../assets/icons/icon-topic-family-fill.svg'; -import IconTopicFamilyOutline from '../assets/icons/icon-topic-family-outline.svg'; -import IconTopicFashionFill from '../assets/icons/icon-topic-fashion-fill.svg'; -import IconTopicFashionOutline from '../assets/icons/icon-topic-fashion-outline.svg'; -import IconTopicFill from '../assets/icons/icon-topic-fill.svg'; -import IconTopicFitnessFill from '../assets/icons/icon-topic-fitness-fill.svg'; -import IconTopicFitnessOutline from '../assets/icons/icon-topic-fitness-outline.svg'; -import IconTopicFoodFill from '../assets/icons/icon-topic-food-fill.svg'; -import IconTopicFoodOutline from '../assets/icons/icon-topic-food-outline.svg'; -import IconTopicFunnyFill from '../assets/icons/icon-topic-funny-fill.svg'; -import IconTopicFunnyOutline from '../assets/icons/icon-topic-funny-outline.svg'; -import IconTopicGenderFill from '../assets/icons/icon-topic-gender-fill.svg'; -import IconTopicGenderOutline from '../assets/icons/icon-topic-gender-outline.svg'; -import IconTopicHealthFill from '../assets/icons/icon-topic-health-fill.svg'; -import IconTopicHealthOutline from '../assets/icons/icon-topic-health-outline.svg'; -import IconTopicHelpFill from '../assets/icons/icon-topic-help-fill.svg'; -import IconTopicHelpOutline from '../assets/icons/icon-topic-help-outline.svg'; -import IconTopicHistoryFill from '../assets/icons/icon-topic-history-fill.svg'; -import IconTopicHistoryOutline from '../assets/icons/icon-topic-history-outline.svg'; -import IconTopicHobbiesFill from '../assets/icons/icon-topic-hobbies-fill.svg'; -import IconTopicHobbiesOutline from '../assets/icons/icon-topic-hobbies-outline.svg'; -import IconTopicHomegardenFill from '../assets/icons/icon-topic-homegarden-fill.svg'; -import IconTopicHomegardenOutline from '../assets/icons/icon-topic-homegarden-outline.svg'; -import IconTopicInternetFill from '../assets/icons/icon-topic-internet-fill.svg'; -import IconTopicInternetOutline from '../assets/icons/icon-topic-internet-outline.svg'; -import IconTopicLawFill from '../assets/icons/icon-topic-law-fill.svg'; -import IconTopicLawOutline from '../assets/icons/icon-topic-law-outline.svg'; -import IconTopicLearningFill from '../assets/icons/icon-topic-learning-fill.svg'; -import IconTopicLearningOutline from '../assets/icons/icon-topic-learning-outline.svg'; -import IconTopicLifestyleFill from '../assets/icons/icon-topic-lifestyle-fill.svg'; -import IconTopicLifestyleOutline from '../assets/icons/icon-topic-lifestyle-outline.svg'; -import IconTopicMarketplaceFill from '../assets/icons/icon-topic-marketplace-fill.svg'; -import IconTopicMarketplaceOutline from '../assets/icons/icon-topic-marketplace-outline.svg'; -import IconTopicMatureFill from '../assets/icons/icon-topic-mature-fill.svg'; -import IconTopicMatureOutline from '../assets/icons/icon-topic-mature-outline.svg'; -import IconTopicMensfashionFill from '../assets/icons/icon-topic-mensfashion-fill.svg'; -import IconTopicMensfashionOutline from '../assets/icons/icon-topic-mensfashion-outline.svg'; -import IconTopicMenshealthFill from '../assets/icons/icon-topic-menshealth-fill.svg'; -import IconTopicMenshealthOutline from '../assets/icons/icon-topic-menshealth-outline.svg'; -import IconTopicMetaFill from '../assets/icons/icon-topic-meta-fill.svg'; -import IconTopicMetaOutline from '../assets/icons/icon-topic-meta-outline.svg'; -import IconTopicMilitaryFill from '../assets/icons/icon-topic-military-fill.svg'; -import IconTopicMilitaryOutline from '../assets/icons/icon-topic-military-outline.svg'; -import IconTopicMoviesFill from '../assets/icons/icon-topic-movies-fill.svg'; -import IconTopicMoviesOutline from '../assets/icons/icon-topic-movies-outline.svg'; -import IconTopicMusicFill from '../assets/icons/icon-topic-music-fill.svg'; -import IconTopicMusicOutline from '../assets/icons/icon-topic-music-outline.svg'; -import IconTopicNewsFill from '../assets/icons/icon-topic-news-fill.svg'; -import IconTopicNewsOutline from '../assets/icons/icon-topic-news-outline.svg'; -import IconTopicOtherFill from '../assets/icons/icon-topic-other-fill.svg'; -import IconTopicOtherOutline from '../assets/icons/icon-topic-other-outline.svg'; -import IconTopicOutdoorsFill from '../assets/icons/icon-topic-outdoors-fill.svg'; -import IconTopicOutdoorsOutline from '../assets/icons/icon-topic-outdoors-outline.svg'; -import IconTopicOutline from '../assets/icons/icon-topic-outline.svg'; -import IconTopicPetsFill from '../assets/icons/icon-topic-pets-fill.svg'; -import IconTopicPetsOutline from '../assets/icons/icon-topic-pets-outline.svg'; -import IconTopicPhotographyFill from '../assets/icons/icon-topic-photography-fill.svg'; -import IconTopicPhotographyOutline from '../assets/icons/icon-topic-photography-outline.svg'; -import IconTopicPlacesFill from '../assets/icons/icon-topic-places-fill.svg'; -import IconTopicPlacesOutline from '../assets/icons/icon-topic-places-outline.svg'; -import IconTopicPodcastsFill from '../assets/icons/icon-topic-podcasts-fill.svg'; -import IconTopicPodcastsOutline from '../assets/icons/icon-topic-podcasts-outline.svg'; -import IconTopicPoliticsFill from '../assets/icons/icon-topic-politics-fill.svg'; -import IconTopicPoliticsOutline from '../assets/icons/icon-topic-politics-outline.svg'; -import IconTopicProgrammingFill from '../assets/icons/icon-topic-programming-fill.svg'; -import IconTopicProgrammingOutline from '../assets/icons/icon-topic-programming-outline.svg'; -import IconTopicReadingFill from '../assets/icons/icon-topic-reading-fill.svg'; -import IconTopicReadingOutline from '../assets/icons/icon-topic-reading-outline.svg'; -import IconTopicReligionFill from '../assets/icons/icon-topic-religion-fill.svg'; -import IconTopicReligionOutline from '../assets/icons/icon-topic-religion-outline.svg'; -import IconTopicScienceFill from '../assets/icons/icon-topic-science-fill.svg'; -import IconTopicScienceOutline from '../assets/icons/icon-topic-science-outline.svg'; -import IconTopicSexorientationFill from '../assets/icons/icon-topic-sexorientation-fill.svg'; -import IconTopicSexorientationOutline from '../assets/icons/icon-topic-sexorientation-outline.svg'; -import IconTopicSportsFill from '../assets/icons/icon-topic-sports-fill.svg'; -import IconTopicSportsOutline from '../assets/icons/icon-topic-sports-outline.svg'; -import IconTopicStyleFill from '../assets/icons/icon-topic-style-fill.svg'; -import IconTopicStyleOutline from '../assets/icons/icon-topic-style-outline.svg'; -import IconTopicTabletopFill from '../assets/icons/icon-topic-tabletop-fill.svg'; -import IconTopicTabletopOutline from '../assets/icons/icon-topic-tabletop-outline.svg'; -import IconTopicTechnologyFill from '../assets/icons/icon-topic-technology-fill.svg'; -import IconTopicTechnologyOutline from '../assets/icons/icon-topic-technology-outline.svg'; -import IconTopicTelevisionFill from '../assets/icons/icon-topic-television-fill.svg'; -import IconTopicTelevisionOutline from '../assets/icons/icon-topic-television-outline.svg'; -import IconTopicTraumasupportFill from '../assets/icons/icon-topic-traumasupport-fill.svg'; -import IconTopicTraumasupportOutline from '../assets/icons/icon-topic-traumasupport-outline.svg'; -import IconTopicTravelFill from '../assets/icons/icon-topic-travel-fill.svg'; -import IconTopicTravelOutline from '../assets/icons/icon-topic-travel-outline.svg'; -import IconTopicVideogamingFill from '../assets/icons/icon-topic-videogaming-fill.svg'; -import IconTopicVideogamingOutline from '../assets/icons/icon-topic-videogaming-outline.svg'; -import IconTopicWomensfashionFill from '../assets/icons/icon-topic-womensfashion-fill.svg'; -import IconTopicWomensfashionOutline from '../assets/icons/icon-topic-womensfashion-outline.svg'; -import IconTopicWomenshealthFill from '../assets/icons/icon-topic-womenshealth-fill.svg'; -import IconTopicWomenshealthOutline from '../assets/icons/icon-topic-womenshealth-outline.svg'; -import IconTranslateFill from '../assets/icons/icon-translate-fill.svg'; -import IconTranslateOutline from '../assets/icons/icon-translate-outline.svg'; -import IconTranslationOffFill from '../assets/icons/icon-translation-off-fill.svg'; -import IconTranslationOffOutline from '../assets/icons/icon-translation-off-outline.svg'; -import IconTrimFill from '../assets/icons/icon-trim-fill.svg'; -import IconTrimOutline from '../assets/icons/icon-trim-outline.svg'; -import IconUSlashFill from '../assets/icons/icon-u-slash-fill.svg'; -import IconUSlashOutline from '../assets/icons/icon-u-slash-outline.svg'; -import IconUnbanFill from '../assets/icons/icon-unban-fill.svg'; -import IconUnbanOutline from '../assets/icons/icon-unban-outline.svg'; -import IconUndoFill from '../assets/icons/icon-undo-fill.svg'; -import IconUndoOutline from '../assets/icons/icon-undo-outline.svg'; -import IconUnheartFill from '../assets/icons/icon-unheart-fill.svg'; -import IconUnheartOutline from '../assets/icons/icon-unheart-outline.svg'; -import IconUnlockFill from '../assets/icons/icon-unlock-fill.svg'; -import IconUnlockOutline from '../assets/icons/icon-unlock-outline.svg'; -import IconUnmodFill from '../assets/icons/icon-unmod-fill.svg'; -import IconUnmodOutline from '../assets/icons/icon-unmod-outline.svg'; -import IconUnpinFill from '../assets/icons/icon-unpin-fill.svg'; -import IconUnpinOutline from '../assets/icons/icon-unpin-outline.svg'; -import IconUnstarFill from '../assets/icons/icon-unstar-fill.svg'; -import IconUnstarOutline from '../assets/icons/icon-unstar-outline.svg'; -import IconUnverifiedFill from '../assets/icons/icon-unverified-fill.svg'; -import IconUnverifiedOutline from '../assets/icons/icon-unverified-outline.svg'; -import IconUpArrowFill from '../assets/icons/icon-up-arrow-fill.svg'; -import IconUpArrowOutline from '../assets/icons/icon-up-arrow-outline.svg'; -import IconUpFill from '../assets/icons/icon-up-fill.svg'; -import IconUpOutline from '../assets/icons/icon-up-outline.svg'; -import IconUploadFill from '../assets/icons/icon-upload-fill.svg'; -import IconUploadOutline from '../assets/icons/icon-upload-outline.svg'; -import IconUpvoteFillMask from '../assets/icons/icon-upvote-fill-mask.svg'; -import IconUpvoteFill from '../assets/icons/icon-upvote-fill.svg'; -import IconUpvoteOffsetmask from '../assets/icons/icon-upvote-offsetmask.svg'; -import IconUpvoteOutline from '../assets/icons/icon-upvote-outline.svg'; -import IconUpvotesFill from '../assets/icons/icon-upvotes-fill.svg'; -import IconUpvotesOutline from '../assets/icons/icon-upvotes-outline.svg'; -import IconUserFill from '../assets/icons/icon-user-fill.svg'; -import IconUserNoteFill from '../assets/icons/icon-user-note-fill.svg'; -import IconUserNoteOutline from '../assets/icons/icon-user-note-outline.svg'; -import IconUserOutline from '../assets/icons/icon-user-outline.svg'; -import IconUsersFill from '../assets/icons/icon-users-fill.svg'; -import IconUsersOutline from '../assets/icons/icon-users-outline.svg'; -import IconValentinesDayFill24 from '../assets/icons/icon-valentines-day-fill-24.svg'; -import IconValentinesDayOutline24 from '../assets/icons/icon-valentines-day-outline-24.svg'; -import IconVaultFill from '../assets/icons/icon-vault-fill.svg'; -import IconVaultOutline from '../assets/icons/icon-vault-outline.svg'; -import IconVerifiedFill from '../assets/icons/icon-verified-fill.svg'; -import IconVerifiedOutline from '../assets/icons/icon-verified-outline.svg'; -import IconVideoCameraFill from '../assets/icons/icon-video-camera-fill.svg'; -import IconVideoCameraOutline from '../assets/icons/icon-video-camera-outline.svg'; -import IconVideoFeedFill from '../assets/icons/icon-video-feed-fill.svg'; -import IconVideoFeedOutline from '../assets/icons/icon-video-feed-outline.svg'; -import IconVideoLiveFill from '../assets/icons/icon-video-live-fill.svg'; -import IconVideoLiveOutline from '../assets/icons/icon-video-live-outline.svg'; -import IconVideoPostFill from '../assets/icons/icon-video-post-fill.svg'; -import IconVideoPostOutline from '../assets/icons/icon-video-post-outline.svg'; -import IconVideoThreadFill from '../assets/icons/icon-video-thread-fill.svg'; -import IconVideoThreadOutline from '../assets/icons/icon-video-thread-outline.svg'; -import IconVideoTranscriptionFill from '../assets/icons/icon-video-transcription-fill.svg'; -import IconVideoTranscriptionOutline from '../assets/icons/icon-video-transcription-outline.svg'; -import IconViewCardFill from '../assets/icons/icon-view-card-fill.svg'; -import IconViewCardOutline from '../assets/icons/icon-view-card-outline.svg'; -import IconViewClassicFill from '../assets/icons/icon-view-classic-fill.svg'; -import IconViewClassicOutline from '../assets/icons/icon-view-classic-outline.svg'; -import IconViewCompactFill from '../assets/icons/icon-view-compact-fill.svg'; -import IconViewCompactOutline from '../assets/icons/icon-view-compact-outline.svg'; -import IconViewGridFill from '../assets/icons/icon-view-grid-fill.svg'; -import IconViewGridOutline from '../assets/icons/icon-view-grid-outline.svg'; -import IconViewsFill from '../assets/icons/icon-views-fill.svg'; -import IconViewsOutline from '../assets/icons/icon-views-outline.svg'; -import IconVolumeFill from '../assets/icons/icon-volume-fill.svg'; -import IconVolumeMuteFill from '../assets/icons/icon-volume-mute-fill.svg'; -import IconVolumeOutline from '../assets/icons/icon-volume-outline.svg'; -import IconWalletFill from '../assets/icons/icon-wallet-fill.svg'; -import IconWalletOutline from '../assets/icons/icon-wallet-outline.svg'; -import IconWarningFill from '../assets/icons/icon-warning-fill.svg'; -import IconWarningOutline from '../assets/icons/icon-warning-outline.svg'; -import IconWebhookFill from '../assets/icons/icon-webhook-fill.svg'; -import IconWebhookOutline from '../assets/icons/icon-webhook-outline.svg'; -import IconWhaleFill from '../assets/icons/icon-whale-fill.svg'; -import IconWhaleOutline from '../assets/icons/icon-whale-outline.svg'; -import IconWikiBanFill from '../assets/icons/icon-wiki-ban-fill.svg'; -import IconWikiBanOutline from '../assets/icons/icon-wiki-ban-outline.svg'; -import IconWikiFill from '../assets/icons/icon-wiki-fill.svg'; -import IconWikiOutline from '../assets/icons/icon-wiki-outline.svg'; -import IconWikiUnbanFill from '../assets/icons/icon-wiki-unban-fill.svg'; -import IconWikiUnbanOutline from '../assets/icons/icon-wiki-unban-outline.svg'; -import IconWorldFill from '../assets/icons/icon-world-fill.svg'; -import IconWorldOutline from '../assets/icons/icon-world-outline.svg'; -import AttributeOnPress from './attributes/_onPress.md'; -import AttributeColor from './attributes/_color.md'; - - -export const icons = { - actions: [ - { name: 'add', variants: [, ] }, - { name: 'add-to-feed', variants: [, ] }, - { name: 'award', variants: [, ] }, - { name: 'backup', variants: [, ] }, - { name: 'browse', variants: [, ] }, - { name: 'chat-new', variants: [, ] }, - { name: 'clear', variants: [, ] }, - { name: 'comment', variants: [, ] }, - { name: 'customize', variants: [, ] }, - { name: 'delete', variants: [, ] }, - { name: 'download', variants: [, ] }, - { name: 'downvote', variants: [, ] }, - { name: 'duplicate', variants: [, ] }, - { name: 'edit', variants: [, ] }, - { name: 'heart', variants: [, ] }, - { name: 'hide', variants: [, ] }, - { name: 'invite', variants: [, ] }, - { name: 'join', variants: [, ] }, - { name: 'joined', variants: [, ] }, - { name: 'leave', variants: [, ] }, - { name: 'logout', variants: [, ] }, - { name: 'mark-read', variants: [, ] }, - { name: 'message', variants: [, ] }, - { name: 'notification', variants: [, ] }, - { name: 'notification-frequent', variants: [, ] }, - { name: 'notification-off', variants: [, ] }, - { name: 'peace', variants: [, ] }, - { name: 'raise-hand', variants: [, ] }, - { name: 'recovery-phrase', variants: [, ] }, - { name: 'reply', variants: [, ] }, - { name: 'report', variants: [, ] }, - { name: 'save', variants: [, ] }, - { name: 'search', variants: [, ] }, - { name: 'send', variants: [, ] }, - { name: 'share-android', variants: [, ] }, - { name: 'share', variants: [, ] }, - { name: 'share-ios', variants: [, ] }, - { name: 'show', variants: [, ] }, - { name: 'star', variants: [, ] }, - { name: 'subtract', variants: [, ] }, - { name: 'tag', variants: [, ] }, - { name: 'translate', variants: [, ] }, - { name: 'translation-off', variants: [, ] }, - { name: 'unheart', variants: [, ] }, - { name: 'unstar', variants: [, ] }, - { name: 'upload', variants: [, ] }, - { name: 'upvote', variants: [, ] } -], -ads: [ - { name: '3rd-party', variants: [, ] }, - { name: 'ads', variants: [, ] }, - { name: 'audience', variants: [, ] }, - { name: 'brand-awareness', variants: [, ] }, - { name: 'campaign', variants: [, ] }, - { name: 'client-list', variants: [, ] }, - { name: 'conversion', variants: [, ] }, - { name: 'copy-clipboard', variants: [, ] }, - { name: 'dashboard', variants: [, ] }, - { name: 'funnel', variants: [, ] }, - { name: 'save-view', variants: [, ] }, - { name: 'spreadsheet', variants: [, ] } -], -beta: [ - { name: 'beta-binoculars', variants: [, ] }, - { name: 'beta-caret-updown', variants: [, ] }, - { name: 'beta-latest', variants: [, ] }, - { name: 'beta-planet', variants: [, ] }, - { name: 'beta-talk-add', variants: [, ] }, - { name: 'beta-telescope', variants: [, ] }, - { name: 'communities', variants: [, ] }, - { name: 'share-new', variants: [, ] } -], -content: [ - { name: 'browser', variants: [, ] }, - { name: 'camera', variants: [, ] }, - { name: 'collection', variants: [, ] }, - { name: 'embed', variants: [, ] }, - { name: 'end-live-chat', variants: [, ] }, - { name: 'gif-post', variants: [, ] }, - { name: 'image-post', variants: [, ] }, - { name: 'link-post', variants: [, ] }, - { name: 'live-chat', variants: [, ] }, - { name: 'media-gallery', variants: [, ] }, - { name: 'pending-posts', variants: [, ] }, - { name: 'poll-post', variants: [, ] }, - { name: 'random', variants: [, ] }, - { name: 'text-post', variants: [, ] }, - { name: 'video-camera', variants: [, ] }, - { name: 'video-live', variants: [, ] }, - { name: 'video-post', variants: [, ] } -], -formatting: [ - { name: 'add-emoji', variants: [, ] }, - { name: 'add-media', variants: [, ] }, - { name: 'align-center', variants: [, ] }, - { name: 'align-left', variants: [, ] }, - { name: 'align-right', variants: [, ] }, - { name: 'attach', variants: [, ] }, - { name: 'bold', variants: [, ] }, - { name: 'code-block', variants: [, ] }, - { name: 'code-inline', variants: [, ] }, - { name: 'emoji', variants: [, ] }, - { name: 'format', variants: [, ] }, - { name: 'hashtag', variants: [, ] }, - { name: 'italic', variants: [, ] }, - { name: 'keyboard', variants: [, ] }, - { name: 'link', variants: [, ] }, - { name: 'list-bulleted', variants: [, ] }, - { name: 'list-numbered', variants: [, ] }, - { name: 'macro', variants: [, ] }, - { name: 'quote', variants: [, ] }, - { name: 'r-slash', variants: [, ] }, - { name: 'rich-text', variants: [, ] }, - { name: 'strikethrough', variants: [, ] }, - { name: 'superscript', variants: [, ] }, - { name: 'table', variants: [, ] }, - { name: 'text-size', variants: [, ] }, - { name: 'u-slash', variants: [, ] }, - { name: 'webhook', variants: [, ] } -], -forms: [ - { name: 'caret-down', variants: [, ] }, - { name: 'caret-left', variants: [, ] }, - { name: 'caret-right', variants: [, ] }, - { name: 'caret-up', variants: [, ] }, - { name: 'checkbox-dismiss', variants: [, ] }, - { name: 'checkbox', variants: [, ] }, - { name: 'checkmark', variants: [, ] }, - { name: 'payment', variants: [, ] }, - { name: 'radio-button', variants: [, ] }, - { name: 'toggle', variants: [, ] } -], -locations: [ - { name: 'all', variants: [, ] }, - { name: 'chat', variants: [, ] }, - { name: 'chat-group', variants: [, ] }, - { name: 'chat-private', variants: [, ] }, - { name: 'chrome', variants: [, ] }, - { name: 'comments', variants: [, ] }, - { name: 'community', variants: [, ] }, - { name: 'custom-feed', variants: [, ] }, - { name: 'discover', variants: [, ] }, - { name: 'feed-video', variants: [, ] }, - { name: 'history', variants: [, ] }, - { name: 'home', variants: [, ] }, - { name: 'inbox', variants: [, ] }, - { name: 'marketplace', variants: [, ] }, - { name: 'popular', variants: [, ] }, - { name: 'posts', variants: [, ] }, - { name: 'predictions', variants: [, ] }, - { name: 'profile', variants: [, ] }, - { name: 'rpan', variants: [, ] }, - { name: 'safari', variants: [, ] }, - { name: 'saved', variants: [, ] }, - { name: 'settings', variants: [, ] }, - { name: 'tools', variants: [, ] }, - { name: 'topic', variants: [, ] }, - { name: 'vault', variants: [, ] }, - { name: 'video-feed', variants: [, ] }, - { name: 'wallet', variants: [, ] }, - { name: 'wiki', variants: [, ] }, - { name: 'world', variants: [, ] } -], -media: [ - { name: 'aspect-ratio', variants: [, ] }, - { name: 'aspect-rectangle', variants: [, ] }, - { name: 'audio', variants: [, ] }, - { name: 'bounce', variants: [, ] }, - { name: 'closed-captioning', variants: [, ] }, - { name: 'crop', variants: [, ] }, - { name: 'effect', variants: [, ] }, - { name: 'loop', variants: [, ] }, - { name: 'mask', variants: [, ] }, - { name: 'meme', variants: [, ] }, - { name: 'mic', variants: [, ] }, - { name: 'mic-mute', variants: [, ] }, - { name: 'music', variants: [, ] }, - { name: 'mute', variants: [, ] }, - { name: 'pause', variants: [, ] }, - { name: 'play', variants: [, ] }, - { name: 'reverse', variants: [, ] }, - { name: 'rotate', variants: [, ] }, - { name: 'rotate-image', variants: [, ] }, - { name: 'skipback10', variants: [, ] }, - { name: 'skipforward10', variants: [, ] }, - { name: 'sticker', variants: [, ] }, - { name: 'swap-camera', variants: [, ] }, - { name: 'text', variants: [, ] }, - { name: 'trim', variants: [, ] }, - { name: 'video-thread', variants: [, ] }, - { name: 'video-transcription', variants: [, ] }, - { name: 'volume', variants: [, ] }, - { name: 'volume-mute', variants: [ ] } -], -moderation: [ - { name: 'admin', variants: [, ] }, - { name: 'approve', variants: [, ] }, - { name: 'automod', variants: [, ] }, - { name: 'ban', variants: [, ] }, - { name: 'block', variants: [, ] }, - { name: 'confidence', variants: [, ] }, - { name: 'crowd-control', variants: [, ] }, - { name: 'distinguish', variants: [, ] }, - { name: 'ignore-reports', variants: [, ] }, - { name: 'kick', variants: [, ] }, - { name: 'lock', variants: [, ] }, - { name: 'mod', variants: [, ] }, - { name: 'mod-mail', variants: [, ] }, - { name: 'mod-mode', variants: [, ] }, - { name: 'mod-mute', variants: [, ] }, - { name: 'mod-overflow', variants: [, ] }, - { name: 'mod-queue', variants: [, ] }, - { name: 'mod-unmute', variants: [, ] }, - { name: 'pin', variants: [, ] }, - { name: 'removal-reasons', variants: [, ] }, - { name: 'remove', variants: [, ] }, - { name: 'rules', variants: [, ] }, - { name: 'severity', variants: [, ] }, - { name: 'spam', variants: [, ] }, - { name: 'unban', variants: [, ] }, - { name: 'unlock', variants: [, ] }, - { name: 'unmod', variants: [, ] }, - { name: 'unpin', variants: [, ] }, - { name: 'user-note', variants: [, ] }, - { name: 'wiki-ban', variants: [, ] }, - { name: 'wiki-unban', variants: [, ] } -], -navigation: [ - { name: 'back', variants: [, ] }, - { name: 'close', variants: [, ] }, - { name: 'collapse-left', variants: [, ] }, - { name: 'collapse-right', variants: [, ] }, - { name: 'dismiss-all', variants: [, ] }, - { name: 'down', variants: [, ] }, - { name: 'down-arrow', variants: [] }, - { name: 'drag', variants: [] }, - { name: 'expand-left', variants: [, ] }, - { name: 'expand-right', variants: [, ] }, - { name: 'external', variants: [, ] }, - { name: 'filter', variants: [, ] }, - { name: 'forward', variants: [, ] }, - { name: 'help', variants: [, ] }, - { name: 'info', variants: [, ] }, - { name: 'jump-down', variants: [, ] }, - { name: 'jump-up', variants: [, ] }, - { name: 'left', variants: [, ] }, - { name: 'load', variants: [] }, - { name: 'menu', variants: [, ] }, - { name: 'overflow-caret', variants: [, ] }, - { name: 'overflow-horizontal', variants: [, ] }, - { name: 'overflow-vertical', variants: [, ] }, - { name: 'qr-code', variants: [, ] }, - { name: 'refresh', variants: [, ] }, - { name: 'right', variants: [, ] }, - { name: 'side-menu', variants: [, ] }, - { name: 'sort', variants: [, ] }, - { name: 'sort-az', variants: [, ] }, - { name: 'sort-price', variants: [, ] }, - { name: 'sort-za', variants: [, ] }, - { name: 'swipe-back', variants: [, ] }, - { name: 'swipe-down', variants: [, ] }, - { name: 'swipe', variants: [, ] }, - { name: 'swipe-up', variants: [, ] }, - { name: 'tap', variants: [, ] }, - { name: 'undo', variants: [, ] }, - { name: 'up-arrow', variants: [, ] }, - { name: 'up', variants: [, ] } -], -ratings: [ - { name: 'drugs', variants: [, ] }, - { name: 'ratings-everyone', variants: [, ] }, - { name: 'ratings-mature', variants: [, ] }, - { name: 'ratings-nsfw', variants: [, ] }, - { name: 'ratings-violence', variants: [, ] } -], -sortsAndViews: [ - { name: 'best', variants: [, ] }, - { name: 'contest', variants: [, ] }, - { name: 'controversial', variants: [, ] }, - { name: 'hot', variants: [, ] }, - { name: 'live', variants: [, ] }, - { name: 'new', variants: [, ] }, - { name: 'qa', variants: [, ] }, - { name: 'rising', variants: [, ] }, - { name: 'top', variants: [, ] }, - { name: 'view-card', variants: [, ] }, - { name: 'view-classic', variants: [, ] }, - { name: 'view-compact', variants: [, ] }, - { name: 'view-grid', variants: [, ] } -], -status: [ - { name: 'activity', variants: [, ] }, - { name: 'appearance', variants: [, ] }, - { name: 'archived', variants: [, ] }, - { name: 'author', variants: [, ] }, - { name: 'avatar-style', variants: [, ] }, - { name: 'blockchain', variants: [, ] }, - { name: 'bot', variants: [, ] }, - { name: 'cake', variants: [, ] }, - { name: 'calendar', variants: [, ] }, - { name: 'coins', variants: [, ] }, - { name: 'collectible-expressions', variants: [, ] }, - { name: 'crosspost', variants: [, ] }, - { name: 'day', variants: [, ] }, - { name: 'downvotes', variants: [, ] }, - { name: 'error', variants: [, ] }, - { name: 'karma', variants: [, ] }, - { name: 'language', variants: [, ] }, - { name: 'location', variants: [, ] }, - { name: 'night', variants: [, ] }, - { name: 'nsfw', variants: [, ] }, - { name: 'nsfw-language', variants: [, ] }, - { name: 'nsfw-violence', variants: [, ] }, - { name: 'official', variants: [, ] }, - { name: 'original', variants: [, ] }, - { name: 'phone', variants: [, ] }, - { name: 'powerup', variants: [, ] }, - { name: 'premium', variants: [, ] }, - { name: 'privacy', variants: [, ] }, - { name: 'quarantined', variants: [, ] }, - { name: 'self', variants: [, ] }, - { name: 'spoiler', variants: [, ] }, - { name: 'sponsored', variants: [, ] }, - { name: 'statistics', variants: [, ] }, - { name: 'status-live', variants: [, ] }, - { name: 'unverified', variants: [, ] }, - { name: 'upvotes', variants: [, ] }, - { name: 'user', variants: [, ] }, - { name: 'users', variants: [, ] }, - { name: 'verified', variants: [, ] }, - { name: 'views', variants: [, ] }, - { name: 'warning', variants: [, ] }, - { name: 'whale', variants: [, ] } -], -topics: [ - { name: 'topic-activism', variants: [, ] }, - { name: 'topic-addictionsupport', variants: [, ] }, - { name: 'topic-advice', variants: [, ] }, - { name: 'topic-animals', variants: [, ] }, - { name: 'topic-anime', variants: [, ] }, - { name: 'topic-art', variants: [, ] }, - { name: 'topic-beauty', variants: [, ] }, - { name: 'topic-business', variants: [, ] }, - { name: 'topic-careers', variants: [, ] }, - { name: 'topic-cars', variants: [, ] }, - { name: 'topic-celebrity', variants: [, ] }, - { name: 'topic-craftsdiy', variants: [, ] }, - { name: 'topic-crypto', variants: [, ] }, - { name: 'topic-culture', variants: [, ] }, - { name: 'topic-diy', variants: [, ] }, - { name: 'topic-entertainment', variants: [, ] }, - { name: 'topic-ethics', variants: [, ] }, - { name: 'topic-family', variants: [, ] }, - { name: 'topic-fashion', variants: [, ] }, - { name: 'topic-fitness', variants: [, ] }, - { name: 'topic-food', variants: [, ] }, - { name: 'topic-funny', variants: [, ] }, - { name: 'topic-gender', variants: [, ] }, - { name: 'topic-health', variants: [, ] }, - { name: 'topic-help', variants: [, ] }, - { name: 'topic-history', variants: [, ] }, - { name: 'topic-hobbies', variants: [, ] }, - { name: 'topic-homegarden', variants: [, ] }, - { name: 'topic-internet', variants: [, ] }, - { name: 'topic-law', variants: [, ] }, - { name: 'topic-learning', variants: [, ] }, - { name: 'topic-lifestyle', variants: [, ] }, - { name: 'topic-marketplace', variants: [, ] }, - { name: 'topic-mature', variants: [, ] }, - { name: 'topic-mensfashion', variants: [, ] }, - { name: 'topic-menshealth', variants: [, ] }, - { name: 'topic-meta', variants: [, ] }, - { name: 'topic-military', variants: [, ] }, - { name: 'topic-movies', variants: [, ] }, - { name: 'topic-music', variants: [, ] }, - { name: 'topic-news', variants: [, ] }, - { name: 'topic-other', variants: [, ] }, - { name: 'topic-outdoors', variants: [, ] }, - { name: 'topic-pets', variants: [, ] }, - { name: 'topic-photography', variants: [, ] }, - { name: 'topic-places', variants: [, ] }, - { name: 'topic-podcasts', variants: [, ] }, - { name: 'topic-politics', variants: [, ] }, - { name: 'topic-programming', variants: [, ] }, - { name: 'topic-reading', variants: [, ] }, - { name: 'topic-religion', variants: [, ] }, - { name: 'topic-science', variants: [, ] }, - { name: 'topic-sexorientation', variants: [, ] }, - { name: 'topic-sports', variants: [, ] }, - { name: 'topic-style', variants: [, ] }, - { name: 'topic-tabletop', variants: [, ] }, - { name: 'topic-technology', variants: [, ] }, - { name: 'topic-television', variants: [, ] }, - { name: 'topic-traumasupport', variants: [, ] }, - { name: 'topic-travel', variants: [, ] }, - { name: 'topic-videogaming', variants: [, ] }, - { name: 'topic-womensfashion', variants: [, ] }, - { name: 'topic-womenshealth', variants: [, ] } -] -}; - -The following is a list of icons available for use within Custom Posts. To use, include the `Name` of the icon in either a ` - - - ); - }, -}); -``` -[View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) - -### Example - -Something you might run into when adding assets is that the images could be large enough to push some content outside of the visible frame. If you notice that happening, you can adjust the container height and image width using `height`, `grow`, and `resizeMode`. See the following example: - -```tsx - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut sed malesuada tortor. Phasellus - velit eros, fermentum vitae cursus ut, condimentum nec tellus. - - ​ - ​ - {/* Footer */} - - - - - - -``` diff --git a/docs/blocks/overview.mdx b/docs/blocks/overview.mdx deleted file mode 100644 index f149f7c2..00000000 --- a/docs/blocks/overview.mdx +++ /dev/null @@ -1,96 +0,0 @@ -# Overview - -Blocks is a cross-platform layout system, optimized for building apps (not web pages, not documents). The goal is to let anyone build simple apps that conform to our design system and best practices for apps, without it being rocket science. If you want to get started right away try a [Devvit Blocks template](../examples/template-library.md#devvit-blocks). - -## Available blocks - -We support the following elements: - -### Containers - -- **Blocks** -- [**HStack**](./stacks.mdx) -- [**VStack**](./stacks.mdx) -- [**ZStack**](./stacks.mdx) - -### Objects - -- [**Text**](./text.mdx) -- [**Button**](./button.mdx) -- [**Spacer**](./spacer.mdx) -- [**Image**](./image.mdx) -- [**Icon**](./icon.mdx) - -Further elements (components) may be derived from these blocks, and obey the same rules. - -## Sizing - -### Paddings and gaps - -- We're operating in a [border-box](https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing) model, where the padding is counted as part of the size of an element. -- Padding is incompressible. -- Gaps are implemented as if we're injecting spacers between all children. - -### Units - -There are two supported units: - -- `px`: device-independent pixels -- `%`: percent of parent container's available content area (i.e. subtracting the parent's padding and gaps) - -### Intrinsic size - -All elements have an _intrinsic size_. This is the size that they would be if there were no sizing modifiers applied to them. - -- **HStack**: Sum of the intrinsic widths of the children × the max of the intrinsic heights of the children (+ gaps and padding) -- **VStack**: Max of the intrinsic widths of the children × the sum of the intrinsic heights of the children (+ gaps and padding) -- **ZStack**: Max of the intrinsic sizes of the children (+ padding) -- **Text**: Size of the text without wrapping or truncation -- **Button**: Size of the text without wrapping or truncation (+ padding) -- **Spacer**: Size in pixels, as specified -- **Image**: imageWidth × imageHeight - -This size provides a baseline, which can be modified by attributes. There are a few sizing attributes: - -- `width` / `height` -- `minWidth` / `minHeight` -- `maxWidth` / `maxHeight` -- `grow` (operates in the _current direction_). - -:::note -Setting both `width` and `grow` simultaneously is not recommended, because then `grow` would become a no-op (overridden by `width`). -::: - -### Preferred size - -The preferred size is calculated based on the intrinsic size and the modifier attributes. The modifiers can conflict, in which case the precedence order is: - -`(most important) minWidth > maxWidth > width > aspect-ratio > grow > intrinsic width (least important)` - -Here, `grow` attempts to set `width="100%"`. Unlike actually setting `width="100%"`, `grow` can be flexibly adjusted later. Examples: - -- `` will always have a preferred size of 50px. (width overrides `grow`) -- `` will always take at least 50px, and will attempt to consume the available `width`. - -### Adjusted size - -Grow elements are flexible. Whenever the full width (or height) of a parent element is not fully utilized, a grow element will expand to fit the parent element, assuming that the other constraints permit. Grow is prioritized lower than the other sizing attributes, e.g. an element will never grow beyond its maxWidth. - -### Direction - -All elements inherit a direction for the purposes of growing. Things only grow in one direction at a time. - -| Element | Self Direction | Child Direction | -| ---------------------- | -------------- | --------------- | -| Blocks | N/A | Vertical | -| [HStack](./stacks.mdx) | Inherit | Horizontal | -| [VStack](./stacks.mdx) | Inherit | Vertical | -| [ZStack](./stacks.mdx) | Inherit | Inherit | -| [Text](./text.mdx) | Horizontal | N/A | -| [Button](./button.mdx) | Horizontal | N/A | -| [Spacer](./spacer.mdx) | Inherit | N/A | -| [Image](./image.mdx) | Inherit | N/A | - -### Overflow - -All containers clip overflown content. diff --git a/docs/blocks/spacer.mdx b/docs/blocks/spacer.mdx deleted file mode 100644 index e663797a..00000000 --- a/docs/blocks/spacer.mdx +++ /dev/null @@ -1,84 +0,0 @@ -import AttributeMinHeight from './attributes/_minHeight.md'; -import AttributeMaxHeight from './attributes/_maxHeight.md'; -import AttributeMinWidth from './attributes/_minWidth.md'; -import AttributeMaxWidth from './attributes/_maxWidth.md'; -import AttributeGrow from './attributes/_grow.md'; -import AttributeWidth from './attributes/_width.mdx'; -import AttributeHeight from './attributes/_height.mdx'; - -# Spacer - -The `` element is used to create adjustable space between UI elements. - -## Attributes - -### `size` - -Sets the spacer size. Possible values: - -- `xsmall` -- `small`: The default if the attribute is not used. -- `medium` -- `large` - -### `grow` - - - -### `shape` - -Possible values: - -- `invisible` -- `thin` -- `square` - -### `width` - - - -### `minWidth` - - - -### `maxWidth` - - - -### `height` - - - -### `minHeight` - - - -### `maxHeight` - - - -## Examples - -```tsx - - - - - -``` - -```tsx - - - - - -``` - -```tsx - - - - - -``` diff --git a/docs/blocks/stacks.mdx b/docs/blocks/stacks.mdx deleted file mode 100644 index fa9d3abb..00000000 --- a/docs/blocks/stacks.mdx +++ /dev/null @@ -1,297 +0,0 @@ -import AttributeMinHeight from './attributes/_minHeight.md'; -import AttributeMaxHeight from './attributes/_maxHeight.md'; -import AttributeMinWidth from './attributes/_minWidth.md'; -import AttributeMaxWidth from './attributes/_maxWidth.md'; -import AttributeGrow from './attributes/_grow.md'; -import AttributeAlignment from './attributes/_alignment.md'; -import AttributeOnPress from './attributes/_onPress.md'; -import AttributeWidth from './attributes/_width.mdx'; -import AttributeHeight from './attributes/_height.mdx'; -import AttributeColor from './attributes/_color.md'; - -# Stacks - -The stack elements (``, ``, and ``) are flexible containers used to arrange child elements in different orientations and layers. Stacks can be nested, styled, and clicked if given an `onPress` handler. Stacks clip any overflowing content. - -## Directions - -### `` - -Vertically stacks elements on top of each other. Perfect for forms, lists, or any vertical grouping of elements. - -### `` - -Horizontally arranges elements side by side. Ideal for creating horizontal layouts, like navigation menus or toolbars. - -### `` - -Layers elements on top of each other along the z-axis. Useful for overlaying elements like modals, badges, or custom stacking layouts. - -## Attributes - -### `padding` - -An `enum` that defines the padding inside the stack. Possible values: - -- `none`: The default if the attribute is not used. -- `xsmall`: 4px -- `small`: 8px -- `medium`: 16px -- `large`: 32px - -![app_image_fixed_size](../assets/blocks/docs-blocks-stacks-padding.png) - -#### Examples - -A padded row of buttons with a gap between: - -```tsx - - - - -``` - -### `gap` - -An `enum` that determines the space between child elements. Possible values: - -- `none`: The default if the attribute is not used. -- `small`: 8px -- `medium`: 16px -- `large`: 32px - -Has no effect on ``. - -![app_image_fixed_size](../assets/blocks/docs-blocks-stacks-gap.png) - -#### Examples - -Two left aligned buttons with a gap between: - -```tsx - - - - -``` - -The gap is automatically subtracted from the available space children can occupy. - -```tsx - - - - -``` - -### `alignment` - - - -#### Examples - -Align content to the top left corner - -```tsx - - - -``` - -Align content to the bottom right corner - -```tsx - - - -``` - -### `reverse` - -A `boolean` that reverses the order of child elements if set to true. Possible values: - -- `true` -- `false`: The default if the attribute is not used. - -![Showing the reverse attribute](../assets/blocks/docs-blocks-stacks-reverse.png) - -### `grow` - - - -#### Examples - -In a `hstack` children grow horizontally. - -```tsx - - - - -``` - -In a `vstack` children grow vertically. - -```tsx - - - - -``` - -Has no effect on children within a ``. - -### `width` - - - -#### Examples - -Two colorful squares, taking up 50% of the width 100px each, with a height of 20px. - -```tsx - - - - -``` - -### `minWidth` - - - -### `maxWidth` - - - -### `height` - - - -#### Examples - -Sets the height as an absolute value - -```tsx - ... -``` - -Sets the height as a percentage of the parent. - -```tsx - ... -``` - -### `minHeight` - - - -### `maxHeight` - - - -### `backgroundColor` - - - -Defaults to `transparent`. - -### `borderWidth` - -An `enum` for setting the border width of the stack. The border sits between edge of the element and the content area. Possible values: - -- `none`: The default if the attribute is not used. -- `thin`: 1px -- `thick`: 2px - -![Showing all the borderWidth options](../assets/blocks/docs-blocks-stacks-border-width.png) - -### `borderColor` - - - -Defaults to `neutral-border-weak`. - -### `cornerRadius` - -En `enum` that rounds all 4 corners of the stack. Clips content. Possible values: - -- `none`: The default if the attribute is not used. -- `small`: 8px. -- `medium`: 16px. -- `large`: 24px. -- `full`: 50%. - -![Showing all the cornerRadius options](../assets/blocks/docs-blocks-stacks-corner-radius.png) - -## Functions - -### `onPress` - - - -#### Examples - -```tsx - console.log('clicked')} -> -``` - -## Examples - -An ``. Open in [Play](https://developers.reddit.com/play#pen/N4IgdghgtgpiBcIQBoQGcBOBjBICWUADgPYYAuABMACIwBudeZAvhQGYbFQUDkAAgBN6jMgHpCAVwBGAGzxYAtBEJ4eAHTAbaDJgDoIAgQGEJaMlwAKxMwBUAnoRgAKYBooVIseLwBy0GDzIbhQYMGBCGN4A+ljEYGQwAB6UALwAfFTB7qFkEhhgFE5Z7hQAPLLEWADWaGnFJWUAFmYQ1RQQcgDmYLDxKWogWGEJGBRQeIYyMAMUncr9ILACeBJQM4QGy2CdC0srayAUUq1VnZwS4UbEMqQLAMRsAAxPTwN1BQ2fpQnJFGh4AC8YAsZBAMJ1pocAO4wPCdRpkBZSa4CN4AUUS0EIUwoNiYU1Koh+ZHeny+aA2Q1GZ2IUIoolJZJK5QkZHMBXkcQWphgGBm-yBuxgy1WM2UjjBEDAQwWhAwBDBdjeAFU0LzCVJWezGWTCc0yCcdczRBVqrVigBKYLMDTMK2aMBJEjkChCNgQCQySjaEQoEB0Xn-OIIACMzCAA). - -```tsx - - - - Example Title - - - - - -``` - -A `` example. Open in [Play](https://developers.reddit.com/play#pen/N4IgdghgtgpiBcIQBoQGcBOBjBICWUADgPYYAuABMACIwBudeZAvhQGYbFQUDkAAgBN6jMgHpCAVwBGAGzxYAtBEJ4eAHTAbaDJgDoIAgQGEJaMlwAKxMwBUAnoRgAKYBooVIseLwBy0GDzIbhQYMGBCGN4A+ljEYGQwAB6UALwAfFTB7qFkEhhgFE5Z7hQAPLLEWADWaGnFJWV0ZhDVFBByAOZgsPEpauhkEOQUWGEJGP0UHcp96FDtMpOEBgJ4YB2zsKsSUP11BQ2HpQnJFGh4AF4wmzDbuyAUAO4weB0AFmSzUsQyAnsA4v4KAB5QhkPBxNClUQnMj7Q5HKQSMjmArnK43O6TZSOIYQMCjWZoGCxcJDOx7UHguIUACM0KRKLi8IRJXKyNRZ0u136WzwO2xhFxGHxhP6xNJAnJlLBEIKACYGRzmfVEcq0dzMfz7m0hTA8QSeegSXEpRgKSA0lS5RQAMxKplgFkI6FNQbVZ1s0QVaq1YoASmCzA0zEDmjASRIwyEbAgEhklG0IhQIDoMAw5ziCFpzCAA). - -```tsx - - - - Game Options - - - - - - -``` - -A `` example. Open in [Play](https://developers.reddit.com/play#pen/N4IgdghgtgpiBcIQBoQGcBOBjBICWUADgPYYAuABMACIwBudeZAvhQGYbFQUDkAAgBN6jMgHpCAVwBGAGzxYAtBEJ4eAHTAbaDJgDoIAgQGEJaMlwAKxMwBUAnoRgAKYBooVIseLwBy0GDzIbhQYMGBCGN4A+ljEYGQwAB6UALwAfFTB7qFkEhhgFE5Z7hQAPLLEWADWaGnFJWUAXmYQ1RQQcgDmYLDxKWogWGEJGBRQeIYyMAMUAO4TZAAWKTwAzAAM64SJPBSLMHidi2QrAEyb27tSrVWdnBLhRsQypP0gAMQA7AAcAJzrvyMAzqBQaYNKCWSFDQeEaMDeiUSMggGE60xAcwORxOAykzwEM1iLwwb3eADEKZSZmY7FM3vsDHgwJ1gQAhACSAHEKDYAKIADRspVEkLIILB4NF0Nh8IGiTQUA6MhmsyxxzeoU6EmRGEJz1eA3JlIp1LItNlIDxAjsMw6hx6wzeeLI5m4Q3iMF1IHFErBAHkCuZCBRiGxoYqZDIKKL6uCRUkxbH3MLmmQbj6GsKKtVasUAJTBZgaZgFzRgJIkcgUIRsCDayjaEQoEB0T0wuIIACMzCAA). - -```tsx - - - - BIG TEXT - - - On top of small text - - - -``` diff --git a/docs/blocks/text.mdx b/docs/blocks/text.mdx deleted file mode 100644 index e44596d0..00000000 --- a/docs/blocks/text.mdx +++ /dev/null @@ -1,193 +0,0 @@ -import AttributeMinHeight from './attributes/_minHeight.md'; -import AttributeMaxHeight from './attributes/_maxHeight.md'; -import AttributeMinWidth from './attributes/_minWidth.md'; -import AttributeMaxWidth from './attributes/_maxWidth.md'; -import AttributeGrow from './attributes/_grow.md'; -import AttributeAlignment from './attributes/_alignment.md'; -import AttributeOnPress from './attributes/_onPress.md'; -import AttributeWidth from './attributes/_width.mdx'; -import AttributeHeight from './attributes/_height.mdx'; -import AttributeColor from './attributes/_color.md'; - -# Text - -The `` element enables you add and format text in your UI. - -By default, text blocks are limited to a single line. If you have a lot of text, it grows horizontally and the overflow gets clipped. There are two properties to make working with multi-line text easier: `wrap` and `overflow`. For text to wrap onto a new line, there needs to be a width defined. - -## Attributes - -### `style` - -An `enum` for defining the text style. Shorthand for setting `size`, `weight`, and `color` at the same time. Possible values: - -- `metadata` -- `body`: The default if the attribute is not used. -- `heading` - -### `size` - -An `enum` for setting the text size and line height. Overrides the `style` attribute if both are set. Possible values: - -- `xsmall` - - Text size: 10px - - Line height: 16px -- `small` - - Text size: 12px - - Line height: 16px -- `medium`: The default if the attribute is not used. - - Text size: 14px - - Line height: 20px -- `large` - - Text size: 16px - - Line height: 20px -- `xlarge` - - Text size: 18px - - Line height: 24px -- `xxlarge` - - Text size: 24px - - Line height: 28px - -![Text sizes](../assets/blocks/docs-blocks-text-size.png) - -### `weight` - -An `enum` for setting the text weight. Possible values: - -- `regular`: The default if the attribute is not used. -- `bold` - -![Text weights](../assets/blocks/docs-blocks-text-weights.png) - -### `color` - - - -Defaults to `neutral-content`. - -### `alignment` - - - -### `grow` - - - -### `outline` - -An `enum` for adding an outline to text. The outline is either `white` or `black`, depending on which color provides the most contrast against the text color. Possible values: - -- `none`: The default if the attribute is not used. -- `thin`: 1px -- `thick`: 2px - -### `selectable` - -A `boolean` for determing if users can select the text. Possible values: - -- `true`: The default if the attribute is not used. -- `false` - -### `wrap` - -A `boolean` for toggling text wrapping, enabling text to wrap onto multiple lines. Only wraps if the width is constrained. Possible values: - -- `true` -- `false`: The default if the attribute is not used. - -### `overflow` - -An `enum` for determing how text overflow is handled. Possible values: - -- `clip`: The default if the attribute is not used. -- `ellipsis` - -### `width` - - - -### `minWidth` - - - -### `maxWidth` - - - -### `height` - - - -### `minHeight` - - - -### `maxHeight` - - - -## Functions - -### `onPress` - - - -#### Examples - -```tsx - console.log('world')}>Hello -``` - -## Examples - -### Text wrapping - -Wrap the text onto a new line once the available width has been filled. The number of lines is a function of the font size and the text string length. - -![default](../assets/text_formats/text_wrapping.png) - -```tsx - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam consectetur rutrum tempor. - -``` - -### Changing the overflow symbol - -Set the overflow attribute to “ellipsis” provides a visual cue to users that there is additional text. - -![default](../assets/text_formats/overflow.png) - -```tsx - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam consectetur rutrum tempor. Donec - lacinia nulla dolor, eget iaculis quam imperdiet quis. Duis nec velit dignissim, lobortis ligula - eu, mattis arcu. - -``` - -### Constricting height and overflow indicator - -Define the height to limit the number of lines occupied by the text. - -![default](../assets/text_formats/constricting_overflow.png) - -```tsx - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam consectetur rutrum tempor. Donec - lacinia nulla dolor, eget iaculis quam imperdiet quis. Duis nec velit dignissim, lobortis ligula - eu, mattis arcu. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam consectetur - rutrum tempor. Donec lacinia nulla dolor, eget iaculis quam imperdiet quis. Duis nec velit - dignissim, lobortis ligula eu, mattis arcu. - -``` - -### Constricting height with limited content - -Avoid UI shift by setting a fixed height. - -![default](../assets/text_formats/constricting_limited.png) - -```tsx -Hello world -``` diff --git a/docs/capabilities/blocks/app_image_assets.md b/docs/capabilities/blocks/app_image_assets.md deleted file mode 100644 index ec8e5c46..00000000 --- a/docs/capabilities/blocks/app_image_assets.md +++ /dev/null @@ -1,116 +0,0 @@ -# Adding images - -Add images to your interactive post. - -You can add things like a logo for the community, a special artistic expression, or new characters to unlock in your game. - -:::note -The [Reddit Content Policy](https://www.redditinc.com/policies/content-policy) applies to images in your app. All project assets are subject to an app review and screened by automated Reddit systems at upload. Images that violate the content policy will be removed. -::: - -## What’s supported? - -- File types: JPG/JPEG, PNG, or GIF -- Image size: 20MB for JPG/PNG; 40MB for GIF -- Folder size: 1GB per app version - -The image file name does not have any constraints, but needs to reside in the “assets” folder. The file type is determined by inspecting its contents. - -## How it works - -Add an image to your app’s `assets` folder. The image is uploaded to Reddit and accessible in your app source code when you upload your app. New projects should have an `assets` folder in the root directory. If you’re updating an existing project, you’ll need to add the `assets` folder. - -Once the images are uploaded to Reddit, you can show the image in one of two ways: - -1. Use the Blocks UI component to embed your image in a post (to learn more about how to style images, check out our [blocks gallery](https://www.reddit.com/r/Devvit/post-viewer/1545cls/custom_post_block_kit_gallery/)). - -:::note -The imageWidth and imageHeight attributes are in device independent pixels (DIPs). -::: - -```tsx - -``` - -2. Use the URL string to get the image’s public URL from the code. - -```ts -context.assets.getURL('imageName.jpg'); -``` - -:::note -The image name is case-sensitive. Additionally, you can use nested folders to organize your assets. For example, -`assets/images/imageName.jpg` will be uploaded to Reddit as `images/imageName.jpg`. -::: - -## Use an image in a post - -1. Locate the `assets` folder at the root of your project directory (or create one if it’s not there). -2. Create your image and add it to the `assets` folder. -3. Update your code to import the new asset. Here’s an example: - -```tsx -Devvit.configure({ - redditAPI: true, -}); - -const render: Devvit.CustomPostComponent = () => { - return ( - - - Hello! - - - - ); -}; - -// addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. -Devvit.addCustomPostType({ - name: 'My custom post', - description: 'Test custom post for showing a custom asset!', - render, -}); - -Devvit.addMenuItem({ - location: 'subreddit', - label: 'Make custom post with image asset', - onPress: async (event, context) => { - const subreddit = await context.reddit.getSubredditById(context.subredditId); - - await context.reddit.submitPost({ - subredditName: subreddit.name, - title: 'Custom post!', - preview: render(context), - }); - }, -}); -``` -[View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) - -4. Run `devvit upload`. -5. Install or update your app on your subreddit. -6. From the subreddit menu, click "Make custom post with image asset". This will create a new post with the image embedded in it. - -![image_in_a_post](../../assets/docs-app-image-assets-1.png) - -## Show an image URL in a toast - -1. Locate the `assets` folder at the root of your project directory (or create one if it’s not there). -2. Create your image and add it to the `assets` folder. -3. Update your code to import the new asset. Here’s an example: - -```ts -Devvit.addMenuItem({ - location: 'subreddit', - label: 'Get image URL', - onPress: async (event, context) => { - const url = await context.assets.getURL('hello.png'); - context.ui.showToast(url); // should show 'https://i.redd.it/.png' - // and if you go to the URL it showed, it should be your art - // Note, it doesn't display the image this way, just the URL as text! - }, -}); -``` - -![image_in_a_post](../../assets/docs-app-image-assets-2.png) diff --git a/docs/capabilities/blocks/blocks_payments.md b/docs/capabilities/blocks/blocks_payments.md deleted file mode 100644 index 961ba400..00000000 --- a/docs/capabilities/blocks/blocks_payments.md +++ /dev/null @@ -1,253 +0,0 @@ -# Adding payments - -You can use the payments template to build your app or add payment functionality to an existing app. - -:::note -[Devvit Web](../../capabilities/devvit-web/devvit_web_overview.mdx) is the recommended approach for all interactive experiences. We recommend [migrating your app](../../earn-money/payments/payments_migrate.mdx) to Devvit Web payments. -::: - -To start with a template, select the payments template when you create a new project or run: - -```bash -devvit new -``` - -To add payments functionality to an existing app, run: - -```bash -npm install @devvit/payments -``` - -:::note -Make sure you’re on Devvit 0.11.3 or higher. See the [quickstart](https://developers.reddit.com/docs/next/quickstart) to get up and running. -::: - -## Register products - -Register products in the src/products.json file in your local app. To add products to your app, run the following command: - -```bash -devvit products add -``` - -Registered products are updated every time an app is uploaded, including when you use [Devvit playtest](../../guides/tools/playtest.md). - -
- Click here for instructions on how to add products manually to your products.json file. -The JSON schema for the file format is available at https://developers.reddit.com/schema/products.json. - -Each product in the products field has the following attributes: -| **Attribute** | **Description** | -| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `sku` | A product identifier that can be used to group orders or organize your products. Each sku must be unique for each product in your app. | -| `displayName` | The official name of the product that is displayed in purchase confirmation screens. The name must be fewer than 50 characters, including spaces. | -| `description` | A text string that describes the product and is displayed in purchase confirmation screens. The description must be fewer than 150 characters, including spaces. | -| `price` | An predefined integer that sets the product price in Reddit Gold. See details below. | -| `image.icon` | **(optional)** The path to the icon that represents your product in your assets folder. | -| `metadata` | **(optional)** An optional object that contains additional attributes you want to use to group and filter products. Keys and values must be alphanumeric (a - Z, 0 - 9, and - ) and contain 30 characters or less. You can add up to 10 metadata keys. Metadata keys cannot start with "devvit-". | -| `accountingType` | Categories for how buyers consume your products. Possible values are:
  • `INSTANT` for purchased items that are used immediately and disappear.
  • `DURABLE` for purchased items that are permanently applied to the account and can be used any number of times
  • `CONSUMABLE` for items that can be used at a later date but are removed once they are used.
  • `VALID_FOR_` values indicate a product can be used throughout a period of time after it is purchased.
| - -
- -## Price products - -Product prices are predefined and must be one of the following gold values: - -- 5 gold ($0.10) -- 25 gold ($0.50) -- 50 gold ($1) -- 100 gold ($2) -- 150 gold ($3) -- 250 gold ($5) -- 500 gold ($10) -- 1000 gold ($20) -- 2500 gold ($50) - -:::note -Actual payments will not be processed until your products are approved. While your app is under development, you can use sandbox payments to [simulate purchases](../../earn-money/payments/payments_test#simulate-purchases). -::: - -## Design guidelines - -You’ll need to clearly identify paid products or services. Here are some best practices to follow: - -- Use a short name, description, and image for each product. -- Don’t overwhelm users with too many items. -- Try to keep purchases in a consistent location or use a consistent visual pattern. -- Only use the gold icon to indicate purchases for Reddit Gold. - -### Product image - -Product images need to meet the following requirements: - -- Minimum size: 256x256 -- Supported file type: .png - -If you don’t provide an image, the default Reddit product image is used. - -![default image](../../assets/default_product_image.png) - -**Example** - -```json -{ - "$schema": "https://developers.reddit.com/schema/products.json", - "products": [ - { - "sku": "god_mode", - "displayName": "God mode", - "description": "God mode gives you superpowers (in theory)", - "price": 25, - "images": { - "icon": "products/extra_life_icon.png" - }, - "metadata": { - "category": "powerup" - }, - "accountingType": "CONSUMABLE" - } - ] -} -``` - -### Purchase buttons (required) - -#### Blocks - -The `ProductButton` is a Devvit blocks component designed to render a product with a purchase button. It can be customized to match your app's look and feel. - -**Usage:** - -```tsx - payments.purchase(p.sku)} - appearance="tile" -/> -``` - -##### `ProductButtonProps` - -| **Prop Name** | **Type** | **Description** | -| ------------------ | ----------------------------------------------- | ------------------------------------------------------------------------------------ | -| `product` | `Product` | The product object containing details such as `sku`, `price`, and `metadata`. | -| `onPress` | `(product: Product) => void` | Callback function triggered when the button is pressed. | -| `showIcon` | `boolean` | Determines whether the product icon is displayed on the button. Defaults to `false`. | -| `appearance` | `'compact'` | `'detailed'` | `'tile'` | Defines the visual style of the button. Defaults to `compact`. | -| `buttonAppearance` | `string` | Optional [button appearance](../../blocks/button.mdx#appearance). | -| `textColor` | `string` | Optional [text color](../../blocks/text.mdx#color). | - -#### Webviews - -Use Reddit’s primary, secondary, or bordered button component and gold icon in one of the following formats: - -![default image](../../assets/payments_button_purchase.png) - -Use a consistent and clear product component to display paid goods or services to your users. Product components can be customized to fit your app, like the examples below. - -![default image](../../assets/payments_component_button.png) - -![default image](../../assets/payments_component_list.png) - -![default image](../../assets/payments_component_tile.png) - -## Complete the payment flow - -Use `addPaymentHandler` to specify the function that is called during the order flow. This customizes how your app fulfills product orders and provides the ability for you to reject an order. - -Errors thrown within the payment handler automatically reject the order. To provide a custom error message to the frontend of your application, you can return {success: false, reason: } with a reason for the order rejection. - -This example shows how to issue an "extra life" to a user when they purchase the "extra_life" product. - -```ts -import { type Context } from "@devvit/public-api"; -import { addPaymentHandler } from "@devvit/payments"; -import { Devvit, useState } from "@devvit/public-api"; - -Devvit.configure({ - redis: true, - redditAPI: true, -}); - -const GOD_MODE_SKU = "god_mode"; - -addPaymentHandler({ - fulfillOrder: async (order, ctx) => { - if (!order.products.some(({ sku }) => sku === GOD_MODE_SKU)) { - throw new Error("Unable to fulfill order: sku not found"); - } - if (order.status !== "PAID") { - throw new Error("Becoming a god has a cost (in Reddit Gold)"); - } - - const redisKey = godModeRedisKey(ctx.postId, ctx.userId); - await ctx.redis.set(redisKey, "true"); - }, -}); -``` - -## Implement payments - -The frontend and backend of your app coordinate order processing. - -![Order workflow diagram](../../assets/payments_order_flow_diagram.png) - -To launch the payment flow, create a hook with `usePayments()` followed by `hook.purchase()` to initiate the purchase from the frontend. - -This triggers a native payment flow on all platforms (web, iOS, Android) that works with the Reddit backend to process the order. The `fulfillOrder()` hook calls your app during this process. - -Your app can acknowledge or reject the order. For example, for goods with limited quantities, your app may reject an order once the product is sold out. - -### Get your product details - -Use the `useProducts` hook or `getProducts` function to fetch details about products. - -```tsx -import { useProducts } from "@devvit/payments"; - -export function ProductsList(context: Devvit.Context): JSX.Element { - // Only query for products with the metadata "category" of value "powerup". - // The metadata field can be empty - if it is, useProducts will not filter on metadata. - const { products } = useProducts(context, { - metadata: { - category: "powerup", - }, - }); - - return ( - - {products.map((product) => ( - - {product.name} - {product.price} - - ))} - - ); -} -``` - -You can also fetch all products using custom-defined metadata or by an array of skus. Only one is required; if you provide both then they will be AND’d. - -```tsx -import { getProducts } from '@devvit/payments'; -const products = await getProducts({, -}); -``` - -### Initiate orders - -Provide the product sku to trigger a purchase. This automatically populates the most recently-approved product metadata for that product id. - -**Example** - -```tsx -import { usePayments } from '@devvit/payments'; - -// handles purchase results -const payments = usePayments((result: OnPurchaseResult) => { console.log('Tried to buy:', result.sku, '; result:', result.status); }); - -// for each sku in products: - -``` diff --git a/docs/capabilities/blocks/dimensions.md b/docs/capabilities/blocks/dimensions.md deleted file mode 100644 index 78651f00..00000000 --- a/docs/capabilities/blocks/dimensions.md +++ /dev/null @@ -1,76 +0,0 @@ -# Dimensions - -Create responsive interactive posts. - -Dimensions provides a way for you to create responsive interactive posts by giving you the dimensions of the root node as part of the context object. This lets you write responsive interfaces based on the space available within the context object. - -Dimensions are dynamic and update in real time if the device or viewport changes. You can also resize your screen as you develop to see how your posts respond in real time. This is the same post displayed on a phone (left) and a tablet (right): - -![A post that is aware of its size](../../assets/custom-posts/custom-posts-dimensions.png) - -You can use dimensions to: - -- Show a different experience on mobile compared to desktop (e.g., one column on mobile and two columns on desktop) -- Use pixel-based calculations for precision where percentages won’t work. - -## Getting dimensions - -:::warning Deprecation notice -`addCustomPostType` is deprecated and will be unsupported. It will not work after June 30. [View announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) -::: - -Dimension information is specified in density-independent pixels. These pixel units are located on the context object. - -| Dimension | Description | -| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Height | The pixel unit height of the interactive post. This is a fixed value that will not change based on the height property provided on `Devvit.addCustomPostType`. | -| Width | The pixel width of the containing box for your interactive post. | -| Scale | The [pixel scale](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio) that determines the resolution for how your custom post renders on the device. | - -## Example - -This example shows a custom post that specifies dimensions. - -```tsx -// addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. -Devvit.addCustomPostType({ - name: 'Dimensions app', - render: (context) => { - return context.dimensions?.width < 300 ? : ; - }, -}); -``` -[View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) - -This example shows that dimensions always references the root element, even if it’s in a child element. Although `MyHeader` is a component within the root element, this does not change the custom post dimensions provided in the context object. - -```tsx -export const MyHeader: Devvit.BlockComponent = (props, context) => { - // Dimensions reflect the custom post, not the Header component - const dimensions = context.dimensions; - return ( - - Header - - ); -}; - -// addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. -Devvit.addCustomPostType({ - name: 'Name', - render: (_context) => { - return ; - }, -}); -``` -[View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) - -## Limitations - -- Dimensions are only for the custom post box. -- Dimensions for specific elements within the custom post box are not supported. -- Dimensions for specific device screen sizes (phone, tablet, desktop) are not supported. diff --git a/docs/capabilities/blocks/optimize_performance.md b/docs/capabilities/blocks/optimize_performance.md deleted file mode 100644 index 031c699a..00000000 --- a/docs/capabilities/blocks/optimize_performance.md +++ /dev/null @@ -1,406 +0,0 @@ -# Optimizing performance - -You can optimize your app to make it run faster, use resources more efficiently, and create a better user experience. Here’s how. - -## Always use the latest public-api - -You can check your current Devvit version on the apps version panel. To update your app to use the latest version of the public API: - -1. Run `npm install -s devvit` in your project folder to update the Devvit CLI locally. - -2. In your project directory, run the following commands: - -- `npx devvit update app` -- `npm install` - -:::note -You must update the CLI before you upgrade @devvit/public-api in your project. -::: - -## Write performant requests - -These best practices will optimize your app. - -### Get as much data as possible from the context - -If you only need the name of the current user: - -- Avoid requests like `context.reddit.getCurrentUser()` or `context.reddit.getUserById(context.userId)`. -- Use `context.reddit.getCurrentUsername()` instead. - -If you only need the subreddit name: - -- Don’t request the whole Subreddit model with `context.reddit.getCurrentSubreddit()`. -- Use `context.reddit.getCurrentSubredditName()` instead. - -### Cache requests to RedditAPI or external resources - -Use `context.cache` to reduce the amount of requests to optimize performance and running costs of your application. - -### Leverage scheduled jobs to fetch or update data - -Use [scheduler](../server/scheduler.mdx) to make large data requests in the background and store it in [Redis](../server/redis.mdx) for later use. You can also [fetch data for multiple users](#how-to-cache-data). - -### Batch API calls to make parallel requests - -Every request defined in `useState` is blocking the render function. You can improve app performance by [making parallel requests](#how-to-make-parallel-requests). - -## Ensure your app has a lightweight first view - -The faster the first view appears on the user’s screen, the better the user experience. You can minimize and delay the data loading necessary for your app to display the first view. To do this: - -- Use setCustomPostPreview to make a dynamic, compelling preview that loads quickly. -- Use [useAsync](./working_with_useasync.md) to load the necessary data without blocking the rendering process. -- Import [useState](./working_with_usestate.md) or [useAsync](./working_with_useasync.md) to load the data needed for the specific component only when that component is rendered. - -## How to: make parallel requests - -In Devvit, the first render happens on the server side. Parallel fetch requests will speed up the first render. - -### Before optimization: individual fetch requests - -In the render function of this interactive post, the app fetches data about the post, the user, the weather, and the leaderboard stats. - -```tsx -import { Devvit, useState } from "@devvit/public-api"; - -render: (context) => { - const [postInfo] = useState(async () => { - return await getThreadInfo(context); - }); - - const [user] = useState(async () => { - return await getUser(context); - }); - - const [weather] = useState(async () => { - return await getTheWeather(context); - }); - - const [leaderboardStats] = useState(async () => { - return await getLeaderboard(context); - }); - - // the rest of the render function -}; -``` - -If each request takes roughly 250 ms, then four requests will take around 1 second to resolve. If we change the example to make those requests in parallel, it would take 250 ms to resolve all four! - -### After optimization: parallel fetch requests - -You can do this in two ways: - -- Use [useAsync](./working_with_useasync.md) to make everything non-blocking. -- Use [Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) inside one [useState](./working_with_usestate.md) method to get all of the information at once. - -:::note -The main difference between these two methods is that `useState` blocks render until it is resolved, and `useAsync` allows the app to render immediately and requires loading states while the requests resolve. -::: - -#### useAsync - -This is the best choice for performance because it allows you to render parts of your application while others may still be loading. Here’s how the same example looks for useAsync: - -```tsx -import { Devvit, useAsync } from "@devvit/public-api"; - -const { data: postInfo, loading: postInfoLoading } = useAsync(async () => { - return await getThreadInfo(context); -}); - -const { data: user, loading: userLoading } = useAsync(async () => { - return await getUser(context); -}); - -const { data: weather, loading: weatherLoading } = useAsync(async () => { - return await getTheWeather(context); -}); - -const { data: leaderboardStats, loading: leaderboardStatsLoading } = useAsync( - async () => { - return await getLeaderboard(context); - }, -); -``` - -#### useState - -This is the same example using useState. - -```tsx -import { Devvit, useState } from "@devvit/public-api"; - -render: (context) => { - const [appState, setAppState] = useState(async () => { - const [postInfo, user, weather, leaderboardStats] = await Promise.all([ - getThreadInfo(context), - getUser(context), - getTheWeather(context), - getLeaderboard(context), - ]); - return { - postInfo, - user, - weather, - leaderboardStats, - }; - }); - - const { postInfo, user, weather, leaderboardStats } = appState; - - // the rest of the render function -}; -``` - -You can see the app gets the same variables from the state object, which means that you won’t need to change the way you access the data from the state in the rest of the app. - -:::note -If you need to update one of the state props, you’ll need to do `setAppState({...appState, postInfo: newPostInfo})` instead of `setPostInfo(newPostInfo)`. -::: - -## How to: cache data - -The following example shows how unoptimized code for fetching data from an external resource, like a weather API, looks: - -```tsx -import { Devvit, useState } from "@devvit/public-api"; - -// naive, non-optimal way of fetching that kind of data -const [externalData] = useState(async () => { - const response = await fetch("https://external.weather.com"); - - return await response.json(); -}); -``` - -### Problem: request overload - -In this case, state is initialized for each user that sees the app. This means that in a large subreddit where thousands of users can see the post at the same time, your app would make thousands of requests to the external resource. If you request the data in an interval, like to get a game score or stock market information, then the load repeats on each interval tick. This is not ideal. - -### Solution: make one request - -You can use a cache helper to make one request for data, save the response, and provide this response to all users requesting the same data. The cache lives at the subreddit level (not the app level). - -**Example: fetch weather data every 2 hours with cache helper** - -```tsx -import { Devvit, useState } from "@devvit/public-api"; - -// optimized, performant way of fetching that kind of data -const [externalData] = useState(async () => { - return context.cache( - async () => { - const response = await fetch("https://external.weather.com"); - return await response.json(); - }, - { - key: `weather_data`, - ttl: 2 * 60 * 60 * 1000, // 2 hours in milliseconds - }, - ); -}); -``` - -:::note -Do not cache sensitive information. Cache helper randomly selects one user to make the real request and saves the response to the cache for others to use. You should only use cache helper for non-personalized fetches, since the same response is available to all users. -::: - -### Solution: schedule a job - -Alternatively, you can use [scheduler](../server/scheduler.mdx) to make the request in background, save the response to [Redis](../server/redis.mdx), and avoid unnecessary requests to the external resource. - -**Example: fetch weather data every 2 hours with a scheduled job** - -```tsx -import { Devvit } from "@devvit/public-api"; - -Devvit.addSchedulerJob({ - name: "fetch_weather_data", - onRun: async (_event, context) => { - const response = await fetch("https://external.weather.com"); - const responseData = await response.json(); - await context.redis.set("weather_data", JSON.stringify(responseData)); - }, -}); - -Devvit.addTrigger({ - event: "AppInstall", - onEvent: async (_event, context) => { - await context.scheduler.runJob({ - cron: "0 */2 * * *", // runs at the top of every second hour - name: "fetch_weather_data", - }); - }, -}); - -// inside the render method -const [externalData] = useState(async () => { - return context.redis.get("fetch_weather_data"); -}); - -export default Devvit; -``` - -## How to: update the client state without intervals​ - -If you have a game with a leaderboard, scores need to be updated immediately for all active sessions. - -One way to achieve this is to set an interval to fetch leaderboard stats as often as possible, but making a request in the interval would switch the execution to server environment and affect the performance of the app. In addition, each time a user viewed the app, it would spam the leaderboard database in an attempt to get the latest data. - -To optimize performance, use [realtime](../realtime/overview.md) to send the leaderboard stats to all users directly. - -### Without realtime​ - -Before using realtime, the leaderboard fetching code looked like this: - -```tsx -const getLeaderboard = async () => - await context.redis.zRange("leaderboard", 0, 5, { - reverse: true, - by: "rank", - }); - -const [leaderboard, setLeaderboard] = useState(async () => { - return await getLeaderboard(); -}); - -const leaderboardInterval = useInterval(async () => { - const newLeaderboard = await getLeaderboard(); - setLeaderboard(newLeaderboard); -}, 1000); - -leaderboardInterval.start(); -``` - -And code for updating the leaderboard looked like this: - -```tsx -await context.redis.zAdd("leaderboard", { member: username, score: gameScore }); -``` - -### With realtime​ - -Using realtime, you can fetch the leaderboard during the initial render and emit the new leaderboard state when the user completes the game. - -This is the updated game completion code: - -```tsx -// stays as is -await context.redis.zAdd("leaderboard", { member: username, score: gameScore }); -// new code -context.realtime.send("leaderboard_updates", { - member: username, - score: gameScore, -}); -``` - -Now replace the interval with the realtime subscription: - -```tsx -const [leaderboard, setLeaderboard] = useState(async () => { - return await getLeaderboard(); -}); // stays as is - -const channel = useChannel({ - name: "leaderboard_updates", - onMessage: (newLeaderboardEntry) => { - const newLeaderboard = [...leaderboard, newLeaderboardEntry] // append new entry - .sort((a, b) => b.score - a.score) // sort by score - .slice(0, 5); // leave top 5 - setLeaderboard(newLeaderboard); // update the state - }, -}); - -channel.subscribe(); -``` - -Using realtime ensures that extra requests will not impact your app’s performance, and the app only emits the event when the data has changed. - -## How to: measure your app’s performance - -You can use `console.log` to calculate the operation time of your app. - -This example shows the render function of a basic post that fetches the number of subreddit members. - -```tsx -const [subscriberCount] = useState(async () => { - const devvitSubredditInfo = await context.reddit.getSubredditInfoByName('devvit'); - return devvitSubredditInfo.subscribersCount || 0; -}); - -return ( - // app markup goes here -); -``` - -### Add a console log - -Before the above post can be rendered, two pieces of data need to be requested: -subreddit subscribers count and user avatar url. You can measure the amount of time it takes to request data inside the `useState` hook. - -To do this, you can add: - -- A variable that stores the timestamp of the operation start. -- A console log between the operation end and the return statement that prints the difference between the start and end in milliseconds. - -```tsx -const [subscriberCount] = useState(async () => { - const startSubscribersRequest = Date.now(); // a reference point for the request start - const devvitSubredditInfo = - await context.reddit.getSubredditInfoByName("devvit"); - - console.log( - `subscribers request took: ${Date.now() - startSubscribersRequest} milliseconds`, - ); - - return devvitSubredditInfo.subscribersCount || 0; -}); -``` - -Alternatively, you can measure the whole data collection step. On the first line of the render method you can declare a state variable: - -```tsx -const [performanceStartRender] = useState(Date.now()); // a reference point for the render start -``` - -Add a console.log before the return statement: - -```tsx -console.log( - `Getting the data took: ${Date.now() - performanceStartRender} milliseconds`, -); -``` - -All of that put together will look like this: - -```tsx -const [performanceStartRender] = useState(Date.now()); // a reference point for the render start - -const [subscriberCount] = useState(async () => { - const startSubscribersRequest = Date.now(); // a reference point for the request start - const devvitSubredditInfo = await context.reddit.getSubredditInfoByName('devvit'); - - console.log(`subscribers request took: ${Date.now() - startSubscribersRequest} milliseconds`); - - return devvitSubredditInfo.subscribersCount || 0; -}); - -console.log(`Getting the data took: ${Date.now() - performanceStartRender} milliseconds`); - -return ( - // app markup goes here -); -``` - -### Review your data - -Once you’ve set up your data collection, you can expect something like that in your logs: - -``` -subscribers request took: 106 milliseconds -getting user avatar url took: 203 milliseconds -getting the data took: 310 milliseconds -``` - -This will help you find the operations that affect your app’s performance. diff --git a/docs/capabilities/blocks/overview.md b/docs/capabilities/blocks/overview.md deleted file mode 100644 index b6cda57a..00000000 --- a/docs/capabilities/blocks/overview.md +++ /dev/null @@ -1,114 +0,0 @@ -# Overview - -:::warning - -With the introduction of [inline webviews](../server/launch_screen_and_entry_points/view_modes_entry_points), Devvit Web is now the recommended approach for all interactive experiences that need in-feed interactions or pop-out views. - -The Blocks documentation below remains to support developers maintaining existing apps built with Blocks. - -::: - -Devvit Blocks is a framework that allows you to build apps with Reddit native components. Blocks is optimized for speed and ease of use, but is not recommended for games due to technical constraints and limitations. - -:::warning -With the introduction of [0.12.2](../../changelog.md#devvit-0122-inline-mode-launch-screensexpanded-app-experiences-and-developer-logs), you can now render apps directly within the feed using Devvit Web. This makes Devvit Web the recommended path for all new projects. -::: - -## Examples - -### [r/WallStreetBets](https://www.reddit.com/r/wallstreetbets) - -Wall Street Bets's daily thread tracks stock performance and discussion on r/wallstreetbets. It's able to render in the feed quickly, and refresh automatically as new data is available. - -## Available blocks - -We support the following elements: - -### Containers - -- **Blocks** -- [**HStack**](../../blocks/stacks.mdx) -- [**VStack**](../../blocks/stacks.mdx) -- [**ZStack**](../../blocks/stacks.mdx) - -### Objects - -- [**Text**](../../blocks/text.mdx) -- [**Button**](../../blocks/button.mdx) -- [**Spacer**](../../blocks/spacer.mdx) -- [**Image**](../../blocks/image.mdx) -- [**Icon**](../../blocks/icon.mdx) - -Further elements (components) may be derived from these blocks, and obey the same rules. - -## Sizing - -### Paddings and gaps - -- We're operating in a [border-box](https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing) model, where the padding is counted as part of the size of an element. -- Padding is incompressible. -- Gaps are implemented as if we're injecting spacers between all children. - -### Units - -There are two supported units: - -- `px`: device-independent pixels -- `%`: percent of parent container's available content area (i.e. subtracting the parent's padding and gaps) - -### Intrinsic size - -All elements have an _intrinsic size_. This is the size that they would be if there were no sizing modifiers applied to them. - -- **HStack**: Sum of the intrinsic widths of the children × the max of the intrinsic heights of the children (+ gaps and padding) -- **VStack**: Max of the intrinsic widths of the children × the sum of the intrinsic heights of the children (+ gaps and padding) -- **ZStack**: Max of the intrinsic sizes of the children (+ padding) -- **Text**: Size of the text without wrapping or truncation -- **Button**: Size of the text without wrapping or truncation (+ padding) -- **Spacer**: Size in pixels, as specified -- **Image**: imageWidth × imageHeight - -This size provides a baseline, which can be modified by attributes. There are a few sizing attributes: - -- `width` / `height` -- `minWidth` / `minHeight` -- `maxWidth` / `maxHeight` -- `grow` (operates in the _current direction_). - -:::note -Setting both `width` and `grow` simultaneously is not recommended, because then `grow` would become a no-op (overridden by `width`). -::: - -### Preferred size - -The preferred size is calculated based on the intrinsic size and the modifier attributes. The modifiers can conflict, in which case the precedence order is: - -`(most important) minWidth > maxWidth > width > aspect-ratio > grow > intrinsic width (least important)` - -Here, `grow` attempts to set `width="100%"`. Unlike actually setting `width="100%"`, `grow` can be flexibly adjusted later. Examples: - -- `` will always have a preferred size of 50px. (width overrides `grow`) -- `` will always take at least 50px, and will attempt to consume the available `width`. - -### Adjusted size - -Grow elements are flexible. Whenever the full width (or height) of a parent element is not fully utilized, a grow element will expand to fit the parent element, assuming that the other constraints permit. Grow is prioritized lower than the other sizing attributes, e.g. an element will never grow beyond its maxWidth. - -### Direction - -All elements inherit a direction for the purposes of growing. Things only grow in one direction at a time. - -| Element | Self Direction | Child Direction | -|-----------------------------------| -------------- | --------------- | -| Blocks | N/A | Vertical | -| [HStack](../../blocks/stacks.mdx) | Inherit | Horizontal | -| [VStack](../../blocks/stacks.mdx) | Inherit | Vertical | -| [ZStack](../../blocks/stacks.mdx) | Inherit | Inherit | -| [Text](../../blocks/text.mdx) | Horizontal | N/A | -| [Button](../../blocks/button.mdx) | Horizontal | N/A | -| [Spacer](../../blocks/spacer.mdx) | Inherit | N/A | -| [Image](../../blocks/image.mdx) | Inherit | N/A | - -### Overflow - -All containers clip overflown content. diff --git a/docs/capabilities/blocks/working_with_useasync.md b/docs/capabilities/blocks/working_with_useasync.md deleted file mode 100644 index c74afd4b..00000000 --- a/docs/capabilities/blocks/working_with_useasync.md +++ /dev/null @@ -1,176 +0,0 @@ -# Working with useAsync - -:::note -This feature is experimental, which means the design is not final but it's still available for you to use. -::: - -`useAsync` is a hook that allows your app to perform server side calls like `redis.get` or `reddit.getCurrentUser` without blocking the render process. - -## Blocking versus non-blocking - -The code you write in Javascript can be blocking or non-blocking. To keep applications speedy, non-blocking code is preferred. - -Blocking code produces a waterfall of actions. One line must happen after another, so the speed in which a program can render takes a large hit. We want to avoid waterfalls to provide a nice experience for users. - -### Without useAsync (blocking) example - -```tsx -const App = () => { - // This will block the render until data is fetched - const [message] = useState(async () => await redis.get('welcomeMessage')); - - return {message}; -}; -``` - -### With useAsync (non-blocking) example - -```tsx -const App = () => { - const { data: message, loading, error } = useAsync(async () => await redis.get('welcomeMessage')); - - return ( - - {loading && Loading...} - {error && Error fetching message} - {message && {message}} - - ); -}; -``` - -This example displays “Loading…” immediately while fetching the data. - -## Understanding useAsync - -### Syntax - -```ts -const { data, loading, error } = useAsync(asyncFunction, { depends: {JSON object}, finally: () => { function } }); -``` - -- asyncFunction: an asynchronous function that must return a valid JSON value. Note that setState is not allowed in this function. Use the finally parameter if you need to use setState (see the example below). -- depends (optional): a JSON object or array of JSON objects that, when changed, will cause the asyncFunction to re-execute. -- finally (optional): a callback function that runs after the async operation completes regardless of success or failure. Ideal for state updates (i.e. calls to setState) and side effects. - -### Return values - -- data: the data returned from the initializer. -- loading: a boolean that denotes if it is loading or not. -- error: an error if the request failed. - -**The initializer for `useAsync` (the first argument of the function) must return a valid JSON value.** This differs from React due to how Devvit components work across server and client boundaries. - -### Example: fetching the time and setting state - -```ts -useAsync( - async () => { - const response = await fetch(`https://date.api/today?timezone=${timezone}`); - return response.json(); - }, - { - depends: [timezone], - finally: (data, error) => { - if (error) { - console.error('Failed to load date data:', error); - } else { - setTodayDate(data['currentDate']); - } - }, - } -); -``` - -### Example: fetching user data - -```ts -// A normal useAsync function -const { - data: username, - loading: usernameLoading, - error: usernameError, -} = useAsync(async () => { - const user = await ctx.reddit.getCurrentUser(); - - return user?.username ?? null; -}); - -// A dependent useAsync function -const { - data: userDetails, - loading: userDetailsLoading, - error: userDetailsError, -} = useAsync( - // This will run every time the value in depends changes - async () => { - if (!username) return null; - - const resp = await fetch(`https://some-api/get-user-details/${username}`); - return await resp.json(); - }, - { - // NOTE: This will be deep equality for objects and arrays! - depends: username, - } -); -``` - -### Example: complete application - -This a simple application that leverages useAsync to fetch data in a non-blocking way and updates the app whenever the page changes. - -```ts -import { useAsync, useState } from '@devvit/public-api'; - -const App = () => { - const [count, setCount] = useState(1); - - const { data, loading, error } = useAsync( - async () => { - const response = await fetch(`https://xkcd.com/${count}/info.0.json`); - if (!response.ok) throw new Error(`HTTP error ${response.status}: ${response.statusText}`); - return await response.json(); - }, - { depends: [count] } - ); - - return ( - - XKCD Titles - - {loading && Loading...} - {error && ( - - {error.message} - - )} - {data && {data.title}} - - Comic Number: {count} - - - ); -}; - -//add your custom post -// addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. -Devvit.addCustomPostType({ - name: 'AppName', - description: 'Using useAsync with XKCD API', - render: App, -}); -``` -[View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) - -## When to use useAsync and useState - -In most cases, you'll want to use `useAsync` over `useState` to keep your app snappy. One downside is that you need to handle `loading` and `error` when using `useAsync` or `useState`. - -One situation where `useState` could be preferable is if your app only has one request and it must be resolved in order to show any part of the app. - -Another time to consider `useState` is if you need to update the value you fetched and display the new result to the user. `useState` provides a better API for that, but keep in mind you still need to persist the updates to Redis. We will be working on making synced updates easier in the future. - -:::note -There isn't an easy way to update `useAsync` data based off of an action in the app at the moment. We are working on a better way to allow this in the future. -::: diff --git a/docs/capabilities/blocks/working_with_useinterval.md b/docs/capabilities/blocks/working_with_useinterval.md deleted file mode 100644 index 3b7a436c..00000000 --- a/docs/capabilities/blocks/working_with_useinterval.md +++ /dev/null @@ -1,80 +0,0 @@ -# Working with useInterval - -Update live interactive posts in real time. - -:::note -This feature is experimental. There are known issues with interval timing especially when using server side calls, like calling Redis, within the useInterval function. This would result in intervals longer than what a developer specifies. -::: - -The `useInterval` method lets you build live apps that automatically update in real time. You can use this method to trigger a render of your interactive post to do things like add a countdown timer, update a scoreboard with new play information, or set up a clock. - -![Countdown timer](../../assets/custom-posts/custom-posts-useinterval.png) - -## How it works - -The `useInterval` method lets you request that your app be re-rendered at a specified interval. Intervals are set in milliseconds, with a 1000ms minimum. - -```tsx -import { Devvit, useState, useInterval } from '@devvit/public-api'; - -const [counter, setCounter] = useState(1000); - -const updateInterval = useInterval(() => { - setCounter((counter) => counter - 1); -}, 1000); - -updateInterval.start(); -``` - -## Key limitations - -- You can only use one active, running `useInterval` per app. -- The minimum allowed interval time is 1 second. -- The timing is specific to the app. Timing begins when a user engages with the app, and the time interval triggers a refresh. For example: - - If you build a stopwatch and add 1000 ms each time the interval runs, the actual elapsed time would include the time between interval triggers and would be something like 1020 ms. - - If you build a countdown timer, the time interval trigger subtracts the current time at render, so it will match the elapsed time. -- An interval runs while the post is visible on screen. If a user navigates away, it is suspended. - -## useInterval vs scheduler - -The `useInterval` method is different from the scheduler, in a few important ways: - -- `useInterval`executes on the client and triggers updates to your app locally (it’s similar to setInterval on the web). It can re-render on the user's device to do things like create animations or refresh the screen. -- Scheduler executes on the server and can be used to run background processes. - -## Example clock app - -### [Playground link](https://developers.reddit.com/play#pen/N4IgdghgtgpiBcIQBoQGcBOBjBICWUADgPYYAuABMACIwBudeZAvhQGYbFQUDkAAgBN6jMgHpCAVwBGAGzxYAtBEJ4eAHTAa2EsFjJ5iYCgHMYZAMISMGGGDIAVAjAAUASioaKFLIbSUwxADuFAC8FGAwwdQQZC6unt6+lAAWxFZooRQAymQYeGDGzgGBAHSmZAASaRhobq4lhBACORDkzgBMyLwADDzxRolgfhRQ+RKxGWE5eQVFQWVmALJjE3UNTS1tnT19CT5DlGgw+wKT2bn5hcULZFnHhqdrjc1krWQdXTy9-V42ZFZGAAGABJgKl0sx4KDRmBxjA0JDQUcTgjARpmBoNLQGEwSk0BJY-FwAArEPz2ACehBcwASkFg8F45hkxCwAGseMgEjYwEIMIz9rEAB6UEIAPg8Ay8+2GAG19LAukcHE4ALqZQUwEUlCRHFqxZxuUIS8qWay2FWwOoJaVJCj6dmZI3iijKxxW50msxmnmWuI-LyDYVkHVHACSdhgGDoEBkzgdbK6AEZuqn6n43m5MVKKH8ARRnDbAwAeOgZx2xvDGMCwOwhHhYC1RkZ4AQCGQwHgUZIwKvJMghNQgFPdACkQ7FRcDFGLwddeAAXjB60KhTJWqYeGLgAqYMxi6Jg5Oc14D2XXuzj9OAxiwMx+hotSRyBQhGwIBIZJRsSINCgQHQUZoAYYAIEmzBAA) - -### Code sample - -```tsx -import { Devvit, useState, useInterval } from '@devvit/public-api'; - -function getCurrentTime() { - const now = new Date(); - const hours = String(now.getHours()).padStart(2, '0'); - const minutes = String(now.getMinutes()).padStart(2, '0'); - const seconds = String(now.getSeconds()).padStart(2, '0'); - return `${hours}:${minutes}:${seconds}`; -} - -// addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. -Devvit.addCustomPostType({ - name: 'Clock', - render: (context) => { - const [time, setTime] = useState(() => getCurrentTime()); - const tick = () => setTime(() => getCurrentTime()); - useInterval(tick, 1000).start(); - - return ( - - {time} - - ); - }, -}); - -export default Devvit; -``` -[View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) \ No newline at end of file diff --git a/docs/capabilities/blocks/working_with_usestate.md b/docs/capabilities/blocks/working_with_usestate.md deleted file mode 100644 index f156f630..00000000 --- a/docs/capabilities/blocks/working_with_usestate.md +++ /dev/null @@ -1,177 +0,0 @@ -# Working with useState - -`useState` is a hook that gives your component reactive memory. It allows you to preserve information across renders and trigger updates to your app when state is updated. - -## Arguments - -`useState` takes an initial state as an argument, and returns an array of two items: - -- a state variable -- a setter function to update the state variable - -The initializer for `useState` (the first argument of the function) can be a static value or a function. **Regardless of how it is initialized, it must return a valid JSON value.** This differs from React due to how Devvit components work across server and client boundaries. - -```ts -// A static value -const [variable, setVariable] = useState('initialState'); - -// An synchronous function -const [variable, setVariable] = useState(() => 'initialState'); - -// An async function -const [count, setCount] = useState(async () => await redis.get('count')); -``` - -:::info -If the initializer function is async it will block render under the value is resolved. This is a common performance pitfall so please use it sparingly or group async calls together into one state variable using [`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all). - -A non-blocking equivalent for fetching data is [useAsync](./working_with_useasync.md). -::: - -## Updating state - -You’ll want to retrieve and update state at various points in your app. There are a few common patterns you’ll see for doing this. - -```tsx -import { Devvit, useState } from '@devvit/public-api'; - -const MyComponent = (_event, context) => { - // Setting up state - const [count, setCount] = useState(async () => { - return await context.redis.get('count'); - }); - - return ( - - Count: {count} - {/* Update set count by using a setter function */} - - - {/* Update set count by setting the value directly */} - - - ); -}; -``` - -## Using state hooks - -Leverage `useState` across components to preserve values and trigger reactive updates across your app. - -In this example, state hooks are used to: - -- Navigate between two app "pages" -- Add a simple counter that increments a value - -```tsx -//define types of props to pass to components -interface Props { - navigate: (page: PageType) => void; - setCount: (count: number) => void; - count: number; -} - -enum PageType { - HOMEPAGE, - COUNTPAGE, -} - -//useState is available as a global import from public API. -const App: Devvit.CustomPostComponent = ({ useState }: Devvit.Context) => { - // set state components - const [page, navigate] = useState(PageType.HOMEPAGE); - const [count, setCount] = useState(0); - - // pass state into a Props object. This can be passed to components for passing state across components in your app - const props: Props = { - navigate, - setCount, - count, - }; - - // pass props into components - if (page === PageType.COUNTPAGE) { - return ; - } else { - return ; - } -}; - -//'HomePage' is a component that returns this app's default UI -const HomePage: Devvit.BlockComponent = ({ navigate }) => { - //defines how to handle 'onPress' button event on page - const countPage: Devvit.Blocks.OnPressEventHandler = () => { - navigate(PageType.COUNTPAGE); - }; - - //UI blocks comprising a page - return ( - - - {'This app will teach you how to count!'} - - - - - - ); -}; - -//'CountPage'component -const CountPage: Devvit.BlockComponent = ({ navigate, setCount, count }, { redis }) => { - const incrementCount: Devvit.Blocks.OnPressEventHandler = () => { - setCount((count) => count + 1); - // note: to preserve the value of 'count' longerterm - // you would need to add a separate method here. For example: - // await redis.set("count", count) - }; - - return ( - - - {'Press the button to add +1'} - - {count} - - - - - ); -}; - -//add your custom post -// addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. -Devvit.addCustomPostType({ - name: 'AppName', - description: 'Navigate between pages and count!', - render: App, -}); -``` -[View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) - -In this example, when a user session ends, state will no longer be available to the app. To save and persist data between sessions you will need to [store data server-side via the Redis](../server/redis). - -## Redis vs useState - -The `redis` [plugin](../server/redis) serves as your app’s long-term, server-side memory, while `useState` serves as its short-term, client-side memory. - -`useState` will keep your custom posts performant and responsive. UI that relies on too many server-side calls will slow down your app. - -The `redis` plugin should be used for storing data that needs to persist across sessions. Make these updates to your app in the background i.e. separate from the UI updates you are making. This way, your app does not need to wait on expensive calls to render new components. You should retrieve `redis` data once on render. - -Use state hooks for single session memory, like changing tabs, selecting a checkbox within the UI, or changing values in the user’s view during their app session. - -## User sessions - -`useState` persists through a single user session. If a user scrolls away from the page, navigates away from the page, or disconnects, the session will end and the state is invalidated. - -## Differences compared to React - -The Devvit `useState` hook is inspired by [React](https://react.dev/reference/react/useState) with a few key differences. - -- `useState` can only return something that can be serialized to JSON. That is booleans, numbers, strings, arrays, and objects. This is due to how the hook persists across server/client boundaries under the hood. -- `useState` can be initialized with an async function. This allows you to get remote information from Redis, Reddit, or by using fetch to place on to state. Please use this sparingly, as using an async initializer blocks render until the promise resolves. This can make your app feel slow. diff --git a/docs/capabilities/client/forms.mdx b/docs/capabilities/client/forms.mdx index b1801026..b6fdd9fd 100644 --- a/docs/capabilities/client/forms.mdx +++ b/docs/capabilities/client/forms.mdx @@ -230,7 +230,7 @@ For forms that open from a menu item, you can use menu responses. This is useful ## Form object -The form object enables you to customize the form container and the [list of form fields](#supported-fields-types) included. The form object structure is the same for both Devvit Web and Devvit Blocks. +The form object enables you to customize the form container and the [list of form fields](#supported-fields-types) included. #### Usage @@ -1158,7 +1158,7 @@ This example includes one of each of the [supported field types](#supported-fiel app.post('/internal/form/image-submit', async (c) => { const { myImage } = await c.req.json(); - // Use the mediaUrl to store in redis and display it in an block, or send to external service to modify + // Store the mediaUrl in Redis and render it via an tag on the client, or send to external service to modify console.log('Image uploaded:', myImage); return c.json({ @@ -1177,7 +1177,7 @@ This example includes one of each of the [supported field types](#supported-fiel router.post("/internal/form/image-submit", async (req, res) => { const { myImage } = req.body; - // Use the mediaUrl to store in redis and display it in an block, or send to external service to modify + // Store the mediaUrl in Redis and render it via an tag on the client, or send to external service to modify console.log('Image uploaded:', myImage); res.json({ diff --git a/docs/capabilities/client/overview.mdx b/docs/capabilities/client/overview.mdx index 5d645d5d..c81dc705 100644 --- a/docs/capabilities/client/overview.mdx +++ b/docs/capabilities/client/overview.mdx @@ -33,11 +33,11 @@ if (result) { ## Available client effects -| Effect | Description | Devvit Web | Devvit Blocks | -|--------|-------------|------------|---------------| -| **Toast** | Show temporary notification messages | `showToast()` | `context.ui.showToast()` | -| **Form** | Display interactive forms with promise-based responses | `showForm()` | `context.ui.showForm()` | -| **Navigation** | Redirect to Reddit content or external URLs | `navigateTo()` | `context.ui.navigateTo()` | +| Effect | Description | API | +|--------|-------------|-----| +| **Toast** | Show temporary notification messages | `showToast()` | +| **Form** | Display interactive forms with promise-based responses | `showForm()` | +| **Navigation** | Redirect to Reddit content or external URLs | `navigateTo()` | :::note When to use client library functions You should only use client library functions in response to a user-initiated action. @@ -45,7 +45,7 @@ You should only use client library functions in response to a user-initiated act ## Menu responses -In Devvit Web, menu items can respond with client effects after server processing. +Menu items can respond with client effects after server processing. Menu responses allow you to: - Process data on the server before showing client effects diff --git a/docs/capabilities/creating_custom_post.md b/docs/capabilities/creating_custom_post.md index 9ce4960e..66ef675b 100644 --- a/docs/capabilities/creating_custom_post.md +++ b/docs/capabilities/creating_custom_post.md @@ -43,7 +43,7 @@ export const createPost = async () => { | `entry` | Key of the entrypoint defined in `devvit.json` | | `postData` | [Updates post data after creation](../capabilities/server/post-data) | | `textFallback` | [Specifies alternative text content](../capabilities/server/text_fallback) | -| `userGeneratedContent` | [Enables user-generated content](..capabilities/server/userActions) | +| `userGeneratedContent` | [Enables user-generated content](../capabilities/server/userActions) | | `styles` | Controls post appearance in the Reddit UI. See [Custom Post Styles](#custom-post-styles) | ## **Custom Post Styles** diff --git a/docs/capabilities/devvit-web/devvit_web_configuration.md b/docs/capabilities/devvit-web/devvit_web_configuration.md index 09a71ebe..57d71804 100644 --- a/docs/capabilities/devvit-web/devvit_web_configuration.md +++ b/docs/capabilities/devvit-web/devvit_web_configuration.md @@ -24,7 +24,6 @@ Additionally, you must include at least one of: - **`post`**: For web view apps - **`server`**: For Node.js server apps -- **`blocks`**: For Blocks ## Configuration sections @@ -37,11 +36,10 @@ Additionally, you must include at least one of: ### App components -| Property | Type | Description | Required | -| -------- | ------ | ---------------------------------- | ------------------------- | -| `post` | object | Custom post/web view configuration | One of post/server/blocks | -| `server` | object | Node.js server configuration | One of post/server/blocks | -| `blocks` | object | Blocks | One of post/server/blocks | +| Property | Type | Description | Required | +| -------- | ------ | ---------------------------------- | ------------------ | +| `post` | object | Custom post/web view configuration | One of post/server | +| `server` | object | Node.js server configuration | One of post/server | ### Permissions & capabilities @@ -316,23 +314,13 @@ Configure development settings: - `subreddit` (string): Default development subreddit (can be overridden by `DEVVIT_SUBREDDIT` env var) -## Migration from `devvit.yaml` - -1. Create a new `devvit.json` file in the project root. -2. Copy over the `name` property from `devvit.yaml`. -3. Delete `devvit.yaml`. -4. Move configuration from `Devvit.configure()` calls to `permissions`. For example, if the app called `Devvit.configure({redis: true})` set `permissions.redis` to `true` in `devvit.json`. -5. If the app has a web view, set `post` in `devvit.json` and either configure `post.entry` to `webroot/` or to your build output directory. Optionally, delete calls to `Devvit.addCustomPostType()` and `Devvit.addMenuItem()`. -6. If the app has a Node.js server, set `server` in `devvit.json`. -7. (Optional) Set `blocks.entry` to `src/main.tsx` (or `src.main.ts`) to continue using `@devvit/public-api` legacy APIs. - ## Validation rules The `devvit.json` configuration is validated against the JSON Schema at build time. Many IDEs will also underline errors as you write. Common validation errors include: - **JSON Syntax:** Adding comments or trailing commas (unsupported by JSON) - **Required Properties:** Missing the required `name` property -- **App Components:** Missing at least one of `post`, `server`, or `blocks` +- **App Components:** Missing at least one of `post` or `server` - **Dependencies:** Missing `server` when `triggers` is specified - **File References:** Missing files referenced in `devvit.json` - **Permissions:** Missing required permissions for used features diff --git a/docs/capabilities/devvit-web/devvit_web_overview.mdx b/docs/capabilities/devvit-web/devvit_web_overview.mdx index 03388204..b208d4e6 100644 --- a/docs/capabilities/devvit-web/devvit_web_overview.mdx +++ b/docs/capabilities/devvit-web/devvit_web_overview.mdx @@ -66,7 +66,7 @@ Devvit Web templates all have the same file structure: └── devvit.json # the devvit config file ``` -Now, instead of passing messages with postMessage (old way), you’ll define `/api/endpoints` (new way). +Your client talks to the server by calling `/api/` endpoints you define with `fetch()`. ### Client folder diff --git a/docs/capabilities/realtime/overview.md b/docs/capabilities/realtime/overview.md index d7613b20..9539fcec 100644 --- a/docs/capabilities/realtime/overview.md +++ b/docs/capabilities/realtime/overview.md @@ -6,9 +6,7 @@ Create live and event-driven interactive posts. Realtime provides a set of primi - **Event-driven**. Posts render automatically in response to server events. - **Synced**. Using realtime with [Redis](../server/redis.mdx) lets you build persistent community experiences that are backed by high performance data synchronization. -Realtime is supported in both [Devvit Web](../devvit-web/devvit_web_overview.mdx) and [Devvit Blocks](../blocks/overview.md) applications. - -Follow this guide, [Realtime in Devvit Blocks](./realtime_in_devvit_blocks.md), for instructions on using realtime in Devvit Blocks. +Realtime is supported in [Devvit Web](../devvit-web/devvit_web_overview.mdx) applications. # Realtime in Devvit Web diff --git a/docs/capabilities/realtime/realtime_in_devvit_blocks.md b/docs/capabilities/realtime/realtime_in_devvit_blocks.md deleted file mode 100644 index de7de7cb..00000000 --- a/docs/capabilities/realtime/realtime_in_devvit_blocks.md +++ /dev/null @@ -1,144 +0,0 @@ -# Realtime in Devvit Blocks - -This guide walks through step-by-step instructions on how to set up [Realtime](./overview.md) in a [Devvit Blocks](../blocks/overview.md) application - -## Create a live interactive post - -#### 1. Configure realtime - -```tsx -Devvit.configure({ - realtime: true, -}); -``` - -#### 2. Create and subscribe to a channel - -`useChannel` hook allows interactive posts to subscribe and send to an event stream. - -A new channel can be setup with function handlers containing custom logic to update state: - -- `onMessage` - called every time a message is received on a channel -- `onSubscribed` - optional hook to be informed when channel has connected -- `onUnsubscribed` - optional hook to be informed when channel has disconnected - -```tsx -import { Devvit, useChannel } from '@devvit/public-api'; - -// Defined within render function of an interactive post - -// Choose a channel name that works for you - -// You have the flexibility to define the message data shape to be published -// via channel.send - same shape will be received in the onMessage handler - -const channel = useChannel({ - name: 'events', - onMessage: (data) => { - // modify local state - }, - onSubscribed: () => { - // handle connection setup - }, - onUnsubscribed: () => { - // handle network degradation with fallback scenarios - }, -}); - -// subscribe to the channel to receive messages -channel.subscribe(); -``` - -#### 3. Send messages to a channel - -`channel.send` is recommended for peer-to-peer synchronization across clients. See [Mini Place](#mini-place) and [Snoo Club](#snoo-club) - -```tsx - - ); - }, - }); - ``` - [View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) - + :::warning @@ -361,41 +277,18 @@ To update post data after creation, fetch the post and use the `setPostData()` m ## Accessing post data Post data is available through `context.postData` in both client and server contexts. - - - ```tsx title="client/index.tsx" - import { context } from '@devvit/web/client'; - - export const App = () => { - return ( -
- Post Data: - {JSON.stringify(context.postData, null, 2) ?? 'undefined'} -
- ); - } - ``` -
- - ```tsx title="main.tsx" - import { Devvit } from '@devvit/public-api'; - - // addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. - Devvit.addCustomPostType({ - name: 'MyCustomPost', - render: (context) => { - return ( - - context.postData: - {JSON.stringify(context.postData, null, 2)} - - ); - }, - }); - ``` - [View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) - -
+```tsx title="client/index.tsx" +import { context } from '@devvit/web/client'; + +export const App = () => { + return ( +
+
Post Data:
+
{JSON.stringify(context.postData, null, 2) ?? 'undefined'}
+
+ ); +} +``` ## Limitations Post data supports: diff --git a/docs/capabilities/server/settings-and-secrets.mdx b/docs/capabilities/server/settings-and-secrets.mdx index 7b70c20f..18b3361a 100644 --- a/docs/capabilities/server/settings-and-secrets.mdx +++ b/docs/capabilities/server/settings-and-secrets.mdx @@ -15,140 +15,78 @@ Local environment variables and `.env` files are read during playtesting only. ## Defining settings -Settings are defined differently depending on whether you're using Devvit Web or Devvit Blocks. - - - - Define settings in your `devvit.json` file under the `settings` object. Settings are organized by scope: `global` for app-wide settings and secrets, `subreddit` for installation-specific settings. - - ```json title="devvit.json" - { - "settings": { - "global": { - "apiKey": { - "type": "string", - "label": "API Key", - "defaultValue": "", - "isSecret": true - }, - "environment": { - "type": "select", - "label": "Environment", - "options": [ - { - "label": "Production", - "value": "production" - }, - { - "label": "Development", - "value": "development" - } - ], - "defaultValue": "production" - } +Define settings in your `devvit.json` file under the `settings` object. Settings are organized by scope: `global` for app-wide settings and secrets, `subreddit` for installation-specific settings. + +```json title="devvit.json" +{ + "settings": { + "global": { + "apiKey": { + "type": "string", + "label": "API Key", + "defaultValue": "", + "isSecret": true }, - "subreddit": { - "welcomeMessage": { - "type": "string", - "label": "Welcome Message", - "validationEndpoint": "/internal/settings/validate-message", - "defaultValue": "Welcome to our community!" - }, - "enabledFeatures": { - "type": "multiSelect", - "label": "Enabled Features", - "options": [ - { - "label": "Auto-moderation", - "value": "automod" - }, - { - "label": "Welcome posts", - "value": "welcome" - }, - { - "label": "Statistics tracking", - "value": "stats" - } - ], - "defaultValue": ["welcome"] - } + "environment": { + "type": "select", + "label": "Environment", + "options": [ + { + "label": "Production", + "value": "production" + }, + { + "label": "Development", + "value": "development" + } + ], + "defaultValue": "production" } - } - } - ``` - - :::note - After defining settings in `devvit.json`, you must build your app (`npm run dev`) before you can set secrets via the CLI. - ::: - - - Use `Devvit.addSettings` to define settings with the appropriate scope. - - ```tsx title="main.tsx" - import { Devvit, SettingScope } from '@devvit/public-api'; - - Devvit.addSettings([ - // Global secret - { - type: 'string', - name: 'apiKey', - label: 'API Key', - isSecret: true, - scope: SettingScope.App, // or 'app' - }, - // Global setting - { - type: 'select', - name: 'environment', - label: 'Environment', - options: [ - { label: 'Production', value: 'production' }, - { label: 'Development', value: 'development' }, - ], - scope: 'app', }, - // Subreddit setting - { - type: 'string', - name: 'welcomeMessage', - label: 'Welcome Message', - scope: SettingScope.Installation, // or 'installation' - onValidate: async ({ value }) => { - if (!value || value.length < 5) { - return 'Message must be at least 5 characters'; - } + "subreddit": { + "welcomeMessage": { + "type": "string", + "label": "Welcome Message", + "validationEndpoint": "/internal/settings/validate-message", + "defaultValue": "Welcome to our community!" }, - }, - // Subreddit multi-select - { - type: 'select', - name: 'enabledFeatures', - label: 'Enabled Features', - options: [ - { label: 'Auto-moderation', value: 'automod' }, - { label: 'Welcome posts', value: 'welcome' }, - { label: 'Statistics tracking', value: 'stats' }, - ], - multiSelect: true, - scope: 'installation', - }, - ]); - ``` - - + "enabledFeatures": { + "type": "multiSelect", + "label": "Enabled Features", + "options": [ + { + "label": "Auto-moderation", + "value": "automod" + }, + { + "label": "Welcome posts", + "value": "welcome" + }, + { + "label": "Statistics tracking", + "value": "stats" + } + ], + "defaultValue": ["welcome"] + } + } + } +} +``` + +:::note +After defining settings in `devvit.json`, you must build your app (`npm run dev`) before you can set secrets via the CLI. +::: ## Setting types -Both frameworks support the following setting types: +The following setting types are supported: - **string**: Text input field - **boolean**: Toggle switch - **number**: Numeric input - **select**: Dropdown selection (single choice) -- **multiSelect** (Web) / **select with multiSelect: true** (Blocks): Multiple choice dropdown -- **paragraph**: Multi-line text input (Blocks only) -- **group**: Grouped settings for organization (Blocks only) +- **multiSelect**: Multiple choice dropdown ## Managing secrets @@ -181,255 +119,175 @@ Successfully added app settings for apiKey! ``` :::warning -At least one app installation is required before you can store secrets via the CLI. You can run `npx devvit playtest` (or `npm run dev` in Devvit Web) to start your first installation. +At least one app installation is required before you can store secrets via the CLI. Run `npm run dev` to start your first installation. ::: ## Accessing settings in your app Settings can be retrieved from within your app. - - - - + + + +```tsx title="server/index.ts" +import { settings } from '@devvit/web/server'; - ```tsx title="server/index.ts" - import { settings } from '@devvit/web/server'; +type ProcessResponse = { success: true }; - type ProcessResponse = { success: true }; +// Get a single setting +const apiKey = await settings.get('apiKey'); - // Get a single setting +// Get multiple settings +const [welcomeMessage, features] = await Promise.all([ + settings.get('welcomeMessage'), + settings.get('enabledFeatures') +]); + +// Use in an endpoint +app.post('/api/process', async (c) => { const apiKey = await settings.get('apiKey'); - - // Get multiple settings - const [welcomeMessage, features] = await Promise.all([ - settings.get('welcomeMessage'), - settings.get('enabledFeatures') - ]); + const environment = await settings.get('environment'); - // Use in an endpoint - app.post('/api/process', async (c) => { - const apiKey = await settings.get('apiKey'); - const environment = await settings.get('environment'); - - const response = await fetch('https://api.example.com/endpoint', { - headers: { - 'Authorization': `Bearer ${apiKey}`, - 'X-Environment': environment - } - }); - - return c.json({ success: true }); + const response = await fetch('https://api.example.com/endpoint', { + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'X-Environment': environment + } }); - ``` - - + return c.json({ success: true }); +}); +``` + + + - ```tsx title="server/index.ts" - import { settings } from '@devvit/web/server'; +```tsx title="server/index.ts" +import { settings } from '@devvit/web/server'; - type ProcessResponse = { success: true }; +type ProcessResponse = { success: true }; - // Get a single setting +// Get a single setting +const apiKey = await settings.get('apiKey'); + +// Get multiple settings +const [welcomeMessage, features] = await Promise.all([ + settings.get('welcomeMessage'), + settings.get('enabledFeatures') +]); + +// Use in an endpoint +router.post('/api/process', async (req, res) => { const apiKey = await settings.get('apiKey'); - - // Get multiple settings - const [welcomeMessage, features] = await Promise.all([ - settings.get('welcomeMessage'), - settings.get('enabledFeatures') - ]); + const environment = await settings.get('environment'); - // Use in an endpoint - router.post('/api/process', async (req, res) => { - const apiKey = await settings.get('apiKey'); - const environment = await settings.get('environment'); - - const response = await fetch('https://api.example.com/endpoint', { - headers: { - 'Authorization': `Bearer ${apiKey}`, - 'X-Environment': environment - } - }); - - res.json({ success: true }); - }); - ``` - - - - - - ```tsx title="main.tsx" - import { Devvit } from '@devvit/public-api'; - - Devvit.addMenuItem({ - label: 'Process with API', - location: 'subreddit', - onPress: async (_event, context) => { - // Get individual settings - const apiKey = await context.settings.get('apiKey'); - const welcomeMessage = await context.settings.get('welcomeMessage'); - - // Get all settings - const allSettings = await context.settings.getAll(); - - // Use the settings - const response = await fetch('https://api.example.com/endpoint', { - headers: { - 'Authorization': `Bearer ${apiKey}`, - } - }); - - context.ui.showToast(`Processed with message: ${welcomeMessage}`); - }, + const response = await fetch('https://api.example.com/endpoint', { + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'X-Environment': environment + } }); - // Access in custom post type - // addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. - Devvit.addCustomPostType({ - name: 'ConfigurablePost', - render: (context) => { - const [features, setFeatures] = useState([]); - - useEffect(async () => { - const enabledFeatures = await context.settings.get('enabledFeatures'); - setFeatures(enabledFeatures || []); - }, []); - - return ( - - Enabled features: {features.join(', ')} - - ); - }, - }); - ``` - [View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) - + res.json({ success: true }); +}); +``` + + ## Input validation -Validate user input to ensure it meets your requirements before saving. - - - - Define a validation endpoint in your `devvit.json` and implement it in your server: - - ```json title="devvit.json" - { - "settings": { - "subreddit": { - "minimumAge": { - "type": "number", - "label": "Minimum Account Age (days)", - "validationEndpoint": "/internal/settings/validate-age", - "defaultValue": 7 - } +Validate user input to ensure it meets your requirements before saving. Define a validation endpoint in your `devvit.json` and implement it in your server: + +```json title="devvit.json" +{ + "settings": { + "subreddit": { + "minimumAge": { + "type": "number", + "label": "Minimum Account Age (days)", + "validationEndpoint": "/internal/settings/validate-age", + "defaultValue": 7 } } } - ``` +} +``` + + + + +```tsx title="server/index.ts" +import type { SettingsValidationRequest, SettingsValidationResponse } from '@devvit/web/shared'; + +app.post('/internal/settings/validate-age', async (c) => { + const { value } = await c.req.json>(); + + if (!value || value < 0) { + return c.json({ + success: false, + error: 'Age must be a positive number', + }); + } + + if (value > 365) { + return c.json({ + success: false, + error: 'Maximum age is 365 days', + }); + } + + return c.json({ success: true }); +}); +``` - - + + - ```tsx title="server/index.ts" - import type { SettingsValidationRequest, SettingsValidationResponse } from '@devvit/web/shared'; +```tsx title="server/index.ts" +import type { SettingsValidationRequest, SettingsValidationResponse } from '@devvit/web/shared'; - app.post('/internal/settings/validate-age', async (c) => { - const { value } = await c.req.json>(); +router.post>( + '/internal/settings/validate-age', + async (req, res): Promise => { + const { value } = req.body; if (!value || value < 0) { - return c.json({ + res.json({ success: false, error: 'Age must be a positive number', }); + return; } if (value > 365) { - return c.json({ + res.json({ success: false, error: 'Maximum age is 365 days', }); + return; } - return c.json({ success: true }); - }); - ``` - - - - - ```tsx title="server/index.ts" - import type { SettingsValidationRequest, SettingsValidationResponse } from '@devvit/web/shared'; - - router.post>( - '/internal/settings/validate-age', - async (req, res): Promise => { - const { value } = req.body; - - if (!value || value < 0) { - res.json({ - success: false, - error: 'Age must be a positive number', - }); - return; - } - - if (value > 365) { - res.json({ - success: false, - error: 'Maximum age is 365 days', - }); - return; - } + res.json({ success: true }); + } +); +``` - res.json({ success: true }); - } - ); - ``` - - - - - - Add an `onValidate` handler to your setting definition: - - ```tsx title="main.tsx" - Devvit.addSettings([ - { - type: 'number', - name: 'minimumAge', - label: 'Minimum Account Age (days)', - scope: 'installation', - onValidate: async ({ value }) => { - if (!value || value < 0) { - return 'Age must be a positive number'; - } - if (value > 365) { - return 'Maximum age is 365 days'; - } - }, - }, - ]); - ``` - + ## Subreddit settings UI @@ -444,210 +302,117 @@ Moderators will see all non-secret settings defined for the subreddit scope and Here's a complete example showing both secrets and subreddit settings in action: - - - ```json title="devvit.json" - { - "settings": { - "global": { - "openaiApiKey": { - "type": "string", - "label": "OpenAI API Key", - "isSecret": true, - "defaultValue": "" - } +```json title="devvit.json" +{ + "settings": { + "global": { + "openaiApiKey": { + "type": "string", + "label": "OpenAI API Key", + "isSecret": true, + "defaultValue": "" + } + }, + "subreddit": { + "aiModel": { + "type": "select", + "label": "AI Model", + "options": [ + { "label": "GPT-4", "value": "gpt-4" }, + { "label": "GPT-3.5", "value": "gpt-3.5-turbo" } + ], + "defaultValue": "gpt-3.5-turbo" }, - "subreddit": { - "aiModel": { - "type": "select", - "label": "AI Model", - "options": [ - { "label": "GPT-4", "value": "gpt-4" }, - { "label": "GPT-3.5", "value": "gpt-3.5-turbo" } - ], - "defaultValue": "gpt-3.5-turbo" - }, - "maxTokens": { - "type": "number", - "label": "Max Response Tokens", - "validationEndpoint": "/internal/settings/validate-tokens", - "defaultValue": 150 - } + "maxTokens": { + "type": "number", + "label": "Max Response Tokens", + "validationEndpoint": "/internal/settings/validate-tokens", + "defaultValue": 150 } } } - ``` - - - - - ```tsx title="server/index.ts" - import type { JsonObject, JsonValue } from '@devvit/web/shared'; - import { settings } from '@devvit/web/server'; - - type GenerateRequest = { messages: JsonValue }; - type GenerateResponse = JsonObject; - - app.post('/api/generate', async (c) => { - const [apiKey, model, maxTokens] = await Promise.all([ - settings.get('openaiApiKey'), - settings.get('aiModel'), - settings.get('maxTokens') - ]); - const { messages } = await c.req.json(); - - const response = await fetch('https://api.openai.com/v1/chat/completions', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${apiKey}`, - }, - body: JSON.stringify({ - model, - max_tokens: maxTokens, - messages, - }), - }); +} +``` - const data = (await response.json()) as GenerateResponse; - return c.json(data); - }); - ``` - - - - - ```tsx title="server/index.ts" - import type { JsonObject, JsonValue } from '@devvit/web/shared'; - import { settings } from '@devvit/web/server'; - - type GenerateRequest = { messages: JsonValue }; - type GenerateResponse = JsonObject; - - router.post('/api/generate', async (req, res) => { - const [apiKey, model, maxTokens] = await Promise.all([ - settings.get('openaiApiKey'), - settings.get('aiModel'), - settings.get('maxTokens') - ]); - - const response = await fetch('https://api.openai.com/v1/chat/completions', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${apiKey}`, - }, - body: JSON.stringify({ - model, - max_tokens: maxTokens, - messages: req.body.messages, - }), - }); + + + +```tsx title="server/index.ts" +import type { JsonObject, JsonValue } from '@devvit/web/shared'; +import { settings } from '@devvit/web/server'; + +type GenerateRequest = { messages: JsonValue }; +type GenerateResponse = JsonObject; + +app.post('/api/generate', async (c) => { + const [apiKey, model, maxTokens] = await Promise.all([ + settings.get('openaiApiKey'), + settings.get('aiModel'), + settings.get('maxTokens') + ]); + const { messages } = await c.req.json(); - const data = (await response.json()) as GenerateResponse; - res.json(data); + const response = await fetch('https://api.openai.com/v1/chat/completions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${apiKey}`, + }, + body: JSON.stringify({ + model, + max_tokens: maxTokens, + messages, + }), }); - ``` - - - - - ```tsx title="main.tsx" - import { Devvit } from '@devvit/public-api'; + const data = (await response.json()) as GenerateResponse; + return c.json(data); +}); +``` - Devvit.configure({ - http: true, - }); + + - Devvit.addSettings([ - { - name: 'openaiApiKey', - label: 'OpenAI API Key', - type: 'string', - isSecret: true, - scope: 'app', - }, - { - name: 'aiModel', - label: 'AI Model', - type: 'select', - options: [ - { label: 'GPT-4', value: 'gpt-4' }, - { label: 'GPT-3.5', value: 'gpt-3.5-turbo' }, - ], - scope: 'installation', - }, - { - name: 'maxTokens', - label: 'Max Response Tokens', - type: 'number', - scope: 'installation', - onValidate: async ({ value }) => { - if (value < 1 || value > 500) { - return 'Tokens must be between 1 and 500'; - } - }, - }, - ]); +```tsx title="server/index.ts" +import type { JsonObject, JsonValue } from '@devvit/web/shared'; +import { settings } from '@devvit/web/server'; - async function generateResponse(context: Devvit.Context, prompt: string): Promise { - const [apiKey, model, maxTokens] = await Promise.all([ - context.settings.get('openaiApiKey'), - context.settings.get('aiModel'), - context.settings.get('maxTokens'), - ]); - - const response = await fetch('https://api.openai.com/v1/chat/completions', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${apiKey}`, - }, - body: JSON.stringify({ - model, - max_tokens: maxTokens, - messages: [{ role: 'user', content: prompt }], - }), - }); +type GenerateRequest = { messages: JsonValue }; +type GenerateResponse = JsonObject; - const data = await response.json(); - return data.choices[0]?.message?.content || 'No response'; - } +router.post('/api/generate', async (req, res) => { + const [apiKey, model, maxTokens] = await Promise.all([ + settings.get('openaiApiKey'), + settings.get('aiModel'), + settings.get('maxTokens') + ]); - // addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. - Devvit.addCustomPostType({ - name: 'AI Assistant', - render: (context) => { - const [response, setResponse] = useState(''); - - return ( - - - {response && {response}} - - ); + const response = await fetch('https://api.openai.com/v1/chat/completions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${apiKey}`, }, + body: JSON.stringify({ + model, + max_tokens: maxTokens, + messages: req.body.messages, + }), }); - export default Devvit; - ``` - [View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) - + const data = (await response.json()) as GenerateResponse; + res.json(data); +}); +``` + + ## Limitations diff --git a/docs/changelog.md b/docs/changelog.md index 1d65eb6c..ec9c46ff 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -93,10 +93,6 @@ This release brings new guidance to help you build more engaging community games The newly updated [Building Community Games](https://developers.reddit.com/docs/guides/best-practices/community_games#player-retention) guide includes new tips for creating engaging gameplay that thrives on Reddit! Learn which mechanics drive long-term engagement and how to improve your chances of [getting featured](https://developers.reddit.com/docs/guides/launch/feature-guide). -**Deprecating Devvit Blocks** - -As we announced last month, [Devvit Blocks will be deprecated](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) effective June 30, 2026. In this release, we added a deprecation notice to `Devvit.addCustomPostType`. New apps that use this API will now be rejected during app review. - **Other Fixes** - **Improved modmail validation and logging**: Added subreddit ID validation for Reddit Modmail requests and improved error logging. - **Fixed image uploads in comments created with RichTextBuilder**: Resolved an issue where comment creation failed when adding images due to media processing conflicts. RichTextBuilder now accepts media URLs instead of media IDs, and the Devvit runtime converts them during processing to ensure compatibility with native post and comment media handling. @@ -201,10 +197,6 @@ In the last release of 2025, we’ve made a slew of minor updates (they're still In this release, we’re excited to bring payment support to Devvit Web. If you’re looking to add payments to your app, check out our [updated docs](./earn-money/payments/payments_overview.md). -Devvit Web has reached full feature parity with blocks, and we strongly recommend using Devvit Web for all new apps. If you want to convert your existing blocks apps (including mod apps) to Devvit Web, check out the [migration guide](./guides/migrate/devvit-singleton.md). - -To keep things clear (and friendlier to AI-assisted IDEs), we're moving all [blocks documentation](./capabilities/blocks/overview.md) into its own dedicated section. - ## Devvit 0.12.4: Ins and Outs **Release Date: Nov 24, 2025** @@ -311,7 +303,7 @@ This release also includes a handful of other fixes including: - Added a method mergePostData() to append to postData. - Fixed reddit.setPostFlair() method. -- Added a new triggers field that fixed the issue where triggers within the blocks entrypoint weren’t working. The migration guide has been updated. +- Added a new triggers field that fixed an entrypoint triggers issue. - Added error handling when trying to `devvit new`on an already existing app name. - Added disconnectRealtime() and isRealtimeConnected() as helper methods for the realtime plugin. @@ -341,8 +333,6 @@ There's also a new [web-based creation flow](https://developers.reddit.com/new/) - **Client/server architecture**: Clear separation between frontend (@devvit/web/client) and backend (@devvit/web/server) - **Full platform access**: Continued access to Redis, Reddit API, and Devvit's hosting services -- **Flexible development**: Use Devvit Web alongside existing Blocks - choose the right tool for each feature - **Current Limitations** - Serverless endpoints only (no long-running connections or streaming) @@ -353,10 +343,6 @@ There's also a new [web-based creation flow](https://developers.reddit.com/new/) **Getting Started** - **New apps**: Go to developers.reddit.com/new to start building new apps -- **Existing apps**: Migration is optional - your current apps continue to work on 0.11, but we recommend using these migration guides to move your app over to Devvit Web. - - [Devvit Web Experimental to Devvit Web](./guides/migrate/devvit-web-experimental.md) - - [useWebView to Devvit Web](./guides/migrate/inline-web-view.md) - - [Blocks app to Devvit Web](./guides/migrate/devvit-singleton.md) **Support & Feedback** diff --git a/docs/earn-money/payments/payments_add.mdx b/docs/earn-money/payments/payments_add.mdx index 5071336f..8db52d08 100644 --- a/docs/earn-money/payments/payments_add.mdx +++ b/docs/earn-money/payments/payments_add.mdx @@ -5,10 +5,6 @@ import TabItem from "@theme/TabItem"; The Devvit payments API is available in Devvit Web. Keep reading to learn how to configure your products and accept payments. -:::note -Devvit Web is recommended for payments. Check out how to [migrate blocks apps](./payments_migrate.mdx) if you're app is currently using a blocks version of payments. -::: - To start with a template, select the payments template when you create a new project or run: ```bash @@ -255,13 +251,7 @@ If you don’t provide an image, the default Reddit product image is used. ### Purchase buttons (required) -#### Devvit Web - -In Devvit Web, use your own UI (e.g. a button or product card) and call `purchase(sku)` from `@devvit/web/client` when the user chooses a product. Follow the [design guidelines](#design-guidelines) (e.g. gold icon, clear labeling). - -#### Blocks (legacy) - -If your app still uses Devvit Blocks, you can use the `ProductButton` component and [migrate to Devvit Web](./payments_migrate.mdx) when ready. The `ProductButton` renders a product with a purchase button; use `payments.purchase(p.sku)` in the `onPress` callback (from `@devvit/payments`). +Use your own UI (e.g. a button or product card) and call `purchase(sku)` from `@devvit/web/client` when the user chooses a product. Follow the [design guidelines](#design-guidelines) (e.g. gold icon, clear labeling). #### Webviews diff --git a/docs/earn-money/payments/payments_migrate.mdx b/docs/earn-money/payments/payments_migrate.mdx deleted file mode 100644 index ab33351a..00000000 --- a/docs/earn-money/payments/payments_migrate.mdx +++ /dev/null @@ -1,78 +0,0 @@ -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -# Migrate Blocks payments - -If you already have payments set up on a Blocks app, use the following steps to migrate your payments functionality. - -1. Update devvit.json - -Reference your `products.json` and declare endpoints. - -```tsx title="devvit.json" -{ - "permissions": { "payments": true }, - "payments": { - "productsFile": "./products.json", - "endpoints": { - "fulfillOrder": "/internal/payments/fulfill", - "refundOrder": "/internal/payments/refund" - } - } -} - -``` - -2. Replace payment hooks with endpoints - -- Blocks: `addPaymentHandler({ fulfillOrder, refundOrder })` -- Devvit Web: implement `/internal/payments/fulfill` and `/internal/payments/refund` - - - - -```tsx -import type { PaymentHandlerResponse, Order } from "@devvit/web/server"; - -app.post("/internal/payments/fulfill", async (c) => { - const order = await c.req.json(); - // migrate your old fulfillOrder logic here - return c.json({ success: true }); -}); -``` - - - - -```tsx -import type { PaymentHandlerResponse, Order } from "@devvit/web/server"; - -router.post( - "/internal/payments/fulfill", - async (req, res) => { - const order = req.body; - // migrate your old fulfillOrder logic here - res.json({ success: true }); - }, -); -``` - - - - -3. Update client purchase calls - -- Blocks: `usePayments().purchase(sku)` -- Devvit Web: `purchase(sku)` from`@devvit/web/client` - -4. Update products and orders APIs - -- Blocks: `useProducts`, `useOrders` -- Devvit Web: server‑side `payments.getProducts()`, `payments.getOrders()`; expose via `/api/` if needed by the client diff --git a/docs/earn-money/payments/support_this_app.md b/docs/earn-money/payments/support_this_app.md index 3b9eb5f1..cc26a0a5 100644 --- a/docs/earn-money/payments/support_this_app.md +++ b/docs/earn-money/payments/support_this_app.md @@ -62,32 +62,6 @@ Example client code: ```tsx title="client/index.ts" import { purchase, OrderResultStatus } from "@devvit/web/client"; -<<<<<<< HEAD -// addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. -Devvit.addCustomPostType({ - render: (context) => { - const { products } = useProducts(context); - const payments = usePayments((result: OnPurchaseResult) => { - if (result.status === OrderResultStatus.Success) { - context.ui.showToast({ - appearance: 'success', - text: 'Thanks for your support!', - }); - } else { - context.ui.showToast( - `Purchase failed! Please try again.` - ); - } - }); - const supportProduct = products.find(products.find((p) => p.sku === 'support-app'); - return ( - payments.purchase(p.sku)} - /> - ); -}) -======= async function handleSupportApp() { const result = await purchase("support-app"); if (result.status === OrderResultStatus.STATUS_SUCCESS) { @@ -96,9 +70,7 @@ async function handleSupportApp() { // show error or retry (result.errorMessage may be set) } } ->>>>>>> 64da331 (DR-370 update payments docs referencing Ddevvit singleton) ``` -[View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) ## Example diff --git a/docs/examples/template-library.md b/docs/examples/template-library.md index d49a28a8..0af2702b 100644 --- a/docs/examples/template-library.md +++ b/docs/examples/template-library.md @@ -8,7 +8,7 @@ Here are some starter projects and templates for your Devvit projects - [React starter](https://github.com/reddit/devvit-template-react) - a boilerplate project scaffolding to help you kickstart your React project. It includes preinstalled libraries for Devvit, Vite, React, Express, Tailwind, and TypeScript for faster development. -- [Three.js starter](https://github.com/reddit/devvit-template-threejs) - a tower blocks example that shows you how to create 3D graphics in the browser. It includes preinstalled libraries for Devvit, Vite, Three.js, Express, and TypeScript. This template is great for visualizations and games. +- [Three.js starter](https://github.com/reddit/devvit-template-threejs) - a 3D stacking game example that shows you how to create 3D graphics in the browser. It includes preinstalled libraries for Devvit, Vite, Three.js, Express, and TypeScript. This template is great for visualizations and games. - [Phaser starter](https://github.com/reddit/devvit-template-phaser) - a feature-rich HTML5 game framework for building 2D games in the browser. It includes preinstalled libraries for Devvit, Vite, Phaser, Express, and TypeScript. This template is good for handling physics, animations, input, sound, and asset management. @@ -18,14 +18,7 @@ Here are some starter projects and templates for your Devvit projects - [Hello world](https://github.com/reddit/devvit-template-hello-world/tree/main) - a simple template to build a counter app with no frameworks or opinions. - -## Devvit Blocks - -[Devvit Blocks](../capabilities/blocks/overview.md) lets you build applications that run inside of a Reddit post, using Reddit's own design system: optimized for performance and cross-platform compatibility. - -- [Blocks Empty Template](https://github.com/reddit/devvit-template-blocks) - an empty project leveraging Devvit Blocks - -- [Payments Template](https://github.com/reddit/devvit-template-payments) - a template that contains all boilerplate code to enable [Devvit Payments](../earn-money/payments/payments_overview.md) in your Blocks app. +- [Payments Template](https://github.com/reddit/devvit-template-payments) - a template that contains all boilerplate code to enable [Devvit Payments](../earn-money/payments/payments_overview.md) in your app. ## Mod Tools diff --git a/docs/guides/faq.mdx b/docs/guides/faq.mdx index 81890681..bacc0cd9 100644 --- a/docs/guides/faq.mdx +++ b/docs/guides/faq.mdx @@ -93,7 +93,7 @@ One other common gotcha: Devvit projects use `vite build --watch` rather than a Prefer updating the project-local CLI dependency instead of relying on a global install. The current docs recommend updating `devvit` in your project, then running `npx devvit update app` so your `@devvit` packages match the CLI. -For the current commands and workflow, see [Devvit CLI](./tools/devvit_cli.mdx), [Optimizing performance](../capabilities/blocks/optimize_performance.md), and the update notes in [Changelog](../changelog.md). +For the current commands and workflow, see [Devvit CLI](./tools/devvit_cli.mdx) and the update notes in [Changelog](../changelog.md). @@ -173,30 +173,6 @@ Devvit Web is the current client/server app model for building Devvit apps with - -
-Should I use Devvit Web or Devvit Blocks for a new game? - -Use Devvit Web for new games. The current docs say Devvit Web has reached feature parity with Blocks for new apps, strongly recommend Devvit Web for all new apps, and note that `Devvit.addCustomPostType()` is deprecated and new apps using it will be rejected during review. The clearest sources are [Devvit Web overview](../capabilities/devvit-web/devvit_web_overview.mdx) and [Changelog](../changelog.md). - -
- - -
-Can I combine Devvit Blocks and Devvit Web? - -Legacy and partially migrated apps can still mix pieces while they transition, and the migration guides explicitly say apps can be partially migrated. But for new game-style interactive posts, the better direction is to move the experience to Devvit Web instead of designing a new Blocks-plus-Web architecture around deprecated custom post APIs. See [Migrating from useWebView to Devvit Web](./migrate/inline-web-view.md) and [Migrating Blocks/Mod Tools to Devvit Web](./migrate/devvit-singleton.md). - -
- - -
-How do I migrate from Blocks or useWebView to Devvit Web? - -There are separate migration guides depending on what you are starting from: Blocks/Mod Tools, `useWebView`, and Devvit Web experimental. In all cases, the main shift is toward `devvit.json`, explicit client/server folders, and direct server endpoints instead of older message-passing or singleton-based setup. Start with [Blocks/Mod Tools to Devvit Web](./migrate/devvit-singleton.md), [useWebView to Devvit Web](./migrate/inline-web-view.md), or [Devvit Web Experimental to Devvit Web](./migrate/devvit-web-experimental.md). - -
-
Where do I customize the first screen or launch screen? @@ -227,7 +203,7 @@ There is no single canonical viewport size for all devices. Design responsively,
How do I add images that ship with my app? -Put static images in the root `assets/` directory when they should ship with the app version, such as logos, backgrounds, or other bundled art. The docs cover both `` usage and `context.assets.getURL()` in [App image assets](../capabilities/blocks/app_image_assets.md). +Put static images in your client `dist/` directory when they should ship with the app version, such as logos, backgrounds, or other bundled art. Reference them directly from your web client (for example via ``).
@@ -245,7 +221,7 @@ See [Forms](../capabilities/client/forms.mdx) and [Media uploads](../capabilitie
What image URLs can I use in a Devvit app? -The documented display paths are bundled asset filenames, Reddit-hosted URLs, and SVG data URLs, depending on the UI surface. If your source image lives somewhere else on the web, do not assume you can hotlink it directly into the UI. Upload it first so Reddit hosts the image, then use the returned URL. See [Image](../blocks/image.mdx), [App image assets](../capabilities/blocks/app_image_assets.md), and [Media uploads](../capabilities/server/media-uploads.mdx). +The documented display paths are bundled client assets, Reddit-hosted URLs, and SVG data URLs, depending on the UI surface. If your source image lives somewhere else on the web, do not assume you can hotlink it directly into the UI. Upload it first so Reddit hosts the image, then use the returned URL. See [Media uploads](../capabilities/server/media-uploads.mdx).
@@ -271,7 +247,7 @@ Set `postData` when creating the post, then read it from `context.postData` on e The standard Devvit pattern is to build leaderboards with Redis sorted sets. Use [Redis](../capabilities/server/redis.mdx) for score storage and ranking, use [Scheduler](../capabilities/server/scheduler.mdx) for daily or periodic resets, and prefer realtime updates over constant polling when you need a live leaderboard. -Keep the FAQ answer short: Redis is the right storage layer, but the exact schema depends on whether you need daily, weekly, or all-time rankings. The best starting points are [Redis](../capabilities/server/redis.mdx) and the leaderboard/performance guidance in [Optimizing performance](../capabilities/blocks/optimize_performance.md). +Keep the FAQ answer short: Redis is the right storage layer, but the exact schema depends on whether you need daily, weekly, or all-time rankings. [Redis](../capabilities/server/redis.mdx) is the best starting point.
@@ -327,14 +303,12 @@ If you need to react to flair changes, Devvit documents an [`onPostFlairUpdate`]
How do I get a user's username or snoovatar? -If you only need the current user's name, prefer `getCurrentUsername()`. The [performance guide](../capabilities/blocks/optimize_performance.md) recommends this over fetching the full user object. +If you only need the current user's name, prefer `getCurrentUsername()` over fetching the full user object. If you need more profile information, use `getCurrentUser()` from the [Reddit API client](../api/redditapi/RedditAPIClient/classes/RedditAPIClient.md). To get a snoovatar URL, use `reddit.getSnoovatarUrl(username)` or `user.getSnoovatarUrl()` on the [User](../api/redditapi/models/classes/User.md) model. Some handlers also expose experimental identity fields such as `username` and `snoovatar` on [BaseContext](../api/public-api/type-aliases/BaseContext.md), but the Reddit client methods are the clearest documented path. -If you are building with Devvit Blocks, the API also exposes an [`avatar`](../api/public-api/@devvit/namespaces/Devvit/namespaces/Blocks/type-aliases/AvatarProps.md) component that renders from a `thingId`. -
@@ -359,7 +333,7 @@ There is no single limits page today, but these are the most commonly referenced - [Devvit Web](../capabilities/devvit-web/devvit_web_overview.mdx): 30 second max request time, 4 MB max payload size, and 10 MB max response size. - [Post data](../capabilities/server/post-data.mdx): 2 KB per post. - [Settings and secrets](../capabilities/server/settings-and-secrets.mdx): 2 KB per setting value. -- [Realtime](../capabilities/realtime/realtime_in_devvit_blocks.md): 1 MB maximum message payload and 100 messages per second per installation. +- [Realtime](../capabilities/realtime/overview.md): 1 MB maximum message payload and 100 messages per second per installation. - [Scheduler](../capabilities/server/scheduler.mdx): up to 10 live recurring actions per installation, plus `runJob()` limits documented on that page. If you need data to survive app updates, do not rely on browser `localStorage`. The [Devvit Web overview](../capabilities/devvit-web/devvit_web_overview.mdx) recommends Redis for persistent storage across versions. @@ -372,7 +346,7 @@ If you need data to survive app updates, do not rely on browser `localStorage`. The docs do not publish one fixed pixel size for every device or view mode. Instead, build responsive layouts and test them across the views supported by the [UI Simulator](./tools/ui_simulator.mdx): mobile, desktop, and fullscreen. -For Devvit Web, inline and expanded mode behave differently: expanded is the larger experience, with more room for rich interaction, while inline should stay lightweight and feed-friendly. For Devvit Blocks, layout sizing is based on [device-independent pixels and percentages](../blocks/overview.mdx), not a single universal viewport. +Inline and expanded mode behave differently: expanded is the larger experience, with more room for rich interaction, while inline should stay lightweight and feed-friendly. If you are tuning layout behavior, start with mobile-first assumptions and validate in the simulator before hard-coding dimensions. The best references are [View modes and entry points](../capabilities/server/launch_screen_and_entry_points/view_modes_entry_points.md), [Launch screen customization](../capabilities/server/launch_screen_and_entry_points/launch_screen_customization.md), and [UI Simulator](./tools/ui_simulator.mdx). @@ -384,7 +358,7 @@ If you are tuning layout behavior, start with mobile-first assumptions and valid There is no separate NSFW platform guide in these docs, so the main source of truth is [Devvit Rules](../devvit_rules.md) plus Reddit's linked platform policies. -The rules explicitly require labels or age-gating before exposing users to graphic, sexually explicit, or otherwise mature content. Static assets and uploaded media are also subject to the same safety checks and policy review described in [App image assets](../capabilities/blocks/app_image_assets.md) and [Media uploads](../capabilities/server/media-uploads.mdx). +The rules explicitly require labels or age-gating before exposing users to graphic, sexually explicit, or otherwise mature content. Static assets and uploaded media are also subject to the same safety checks and policy review described in [Media uploads](../capabilities/server/media-uploads.mdx). If your app idea depends on adult content or NSFW communities, review those rules first and get clarification in the [Devvit Discord](https://developers.reddit.com/discord) before you ship. diff --git a/docs/guides/migrate/devvit-singleton.md b/docs/guides/migrate/devvit-singleton.md deleted file mode 100644 index 5c34a769..00000000 --- a/docs/guides/migrate/devvit-singleton.md +++ /dev/null @@ -1,64 +0,0 @@ -# Migrating Blocks/Mod Tools to Devvit Web - -This guide covers migrating traditional Devvit apps (using only Blocks or Mod Tools, without web views) to the Devvit Web setup. This is a straightforward migration that requires minimal changes. - -## Overview - -The migration primarily involves switching from `devvit.yaml` to `devvit.json` configuration. Your existing Blocks and Mod Tools code will continue to work with minimal changes. - -## Migration steps - -### 1. Create devvit.json - -Create a `devvit.json` file in your project root with your app configuration: - -```json -{ - "name": "your-app-name", - "blocks": { - "entry": "src/main.tsx", - "triggers": ["onPostCreate"] - } -} -``` - -Replace: - -- `"your-app-name"` with your actual app name -- `"src/main.tsx"` with the path to your main Blocks entry point (where you export your Devvit instance) -- Include any triggers used in your src/main.tsx in the triggers array (or remove the parameter) - -### 2. Remove devvit.yaml - -Delete the `devvit.yaml` file from your project root. All configuration is now handled by `devvit.json`. - -### 3. Handle static assets - -If your app uses static assets (images, fonts, etc.) from an `assets` folder, you'll need to define this in update your `devvit.json` to point to these assets: - -```json -{ - "name": "your-app-name", - "blocks": { - "entry": "src/main.tsx", - "triggers": ["onPostCreate"] - }, - "media": { - "dir": "assets/" - } -} -``` - -### 4. Test your app - -Run your app locally to ensure everything works: - -```bash -devvit playtest -``` - -## That's it! - -Your Blocks and Mod Tools code should work as intended without any other changes. The Devvit runtime handles the compatibility layer automatically. - -While your app will work with just these changes, we recommend exploring the additional capabilities available in Devvit Web over time. diff --git a/docs/guides/migrate/devvit-web-experimental.md b/docs/guides/migrate/devvit-web-experimental.md deleted file mode 100644 index 9fa9983c..00000000 --- a/docs/guides/migrate/devvit-web-experimental.md +++ /dev/null @@ -1,190 +0,0 @@ -# Migrating from Devvit Web Experimental to Devvit Web - -This guide will help you migrate from the experimental version of Devvit Web to the official Devvit Web setup. You must complete this migration to publish and grow your app. - -> **Note**: Apps can be partially migrated, you don't need to re-write everything! - -## How to identify if you're using the experimental version - -You're using Devvit Web experimental if: - -- Your project is based on either of these templates: - - https://github.com/reddit/devvit-bolt-starter-experimental - - https://github.com/reddit/devvit-template-react -- You have a `defineConfig` function in `src/devvit/main.tsx` - -## What's changing - -### Before (experimental) - -- Uses `defineConfig` function in blocks -- Multiple `@devvit/X` packages for different capabilities -- Webroot-based dist outputs - -### After (final version) - -- Uses `devvit.json` for all configuration -- Single `@devvit/web` package with submodule imports -- Cleaner dist folder structure -- Clear separation of client and server code - -## Migration steps - -### 1. Install the latest @devvit/web - -```bash -npm install @devvit/web@latest -``` - -### 2. Remove individual @devvit packages - -Remove all individual capability packages: - -```bash -npm uninstall @devvit/redis @devvit/server @devvit/client -``` - -### 3. Create your devvit.json - -Create a `devvit.json` file in your project root. This replaces all configuration previously done through `defineConfig`: - -```json -{ - "post": { - "client": { - "dir": "dist/client", - "entry": "dist/client/index.html" - } - }, - "blocks": { - "entry": "src/devvit/main.tsx" - }, - "server": { - "entry": "dist/server/index.cjs" - } -} -``` - -> **Note**: Output directories no longer need to go to `webroot`. Use `dist/client` and `dist/server` for cleaner organization. - -### 4. Update your imports - -Change all imports from individual packages to the unified `@devvit/web` package: - -#### Server-side imports - -```typescript -// Before -import { redis } from '@devvit/redis'; -import { createServer, context } from '@devvit/server'; - -// After -import { redis } from '@devvit/web/server'; -import { createServer, context } from '@devvit/web/server'; -``` - -#### Client-side imports - -```typescript -// Before -import { navigateTo } from '@devvit/client'; - -// After -import { navigateTo } from '@devvit/web/client'; -``` - -### 5. Remove defineConfig from main.tsx - -In your `src/devvit/main.tsx`, remove the `defineConfig` function and any configuration it contained. This configuration now lives in `devvit.json`. - -```typescript -// Before in src/devvit/main.tsx -import { defineConfig } from '@devvit/server'; - -export default defineConfig({ - // ... configuration -}); - -// After -// Simply export your Devvit instance or any Devvit.addX functions -import { Devvit } from '@devvit/web'; - -// Your Devvit setup code here -export default Devvit; -``` - -### 6. Reorganize your project structure - -We recommend using a clean folder structure: - -``` -your-app/ -├── src/ -│ ├── client/ # Your web app (React, etc.) -│ │ └── index.tsx -│ ├── server/ # Your server endpoints -│ │ └── index.ts -│ └── devvit/ # Blocks-related code (optional now) -│ └── main.tsx -├── dist/ # Built assets -│ ├── client/ -│ └── server/ -├── devvit.json -└── package.json -``` - -### 7. Update your build configuration - -Ensure your bundler outputs to the correct directories specified in `devvit.json`: - -#### Server Vite config example - -```typescript -export default defineConfig({ - ssr: { - noExternal: true, - }, - build: { - emptyOutDir: false, - ssr: 'index.ts', - outDir: '../../dist/server', - target: 'node22', - sourcemap: true, - rollupOptions: { - external: [...builtinModules], - output: { - format: 'cjs', - entryFileNames: 'index.cjs', - inlineDynamicImports: true, - }, - }, - }, -}); -``` - -#### Client Vite config example - -```typescript -export default defineConfig({ - build: { - outDir: '../../dist/client', // No longer webroot - }, -}); -``` - -## Quick migration path - -For the fastest migration: - -1. **Start with a new template**: Clone https://github.com/reddit/devvit-template-react -2. **Move your server endpoints**: Copy your server code to the `src/server` folder -3. **Move your client app**: Copy your React/web code to the `src/client` folder -4. **Update imports**: Find and replace all `@devvit/X` imports with `@devvit/web/server` or `@devvit/web/client` -5. **Configure devvit.json**: Set up your entrypoints as shown above and update your app name -6. **Test locally**: Run `npm run dev` to ensure everything works - -## Additional considerations - -- All capabilities previously available through the experimental API are still available in the final version -- The context object and Redis access work the same way, just with different import paths -- Your app logic can still be split between client and server as before diff --git a/docs/guides/migrate/inline-web-view.md b/docs/guides/migrate/inline-web-view.md deleted file mode 100644 index e1700e0f..00000000 --- a/docs/guides/migrate/inline-web-view.md +++ /dev/null @@ -1,202 +0,0 @@ -# Migrating from useWebView to Devvit Web - -This guide will migrate your legacy webview implementation (using useWebView inside of Blocks) to the official Devvit Web setup. - -:::note -Apps can be partially migrated, you don't need to re-write everything! -::: - -# Before - -- Use postMessage for message passing -- App logic is isomorphic (server/client) in Blocks -- No client effects available - -# After - -- No postMessage required -- Use web native fetch() to server endpoints directly -- App logic is either on the client, or the server, with clear deliniation -- Client effects are available directly from web views - -## Setting up devvit.json - -The first thing you need to do is setup `devvit.json`. - -Schema here: https://developers.reddit.com/schema/config-file.v1.json - -`devvit.json` supports all capabilities previously available in the `Devvit` singleton, e.g. `Devvit.addCustomPostType()`. For the purposes of this guide, only the post rendering logic will be migrated. - -### Understanding entrypoints - -Your `devvit.json` must have entrypoints that point to **outputs** of your code. It is assumed that you have installed a bundler or can otherwise prepare static assets to appear in your dist folders. - -```js -{ - "post": { - "client": { // The output of your client app, probably /src/webroot - "dir": "dist/client", - "entry": "dist/client/index.html" - } - }, - "blocks": { // point to where you export Devvit singleton, probably src/main.tsx - "entry": "src/devvit/main.tsx" - }, - "server": { // new folder which will contain your Node server - "entry": "dist/server/index.cjs" - }, -} -``` - -> You'll notice that the `blocks` entrypoint points to your TypeScript source file (`src/devvit/main.tsx`). This is because the Devvit CLI handles bundling for Blocks automatically. For your `client` and `server` entrypoints, however, you are responsible for bundling your code and pointing to the final output files in your `dist` directory. - -### Building your client and server - -The `devvit.json` configuration for `client` and `server` points to files in a `dist` directory. This means you're responsible for building your web and server assets. You can use any bundler you like, such as `vite`. - -For example, your `package.json` might include scripts to output your assets to the `dist` folder. - -Sample server vite config - -```ts title="src/server/vite.config.ts -import { defineConfig } from 'vite'; -import { builtinModules } from 'node:module'; - -export default defineConfig({ - ssr: { - noExternal: true, - }, - build: { - ssr: 'index.ts', - outDir: '../../dist/server', - target: 'node22', - sourcemap: true, - rollupOptions: { - external: [...builtinModules], - - output: { - format: 'cjs', - entryFileNames: 'index.cjs', - inlineDynamicImports: true, - }, - }, - }, -}); -``` - -Sample client Vite config (for React) - -```ts title="src/client/vite.config.ts -import { defineConfig } from 'vite'; -import tailwind from '@tailwindcss/vite'; -import react from '@vitejs/plugin-react'; - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react(), tailwind()], - build: { - outDir: '../../dist/client', - sourcemap: true, - chunkSizeWarningLimit: 1500, - }, -}); -``` - -## Setting up your server endpoints - -You can use any Node server for your server endpoints. This guide will use [Express](https://expressjs.com/). - -1. Install Express - -``` -npm i express -``` - -2. Create a server index file - -```ts title='src/server/index.ts' -import express from 'express'; -// The `@devvit/server` package provides the tools to create a server, -// and gives you access to the request context. -import { createServer, context, getServerPort, redis } from '@devvit/web/server'; - -const app = express(); - -// Middleware for JSON body parsing -app.use(express.json()); -// Middleware for URL-encoded body parsing -app.use(express.urlencoded({ extended: true })); -// Middleware for plain text body parsing -app.use(express.text()); - -const router = express.Router(); - -// The `context` object is automatically populated with useful information, -// like the current user's ID. Devvit's services, like redis, are also -// available via named imports from `@devvit/server`. -router.get<{ postId: string }, { message: string }>( - '/api/hello', - async (_req, res): Promise => { - const { userId } = context; - res.status(200).json({ - message: `Hello ${userId}`, - }); - } -); - -router.get('/api/init', async (_req, res): Promise => { - res.json({ initialState: await redis.get('initialState') }); -}); - -// Use router middleware -app.use(router); - -// Get port from environment variable with fallback -const port = getServerPort(); - -const server = createServer(app); -server.on('error', (err) => console.error(`server error; ${err.stack}`)); -server.listen(port, () => console.log(`http://localhost:${port}`)); -``` - -### Calling your server endpoints - -Now - -Instead of using `postMessage`, your client-side code can now directly fetch the initial state from the `/api/init` endpoint we defined in the server. - -```ts title=/src/client/app.ts -const res = await fetch('/api/init'); -const data = await res.json(); -console.log(data.initialState); // Logs the state from Redis -``` - -## Client effects - -Previously, client effects were not available to your webview app. You had to pass a custom postMessage and handle that message in Blocks. Now, all client effects are available directly in the web-view through `@devvit/client`. - -Before - -```ts title=/src/devvit/main.tsx -const BlocksComponent = () => { - const wv = useWebView({ - onMessage: (message) => { - if (message.type === 'navigate_to') { - ui.navigateTo(message.data.destination); - } - }, - }); -}; -``` - -```js title=webroot/app.js -window.postMessage({ type: 'navigate_to', destination: 'reddit.com' }); -``` - -Now - -```ts title=client/app.ts -import { navigateTo } from '@devvit/web/client'; - -navigateTo('reddit.com'); -``` diff --git a/docs/guides/tools/devvit_cli.mdx b/docs/guides/tools/devvit_cli.mdx index 3ff2308a..fe985951 100644 --- a/docs/guides/tools/devvit_cli.mdx +++ b/docs/guides/tools/devvit_cli.mdx @@ -34,25 +34,13 @@ $ npx devvit create icons "src/my-icons.ts" #### Using the SVG files in app code -```tsx -import { Devvit } from '@devvit/public-api'; +```tsx title="src/client/App.tsx" import Icons from './my-icons.ts'; -// addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. -Devvit.addCustomPostType({ - name: 'my-custom-post', - render: (_context) => { - return ( - - - - ); - }, -}); - -export default Devvit; +export const App = () => ( + +); ``` -[View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) ### devvit help diff --git a/docs/quickstart/quickstart-mod-tool.md b/docs/quickstart/quickstart-mod-tool.md index b3ae9b65..b907e557 100644 --- a/docs/quickstart/quickstart-mod-tool.md +++ b/docs/quickstart/quickstart-mod-tool.md @@ -34,135 +34,120 @@ Cutting the template to the target directory... This tutorial lets you build your own version of [Comment Mop](https://developers.reddit.com/apps/comment-nuke). This tool allows moderators to remove and/or lock a full comment tree with a single menu action, avoiding repetitive mechanical tasks for community moderators. -### Create a menu action for moderators +### Declare a menu action for moderators -The template leverages [Menu Actions](../capabilities/client/menu-actions) to enable moderators to Delete/Lock child comments of a post or comment. Menu Actions appear in the moderator menu: -![menu actions](../assets/quickstart/quickstart-mod-tool-1.png) +Menu items are declared in `devvit.json`. Each entry points at a server endpoint that runs when a moderator clicks it. The template leverages [Menu Actions](../capabilities/client/menu-actions.mdx) to enable moderators to Delete/Lock child comments of a post or comment. -The following code adds a menu action to comments. Once the app is installed on a subreddit, all moderators of the subreddit will see this option appear in the moderator menu for all comments. +![menu actions](../assets/quickstart/quickstart-mod-tool-1.png) -```ts -Devvit.addMenuItem({ - label: 'Mop comments', - description: 'Remove this comment and all child comments. This might take a few seconds to run.', - location: 'comment', - forUserType: 'moderator', - onPress: (_event, context) => { - context.ui.showForm(nukeForm); +```json title="devvit.json" +{ + "menu": { + "items": [ + { + "label": "Mop comments", + "description": "Remove this comment and all child comments. This might take a few seconds to run.", + "forUserType": "moderator", + "location": ["comment"], + "endpoint": "/internal/menu/mop-comments" + } + ] }, -}); + "forms": { + "mopForm": "/internal/form/mop-submit" + }, + "permissions": { + "reddit": { "enable": true, "scope": "moderator" } + } +} ``` -You'll notice that the `onPress` handler of this Menu Item action invokes a form with `context.ui.showForm()`. This will be explained in the next step. +### Show a form from the menu action -### Devvit forms - -Optionally, some moderator tools might need to request some additional information from the moderator before they can execute. In these cases we can leverage [Devvit Forms](../capabilities/client/forms). Comment Mop will display a form with some options regarding the action to be taken: +Some moderator tools need additional information before they execute. The menu endpoint can respond with a form using [menu responses](../capabilities/client/menu-actions.mdx). Comment Mop displays a form with options for the action to be taken: ![forms](../assets/quickstart/quickstart-mod-tool-2.png) -The code that defines this form is: +```ts title="server/index.ts" +import type { MenuItemRequest, UiResponse } from '@devvit/web/shared'; + +app.post('/internal/menu/mop-comments', async (c) => { + const _input = await c.req.json(); + return c.json({ + showForm: { + name: 'mopForm', + form: { + title: 'Mop Comments', + acceptLabel: 'Mop', + cancelLabel: 'Cancel', + fields: [ + { name: 'remove', label: 'Remove comments', type: 'boolean', defaultValue: true }, + { name: 'lock', label: 'Lock comments', type: 'boolean', defaultValue: false }, + { + name: 'skipDistinguished', + label: 'Skip distinguished comments', + type: 'boolean', + defaultValue: false, + }, + ], + }, + }, + }); +}); +``` -```ts -// Define form fields -const nukeFields: FormField[] = [ - { - name: 'remove', - label: 'Remove comments', - type: 'boolean', - defaultValue: true, - }, - { - name: 'lock', - label: 'Lock comments', - type: 'boolean', - defaultValue: false, - }, - { - name: 'skipDistinguished', - label: 'Skip distinguished comments', - type: 'boolean', - defaultValue: false, - }, -] as const; - -// Create form -const nukeForm = Devvit.createForm( - () => { - return { - fields: nukeFields, - title: 'Mop Comments', - acceptLabel: 'Mop', - cancelLabel: 'Cancel', - }; - }, - // Form confirmation handler - async ({ values }, context) => { - if (!values.lock && !values.remove) { - context.ui.showToast('You must select either lock or remove.'); - return; - } +### Handle the form submission with the Reddit API - if (context.commentId) { - // ... - // mop comments here - // ... - } - } -); -``` +Devvit apps can use the Reddit API to act on comments and posts. The form submission endpoint receives the moderator's selections and traverses the comment tree: -The code that handles mopping the comment has been redacted from the sample above. It uses the Reddit API to traverse through the comments and perform the necessary actions. It will be explained in the next step. +```ts title="server/index.ts" +import { reddit, context } from '@devvit/web/server'; +import type { UiResponse } from '@devvit/web/shared'; -### Reddit API +type MopFormRequest = { + remove: boolean; + lock: boolean; + skipDistinguished: boolean; +}; -Apps made with Devvit can leverage the Reddit API to perform actions on comments, posts, get information about the current session, etc. The following code uses the Reddit API to find the child comments of the selected comment and delete all of them: +app.post('/internal/form/mop-submit', async (c) => { + const { remove, lock, skipDistinguished } = await c.req.json(); + const { commentId } = context; -```ts -Devvit.configure({ - redditAPI: true, -}); + if (!remove && !lock) { + return c.json({ showToast: 'You must select either lock or remove.' }); + } + + if (!commentId) { + return c.json({ showToast: 'This action must be run on a comment.' }); + } -export async function handleNuke(props: NukeProps, context: Devvit.Context) { try { - // Get Comment and User from Reddit API - const comment = await context.reddit.getCommentById(props.commentId); - const user = await context.reddit.getCurrentUser(); - - // Get Comments for Removal - const comments: Comment[] = []; - for await (const eachComment of getAllCommentsInThread(comment, skipDistinguished)) { - comments.push(eachComment); - } + const rootComment = await reddit.getCommentById(commentId); - // Remove all comments - await Promise.all(comments.map((comment) => comment.removed || comment.remove())); - - // Add to Mod Log - try { - await context.modLog.add({ - action: 'removecomment', - target: props.commentId, - details: 'comment-mop app', - description: `u/${user.username} used comment-mop to ${verbage} this comment and all child comments.`, - }); - } catch (e: unknown) { - console.error(`Failed to add modlog for comment: ${props.commentId}.`, (e as Error).message); + for await (const comment of walkReplies(rootComment, skipDistinguished)) { + if (remove && !comment.removed) await comment.remove(); + if (lock && !comment.locked) await comment.lock(); } - // Show Toast with Result - context.ui.showToast('Comments removed! Refresh the page to see the cleanup.'); - } catch (err: unknown) { - context.ui.showToast('Mop failed! Please try again later.'); + return c.json({ + showToast: 'Comments mopped! Refresh the page to see the cleanup.', + }); + } catch (err) { console.error(err); + return c.json({ showToast: 'Mop failed! Please try again later.' }); } -} +}); -// Helper Function - Depth-first traversal to get all comments in a thread -async function* getAllCommentsInThread(comment: Comment): AsyncGenerator { +async function* walkReplies( + comment: Awaited>, + skipDistinguished: boolean, +): AsyncGenerator { + if (skipDistinguished && comment.distinguishedBy) return; + yield comment; const replies = await comment.replies.all(); for (const reply of replies) { - yield* getAllCommentsInThread(reply); + yield* walkReplies(reply, skipDistinguished); } } ``` @@ -186,6 +171,6 @@ Now you have a mod tool running from the code that you deployed yourself. Feel f ## Further reading - Use our [launch guide](../guides/launch/launch-guide.md) to guide you where to get your first users. -- [Devvit Forms](../capabilities/client/forms) -- [Menu Actions](../capabilities/client/menu-actions) +- [Devvit Forms](../capabilities/client/forms.mdx) +- [Menu Actions](../capabilities/client/menu-actions.mdx) - [Reddit Developer Funds](../earn-money/reddit_developer_funds) diff --git a/sidebars.ts b/sidebars.ts index 3eef0480..80bc6f17 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -168,7 +168,6 @@ const sidebars: SidebarsConfig = { items: [ "earn-money/payments/payments_overview", "earn-money/payments/payments_add", - "earn-money/payments/payments_migrate", "earn-money/payments/payments_test", "earn-money/payments/payments_publish", "earn-money/payments/payments_manage", @@ -295,9 +294,6 @@ const sidebars: SidebarsConfig = { type: "category", label: "Migration Guides", items: [ - "guides/migrate/devvit-singleton", - "guides/migrate/devvit-web-experimental", - "guides/migrate/inline-web-view", { type: "category", label: "Splash Screens", @@ -328,33 +324,6 @@ const sidebars: SidebarsConfig = { className: "sidebar-divider", defaultStyle: false, }, - { - type: "category", - label: "Devvit Blocks", - items: [ - "capabilities/blocks/overview", - "capabilities/blocks/blocks_payments", - "capabilities/blocks/dimensions", - "capabilities/blocks/working_with_usestate", - "capabilities/blocks/working_with_useinterval", - "capabilities/blocks/working_with_useasync", - "capabilities/blocks/app_image_assets", - "capabilities/realtime/realtime_in_devvit_blocks", - { - type: "category", - label: "Blocks Reference", - items: [ - "blocks/stacks", - "blocks/text", - "blocks/icon", - "blocks/button", - "blocks/image", - "blocks/spacer", - "blocks/colors", - ], - }, - ], - }, { type: "doc", label: "Changelog", diff --git a/versioned_docs/version-0.12/blocks/attributes/_alignment.md b/versioned_docs/version-0.12/blocks/attributes/_alignment.md deleted file mode 100644 index f0a3838d..00000000 --- a/versioned_docs/version-0.12/blocks/attributes/_alignment.md +++ /dev/null @@ -1,19 +0,0 @@ -A `string` for defining the text alignment within the text block, both vertically and horizontally. Can be 1 or 2 values, separated by a space. e.g. `top` or `top start`. - -![Showing all the alignment options](../../assets/blocks/docs-blocks-stacks-alignment.png) - -#### Vertical alignment - -- `top`: The default if the attribute is not used. -- `middle` -- `bottom` - -Note: If vertical alignment is not used in a `hstack`, then the content will stretch vertically. - -#### Horizontal alignment - -- `start`: The default if the attribute is not used. -- `center` -- `end` - -Note: If horizontal alignment is not used in a `vstack`, then the content will stretch horizontally. diff --git a/versioned_docs/version-0.12/blocks/attributes/_color.md b/versioned_docs/version-0.12/blocks/attributes/_color.md deleted file mode 100644 index 6624d328..00000000 --- a/versioned_docs/version-0.12/blocks/attributes/_color.md +++ /dev/null @@ -1,7 +0,0 @@ -A `string` for setting the color to any [Blocks-compatible color value](../colors.mdx). Possible values: - -- HTML color names: `red` -- HEX: `#ff4500` -- RGB: `rgb(255, 69, 0)` -- RGBA: `rgba(255, 69, 0, 0.5)` -- RPL Token: `neutral-content` diff --git a/versioned_docs/version-0.12/blocks/attributes/_grow.md b/versioned_docs/version-0.12/blocks/attributes/_grow.md deleted file mode 100644 index b947dddf..00000000 --- a/versioned_docs/version-0.12/blocks/attributes/_grow.md +++ /dev/null @@ -1,6 +0,0 @@ -A `boolean` that allows the block to expand to fill all available space along the axis of its parent container if set to true. Possible values: - -- `true` -- `false`: The default if the attribute is not used. - -Has no effect inside a ``. diff --git a/versioned_docs/version-0.12/blocks/attributes/_height.mdx b/versioned_docs/version-0.12/blocks/attributes/_height.mdx deleted file mode 100644 index 93e00b32..00000000 --- a/versioned_docs/version-0.12/blocks/attributes/_height.mdx +++ /dev/null @@ -1,8 +0,0 @@ -import PercentDimensionWarning from './_percent_dimension_warning.mdx'; - -A `string` that sets a height of the block. Possible values: - -- Absolute: `100px` -- Relative: `50%` (The default if the unit is not specified, i.e. `height="50"`) - - diff --git a/versioned_docs/version-0.12/blocks/attributes/_maxHeight.md b/versioned_docs/version-0.12/blocks/attributes/_maxHeight.md deleted file mode 100644 index f2fadf20..00000000 --- a/versioned_docs/version-0.12/blocks/attributes/_maxHeight.md +++ /dev/null @@ -1 +0,0 @@ -Prevents the used value of `height` from becoming larger than the maximum height if set. diff --git a/versioned_docs/version-0.12/blocks/attributes/_maxWidth.md b/versioned_docs/version-0.12/blocks/attributes/_maxWidth.md deleted file mode 100644 index 9720e519..00000000 --- a/versioned_docs/version-0.12/blocks/attributes/_maxWidth.md +++ /dev/null @@ -1 +0,0 @@ -Prevents the used value of `width` from becoming larger than the maximum width if set. diff --git a/versioned_docs/version-0.12/blocks/attributes/_minHeight.md b/versioned_docs/version-0.12/blocks/attributes/_minHeight.md deleted file mode 100644 index c091cdd2..00000000 --- a/versioned_docs/version-0.12/blocks/attributes/_minHeight.md +++ /dev/null @@ -1 +0,0 @@ -Prevents the used value of `height` from becoming smaller than the minimum height if set. diff --git a/versioned_docs/version-0.12/blocks/attributes/_minWidth.md b/versioned_docs/version-0.12/blocks/attributes/_minWidth.md deleted file mode 100644 index 52d1c55e..00000000 --- a/versioned_docs/version-0.12/blocks/attributes/_minWidth.md +++ /dev/null @@ -1 +0,0 @@ -Prevents the used value of `width` from becoming smaller than the minimum width if set. diff --git a/versioned_docs/version-0.12/blocks/attributes/_onPress.md b/versioned_docs/version-0.12/blocks/attributes/_onPress.md deleted file mode 100644 index 64207407..00000000 --- a/versioned_docs/version-0.12/blocks/attributes/_onPress.md +++ /dev/null @@ -1 +0,0 @@ -Attaches an event handler for press events on the block. diff --git a/versioned_docs/version-0.12/blocks/attributes/_percent_dimension_examples.mdx b/versioned_docs/version-0.12/blocks/attributes/_percent_dimension_examples.mdx deleted file mode 100644 index dc00b58a..00000000 --- a/versioned_docs/version-0.12/blocks/attributes/_percent_dimension_examples.mdx +++ /dev/null @@ -1,95 +0,0 @@ -**Valid dimensions** - -```tsx - - - -``` - -Z stack dimensions are valid because it satisfies the following rules: - -- Height: Parent block must have the height defined. - -- Width: Parent block must have the width defined. - ---- - -```tsx - - - - - -``` - -Z stack dimensions are valid because it satisfies the following rules: - -- Height: Parent block must have the height defined. - -- Width: Parent block must be stretching horizontally in a `vstack`. - The parent `hstack` is stretching horizontally in the `vstack`, because the `vstack` does not have any alignment indicated on - the cross axis. (See [alignment](../blocks/stacks#alignment)) - ---- - -```tsx - - - - - -``` - -Z stack dimensions are valid because it satisfies the following rules: - -- Height: Parent block must be growing vertically in a `vstack`. - The parent `hstack` is growing vertically along the main axis of the `vstack` because it has grow indicated. - (See [grow](../blocks/stacks#grow)) - -- Width: Parent block must have the width defined. - ---- - -**Invalid dimensions** - -```tsx - - - - - -``` - -Z stack dimensions are invalid because it does not satisfy any of the rules: - -- Height: `hstack` is not growing vertically because it does not indicate `grow`. - Therefore, there is no parent height for the `zstack` to base its `height=100%` on. -- Width: `hstack` is not stretching horizontally because because the `vstack` indicates an alignment. - Therefore, there is no parent width for the `zstack` to based its `width=100%` on. Width will be omitted, because the relative width is invalid and the parent is an `hstack`. - ---- - -**Inferred dimensions** - -```tsx - - - - - -``` - -In this example, the `hstack` infers its height definition from the `vstack` (height = 300px), so -the `zstack` dimensions are **valid**. - ---- - -```tsx - - - - - -``` - -In this example, the `vstack` does not specify width, so there’s nothing for the `hstack` to infer. The width of the `zstack` is **invalid**. Therefore, the width will be omitted. diff --git a/versioned_docs/version-0.12/blocks/attributes/_percent_dimension_warning.mdx b/versioned_docs/version-0.12/blocks/attributes/_percent_dimension_warning.mdx deleted file mode 100644 index 551f7394..00000000 --- a/versioned_docs/version-0.12/blocks/attributes/_percent_dimension_warning.mdx +++ /dev/null @@ -1,26 +0,0 @@ -import PercentDimensionExamples from './_percent_dimension_examples.mdx'; - -export const widthStretch = 'horizontally in a vstack'; -export const heightStretch = 'vertically in a hstack'; -export const widthGrow = 'horizontally in a hstack'; -export const heightGrow = 'vertically in a vstack'; - -A block's {props.dimension} will be omitted if the relative {props.dimension} is invalid and the parent is a {(props.dimension == "width") && ("hstack")}{(props.dimension != "width") && ("vstack")}. -If the parent is a `zstack`, then an invalid {props.dimension} will always be omitted. - -A relative {props.dimension} is valid if it meets one of the following rules: - -- Parent block must have a defined {props.dimension} or infer its {props.dimension} from an ancestor. -- Parent block must stretch {(props.dimension == "width") && (widthStretch)}{(props.dimension != "width") && (heightStretch)} - (see [alignment](../blocks/stacks#alignment)). -- Parent block must [grow](../blocks/stacks#grow) {(props.dimension == "width") && (widthGrow)}{(props.dimension != "width") && (heightGrow)}. - -:::note -Relative {props.dimension} can only grow or stretch in a constrained dimension. The nearest ancestor without a definition will break -the chain, and the block's {props.dimension} will be omitted. -::: - -
- Relative (Percent) Examples - -
diff --git a/versioned_docs/version-0.12/blocks/attributes/_width.mdx b/versioned_docs/version-0.12/blocks/attributes/_width.mdx deleted file mode 100644 index e41bff80..00000000 --- a/versioned_docs/version-0.12/blocks/attributes/_width.mdx +++ /dev/null @@ -1,8 +0,0 @@ -import PercentDimensionWarning from './_percent_dimension_warning.mdx'; - -A `string` that sets a width of the block. Possible values: - -- Absolute: `100px` -- Relative: `50%` (The default if the unit is not specified, i.e. `width="50"`) - - diff --git a/versioned_docs/version-0.12/blocks/button.mdx b/versioned_docs/version-0.12/blocks/button.mdx deleted file mode 100644 index ee521f55..00000000 --- a/versioned_docs/version-0.12/blocks/button.mdx +++ /dev/null @@ -1,183 +0,0 @@ -import AttributeMinHeight from './attributes/_minHeight.md'; -import AttributeMaxHeight from './attributes/_maxHeight.md'; -import AttributeMinWidth from './attributes/_minWidth.md'; -import AttributeMaxWidth from './attributes/_maxWidth.md'; -import AttributeGrow from './attributes/_grow.md'; -import AttributeOnPress from './attributes/_onPress.md'; -import AttributeWidth from './attributes/_width.mdx'; -import AttributeHeight from './attributes/_height.mdx'; - -# Button - -The ` -``` - -A button with no icon - -```tsx - -``` - -### `size` - -Determines the button size. Possible values: - -- `small`: 32px -- `medium`: 40px. The default if the attribute is not used. -- `large`: 48px - -![Button sizes](../assets/blocks/docs-blocks-button-sizes.png) - -#### Example - -A small button - -```tsx - -``` - -### `appearance` - -Sets the button style. Possible values: - -- `primary` -- `secondary`: The default if the attribute is not used. -- `plain` -- `bordered` -- `media` -- `destructive` -- `caution` -- `success` - -![Button appearance](../assets/blocks/docs-blocks-button-appearance.png) - -#### Example - -A primary button - -```tsx - -``` - -### `disabled` - -Disables the button if set to true, preventing interactions. - -![Disabled buttons](../assets/blocks/docs-blocks-button-disabled.png) - -#### Examples - -A disabled button button - -```tsx - -``` - -A disabled button button - -```tsx - -``` - -### `grow` - - - -#### Example - -A growing button - -```tsx - -``` - -### `width` - - - -### `minWidth` - - - -### `maxWidth` - - - -### `height` - - - -### `minHeight` - - - -### `maxHeight` - - - -## Functions - -### `onPress` - - - -#### Examples - -```tsx - -``` - -## Notes - - - -## Examples - -A button that increments a counter - -```tsx -import { Devvit, useState } from '@devvit/public-api'; - -// addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. View the announcement below this example. -Devvit.addCustomPostType({ - name: 'Say Hello', - render: (context) => { - const [votes, setCounter] = useState(0); - return ( - - - - - - ); - }, -}); -``` -[View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) \ No newline at end of file diff --git a/versioned_docs/version-0.12/blocks/colors.mdx b/versioned_docs/version-0.12/blocks/colors.mdx deleted file mode 100644 index 26c53c55..00000000 --- a/versioned_docs/version-0.12/blocks/colors.mdx +++ /dev/null @@ -1,297 +0,0 @@ -# Colors - -Colors in Blocks can be defined by any of the following formats. - -## Supported color formats - -### HEX - -Specify a color in hexadecimal format using `#RRGGBB`, where RR represents red, GG represents green, and BB represents blue. Ensure that the values for each component fall within the range of 00 to FF. - -- Example: `#ff4500` - -### RGB - -Specify an RGB color using the `rgb()` function with the syntax `rgb(red, green, blue)`. Each parameter (red, green, and blue) determines color intensity and can be an integer ranging from 0 to 255. For instance, `rgb(0,0,255)` renders as blue, as the blue parameter is set to its maximum value (255) while the others are set to 0. - -- Example: `rgb(255, 69, 0)` - -### RGBA - -Extend RGB color values with transparency using RGBA with the `rgba()` function with the syntax `rgba(red, green, blue, alpha)`. The alpha parameter, ranging from 0.0 (fully transparent) to 1.0 (fully opaque), determines the object's opacity. - -- Example: `rgba(255, 69, 0, 0.5)` - -### HTML Color Names - -There are ~150 standard color names defined. These include common names like "red", "blue', and "green". You can see the full list in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/CSS/named-color). - -Examples: - -- `red` -- `white` -- `blue` - -### RPL Tokens - -A set of color tokens from the Reddit Product Language (RPL). - -#### RPL Semantic Tokens - -- A semantic color token that changes based on light and dark mode. An alias to 2 primive colors. -- Example: `neutral-background` - -#### Primitives - -- A static color that does not change between light and dark mode. -- Example: `orangered-500` - ---- - -## RPL Semantic Tokens - -Semantic color tokens and their light/dark mode color aliases. - -### Global - -| Token | Light mode primitive | Dark mode primitive | -| ------------------ | -------------------- | ------------------- | -| `global-black` | Global-Black | Global-Black | -| `global-white` | Global-White | Global-White | -| `global-orangered` | Global-Orangered | Global-Orangered | -| `global-online` | KiwiGreen-400 | KiwiGreen-400 | -| `global-offline` | PureGray-500 | PureGray-500 | -| `global-admin` | Orangered-500 | Orangered-500 | -| `global-moderator` | KiwiGreen-500 | KiwiGreen-500 | -| `global-self ` | MintGreen-500 | MintGreen-500 | -| `global-coins` | Yellow-400 | Yellow-400 | -| `global-stars` | Yellow-500 | Yellow-400 | -| `global-live` | Global-Orangered | Global-Orangered | -| `global-nsfw` | SakuraPink-500 | SakuraPink-500 | - -### Neutral - -| Token | Light mode primitive | Dark mode primitive | -| ------------------------------ | -------------------- | ------------------- | -| `neutral-content-strong` | PureGray-850 | PureGray-100 | -| `neutral-content` | PureGray-650 | PureGray-350 | -| `neutral-content-weak ` | PureGray-525 | PureGray-450 | -| `neutral-background-strong` | Global-White | PureGray-850 | -| `neutral-background` | Global-White | PureGray-900 | -| `neutral-background-weak` | PureGray-50 | PureGray-950 | -| `neutral-background-hovered` | CoolGray-50 | CoolGray-850 | -| `neutral-background-selected` | PureGray-150 | PureGray-750 | -| `neutral-background-pinned` | Global-White | CoolGray-900 | -| `neutral-background-container` | CoolGray-50 | CoolGray-850 | -| `neutral-border-strong` | PureGray-850 | PureGray-100 | -| `neutral-border-medium` | Black 50% | White 50% | -| `neutral-border` | Black 20% | White 20% | -| `neutral-border-weak` | Black 10% | White 10% | - -### Primary - -| Token | Light mode primitive | Dark mode primitive | -| ------------------------------- | -------------------- | ------------------- | -| `primary-plain` | AlienBlue-600 | AlienBlue-400 | -| `primary-plain-hovered` | AlienBlue-700 | AlienBlue-300 | -| `primary-plain-visited` | BerryPurple-600 | BerryPurple-400 | -| `primary-onBackground` | Global-White | Global-White | -| `primary-onBackground-selected` | Global-White | Global-Black | -| `primary-background` | AlienBlue-600 | AlienBlue-600 | -| `primary-background-hovered` | AlienBlue-700 | AlienBlue-500 | -| `primary-background-selected` | AlienBlue-800 | AlienBlue-400 | -| `primary-border` | AlienBlue-600 | AlienBlue-400 | -| `primary-border-hovered` | AlienBlue-700 | AlienBlue-300 | - -### Secondary - -| Token | Light mode primitive | Dark mode primitive | -| ------------------------------- | -------------------- | ------------------- | -| `secondary-plain` | PureGray-850 | PureGray-100 | -| `secondary-plain-weak` | PureGray-525 | PureGray-450 | -| `secondary-plain-hovered` | Global-Black | Global-White | -| `secondary-onBackground` | Global-Black | Global-White | -| `secondary-background` | PureGray-150 | PureGray-750 | -| `secondary-background-hovered` | PureGray-200 | PureGray-700 | -| `secondary-background-selected` | PureGray-300 | PureGray-600 | - -### Media - -| Token | Light mode primitive | Dark mode primitive | -| ----------------------------- | -------------------- | ------------------- | -| `media-onBackground` | Global-White | Global-White | -| `media-onBackground-disabled` | White 25% | White 25% | -| `media-onBackground-weak` | CoolGray-200 | CoolGray-200 | -| `media-background` | Black 54% | Black 54% | -| `media-background-hovered` | Black 75% | Black 75% | -| `media-background-selected` | Black 75% | Black 75% | -| `media-border-selected` | Global-White | Global-White | - -### Danger - -| Token | Light mode primitive | Dark mode primitive | -| --------------------------- | -------------------- | ------------------- | -| `danger-plain` | Red-600 | Red-400 | -| `danger-plain-hovered` | Red-700 | Red-300 | -| `danger-onBackground` | Global-White | Global-White | -| `danger-background` | Red-500 | Red-600 | -| `danger-background-hovered` | Red-600 | Red-500 | -| `danger-background-weaker` | Red-200 | Red-800 | - -### Success - -| Token | Light mode primitive | Dark mode primitive | -| ---------------------------- | -------------------- | ------------------- | -| `success-plain` | KiwiGreen-600 | KiwiGreen-400 | -| `success-plain-hovered` | KiwiGreen-700 | KiwiGreen-300 | -| `success-onBackground` | Global-White | Global-White | -| `success-background` | KiwiGreen-500 | KiwiGreen-600 | -| `success-background-hovered` | KiwiGreen-600 | KiwiGreen-500 | - -### Caution - -| Token | Light mode primitive | Dark mode primitive | -| ---------------------------- | -------------------- | ------------------- | -| `caution-plain` | Yellow-600 | Yellow-400 | -| `caution-plain-hovered` | Yellow-700 | Yellow-300 | -| `caution-onBackground` | Global-White | Global-White | -| `caution-background` | Yellow-500 | Yellow-600 | -| `caution-background-hovered` | Yellow-500 | Yellow-300 | - -### Brand - -| Token | Light mode primitive | Dark mode primitive | -| -------------------------- | -------------------- | ------------------- | -| `brand-onBackground` | Global-White | Global-White | -| `brand-background` | Orangered-500 | Orangered-500 | -| `brand-background-hovered` | Orangered-600 | Orangered-600 | - -### Scrim - -| Token | Light mode primitive | Dark mode primitive | -| ------------------------- | -------------------- | ------------------- | -| `scrim-background-strong` | Black 80% | Black 80% | -| `scrim-background` | Black 60% | Black 60% | - -### Interactive - -| Token | Light mode primitive | Dark mode primitive | -| --------------------------------- | -------------------- | ------------------- | -| `interactive-content-disabled` | Black 25% | White 25% | -| `interactive-pressed` | Black 16% | White 16% | -| `interactive-background-disabled` | Global-Black 5% | Global-White 5% | -| `interactive-focused` | LightBlue-500 | LightBlue-500 | - -### Upvote - -| Token | Light mode primitive | Dark mode primitive | -| ------------------------------- | -------------------- | ------------------- | -| `upvote-plain` | Orangered-600 | Orangered-400 | -| `upvote-plain-weaker` | Brand-Orangered | Brand-Orangered | -| `upvote-plain-disabled` | Orangered-600 30% | Orangered-400 30% | -| `upvote-onBackground` | Global-White | Global-White | -| `upvote-background` | Orangered-500 | Orangered-500 | -| `upvote-background-hovered` | Orangered-600 | Orangered-600 | -| `upvote-background-disabled` | Orangered-500 30% | Orangered-500 30% | -| `upvote-onStrongScrim` | Orangered-400 | Orangered-400 | -| `upvote-onStrongScrim-weaker` | Brand-Orangered | Brand-Orangered | -| `upvote-onStrongScrim-disabled` | Orangered-400 30% | Orangered-400 30% | - -### Downvote - -| Token | Light mode primitive | Dark mode primitive | -| --------------------------------- | -------------------- | ------------------- | -| `downvote-plain` | Periwinkle-600 | Periwinkle-400 | -| `downvote-plain-weaker` | Periwinkle-500 | Periwinkle-500 | -| `downvote-plain-disabled` | Periwinkle-600 30% | Periwinkle-400 30% | -| `downvote-onBackground` | Global-White | Global-White | -| `downvote-background` | Periwinkle-500 | Periwinkle-600 | -| `downvote-background-hovered` | Periwinkle-600 | Periwinkle-600 | -| `downvote-background-disabled` | Periwinkle-500 30% | Periwinkle-500 30% | -| `downvote-onStrongScrim` | Periwinkle-400 | Periwinkle-400 | -| `downvote-onStrongScrim-weaker` | Periwinkle-500 | Periwinkle-500 | -| `downvote-onStrongScrim-disabled` | Periwinkle-400 30% | Periwinkle-400 30% | - ---- - -## RPL Color Primitives - -### CoolGray - -![CoolGray Colors](../assets/blocks/docs-blocks-colors-coolgray.png) - -`CoolGray-50`, `CoolGray-100`, `CoolGray-150`, `CoolGray-200`, `CoolGray-250`, `CoolGray-300`, `CoolGray-350`, `CoolGray-400`, `CoolGray-450`, `CoolGray-500`, `CoolGray-525`, `CoolGray-550`, `CoolGray-600`, `CoolGray-650`, `CoolGray-700`, `CoolGray-750`, `CoolGray-800`, `CoolGray-850`, `CoolGray-900`, `CoolGray-950` - -### PureGray - -![PureGray Colors](../assets/blocks/docs-blocks-colors-puregray.png) - -`PureGray-50`, `PureGray-100`, `PureGray-150`, `PureGray-200`, `PureGray-250`, `PureGray-300`, `PureGray-350`, `PureGray-400`, `PureGray-450`, `PureGray-500`, `PureGray-525`, `PureGray-550`, `PureGray-600`, `PureGray-650`, `PureGray-700`, `PureGray-750`, `PureGray-800`, `PureGray-850`, `PureGray-900`, `PureGray-950` - -### Orangered - -![Orangered Colors](../assets/blocks/docs-blocks-colors-orangered.png) - -`Orangered-50`, `Orangered-100`, `Orangered-200`, `Orangered-300`, `Orangered-400`, `Orangered-500`, `Orangered-600`, `Orangered-700`, `Orangered-800`, `Orangered-900`, `Orangered-950` - -### Red - -![Red Colors](../assets/blocks/docs-blocks-colors-red.png) - -`Red-50`, `Red-100`, `Red-200`, `Red-300`, `Red-400`, `Red-500`, `Red-600`, `Red-700`, `Red-800`, `Red-900`, `Red-950` - -### SakuraPink - -![SakuraPink Colors](../assets/blocks/docs-blocks-colors-sakurapink.png) - -`SakuraPink-50`, `SakuraPink-100`, `SakuraPink-200`, `SakuraPink-300`, `SakuraPink-400`, `SakuraPink-500`, `SakuraPink-600`, `SakuraPink-700`, `SakuraPink-800`, `SakuraPink-900`, `SakuraPink-950` - -### BerryPurple - -![BerryPurple Colors](../assets/blocks/docs-blocks-colors-berrypurple.png) -`BerryPurple-50`, `BerryPurple-100`, `BerryPurple-200`, `BerryPurple-300`, `BerryPurple-400`, `BerryPurple-500`, `BerryPurple-600`, `BerryPurple-700`, `BerryPurple-800`, `BerryPurple-900`, `BerryPurple-950` - -### Periwinkle - -![Periwinkle Colors](../assets/blocks/docs-blocks-colors-periwinkle.png) -`Periwinkle-50`, `Periwinkle-100`, `Periwinkle-200`, `Periwinkle-300`, `Periwinkle-400`, `Periwinkle-500`, `Periwinkle-600`, `Periwinkle-700`, `Periwinkle-800`, `Periwinkle-900`, `Periwinkle-950` - -### AlienBlue - -![AlienBlue Colors](../assets/blocks/docs-blocks-colors-alienblue.png) -`AlienBlue-50`, `AlienBlue-100`, `AlienBlue-200`, `AlienBlue-300`, `AlienBlue-400`, `AlienBlue-500`, `AlienBlue-600`, `AlienBlue-700`, `AlienBlue-800`, `AlienBlue-900`, `AlienBlue-950` - -### LightBlue - -![LightBlue Colors](../assets/blocks/docs-blocks-colors-lightblue.png) -`LightBlue-50`, `LightBlue-100`, `LightBlue-200`, `LightBlue-300`, `LightBlue-400`, `LightBlue-500`, `LightBlue-600`, `LightBlue-700`, `LightBlue-800`, `LightBlue-900`, `LightBlue-950` - -### MintGreen - -![MintGreen Colors](../assets/blocks/docs-blocks-colors-mintgreen.png) -`MintGreen-50`, `MintGreen-100`, `MintGreen-200`, `MintGreen-300`, `MintGreen-400`, `MintGreen-500`, `MintGreen-600`, `MintGreen-700`, `MintGreen-800`, `MintGreen-900`, `MintGreen-950` - -### KiwiGreen - -![KiwiGreen Colors](../assets/blocks/docs-blocks-colors-kiwigreen.png) -`KiwiGreen-50`, `KiwiGreen-100`, `KiwiGreen-200`, `KiwiGreen-300`, `KiwiGreen-400`, `KiwiGreen-500`, `KiwiGreen-600`, `KiwiGreen-700`, `KiwiGreen-800`, `KiwiGreen-900`, `KiwiGreen-950` - -### Lime - -![Lime Colors](../assets/blocks/docs-blocks-colors-lime.png) -`Lime-50`, `Lime-100`, `Lime-200`, `Lime-300`, `Lime-400`, `Lime-500`, `Lime-600`, `Lime-700`, `Lime-800`, `Lime-900`, `Lime-950` - -### Yellow - -![Yellow Colors](../assets/blocks/docs-blocks-colors-yellow.png) -`Yellow-50`, `Yellow-100`, `Yellow-200`, `Yellow-300`, `Yellow-400`, `Yellow-500`, `Yellow-600`, `Yellow-700`, `Yellow-800`, `Yellow-900`, `Yellow-950` - -### YellowOrange - -![YellowOrange Colors](../assets/blocks/docs-blocks-colors-yelloworange.png) -`YellowOrange-50`, `YellowOrange-100`, `YellowOrange-200`, `YellowOrange-300`, `YellowOrange-400`, `YellowOrange-500`, `YellowOrange-600`, `YellowOrange-700`, `YellowOrange-800`, `YellowOrange-900`, `YellowOrange-950` - -### Brown - -![Brown Colors](../assets/blocks/docs-blocks-colors-brown.png) -`Brown-50`, `Brown-100`, `Brown-200`, `Brown-300`, `Brown-400`, `Brown-500`, `Brown-600`, `Brown-700`, `Brown-800`, `Brown-900`, `Brown-950` diff --git a/versioned_docs/version-0.12/blocks/icon.mdx b/versioned_docs/version-0.12/blocks/icon.mdx deleted file mode 100644 index 63637ea7..00000000 --- a/versioned_docs/version-0.12/blocks/icon.mdx +++ /dev/null @@ -1,1426 +0,0 @@ -# Icons - -import IconTile from '@site/src/components/IconTile'; -import IconTileGrid from '@site/src/components/IconTileGrid'; - -import Icon3rdPartyFill from '../assets/icons/icon-3rd-party-fill.svg'; -import Icon3rdPartyOutline from '../assets/icons/icon-3rd-party-outline.svg'; -import IconActivityFill from '../assets/icons/icon-activity-fill.svg'; -import IconActivityOutline from '../assets/icons/icon-activity-outline.svg'; -import IconAddEmojiFill from '../assets/icons/icon-add-emoji-fill.svg'; -import IconAddEmojiOutline from '../assets/icons/icon-add-emoji-outline.svg'; -import IconAddFill24 from '../assets/icons/icon-add-fill-24.svg'; -import IconAddFill from '../assets/icons/icon-add-fill.svg'; -import IconAddMediaFill from '../assets/icons/icon-add-media-fill.svg'; -import IconAddMediaOutline from '../assets/icons/icon-add-media-outline.svg'; -import IconAddOutline24 from '../assets/icons/icon-add-outline-24.svg'; -import IconAddOutline from '../assets/icons/icon-add-outline.svg'; -import IconAddToFeedFill from '../assets/icons/icon-add-to-feed-fill.svg'; -import IconAddToFeedOutline from '../assets/icons/icon-add-to-feed-outline.svg'; -import IconAdminFill from '../assets/icons/icon-admin-fill.svg'; -import IconAdminOutline from '../assets/icons/icon-admin-outline.svg'; -import IconAdsFill from '../assets/icons/icon-ads-fill.svg'; -import IconAdsOutline from '../assets/icons/icon-ads-outline.svg'; -import IconAlignCenterFill from '../assets/icons/icon-align-center-fill.svg'; -import IconAlignCenterOutline from '../assets/icons/icon-align-center-outline.svg'; -import IconAlignLeftFill from '../assets/icons/icon-align-left-fill.svg'; -import IconAlignLeftOutline from '../assets/icons/icon-align-left-outline.svg'; -import IconAlignRightFill from '../assets/icons/icon-align-right-fill.svg'; -import IconAlignRightOutline from '../assets/icons/icon-align-right-outline.svg'; -import IconAllFill from '../assets/icons/icon-all-fill.svg'; -import IconAllOutline from '../assets/icons/icon-all-outline.svg'; -import IconAppearanceFill from '../assets/icons/icon-appearance-fill.svg'; -import IconAppearanceOutline from '../assets/icons/icon-appearance-outline.svg'; -import IconApproveFill from '../assets/icons/icon-approve-fill.svg'; -import IconApproveOutline from '../assets/icons/icon-approve-outline.svg'; -import IconArchivedFill from '../assets/icons/icon-archived-fill.svg'; -import IconArchivedOutline from '../assets/icons/icon-archived-outline.svg'; -import IconAspectRatioFill from '../assets/icons/icon-aspect-ratio-fill.svg'; -import IconAspectRatioOutline from '../assets/icons/icon-aspect-ratio-outline.svg'; -import IconAspectRectangleFill from '../assets/icons/icon-aspect-rectangle-fill.svg'; -import IconAspectRectangleOutline from '../assets/icons/icon-aspect-rectangle-outline.svg'; -import IconAttachFill from '../assets/icons/icon-attach-fill.svg'; -import IconAttachOutline from '../assets/icons/icon-attach-outline.svg'; -import IconAudienceFill from '../assets/icons/icon-audience-fill.svg'; -import IconAudienceOutline from '../assets/icons/icon-audience-outline.svg'; -import IconAudioFill from '../assets/icons/icon-audio-fill.svg'; -import IconAudioOutline from '../assets/icons/icon-audio-outline.svg'; -import IconAuthorFill from '../assets/icons/icon-author-fill.svg'; -import IconAuthorOutline from '../assets/icons/icon-author-outline.svg'; -import IconAutomodFill from '../assets/icons/icon-automod-fill.svg'; -import IconAutomodOutline from '../assets/icons/icon-automod-outline.svg'; -import IconAvatarStyleFill from '../assets/icons/icon-avatar-style-fill.svg'; -import IconAvatarStyleOutline from '../assets/icons/icon-avatar-style-outline.svg'; -import IconAwardFill from '../assets/icons/icon-award-fill.svg'; -import IconAwardOutline from '../assets/icons/icon-award-outline.svg'; -import IconBackFill24 from '../assets/icons/icon-back-fill-24.svg'; -import IconBackFill from '../assets/icons/icon-back-fill.svg'; -import IconBackOutline24 from '../assets/icons/icon-back-outline-24.svg'; -import IconBackOutline from '../assets/icons/icon-back-outline.svg'; -import IconBackupFill from '../assets/icons/icon-backup-fill.svg'; -import IconBackupOutline from '../assets/icons/icon-backup-outline.svg'; -import IconBanFill from '../assets/icons/icon-ban-fill.svg'; -import IconBanOutline from '../assets/icons/icon-ban-outline.svg'; -import IconBasketballColor24 from '../assets/icons/icon-basketball-color-24.svg'; -import IconBasketballFill24 from '../assets/icons/icon-basketball-fill-24.svg'; -import IconBasketballOutline24 from '../assets/icons/icon-basketball-outline-24.svg'; -import IconBestFill from '../assets/icons/icon-best-fill.svg'; -import IconBestOutline from '../assets/icons/icon-best-outline.svg'; -import IconBetaBinocularsFill from '../assets/icons/icon-beta-binoculars-fill.svg'; -import IconBetaBinocularsOutline from '../assets/icons/icon-beta-binoculars-outline.svg'; -import IconBetaCaretUpdownFill from '../assets/icons/icon-beta-caret-updown-fill.svg'; -import IconBetaCaretUpdownOutline from '../assets/icons/icon-beta-caret-updown-outline.svg'; -import IconBetaLatestFill from '../assets/icons/icon-beta-latest-fill.svg'; -import IconBetaLatestOutline from '../assets/icons/icon-beta-latest-outline.svg'; -import IconBetaPlanetFill from '../assets/icons/icon-beta-planet-fill.svg'; -import IconBetaPlanetOutline from '../assets/icons/icon-beta-planet-outline.svg'; -import IconBetaTalk01Outline from '../assets/icons/icon-beta-talk-01-outline.svg'; -import IconBetaTalk02Outline from '../assets/icons/icon-beta-talk-02-outline.svg'; -import IconBetaTalkAddFill from '../assets/icons/icon-beta-talk-add-fill.svg'; -import IconBetaTalkAddOutline from '../assets/icons/icon-beta-talk-add-outline.svg'; -import IconBetaTelescopeFill from '../assets/icons/icon-beta-telescope-fill.svg'; -import IconBetaTelescopeOutline from '../assets/icons/icon-beta-telescope-outline.svg'; -import IconBlockFill from '../assets/icons/icon-block-fill.svg'; -import IconBlockOutline from '../assets/icons/icon-block-outline.svg'; -import IconBlockchainFill from '../assets/icons/icon-blockchain-fill.svg'; -import IconBlockchainOutline from '../assets/icons/icon-blockchain-outline.svg'; -import IconBoldFill from '../assets/icons/icon-bold-fill.svg'; -import IconBoldOutline from '../assets/icons/icon-bold-outline.svg'; -import IconBotFill from '../assets/icons/icon-bot-fill.svg'; -import IconBotOutline from '../assets/icons/icon-bot-outline.svg'; -import IconBounceFill from '../assets/icons/icon-bounce-fill.svg'; -import IconBounceOutline from '../assets/icons/icon-bounce-outline.svg'; -import IconBrandAwarenessFill from '../assets/icons/icon-brand-awareness-fill.svg'; -import IconBrandAwarenessOutline from '../assets/icons/icon-brand-awareness-outline.svg'; -import IconBrowseFill from '../assets/icons/icon-browse-fill.svg'; -import IconBrowseOutline from '../assets/icons/icon-browse-outline.svg'; -import IconBrowserFill from '../assets/icons/icon-browser-fill.svg'; -import IconBrowserOutline from '../assets/icons/icon-browser-outline.svg'; -import IconCakeFill from '../assets/icons/icon-cake-fill.svg'; -import IconCakeOutline from '../assets/icons/icon-cake-outline.svg'; -import IconCalendarFill from '../assets/icons/icon-calendar-fill.svg'; -import IconCalendarOutline from '../assets/icons/icon-calendar-outline.svg'; -import IconCameraFill24 from '../assets/icons/icon-camera-fill-24.svg'; -import IconCameraFill from '../assets/icons/icon-camera-fill.svg'; -import IconCameraOutline24 from '../assets/icons/icon-camera-outline-24.svg'; -import IconCameraOutline from '../assets/icons/icon-camera-outline.svg'; -import IconCampaignFill from '../assets/icons/icon-campaign-fill.svg'; -import IconCampaignOutline from '../assets/icons/icon-campaign-outline.svg'; -import IconCaretDownFill from '../assets/icons/icon-caret-down-fill.svg'; -import IconCaretDownOutline from '../assets/icons/icon-caret-down-outline.svg'; -import IconCaretLeftFill from '../assets/icons/icon-caret-left-fill.svg'; -import IconCaretLeftOutline from '../assets/icons/icon-caret-left-outline.svg'; -import IconCaretRightFill from '../assets/icons/icon-caret-right-fill.svg'; -import IconCaretRightOutline from '../assets/icons/icon-caret-right-outline.svg'; -import IconCaretUpFill from '../assets/icons/icon-caret-up-fill.svg'; -import IconCaretUpOutline from '../assets/icons/icon-caret-up-outline.svg'; -import IconChatFill24 from '../assets/icons/icon-chat-fill-24.svg'; -import IconChatFill from '../assets/icons/icon-chat-fill.svg'; -import IconChatGroupFill from '../assets/icons/icon-chat-group-fill.svg'; -import IconChatGroupOutline from '../assets/icons/icon-chat-group-outline.svg'; -import IconChatNewFill from '../assets/icons/icon-chat-new-fill.svg'; -import IconChatNewOutline from '../assets/icons/icon-chat-new-outline.svg'; -import IconChatOutline24 from '../assets/icons/icon-chat-outline-24.svg'; -import IconChatOutline from '../assets/icons/icon-chat-outline.svg'; -import IconChatPrivateFill from '../assets/icons/icon-chat-private-fill.svg'; -import IconChatPrivateOutline from '../assets/icons/icon-chat-private-outline.svg'; -import IconCheckboxDismissFill from '../assets/icons/icon-checkbox-dismiss-fill.svg'; -import IconCheckboxDismissOutline from '../assets/icons/icon-checkbox-dismiss-outline.svg'; -import IconCheckboxFill from '../assets/icons/icon-checkbox-fill.svg'; -import IconCheckboxOutline from '../assets/icons/icon-checkbox-outline.svg'; -import IconCheckmarkFill from '../assets/icons/icon-checkmark-fill.svg'; -import IconCheckmarkOutline from '../assets/icons/icon-checkmark-outline.svg'; -import IconChromeFill from '../assets/icons/icon-chrome-fill.svg'; -import IconChromeOutline from '../assets/icons/icon-chrome-outline.svg'; -import IconClearFill from '../assets/icons/icon-clear-fill.svg'; -import IconClearOutline from '../assets/icons/icon-clear-outline.svg'; -import IconClientListFill from '../assets/icons/icon-client-list-fill.svg'; -import IconClientListOutline from '../assets/icons/icon-client-list-outline.svg'; -import IconCloseFill from '../assets/icons/icon-close-fill.svg'; -import IconCloseOutline from '../assets/icons/icon-close-outline.svg'; -import IconClosedCaptioningFill from '../assets/icons/icon-closed-captioning-fill.svg'; -import IconClosedCaptioningOutline from '../assets/icons/icon-closed-captioning-outline.svg'; -import IconCodeBlockFill from '../assets/icons/icon-code-block-fill.svg'; -import IconCodeBlockOutline from '../assets/icons/icon-code-block-outline.svg'; -import IconCodeInlineFill from '../assets/icons/icon-code-inline-fill.svg'; -import IconCodeInlineOutline from '../assets/icons/icon-code-inline-outline.svg'; -import IconCoinsColorOld from '../assets/icons/icon-coins-color-old.svg'; -import IconCoinsColor from '../assets/icons/icon-coins-color.svg'; -import IconCoinsFill from '../assets/icons/icon-coins-fill.svg'; -import IconCoinsOutline from '../assets/icons/icon-coins-outline.svg'; -import IconCollapseLeftFill from '../assets/icons/icon-collapse-left-fill.svg'; -import IconCollapseLeftOutline from '../assets/icons/icon-collapse-left-outline.svg'; -import IconCollapseRightFill from '../assets/icons/icon-collapse-right-fill.svg'; -import IconCollapseRightOutline from '../assets/icons/icon-collapse-right-outline.svg'; -import IconCollectibleExpressionsFill from '../assets/icons/icon-collectible-expressions-fill.svg'; -import IconCollectibleExpressionsOutline from '../assets/icons/icon-collectible-expressions-outline.svg'; -import IconCollectionFill from '../assets/icons/icon-collection-fill.svg'; -import IconCollectionOutline from '../assets/icons/icon-collection-outline.svg'; -import IconCommentFill from '../assets/icons/icon-comment-fill.svg'; -import IconCommentOutline from '../assets/icons/icon-comment-outline.svg'; -import IconCommentsFill from '../assets/icons/icon-comments-fill.svg'; -import IconCommentsOutline from '../assets/icons/icon-comments-outline.svg'; -import IconCommunitiesFill from '../assets/icons/icon-communities-fill.svg'; -import IconCommunitiesOutline from '../assets/icons/icon-communities-outline.svg'; -import IconCommunityFill from '../assets/icons/icon-community-fill.svg'; -import IconCommunityOutline from '../assets/icons/icon-community-outline.svg'; -import IconConfidenceFill from '../assets/icons/icon-confidence-fill.svg'; -import IconConfidenceOutline from '../assets/icons/icon-confidence-outline.svg'; -import IconContestFill from '../assets/icons/icon-contest-fill.svg'; -import IconContestOutline from '../assets/icons/icon-contest-outline.svg'; -import IconControversialFill from '../assets/icons/icon-controversial-fill.svg'; -import IconControversialOutline from '../assets/icons/icon-controversial-outline.svg'; -import IconConversionFill from '../assets/icons/icon-conversion-fill.svg'; -import IconConversionOutline from '../assets/icons/icon-conversion-outline.svg'; -import IconCopyClipboardFill from '../assets/icons/icon-copy-clipboard-fill.svg'; -import IconCopyClipboardOutline from '../assets/icons/icon-copy-clipboard-outline.svg'; -import IconCricketFillOutline24 from '../assets/icons/icon-cricket-fill-outline-24.svg'; -import IconCricketOutline24 from '../assets/icons/icon-cricket-outline-24.svg'; -import IconCropFill from '../assets/icons/icon-crop-fill.svg'; -import IconCropOutline from '../assets/icons/icon-crop-outline.svg'; -import IconCrosspostFill from '../assets/icons/icon-crosspost-fill.svg'; -import IconCrosspostOutline from '../assets/icons/icon-crosspost-outline.svg'; -import IconCrowdControlFill from '../assets/icons/icon-crowd-control-fill.svg'; -import IconCrowdControlOutline from '../assets/icons/icon-crowd-control-outline.svg'; -import IconCustomFeedFill from '../assets/icons/icon-custom-feed-fill.svg'; -import IconCustomFeedOutline from '../assets/icons/icon-custom-feed-outline.svg'; -import IconCustomizeFill from '../assets/icons/icon-customize-fill.svg'; -import IconCustomizeOutline from '../assets/icons/icon-customize-outline.svg'; -import IconDashboardFill from '../assets/icons/icon-dashboard-fill.svg'; -import IconDashboardOutline from '../assets/icons/icon-dashboard-outline.svg'; -import IconDayFill from '../assets/icons/icon-day-fill.svg'; -import IconDayOutline from '../assets/icons/icon-day-outline.svg'; -import IconDeleteFill from '../assets/icons/icon-delete-fill.svg'; -import IconDeleteOutline from '../assets/icons/icon-delete-outline.svg'; -import IconDiscoverFill24 from '../assets/icons/icon-discover-fill-24.svg'; -import IconDiscoverFill from '../assets/icons/icon-discover-fill.svg'; -import IconDiscoverOutline24 from '../assets/icons/icon-discover-outline-24.svg'; -import IconDiscoverOutline from '../assets/icons/icon-discover-outline.svg'; -import IconDismissAllFill from '../assets/icons/icon-dismiss-all-fill.svg'; -import IconDismissAllOutline from '../assets/icons/icon-dismiss-all-outline.svg'; -import IconDistinguishFill from '../assets/icons/icon-distinguish-fill.svg'; -import IconDistinguishOutline from '../assets/icons/icon-distinguish-outline.svg'; -import IconDownArrowOutline from '../assets/icons/icon-down-arrow-outline.svg'; -import IconDownFill from '../assets/icons/icon-down-fill.svg'; -import IconDownOutline from '../assets/icons/icon-down-outline.svg'; -import IconDownloadFill from '../assets/icons/icon-download-fill.svg'; -import IconDownloadOutline from '../assets/icons/icon-download-outline.svg'; -import IconDownvoteFillMask from '../assets/icons/icon-downvote-fill-mask.svg'; -import IconDownvoteFill from '../assets/icons/icon-downvote-fill.svg'; -import IconDownvoteOffsetmask from '../assets/icons/icon-downvote-offsetmask.svg'; -import IconDownvoteOutline from '../assets/icons/icon-downvote-outline.svg'; -import IconDownvotesFill from '../assets/icons/icon-downvotes-fill.svg'; -import IconDownvotesOutline from '../assets/icons/icon-downvotes-outline.svg'; -import IconDragOutline from '../assets/icons/icon-drag-outline.svg'; -import IconDrugsFill from '../assets/icons/icon-drugs-fill.svg'; -import IconDrugsOutline from '../assets/icons/icon-drugs-outline.svg'; -import IconDuplicateFill from '../assets/icons/icon-duplicate-fill.svg'; -import IconDuplicateOutline from '../assets/icons/icon-duplicate-outline.svg'; -import IconEditFill from '../assets/icons/icon-edit-fill.svg'; -import IconEditOutline from '../assets/icons/icon-edit-outline.svg'; -import IconEffectFill from '../assets/icons/icon-effect-fill.svg'; -import IconEffectOutline from '../assets/icons/icon-effect-outline.svg'; -import IconEmbedFill from '../assets/icons/icon-embed-fill.svg'; -import IconEmbedOutline from '../assets/icons/icon-embed-outline.svg'; -import IconEmojiFill from '../assets/icons/icon-emoji-fill.svg'; -import IconEmojiOutline from '../assets/icons/icon-emoji-outline.svg'; -import IconEndLiveChatFill from '../assets/icons/icon-end-live-chat-fill.svg'; -import IconEndLiveChatOutline from '../assets/icons/icon-end-live-chat-outline.svg'; -import IconErrorFill from '../assets/icons/icon-error-fill.svg'; -import IconErrorOutline from '../assets/icons/icon-error-outline.svg'; -import IconExpandLeftFill from '../assets/icons/icon-expand-left-fill.svg'; -import IconExpandLeftOutline from '../assets/icons/icon-expand-left-outline.svg'; -import IconExpandRightFill from '../assets/icons/icon-expand-right-fill.svg'; -import IconExpandRightOutline from '../assets/icons/icon-expand-right-outline.svg'; -import IconExternalFill from '../assets/icons/icon-external-fill.svg'; -import IconExternalOutline from '../assets/icons/icon-external-outline.svg'; -import IconFeedVideoFill from '../assets/icons/icon-feed-video-fill.svg'; -import IconFeedVideoOutline from '../assets/icons/icon-feed-video-outline.svg'; -import IconFilterFill24 from '../assets/icons/icon-filter-fill-24.svg'; -import IconFilterFill from '../assets/icons/icon-filter-fill.svg'; -import IconFilterOutline24 from '../assets/icons/icon-filter-outline-24.svg'; -import IconFilterOutline from '../assets/icons/icon-filter-outline.svg'; -import IconFootballFill24 from '../assets/icons/icon-football-fill-24.svg'; -import IconFootballOutline24 from '../assets/icons/icon-football-outline-24.svg'; -import IconFormatFill from '../assets/icons/icon-format-fill.svg'; -import IconFormatOutline from '../assets/icons/icon-format-outline.svg'; -import IconForwardFill from '../assets/icons/icon-forward-fill.svg'; -import IconForwardOutline from '../assets/icons/icon-forward-outline.svg'; -import IconFunnelFill from '../assets/icons/icon-funnel-fill.svg'; -import IconFunnelOutline from '../assets/icons/icon-funnel-outline.svg'; -import IconGifPostFill from '../assets/icons/icon-gif-post-fill.svg'; -import IconGifPostOutline from '../assets/icons/icon-gif-post-outline.svg'; -import IconHashtagFill from '../assets/icons/icon-hashtag-fill.svg'; -import IconHashtagOutline from '../assets/icons/icon-hashtag-outline.svg'; -import IconHeartFill from '../assets/icons/icon-heart-fill.svg'; -import IconHeartOutline from '../assets/icons/icon-heart-outline.svg'; -import IconHelpFill from '../assets/icons/icon-help-fill.svg'; -import IconHelpOutline from '../assets/icons/icon-help-outline.svg'; -import IconHideFill from '../assets/icons/icon-hide-fill.svg'; -import IconHideOutline from '../assets/icons/icon-hide-outline.svg'; -import IconHistoryFill from '../assets/icons/icon-history-fill.svg'; -import IconHistoryOutline from '../assets/icons/icon-history-outline.svg'; -import IconHockeyFill24 from '../assets/icons/icon-hockey-fill-24.svg'; -import IconHockeyOutline24 from '../assets/icons/icon-hockey-outline-24.svg'; -import IconHomeFill24 from '../assets/icons/icon-home-fill-24.svg'; -import IconHomeFill from '../assets/icons/icon-home-fill.svg'; -import IconHomeOutline24 from '../assets/icons/icon-home-outline-24.svg'; -import IconHomeOutline from '../assets/icons/icon-home-outline.svg'; -import IconHotFill from '../assets/icons/icon-hot-fill.svg'; -import IconHotOutline from '../assets/icons/icon-hot-outline.svg'; -import IconIgnoreReportsFill from '../assets/icons/icon-ignore-reports-fill.svg'; -import IconIgnoreReportsOutline from '../assets/icons/icon-ignore-reports-outline.svg'; -import IconImagePostFill from '../assets/icons/icon-image-post-fill.svg'; -import IconImagePostOutline from '../assets/icons/icon-image-post-outline.svg'; -import IconInboxFill from '../assets/icons/icon-inbox-fill.svg'; -import IconInboxOutline from '../assets/icons/icon-inbox-outline.svg'; -import IconIndiaIndependence24Color from '../assets/icons/icon-india-independence-24-color.svg'; -import IconIndiaIndependenceColor from '../assets/icons/icon-india-independence-color.svg'; -import IconIndiaIndependenceOutline24 from '../assets/icons/icon-india-independence-outline-24.svg'; -import IconInfoFill from '../assets/icons/icon-info-fill.svg'; -import IconInfoOutline from '../assets/icons/icon-info-outline.svg'; -import IconInviteFill from '../assets/icons/icon-invite-fill.svg'; -import IconInviteOutline from '../assets/icons/icon-invite-outline.svg'; -import IconItalicFill from '../assets/icons/icon-italic-fill.svg'; -import IconItalicOutline from '../assets/icons/icon-italic-outline.svg'; -import IconJoinFill from '../assets/icons/icon-join-fill.svg'; -import IconJoinOutline from '../assets/icons/icon-join-outline.svg'; -import IconJoinedFill from '../assets/icons/icon-joined-fill.svg'; -import IconJoinedOutline from '../assets/icons/icon-joined-outline.svg'; -import IconJumpDownFill from '../assets/icons/icon-jump-down-fill.svg'; -import IconJumpDownOutline from '../assets/icons/icon-jump-down-outline.svg'; -import IconJumpUpFill from '../assets/icons/icon-jump-up-fill.svg'; -import IconJumpUpOutline from '../assets/icons/icon-jump-up-outline.svg'; -import IconKarmaFill from '../assets/icons/icon-karma-fill.svg'; -import IconKarmaOutline from '../assets/icons/icon-karma-outline.svg'; -import IconKeyboardFill from '../assets/icons/icon-keyboard-fill.svg'; -import IconKeyboardOutline from '../assets/icons/icon-keyboard-outline.svg'; -import IconKickFill from '../assets/icons/icon-kick-fill.svg'; -import IconKickOutline from '../assets/icons/icon-kick-outline.svg'; -import IconLanguageFill from '../assets/icons/icon-language-fill.svg'; -import IconLanguageOutline from '../assets/icons/icon-language-outline.svg'; -import IconLeaveFill from '../assets/icons/icon-leave-fill.svg'; -import IconLeaveOutline from '../assets/icons/icon-leave-outline.svg'; -import IconLeftFill24 from '../assets/icons/icon-left-fill-24.svg'; -import IconLeftFill from '../assets/icons/icon-left-fill.svg'; -import IconLeftOutline24 from '../assets/icons/icon-left-outline-24.svg'; -import IconLeftOutline from '../assets/icons/icon-left-outline.svg'; -import IconLinkFill from '../assets/icons/icon-link-fill.svg'; -import IconLinkOutline from '../assets/icons/icon-link-outline.svg'; -import IconLinkPostFill from '../assets/icons/icon-link-post-fill.svg'; -import IconLinkPostOutline from '../assets/icons/icon-link-post-outline.svg'; -import IconListBulletedFill from '../assets/icons/icon-list-bulleted-fill.svg'; -import IconListBulletedOutline from '../assets/icons/icon-list-bulleted-outline.svg'; -import IconListNumberedFill from '../assets/icons/icon-list-numbered-fill.svg'; -import IconListNumberedOutline from '../assets/icons/icon-list-numbered-outline.svg'; -import IconLiveChatFill from '../assets/icons/icon-live-chat-fill.svg'; -import IconLiveChatOutline from '../assets/icons/icon-live-chat-outline.svg'; -import IconLiveFill from '../assets/icons/icon-live-fill.svg'; -import IconLiveOutline from '../assets/icons/icon-live-outline.svg'; -import IconLoadOutline from '../assets/icons/icon-load-outline.svg'; -import IconLocationFill from '../assets/icons/icon-location-fill.svg'; -import IconLocationOutline from '../assets/icons/icon-location-outline.svg'; -import IconLockFill from '../assets/icons/icon-lock-fill.svg'; -import IconLockOutline from '../assets/icons/icon-lock-outline.svg'; -import IconLogoutFill from '../assets/icons/icon-logout-fill.svg'; -import IconLogoutOutline from '../assets/icons/icon-logout-outline.svg'; -import IconLoopFill from '../assets/icons/icon-loop-fill.svg'; -import IconLoopOutline from '../assets/icons/icon-loop-outline.svg'; -import IconMacroFill from '../assets/icons/icon-macro-fill.svg'; -import IconMacroOutline from '../assets/icons/icon-macro-outline.svg'; -import IconMarkReadFill from '../assets/icons/icon-mark-read-fill.svg'; -import IconMarkReadOutline from '../assets/icons/icon-mark-read-outline.svg'; -import IconMarketplaceFill from '../assets/icons/icon-marketplace-fill.svg'; -import IconMarketplaceOutline from '../assets/icons/icon-marketplace-outline.svg'; -import IconMaskFill from '../assets/icons/icon-mask-fill.svg'; -import IconMaskOutline from '../assets/icons/icon-mask-outline.svg'; -import IconMediaGalleryFill from '../assets/icons/icon-media-gallery-fill.svg'; -import IconMediaGalleryOutline from '../assets/icons/icon-media-gallery-outline.svg'; -import IconMemeFill from '../assets/icons/icon-meme-fill.svg'; -import IconMemeOutline from '../assets/icons/icon-meme-outline.svg'; -import IconMenuFill24 from '../assets/icons/icon-menu-fill-24.svg'; -import IconMenuFill from '../assets/icons/icon-menu-fill.svg'; -import IconMenuOutline24 from '../assets/icons/icon-menu-outline-24.svg'; -import IconMenuOutline from '../assets/icons/icon-menu-outline.svg'; -import IconMessageFill from '../assets/icons/icon-message-fill.svg'; -import IconMessageOutline from '../assets/icons/icon-message-outline.svg'; -import IconMicFill from '../assets/icons/icon-mic-fill.svg'; -import IconMicMuteFill from '../assets/icons/icon-mic-mute-fill.svg'; -import IconMicMuteOutline from '../assets/icons/icon-mic-mute-outline.svg'; -import IconMicOutline from '../assets/icons/icon-mic-outline.svg'; -import IconModFill from '../assets/icons/icon-mod-fill.svg'; -import IconModMailFill from '../assets/icons/icon-mod-mail-fill.svg'; -import IconModMailOutline from '../assets/icons/icon-mod-mail-outline.svg'; -import IconModModeFill from '../assets/icons/icon-mod-mode-fill.svg'; -import IconModModeOutline from '../assets/icons/icon-mod-mode-outline.svg'; -import IconModMuteFill from '../assets/icons/icon-mod-mute-fill.svg'; -import IconModMuteOutline from '../assets/icons/icon-mod-mute-outline.svg'; -import IconModOutline from '../assets/icons/icon-mod-outline.svg'; -import IconModOverflowFill from '../assets/icons/icon-mod-overflow-fill.svg'; -import IconModOverflowOutline from '../assets/icons/icon-mod-overflow-outline.svg'; -import IconModQueueFill from '../assets/icons/icon-mod-queue-fill.svg'; -import IconModQueueOutline from '../assets/icons/icon-mod-queue-outline.svg'; -import IconModUnmuteFill from '../assets/icons/icon-mod-unmute-fill.svg'; -import IconModUnmuteOutline from '../assets/icons/icon-mod-unmute-outline.svg'; -import IconMusicFill from '../assets/icons/icon-music-fill.svg'; -import IconMusicOutline from '../assets/icons/icon-music-outline.svg'; -import IconMuteFill from '../assets/icons/icon-mute-fill.svg'; -import IconMuteOutline from '../assets/icons/icon-mute-outline.svg'; -import IconNewFill from '../assets/icons/icon-new-fill.svg'; -import IconNewOutline from '../assets/icons/icon-new-outline.svg'; -import IconNightFill from '../assets/icons/icon-night-fill.svg'; -import IconNightOutline from '../assets/icons/icon-night-outline.svg'; -import IconNotificationFill24 from '../assets/icons/icon-notification-fill-24.svg'; -import IconNotificationFill from '../assets/icons/icon-notification-fill.svg'; -import IconNotificationFrequentFill from '../assets/icons/icon-notification-frequent-fill.svg'; -import IconNotificationFrequentOutline from '../assets/icons/icon-notification-frequent-outline.svg'; -import IconNotificationOffFill from '../assets/icons/icon-notification-off-fill.svg'; -import IconNotificationOffOutline from '../assets/icons/icon-notification-off-outline.svg'; -import IconNotificationOutline24 from '../assets/icons/icon-notification-outline-24.svg'; -import IconNotificationOutline from '../assets/icons/icon-notification-outline.svg'; -import IconNsfwFill from '../assets/icons/icon-nsfw-fill.svg'; -import IconNsfwLanguageFill from '../assets/icons/icon-nsfw-language-fill.svg'; -import IconNsfwLanguageOutline from '../assets/icons/icon-nsfw-language-outline.svg'; -import IconNsfwOutline from '../assets/icons/icon-nsfw-outline.svg'; -import IconNsfwViolenceFill from '../assets/icons/icon-nsfw-violence-fill.svg'; -import IconNsfwViolenceOutline from '../assets/icons/icon-nsfw-violence-outline.svg'; -import IconOfficialFill from '../assets/icons/icon-official-fill.svg'; -import IconOfficialOutline from '../assets/icons/icon-official-outline.svg'; -import IconOriginalFill from '../assets/icons/icon-original-fill.svg'; -import IconOriginalOutline from '../assets/icons/icon-original-outline.svg'; -import IconOverflowCaretFill from '../assets/icons/icon-overflow-caret-fill.svg'; -import IconOverflowCaretOutline from '../assets/icons/icon-overflow-caret-outline.svg'; -import IconOverflowHorizontalFill24 from '../assets/icons/icon-overflow-horizontal-fill-24.svg'; -import IconOverflowHorizontalFill from '../assets/icons/icon-overflow-horizontal-fill.svg'; -import IconOverflowHorizontalOutline24 from '../assets/icons/icon-overflow-horizontal-outline-24.svg'; -import IconOverflowHorizontalOutline from '../assets/icons/icon-overflow-horizontal-outline.svg'; -import IconOverflowVerticalFill from '../assets/icons/icon-overflow-vertical-fill.svg'; -import IconOverflowVerticalOutline from '../assets/icons/icon-overflow-vertical-outline.svg'; -import IconPauseFill from '../assets/icons/icon-pause-fill.svg'; -import IconPauseOutline from '../assets/icons/icon-pause-outline.svg'; -import IconPaymentFill from '../assets/icons/icon-payment-fill.svg'; -import IconPaymentOutline from '../assets/icons/icon-payment-outline.svg'; -import IconPeaceFill from '../assets/icons/icon-peace-fill.svg'; -import IconPeaceOutline from '../assets/icons/icon-peace-outline.svg'; -import IconPendingPostsFill from '../assets/icons/icon-pending-posts-fill.svg'; -import IconPendingPostsOutline from '../assets/icons/icon-pending-posts-outline.svg'; -import IconPhoneFill from '../assets/icons/icon-phone-fill.svg'; -import IconPhoneOutline from '../assets/icons/icon-phone-outline.svg'; -import IconPinFill from '../assets/icons/icon-pin-fill.svg'; -import IconPinOutline from '../assets/icons/icon-pin-outline.svg'; -import IconPlayFill from '../assets/icons/icon-play-fill.svg'; -import IconPlayOutline from '../assets/icons/icon-play-outline.svg'; -import IconPollPostFill from '../assets/icons/icon-poll-post-fill.svg'; -import IconPollPostOutline from '../assets/icons/icon-poll-post-outline.svg'; -import IconPopularFill from '../assets/icons/icon-popular-fill.svg'; -import IconPopularOutline from '../assets/icons/icon-popular-outline.svg'; -import IconPostsFill from '../assets/icons/icon-posts-fill.svg'; -import IconPostsOutline from '../assets/icons/icon-posts-outline.svg'; -import IconPowerupColor from '../assets/icons/icon-powerup-color.svg'; -import IconPowerupFillColor from '../assets/icons/icon-powerup-fill-color.svg'; -import IconPowerupFill from '../assets/icons/icon-powerup-fill.svg'; -import IconPowerupOutline from '../assets/icons/icon-powerup-outline.svg'; -import IconPredictionsFill from '../assets/icons/icon-predictions-fill.svg'; -import IconPredictionsOutline from '../assets/icons/icon-predictions-outline.svg'; -import IconPremiumFill from '../assets/icons/icon-premium-fill.svg'; -import IconPremiumOutline from '../assets/icons/icon-premium-outline.svg'; -import IconPrivacyFill from '../assets/icons/icon-privacy-fill.svg'; -import IconPrivacyOutline from '../assets/icons/icon-privacy-outline.svg'; -import IconProfileFill from '../assets/icons/icon-profile-fill.svg'; -import IconProfileOutline from '../assets/icons/icon-profile-outline.svg'; -import IconQaFill from '../assets/icons/icon-qa-fill.svg'; -import IconQaOutline from '../assets/icons/icon-qa-outline.svg'; -import IconQrCodeFill from '../assets/icons/icon-qr-code-fill.svg'; -import IconQrCodeOutline from '../assets/icons/icon-qr-code-outline.svg'; -import IconQuarantinedFill from '../assets/icons/icon-quarantined-fill.svg'; -import IconQuarantinedOutline from '../assets/icons/icon-quarantined-outline.svg'; -import IconQuoteFill from '../assets/icons/icon-quote-fill.svg'; -import IconQuoteOutline from '../assets/icons/icon-quote-outline.svg'; -import IconRSlashFill from '../assets/icons/icon-r-slash-fill.svg'; -import IconRSlashOutline from '../assets/icons/icon-r-slash-outline.svg'; -import IconRadioButtonFill from '../assets/icons/icon-radio-button-fill.svg'; -import IconRadioButtonOutline from '../assets/icons/icon-radio-button-outline.svg'; -import IconRaiseHandFill from '../assets/icons/icon-raise-hand-fill.svg'; -import IconRaiseHandOutline from '../assets/icons/icon-raise-hand-outline.svg'; -import IconRandomFill from '../assets/icons/icon-random-fill.svg'; -import IconRandomOutline from '../assets/icons/icon-random-outline.svg'; -import IconRatingsEveryoneFill from '../assets/icons/icon-ratings-everyone-fill.svg'; -import IconRatingsEveryoneOutline from '../assets/icons/icon-ratings-everyone-outline.svg'; -import IconRatingsMatureFill from '../assets/icons/icon-ratings-mature-fill.svg'; -import IconRatingsMatureOutline from '../assets/icons/icon-ratings-mature-outline.svg'; -import IconRatingsNsfwFill from '../assets/icons/icon-ratings-nsfw-fill.svg'; -import IconRatingsNsfwOutline from '../assets/icons/icon-ratings-nsfw-outline.svg'; -import IconRatingsViolenceFill from '../assets/icons/icon-ratings-violence-fill.svg'; -import IconRatingsViolenceOutline from '../assets/icons/icon-ratings-violence-outline.svg'; -import IconRecoveryPhraseFill from '../assets/icons/icon-recovery-phrase-fill.svg'; -import IconRecoveryPhraseOutline from '../assets/icons/icon-recovery-phrase-outline.svg'; -import IconRefreshFill from '../assets/icons/icon-refresh-fill.svg'; -import IconRefreshOutline from '../assets/icons/icon-refresh-outline.svg'; -import IconRemovalReasonsFill from '../assets/icons/icon-removal-reasons-fill.svg'; -import IconRemovalReasonsOutline from '../assets/icons/icon-removal-reasons-outline.svg'; -import IconRemoveFill from '../assets/icons/icon-remove-fill.svg'; -import IconRemoveOutline from '../assets/icons/icon-remove-outline.svg'; -import IconReplyFill from '../assets/icons/icon-reply-fill.svg'; -import IconReplyOutline from '../assets/icons/icon-reply-outline.svg'; -import IconReportFill from '../assets/icons/icon-report-fill.svg'; -import IconReportOutline from '../assets/icons/icon-report-outline.svg'; -import IconReverseFill from '../assets/icons/icon-reverse-fill.svg'; -import IconReverseOutline from '../assets/icons/icon-reverse-outline.svg'; -import IconRichTextFill from '../assets/icons/icon-rich-text-fill.svg'; -import IconRichTextOutline from '../assets/icons/icon-rich-text-outline.svg'; -import IconRightFill from '../assets/icons/icon-right-fill.svg'; -import IconRightOutline from '../assets/icons/icon-right-outline.svg'; -import IconRisingFill from '../assets/icons/icon-rising-fill.svg'; -import IconRisingOutline from '../assets/icons/icon-rising-outline.svg'; -import IconRotateFill from '../assets/icons/icon-rotate-fill.svg'; -import IconRotateImageFill from '../assets/icons/icon-rotate-image-fill.svg'; -import IconRotateImageOutline from '../assets/icons/icon-rotate-image-outline.svg'; -import IconRotateOutline from '../assets/icons/icon-rotate-outline.svg'; -import IconRpanFill from '../assets/icons/icon-rpan-fill.svg'; -import IconRpanOutline from '../assets/icons/icon-rpan-outline.svg'; -import IconRulesFill from '../assets/icons/icon-rules-fill.svg'; -import IconRulesOutline from '../assets/icons/icon-rules-outline.svg'; -import IconSafariFill from '../assets/icons/icon-safari-fill.svg'; -import IconSafariOutline from '../assets/icons/icon-safari-outline.svg'; -import IconSaveFill from '../assets/icons/icon-save-fill.svg'; -import IconSaveOutline from '../assets/icons/icon-save-outline.svg'; -import IconSaveViewFill from '../assets/icons/icon-save-view-fill.svg'; -import IconSaveViewOutline from '../assets/icons/icon-save-view-outline.svg'; -import IconSavedFill from '../assets/icons/icon-saved-fill.svg'; -import IconSavedOutline from '../assets/icons/icon-saved-outline.svg'; -import IconSearchFill24 from '../assets/icons/icon-search-fill-24.svg'; -import IconSearchFill from '../assets/icons/icon-search-fill.svg'; -import IconSearchOutline24 from '../assets/icons/icon-search-outline-24.svg'; -import IconSearchOutline from '../assets/icons/icon-search-outline.svg'; -import IconSelfFill from '../assets/icons/icon-self-fill.svg'; -import IconSelfOutline from '../assets/icons/icon-self-outline.svg'; -import IconSendFill from '../assets/icons/icon-send-fill.svg'; -import IconSendOutline from '../assets/icons/icon-send-outline.svg'; -import IconSettingsFill from '../assets/icons/icon-settings-fill.svg'; -import IconSettingsOutline from '../assets/icons/icon-settings-outline.svg'; -import IconSeverityFill from '../assets/icons/icon-severity-fill.svg'; -import IconSeverityOutline from '../assets/icons/icon-severity-outline.svg'; -import IconShareAndroidFill from '../assets/icons/icon-share-android-fill.svg'; -import IconShareAndroidOutline from '../assets/icons/icon-share-android-outline.svg'; -import IconShareFill from '../assets/icons/icon-share-fill.svg'; -import IconShareIosFill from '../assets/icons/icon-share-ios-fill.svg'; -import IconShareIosOutline from '../assets/icons/icon-share-ios-outline.svg'; -import IconShareNewFill from '../assets/icons/icon-share-new-fill.svg'; -import IconShareNewOutline from '../assets/icons/icon-share-new-outline.svg'; -import IconShareOutline from '../assets/icons/icon-share-outline.svg'; -import IconShowFill from '../assets/icons/icon-show-fill.svg'; -import IconShowOutline from '../assets/icons/icon-show-outline.svg'; -import IconSideMenuFill from '../assets/icons/icon-side-menu-fill.svg'; -import IconSideMenuOutline from '../assets/icons/icon-side-menu-outline.svg'; -import IconSkipback10Fill from '../assets/icons/icon-skipback10-fill.svg'; -import IconSkipback10Outline from '../assets/icons/icon-skipback10-outline.svg'; -import IconSkipforward10Fill from '../assets/icons/icon-skipforward10-fill.svg'; -import IconSkipforward10Outline from '../assets/icons/icon-skipforward10-outline.svg'; -import IconSortAzFill from '../assets/icons/icon-sort-az-fill.svg'; -import IconSortAzOutline from '../assets/icons/icon-sort-az-outline.svg'; -import IconSortFill from '../assets/icons/icon-sort-fill.svg'; -import IconSortOutline from '../assets/icons/icon-sort-outline.svg'; -import IconSortPriceFill from '../assets/icons/icon-sort-price-fill.svg'; -import IconSortPriceOutline from '../assets/icons/icon-sort-price-outline.svg'; -import IconSortZaFill from '../assets/icons/icon-sort-za-fill.svg'; -import IconSortZaOutline from '../assets/icons/icon-sort-za-outline.svg'; -import IconSpamFill from '../assets/icons/icon-spam-fill.svg'; -import IconSpamOutline from '../assets/icons/icon-spam-outline.svg'; -import IconSpoilerFill from '../assets/icons/icon-spoiler-fill.svg'; -import IconSpoilerOutline from '../assets/icons/icon-spoiler-outline.svg'; -import IconSponsoredFill from '../assets/icons/icon-sponsored-fill.svg'; -import IconSponsoredOutline from '../assets/icons/icon-sponsored-outline.svg'; -import IconSpreadsheetFill from '../assets/icons/icon-spreadsheet-fill.svg'; -import IconSpreadsheetOutline from '../assets/icons/icon-spreadsheet-outline.svg'; -import IconStarFill from '../assets/icons/icon-star-fill.svg'; -import IconStarOutline from '../assets/icons/icon-star-outline.svg'; -import IconStatisticsFill from '../assets/icons/icon-statistics-fill.svg'; -import IconStatisticsOutline from '../assets/icons/icon-statistics-outline.svg'; -import IconStatusLiveFill from '../assets/icons/icon-status-live-fill.svg'; -import IconStatusLiveOutline from '../assets/icons/icon-status-live-outline.svg'; -import IconStickerFill from '../assets/icons/icon-sticker-fill.svg'; -import IconStickerOutline from '../assets/icons/icon-sticker-outline.svg'; -import IconStrikethroughFill from '../assets/icons/icon-strikethrough-fill.svg'; -import IconStrikethroughOutline from '../assets/icons/icon-strikethrough-outline.svg'; -import IconSubtractFill from '../assets/icons/icon-subtract-fill.svg'; -import IconSubtractOutline from '../assets/icons/icon-subtract-outline.svg'; -import IconSuperscriptFill from '../assets/icons/icon-superscript-fill.svg'; -import IconSuperscriptOutline from '../assets/icons/icon-superscript-outline.svg'; -import IconSwapCameraFill from '../assets/icons/icon-swap-camera-fill.svg'; -import IconSwapCameraOutline from '../assets/icons/icon-swap-camera-outline.svg'; -import IconSwipeBackFill from '../assets/icons/icon-swipe-back-fill.svg'; -import IconSwipeBackOutline from '../assets/icons/icon-swipe-back-outline.svg'; -import IconSwipeDownFill from '../assets/icons/icon-swipe-down-fill.svg'; -import IconSwipeDownOutline from '../assets/icons/icon-swipe-down-outline.svg'; -import IconSwipeFill from '../assets/icons/icon-swipe-fill.svg'; -import IconSwipeOutline from '../assets/icons/icon-swipe-outline.svg'; -import IconSwipeUpFill from '../assets/icons/icon-swipe-up-fill.svg'; -import IconSwipeUpOutline from '../assets/icons/icon-swipe-up-outline.svg'; -import IconTableFill from '../assets/icons/icon-table-fill.svg'; -import IconTableOutline from '../assets/icons/icon-table-outline.svg'; -import IconTagFill from '../assets/icons/icon-tag-fill.svg'; -import IconTagOutline from '../assets/icons/icon-tag-outline.svg'; -import IconTapFill from '../assets/icons/icon-tap-fill.svg'; -import IconTapOutline from '../assets/icons/icon-tap-outline.svg'; -import IconTextFill from '../assets/icons/icon-text-fill.svg'; -import IconTextOutline from '../assets/icons/icon-text-outline.svg'; -import IconTextPostFill from '../assets/icons/icon-text-post-fill.svg'; -import IconTextPostOutline from '../assets/icons/icon-text-post-outline.svg'; -import IconTextSizeFill from '../assets/icons/icon-text-size-fill.svg'; -import IconTextSizeOutline from '../assets/icons/icon-text-size-outline.svg'; -import IconToggleFill from '../assets/icons/icon-toggle-fill.svg'; -import IconToggleOutline from '../assets/icons/icon-toggle-outline.svg'; -import IconToolsFill from '../assets/icons/icon-tools-fill.svg'; -import IconToolsOutline from '../assets/icons/icon-tools-outline.svg'; -import IconTopFill from '../assets/icons/icon-top-fill.svg'; -import IconTopOutline from '../assets/icons/icon-top-outline.svg'; -import IconTopicActivismFill from '../assets/icons/icon-topic-activism-fill.svg'; -import IconTopicActivismOutline from '../assets/icons/icon-topic-activism-outline.svg'; -import IconTopicAddictionsupportFill from '../assets/icons/icon-topic-addictionsupport-fill.svg'; -import IconTopicAddictionsupportOutline from '../assets/icons/icon-topic-addictionsupport-outline.svg'; -import IconTopicAdviceFill from '../assets/icons/icon-topic-advice-fill.svg'; -import IconTopicAdviceOutline from '../assets/icons/icon-topic-advice-outline.svg'; -import IconTopicAnimalsFill from '../assets/icons/icon-topic-animals-fill.svg'; -import IconTopicAnimalsOutline from '../assets/icons/icon-topic-animals-outline.svg'; -import IconTopicAnimeFill from '../assets/icons/icon-topic-anime-fill.svg'; -import IconTopicAnimeOutline from '../assets/icons/icon-topic-anime-outline.svg'; -import IconTopicArtFill from '../assets/icons/icon-topic-art-fill.svg'; -import IconTopicArtOutline from '../assets/icons/icon-topic-art-outline.svg'; -import IconTopicBeautyFill from '../assets/icons/icon-topic-beauty-fill.svg'; -import IconTopicBeautyOutline from '../assets/icons/icon-topic-beauty-outline.svg'; -import IconTopicBusinessFill from '../assets/icons/icon-topic-business-fill.svg'; -import IconTopicBusinessOutline from '../assets/icons/icon-topic-business-outline.svg'; -import IconTopicCareersFill from '../assets/icons/icon-topic-careers-fill.svg'; -import IconTopicCareersOutline from '../assets/icons/icon-topic-careers-outline.svg'; -import IconTopicCarsFill from '../assets/icons/icon-topic-cars-fill.svg'; -import IconTopicCarsOutline from '../assets/icons/icon-topic-cars-outline.svg'; -import IconTopicCelebrityFill from '../assets/icons/icon-topic-celebrity-fill.svg'; -import IconTopicCelebrityOutline from '../assets/icons/icon-topic-celebrity-outline.svg'; -import IconTopicCraftsdiyFill from '../assets/icons/icon-topic-craftsdiy-fill.svg'; -import IconTopicCraftsdiyOutline from '../assets/icons/icon-topic-craftsdiy-outline.svg'; -import IconTopicCryptoFill from '../assets/icons/icon-topic-crypto-fill.svg'; -import IconTopicCryptoOutline from '../assets/icons/icon-topic-crypto-outline.svg'; -import IconTopicCultureFill from '../assets/icons/icon-topic-culture-fill.svg'; -import IconTopicCultureOutline from '../assets/icons/icon-topic-culture-outline.svg'; -import IconTopicDiyFill from '../assets/icons/icon-topic-diy-fill.svg'; -import IconTopicDiyOutline from '../assets/icons/icon-topic-diy-outline.svg'; -import IconTopicEntertainmentFill from '../assets/icons/icon-topic-entertainment-fill.svg'; -import IconTopicEntertainmentOutline from '../assets/icons/icon-topic-entertainment-outline.svg'; -import IconTopicEthicsFill from '../assets/icons/icon-topic-ethics-fill.svg'; -import IconTopicEthicsOutline from '../assets/icons/icon-topic-ethics-outline.svg'; -import IconTopicFamilyFill from '../assets/icons/icon-topic-family-fill.svg'; -import IconTopicFamilyOutline from '../assets/icons/icon-topic-family-outline.svg'; -import IconTopicFashionFill from '../assets/icons/icon-topic-fashion-fill.svg'; -import IconTopicFashionOutline from '../assets/icons/icon-topic-fashion-outline.svg'; -import IconTopicFill from '../assets/icons/icon-topic-fill.svg'; -import IconTopicFitnessFill from '../assets/icons/icon-topic-fitness-fill.svg'; -import IconTopicFitnessOutline from '../assets/icons/icon-topic-fitness-outline.svg'; -import IconTopicFoodFill from '../assets/icons/icon-topic-food-fill.svg'; -import IconTopicFoodOutline from '../assets/icons/icon-topic-food-outline.svg'; -import IconTopicFunnyFill from '../assets/icons/icon-topic-funny-fill.svg'; -import IconTopicFunnyOutline from '../assets/icons/icon-topic-funny-outline.svg'; -import IconTopicGenderFill from '../assets/icons/icon-topic-gender-fill.svg'; -import IconTopicGenderOutline from '../assets/icons/icon-topic-gender-outline.svg'; -import IconTopicHealthFill from '../assets/icons/icon-topic-health-fill.svg'; -import IconTopicHealthOutline from '../assets/icons/icon-topic-health-outline.svg'; -import IconTopicHelpFill from '../assets/icons/icon-topic-help-fill.svg'; -import IconTopicHelpOutline from '../assets/icons/icon-topic-help-outline.svg'; -import IconTopicHistoryFill from '../assets/icons/icon-topic-history-fill.svg'; -import IconTopicHistoryOutline from '../assets/icons/icon-topic-history-outline.svg'; -import IconTopicHobbiesFill from '../assets/icons/icon-topic-hobbies-fill.svg'; -import IconTopicHobbiesOutline from '../assets/icons/icon-topic-hobbies-outline.svg'; -import IconTopicHomegardenFill from '../assets/icons/icon-topic-homegarden-fill.svg'; -import IconTopicHomegardenOutline from '../assets/icons/icon-topic-homegarden-outline.svg'; -import IconTopicInternetFill from '../assets/icons/icon-topic-internet-fill.svg'; -import IconTopicInternetOutline from '../assets/icons/icon-topic-internet-outline.svg'; -import IconTopicLawFill from '../assets/icons/icon-topic-law-fill.svg'; -import IconTopicLawOutline from '../assets/icons/icon-topic-law-outline.svg'; -import IconTopicLearningFill from '../assets/icons/icon-topic-learning-fill.svg'; -import IconTopicLearningOutline from '../assets/icons/icon-topic-learning-outline.svg'; -import IconTopicLifestyleFill from '../assets/icons/icon-topic-lifestyle-fill.svg'; -import IconTopicLifestyleOutline from '../assets/icons/icon-topic-lifestyle-outline.svg'; -import IconTopicMarketplaceFill from '../assets/icons/icon-topic-marketplace-fill.svg'; -import IconTopicMarketplaceOutline from '../assets/icons/icon-topic-marketplace-outline.svg'; -import IconTopicMatureFill from '../assets/icons/icon-topic-mature-fill.svg'; -import IconTopicMatureOutline from '../assets/icons/icon-topic-mature-outline.svg'; -import IconTopicMensfashionFill from '../assets/icons/icon-topic-mensfashion-fill.svg'; -import IconTopicMensfashionOutline from '../assets/icons/icon-topic-mensfashion-outline.svg'; -import IconTopicMenshealthFill from '../assets/icons/icon-topic-menshealth-fill.svg'; -import IconTopicMenshealthOutline from '../assets/icons/icon-topic-menshealth-outline.svg'; -import IconTopicMetaFill from '../assets/icons/icon-topic-meta-fill.svg'; -import IconTopicMetaOutline from '../assets/icons/icon-topic-meta-outline.svg'; -import IconTopicMilitaryFill from '../assets/icons/icon-topic-military-fill.svg'; -import IconTopicMilitaryOutline from '../assets/icons/icon-topic-military-outline.svg'; -import IconTopicMoviesFill from '../assets/icons/icon-topic-movies-fill.svg'; -import IconTopicMoviesOutline from '../assets/icons/icon-topic-movies-outline.svg'; -import IconTopicMusicFill from '../assets/icons/icon-topic-music-fill.svg'; -import IconTopicMusicOutline from '../assets/icons/icon-topic-music-outline.svg'; -import IconTopicNewsFill from '../assets/icons/icon-topic-news-fill.svg'; -import IconTopicNewsOutline from '../assets/icons/icon-topic-news-outline.svg'; -import IconTopicOtherFill from '../assets/icons/icon-topic-other-fill.svg'; -import IconTopicOtherOutline from '../assets/icons/icon-topic-other-outline.svg'; -import IconTopicOutdoorsFill from '../assets/icons/icon-topic-outdoors-fill.svg'; -import IconTopicOutdoorsOutline from '../assets/icons/icon-topic-outdoors-outline.svg'; -import IconTopicOutline from '../assets/icons/icon-topic-outline.svg'; -import IconTopicPetsFill from '../assets/icons/icon-topic-pets-fill.svg'; -import IconTopicPetsOutline from '../assets/icons/icon-topic-pets-outline.svg'; -import IconTopicPhotographyFill from '../assets/icons/icon-topic-photography-fill.svg'; -import IconTopicPhotographyOutline from '../assets/icons/icon-topic-photography-outline.svg'; -import IconTopicPlacesFill from '../assets/icons/icon-topic-places-fill.svg'; -import IconTopicPlacesOutline from '../assets/icons/icon-topic-places-outline.svg'; -import IconTopicPodcastsFill from '../assets/icons/icon-topic-podcasts-fill.svg'; -import IconTopicPodcastsOutline from '../assets/icons/icon-topic-podcasts-outline.svg'; -import IconTopicPoliticsFill from '../assets/icons/icon-topic-politics-fill.svg'; -import IconTopicPoliticsOutline from '../assets/icons/icon-topic-politics-outline.svg'; -import IconTopicProgrammingFill from '../assets/icons/icon-topic-programming-fill.svg'; -import IconTopicProgrammingOutline from '../assets/icons/icon-topic-programming-outline.svg'; -import IconTopicReadingFill from '../assets/icons/icon-topic-reading-fill.svg'; -import IconTopicReadingOutline from '../assets/icons/icon-topic-reading-outline.svg'; -import IconTopicReligionFill from '../assets/icons/icon-topic-religion-fill.svg'; -import IconTopicReligionOutline from '../assets/icons/icon-topic-religion-outline.svg'; -import IconTopicScienceFill from '../assets/icons/icon-topic-science-fill.svg'; -import IconTopicScienceOutline from '../assets/icons/icon-topic-science-outline.svg'; -import IconTopicSexorientationFill from '../assets/icons/icon-topic-sexorientation-fill.svg'; -import IconTopicSexorientationOutline from '../assets/icons/icon-topic-sexorientation-outline.svg'; -import IconTopicSportsFill from '../assets/icons/icon-topic-sports-fill.svg'; -import IconTopicSportsOutline from '../assets/icons/icon-topic-sports-outline.svg'; -import IconTopicStyleFill from '../assets/icons/icon-topic-style-fill.svg'; -import IconTopicStyleOutline from '../assets/icons/icon-topic-style-outline.svg'; -import IconTopicTabletopFill from '../assets/icons/icon-topic-tabletop-fill.svg'; -import IconTopicTabletopOutline from '../assets/icons/icon-topic-tabletop-outline.svg'; -import IconTopicTechnologyFill from '../assets/icons/icon-topic-technology-fill.svg'; -import IconTopicTechnologyOutline from '../assets/icons/icon-topic-technology-outline.svg'; -import IconTopicTelevisionFill from '../assets/icons/icon-topic-television-fill.svg'; -import IconTopicTelevisionOutline from '../assets/icons/icon-topic-television-outline.svg'; -import IconTopicTraumasupportFill from '../assets/icons/icon-topic-traumasupport-fill.svg'; -import IconTopicTraumasupportOutline from '../assets/icons/icon-topic-traumasupport-outline.svg'; -import IconTopicTravelFill from '../assets/icons/icon-topic-travel-fill.svg'; -import IconTopicTravelOutline from '../assets/icons/icon-topic-travel-outline.svg'; -import IconTopicVideogamingFill from '../assets/icons/icon-topic-videogaming-fill.svg'; -import IconTopicVideogamingOutline from '../assets/icons/icon-topic-videogaming-outline.svg'; -import IconTopicWomensfashionFill from '../assets/icons/icon-topic-womensfashion-fill.svg'; -import IconTopicWomensfashionOutline from '../assets/icons/icon-topic-womensfashion-outline.svg'; -import IconTopicWomenshealthFill from '../assets/icons/icon-topic-womenshealth-fill.svg'; -import IconTopicWomenshealthOutline from '../assets/icons/icon-topic-womenshealth-outline.svg'; -import IconTranslateFill from '../assets/icons/icon-translate-fill.svg'; -import IconTranslateOutline from '../assets/icons/icon-translate-outline.svg'; -import IconTranslationOffFill from '../assets/icons/icon-translation-off-fill.svg'; -import IconTranslationOffOutline from '../assets/icons/icon-translation-off-outline.svg'; -import IconTrimFill from '../assets/icons/icon-trim-fill.svg'; -import IconTrimOutline from '../assets/icons/icon-trim-outline.svg'; -import IconUSlashFill from '../assets/icons/icon-u-slash-fill.svg'; -import IconUSlashOutline from '../assets/icons/icon-u-slash-outline.svg'; -import IconUnbanFill from '../assets/icons/icon-unban-fill.svg'; -import IconUnbanOutline from '../assets/icons/icon-unban-outline.svg'; -import IconUndoFill from '../assets/icons/icon-undo-fill.svg'; -import IconUndoOutline from '../assets/icons/icon-undo-outline.svg'; -import IconUnheartFill from '../assets/icons/icon-unheart-fill.svg'; -import IconUnheartOutline from '../assets/icons/icon-unheart-outline.svg'; -import IconUnlockFill from '../assets/icons/icon-unlock-fill.svg'; -import IconUnlockOutline from '../assets/icons/icon-unlock-outline.svg'; -import IconUnmodFill from '../assets/icons/icon-unmod-fill.svg'; -import IconUnmodOutline from '../assets/icons/icon-unmod-outline.svg'; -import IconUnpinFill from '../assets/icons/icon-unpin-fill.svg'; -import IconUnpinOutline from '../assets/icons/icon-unpin-outline.svg'; -import IconUnstarFill from '../assets/icons/icon-unstar-fill.svg'; -import IconUnstarOutline from '../assets/icons/icon-unstar-outline.svg'; -import IconUnverifiedFill from '../assets/icons/icon-unverified-fill.svg'; -import IconUnverifiedOutline from '../assets/icons/icon-unverified-outline.svg'; -import IconUpArrowFill from '../assets/icons/icon-up-arrow-fill.svg'; -import IconUpArrowOutline from '../assets/icons/icon-up-arrow-outline.svg'; -import IconUpFill from '../assets/icons/icon-up-fill.svg'; -import IconUpOutline from '../assets/icons/icon-up-outline.svg'; -import IconUploadFill from '../assets/icons/icon-upload-fill.svg'; -import IconUploadOutline from '../assets/icons/icon-upload-outline.svg'; -import IconUpvoteFillMask from '../assets/icons/icon-upvote-fill-mask.svg'; -import IconUpvoteFill from '../assets/icons/icon-upvote-fill.svg'; -import IconUpvoteOffsetmask from '../assets/icons/icon-upvote-offsetmask.svg'; -import IconUpvoteOutline from '../assets/icons/icon-upvote-outline.svg'; -import IconUpvotesFill from '../assets/icons/icon-upvotes-fill.svg'; -import IconUpvotesOutline from '../assets/icons/icon-upvotes-outline.svg'; -import IconUserFill from '../assets/icons/icon-user-fill.svg'; -import IconUserNoteFill from '../assets/icons/icon-user-note-fill.svg'; -import IconUserNoteOutline from '../assets/icons/icon-user-note-outline.svg'; -import IconUserOutline from '../assets/icons/icon-user-outline.svg'; -import IconUsersFill from '../assets/icons/icon-users-fill.svg'; -import IconUsersOutline from '../assets/icons/icon-users-outline.svg'; -import IconValentinesDayFill24 from '../assets/icons/icon-valentines-day-fill-24.svg'; -import IconValentinesDayOutline24 from '../assets/icons/icon-valentines-day-outline-24.svg'; -import IconVaultFill from '../assets/icons/icon-vault-fill.svg'; -import IconVaultOutline from '../assets/icons/icon-vault-outline.svg'; -import IconVerifiedFill from '../assets/icons/icon-verified-fill.svg'; -import IconVerifiedOutline from '../assets/icons/icon-verified-outline.svg'; -import IconVideoCameraFill from '../assets/icons/icon-video-camera-fill.svg'; -import IconVideoCameraOutline from '../assets/icons/icon-video-camera-outline.svg'; -import IconVideoFeedFill from '../assets/icons/icon-video-feed-fill.svg'; -import IconVideoFeedOutline from '../assets/icons/icon-video-feed-outline.svg'; -import IconVideoLiveFill from '../assets/icons/icon-video-live-fill.svg'; -import IconVideoLiveOutline from '../assets/icons/icon-video-live-outline.svg'; -import IconVideoPostFill from '../assets/icons/icon-video-post-fill.svg'; -import IconVideoPostOutline from '../assets/icons/icon-video-post-outline.svg'; -import IconVideoThreadFill from '../assets/icons/icon-video-thread-fill.svg'; -import IconVideoThreadOutline from '../assets/icons/icon-video-thread-outline.svg'; -import IconVideoTranscriptionFill from '../assets/icons/icon-video-transcription-fill.svg'; -import IconVideoTranscriptionOutline from '../assets/icons/icon-video-transcription-outline.svg'; -import IconViewCardFill from '../assets/icons/icon-view-card-fill.svg'; -import IconViewCardOutline from '../assets/icons/icon-view-card-outline.svg'; -import IconViewClassicFill from '../assets/icons/icon-view-classic-fill.svg'; -import IconViewClassicOutline from '../assets/icons/icon-view-classic-outline.svg'; -import IconViewCompactFill from '../assets/icons/icon-view-compact-fill.svg'; -import IconViewCompactOutline from '../assets/icons/icon-view-compact-outline.svg'; -import IconViewGridFill from '../assets/icons/icon-view-grid-fill.svg'; -import IconViewGridOutline from '../assets/icons/icon-view-grid-outline.svg'; -import IconViewsFill from '../assets/icons/icon-views-fill.svg'; -import IconViewsOutline from '../assets/icons/icon-views-outline.svg'; -import IconVolumeFill from '../assets/icons/icon-volume-fill.svg'; -import IconVolumeMuteFill from '../assets/icons/icon-volume-mute-fill.svg'; -import IconVolumeOutline from '../assets/icons/icon-volume-outline.svg'; -import IconWalletFill from '../assets/icons/icon-wallet-fill.svg'; -import IconWalletOutline from '../assets/icons/icon-wallet-outline.svg'; -import IconWarningFill from '../assets/icons/icon-warning-fill.svg'; -import IconWarningOutline from '../assets/icons/icon-warning-outline.svg'; -import IconWebhookFill from '../assets/icons/icon-webhook-fill.svg'; -import IconWebhookOutline from '../assets/icons/icon-webhook-outline.svg'; -import IconWhaleFill from '../assets/icons/icon-whale-fill.svg'; -import IconWhaleOutline from '../assets/icons/icon-whale-outline.svg'; -import IconWikiBanFill from '../assets/icons/icon-wiki-ban-fill.svg'; -import IconWikiBanOutline from '../assets/icons/icon-wiki-ban-outline.svg'; -import IconWikiFill from '../assets/icons/icon-wiki-fill.svg'; -import IconWikiOutline from '../assets/icons/icon-wiki-outline.svg'; -import IconWikiUnbanFill from '../assets/icons/icon-wiki-unban-fill.svg'; -import IconWikiUnbanOutline from '../assets/icons/icon-wiki-unban-outline.svg'; -import IconWorldFill from '../assets/icons/icon-world-fill.svg'; -import IconWorldOutline from '../assets/icons/icon-world-outline.svg'; -import AttributeOnPress from './attributes/_onPress.md'; -import AttributeColor from './attributes/_color.md'; - - -export const icons = { - actions: [ - { name: 'add', variants: [, ] }, - { name: 'add-to-feed', variants: [, ] }, - { name: 'award', variants: [, ] }, - { name: 'backup', variants: [, ] }, - { name: 'browse', variants: [, ] }, - { name: 'chat-new', variants: [, ] }, - { name: 'clear', variants: [, ] }, - { name: 'comment', variants: [, ] }, - { name: 'customize', variants: [, ] }, - { name: 'delete', variants: [, ] }, - { name: 'download', variants: [, ] }, - { name: 'downvote', variants: [, ] }, - { name: 'duplicate', variants: [, ] }, - { name: 'edit', variants: [, ] }, - { name: 'heart', variants: [, ] }, - { name: 'hide', variants: [, ] }, - { name: 'invite', variants: [, ] }, - { name: 'join', variants: [, ] }, - { name: 'joined', variants: [, ] }, - { name: 'leave', variants: [, ] }, - { name: 'logout', variants: [, ] }, - { name: 'mark-read', variants: [, ] }, - { name: 'message', variants: [, ] }, - { name: 'notification', variants: [, ] }, - { name: 'notification-frequent', variants: [, ] }, - { name: 'notification-off', variants: [, ] }, - { name: 'peace', variants: [, ] }, - { name: 'raise-hand', variants: [, ] }, - { name: 'recovery-phrase', variants: [, ] }, - { name: 'reply', variants: [, ] }, - { name: 'report', variants: [, ] }, - { name: 'save', variants: [, ] }, - { name: 'search', variants: [, ] }, - { name: 'send', variants: [, ] }, - { name: 'share-android', variants: [, ] }, - { name: 'share', variants: [, ] }, - { name: 'share-ios', variants: [, ] }, - { name: 'show', variants: [, ] }, - { name: 'star', variants: [, ] }, - { name: 'subtract', variants: [, ] }, - { name: 'tag', variants: [, ] }, - { name: 'translate', variants: [, ] }, - { name: 'translation-off', variants: [, ] }, - { name: 'unheart', variants: [, ] }, - { name: 'unstar', variants: [, ] }, - { name: 'upload', variants: [, ] }, - { name: 'upvote', variants: [, ] } -], -ads: [ - { name: '3rd-party', variants: [, ] }, - { name: 'ads', variants: [, ] }, - { name: 'audience', variants: [, ] }, - { name: 'brand-awareness', variants: [, ] }, - { name: 'campaign', variants: [, ] }, - { name: 'client-list', variants: [, ] }, - { name: 'conversion', variants: [, ] }, - { name: 'copy-clipboard', variants: [, ] }, - { name: 'dashboard', variants: [, ] }, - { name: 'funnel', variants: [, ] }, - { name: 'save-view', variants: [, ] }, - { name: 'spreadsheet', variants: [, ] } -], -beta: [ - { name: 'beta-binoculars', variants: [, ] }, - { name: 'beta-caret-updown', variants: [, ] }, - { name: 'beta-latest', variants: [, ] }, - { name: 'beta-planet', variants: [, ] }, - { name: 'beta-talk-add', variants: [, ] }, - { name: 'beta-telescope', variants: [, ] }, - { name: 'communities', variants: [, ] }, - { name: 'share-new', variants: [, ] } -], -content: [ - { name: 'browser', variants: [, ] }, - { name: 'camera', variants: [, ] }, - { name: 'collection', variants: [, ] }, - { name: 'embed', variants: [, ] }, - { name: 'end-live-chat', variants: [, ] }, - { name: 'gif-post', variants: [, ] }, - { name: 'image-post', variants: [, ] }, - { name: 'link-post', variants: [, ] }, - { name: 'live-chat', variants: [, ] }, - { name: 'media-gallery', variants: [, ] }, - { name: 'pending-posts', variants: [, ] }, - { name: 'poll-post', variants: [, ] }, - { name: 'random', variants: [, ] }, - { name: 'text-post', variants: [, ] }, - { name: 'video-camera', variants: [, ] }, - { name: 'video-live', variants: [, ] }, - { name: 'video-post', variants: [, ] } -], -formatting: [ - { name: 'add-emoji', variants: [, ] }, - { name: 'add-media', variants: [, ] }, - { name: 'align-center', variants: [, ] }, - { name: 'align-left', variants: [, ] }, - { name: 'align-right', variants: [, ] }, - { name: 'attach', variants: [, ] }, - { name: 'bold', variants: [, ] }, - { name: 'code-block', variants: [, ] }, - { name: 'code-inline', variants: [, ] }, - { name: 'emoji', variants: [, ] }, - { name: 'format', variants: [, ] }, - { name: 'hashtag', variants: [, ] }, - { name: 'italic', variants: [, ] }, - { name: 'keyboard', variants: [, ] }, - { name: 'link', variants: [, ] }, - { name: 'list-bulleted', variants: [, ] }, - { name: 'list-numbered', variants: [, ] }, - { name: 'macro', variants: [, ] }, - { name: 'quote', variants: [, ] }, - { name: 'r-slash', variants: [, ] }, - { name: 'rich-text', variants: [, ] }, - { name: 'strikethrough', variants: [, ] }, - { name: 'superscript', variants: [, ] }, - { name: 'table', variants: [, ] }, - { name: 'text-size', variants: [, ] }, - { name: 'u-slash', variants: [, ] }, - { name: 'webhook', variants: [, ] } -], -forms: [ - { name: 'caret-down', variants: [, ] }, - { name: 'caret-left', variants: [, ] }, - { name: 'caret-right', variants: [, ] }, - { name: 'caret-up', variants: [, ] }, - { name: 'checkbox-dismiss', variants: [, ] }, - { name: 'checkbox', variants: [, ] }, - { name: 'checkmark', variants: [, ] }, - { name: 'payment', variants: [, ] }, - { name: 'radio-button', variants: [, ] }, - { name: 'toggle', variants: [, ] } -], -locations: [ - { name: 'all', variants: [, ] }, - { name: 'chat', variants: [, ] }, - { name: 'chat-group', variants: [, ] }, - { name: 'chat-private', variants: [, ] }, - { name: 'chrome', variants: [, ] }, - { name: 'comments', variants: [, ] }, - { name: 'community', variants: [, ] }, - { name: 'custom-feed', variants: [, ] }, - { name: 'discover', variants: [, ] }, - { name: 'feed-video', variants: [, ] }, - { name: 'history', variants: [, ] }, - { name: 'home', variants: [, ] }, - { name: 'inbox', variants: [, ] }, - { name: 'marketplace', variants: [, ] }, - { name: 'popular', variants: [, ] }, - { name: 'posts', variants: [, ] }, - { name: 'predictions', variants: [, ] }, - { name: 'profile', variants: [, ] }, - { name: 'rpan', variants: [, ] }, - { name: 'safari', variants: [, ] }, - { name: 'saved', variants: [, ] }, - { name: 'settings', variants: [, ] }, - { name: 'tools', variants: [, ] }, - { name: 'topic', variants: [, ] }, - { name: 'vault', variants: [, ] }, - { name: 'video-feed', variants: [, ] }, - { name: 'wallet', variants: [, ] }, - { name: 'wiki', variants: [, ] }, - { name: 'world', variants: [, ] } -], -media: [ - { name: 'aspect-ratio', variants: [, ] }, - { name: 'aspect-rectangle', variants: [, ] }, - { name: 'audio', variants: [, ] }, - { name: 'bounce', variants: [, ] }, - { name: 'closed-captioning', variants: [, ] }, - { name: 'crop', variants: [, ] }, - { name: 'effect', variants: [, ] }, - { name: 'loop', variants: [, ] }, - { name: 'mask', variants: [, ] }, - { name: 'meme', variants: [, ] }, - { name: 'mic', variants: [, ] }, - { name: 'mic-mute', variants: [, ] }, - { name: 'music', variants: [, ] }, - { name: 'mute', variants: [, ] }, - { name: 'pause', variants: [, ] }, - { name: 'play', variants: [, ] }, - { name: 'reverse', variants: [, ] }, - { name: 'rotate', variants: [, ] }, - { name: 'rotate-image', variants: [, ] }, - { name: 'skipback10', variants: [, ] }, - { name: 'skipforward10', variants: [, ] }, - { name: 'sticker', variants: [, ] }, - { name: 'swap-camera', variants: [, ] }, - { name: 'text', variants: [, ] }, - { name: 'trim', variants: [, ] }, - { name: 'video-thread', variants: [, ] }, - { name: 'video-transcription', variants: [, ] }, - { name: 'volume', variants: [, ] }, - { name: 'volume-mute', variants: [ ] } -], -moderation: [ - { name: 'admin', variants: [, ] }, - { name: 'approve', variants: [, ] }, - { name: 'automod', variants: [, ] }, - { name: 'ban', variants: [, ] }, - { name: 'block', variants: [, ] }, - { name: 'confidence', variants: [, ] }, - { name: 'crowd-control', variants: [, ] }, - { name: 'distinguish', variants: [, ] }, - { name: 'ignore-reports', variants: [, ] }, - { name: 'kick', variants: [, ] }, - { name: 'lock', variants: [, ] }, - { name: 'mod', variants: [, ] }, - { name: 'mod-mail', variants: [, ] }, - { name: 'mod-mode', variants: [, ] }, - { name: 'mod-mute', variants: [, ] }, - { name: 'mod-overflow', variants: [, ] }, - { name: 'mod-queue', variants: [, ] }, - { name: 'mod-unmute', variants: [, ] }, - { name: 'pin', variants: [, ] }, - { name: 'removal-reasons', variants: [, ] }, - { name: 'remove', variants: [, ] }, - { name: 'rules', variants: [, ] }, - { name: 'severity', variants: [, ] }, - { name: 'spam', variants: [, ] }, - { name: 'unban', variants: [, ] }, - { name: 'unlock', variants: [, ] }, - { name: 'unmod', variants: [, ] }, - { name: 'unpin', variants: [, ] }, - { name: 'user-note', variants: [, ] }, - { name: 'wiki-ban', variants: [, ] }, - { name: 'wiki-unban', variants: [, ] } -], -navigation: [ - { name: 'back', variants: [, ] }, - { name: 'close', variants: [, ] }, - { name: 'collapse-left', variants: [, ] }, - { name: 'collapse-right', variants: [, ] }, - { name: 'dismiss-all', variants: [, ] }, - { name: 'down', variants: [, ] }, - { name: 'down-arrow', variants: [] }, - { name: 'drag', variants: [] }, - { name: 'expand-left', variants: [, ] }, - { name: 'expand-right', variants: [, ] }, - { name: 'external', variants: [, ] }, - { name: 'filter', variants: [, ] }, - { name: 'forward', variants: [, ] }, - { name: 'help', variants: [, ] }, - { name: 'info', variants: [, ] }, - { name: 'jump-down', variants: [, ] }, - { name: 'jump-up', variants: [, ] }, - { name: 'left', variants: [, ] }, - { name: 'load', variants: [] }, - { name: 'menu', variants: [, ] }, - { name: 'overflow-caret', variants: [, ] }, - { name: 'overflow-horizontal', variants: [, ] }, - { name: 'overflow-vertical', variants: [, ] }, - { name: 'qr-code', variants: [, ] }, - { name: 'refresh', variants: [, ] }, - { name: 'right', variants: [, ] }, - { name: 'side-menu', variants: [, ] }, - { name: 'sort', variants: [, ] }, - { name: 'sort-az', variants: [, ] }, - { name: 'sort-price', variants: [, ] }, - { name: 'sort-za', variants: [, ] }, - { name: 'swipe-back', variants: [, ] }, - { name: 'swipe-down', variants: [, ] }, - { name: 'swipe', variants: [, ] }, - { name: 'swipe-up', variants: [, ] }, - { name: 'tap', variants: [, ] }, - { name: 'undo', variants: [, ] }, - { name: 'up-arrow', variants: [, ] }, - { name: 'up', variants: [, ] } -], -ratings: [ - { name: 'drugs', variants: [, ] }, - { name: 'ratings-everyone', variants: [, ] }, - { name: 'ratings-mature', variants: [, ] }, - { name: 'ratings-nsfw', variants: [, ] }, - { name: 'ratings-violence', variants: [, ] } -], -sortsAndViews: [ - { name: 'best', variants: [, ] }, - { name: 'contest', variants: [, ] }, - { name: 'controversial', variants: [, ] }, - { name: 'hot', variants: [, ] }, - { name: 'live', variants: [, ] }, - { name: 'new', variants: [, ] }, - { name: 'qa', variants: [, ] }, - { name: 'rising', variants: [, ] }, - { name: 'top', variants: [, ] }, - { name: 'view-card', variants: [, ] }, - { name: 'view-classic', variants: [, ] }, - { name: 'view-compact', variants: [, ] }, - { name: 'view-grid', variants: [, ] } -], -status: [ - { name: 'activity', variants: [, ] }, - { name: 'appearance', variants: [, ] }, - { name: 'archived', variants: [, ] }, - { name: 'author', variants: [, ] }, - { name: 'avatar-style', variants: [, ] }, - { name: 'blockchain', variants: [, ] }, - { name: 'bot', variants: [, ] }, - { name: 'cake', variants: [, ] }, - { name: 'calendar', variants: [, ] }, - { name: 'coins', variants: [, ] }, - { name: 'collectible-expressions', variants: [, ] }, - { name: 'crosspost', variants: [, ] }, - { name: 'day', variants: [, ] }, - { name: 'downvotes', variants: [, ] }, - { name: 'error', variants: [, ] }, - { name: 'karma', variants: [, ] }, - { name: 'language', variants: [, ] }, - { name: 'location', variants: [, ] }, - { name: 'night', variants: [, ] }, - { name: 'nsfw', variants: [, ] }, - { name: 'nsfw-language', variants: [, ] }, - { name: 'nsfw-violence', variants: [, ] }, - { name: 'official', variants: [, ] }, - { name: 'original', variants: [, ] }, - { name: 'phone', variants: [, ] }, - { name: 'powerup', variants: [, ] }, - { name: 'premium', variants: [, ] }, - { name: 'privacy', variants: [, ] }, - { name: 'quarantined', variants: [, ] }, - { name: 'self', variants: [, ] }, - { name: 'spoiler', variants: [, ] }, - { name: 'sponsored', variants: [, ] }, - { name: 'statistics', variants: [, ] }, - { name: 'status-live', variants: [, ] }, - { name: 'unverified', variants: [, ] }, - { name: 'upvotes', variants: [, ] }, - { name: 'user', variants: [, ] }, - { name: 'users', variants: [, ] }, - { name: 'verified', variants: [, ] }, - { name: 'views', variants: [, ] }, - { name: 'warning', variants: [, ] }, - { name: 'whale', variants: [, ] } -], -topics: [ - { name: 'topic-activism', variants: [, ] }, - { name: 'topic-addictionsupport', variants: [, ] }, - { name: 'topic-advice', variants: [, ] }, - { name: 'topic-animals', variants: [, ] }, - { name: 'topic-anime', variants: [, ] }, - { name: 'topic-art', variants: [, ] }, - { name: 'topic-beauty', variants: [, ] }, - { name: 'topic-business', variants: [, ] }, - { name: 'topic-careers', variants: [, ] }, - { name: 'topic-cars', variants: [, ] }, - { name: 'topic-celebrity', variants: [, ] }, - { name: 'topic-craftsdiy', variants: [, ] }, - { name: 'topic-crypto', variants: [, ] }, - { name: 'topic-culture', variants: [, ] }, - { name: 'topic-diy', variants: [, ] }, - { name: 'topic-entertainment', variants: [, ] }, - { name: 'topic-ethics', variants: [, ] }, - { name: 'topic-family', variants: [, ] }, - { name: 'topic-fashion', variants: [, ] }, - { name: 'topic-fitness', variants: [, ] }, - { name: 'topic-food', variants: [, ] }, - { name: 'topic-funny', variants: [, ] }, - { name: 'topic-gender', variants: [, ] }, - { name: 'topic-health', variants: [, ] }, - { name: 'topic-help', variants: [, ] }, - { name: 'topic-history', variants: [, ] }, - { name: 'topic-hobbies', variants: [, ] }, - { name: 'topic-homegarden', variants: [, ] }, - { name: 'topic-internet', variants: [, ] }, - { name: 'topic-law', variants: [, ] }, - { name: 'topic-learning', variants: [, ] }, - { name: 'topic-lifestyle', variants: [, ] }, - { name: 'topic-marketplace', variants: [, ] }, - { name: 'topic-mature', variants: [, ] }, - { name: 'topic-mensfashion', variants: [, ] }, - { name: 'topic-menshealth', variants: [, ] }, - { name: 'topic-meta', variants: [, ] }, - { name: 'topic-military', variants: [, ] }, - { name: 'topic-movies', variants: [, ] }, - { name: 'topic-music', variants: [, ] }, - { name: 'topic-news', variants: [, ] }, - { name: 'topic-other', variants: [, ] }, - { name: 'topic-outdoors', variants: [, ] }, - { name: 'topic-pets', variants: [, ] }, - { name: 'topic-photography', variants: [, ] }, - { name: 'topic-places', variants: [, ] }, - { name: 'topic-podcasts', variants: [, ] }, - { name: 'topic-politics', variants: [, ] }, - { name: 'topic-programming', variants: [, ] }, - { name: 'topic-reading', variants: [, ] }, - { name: 'topic-religion', variants: [, ] }, - { name: 'topic-science', variants: [, ] }, - { name: 'topic-sexorientation', variants: [, ] }, - { name: 'topic-sports', variants: [, ] }, - { name: 'topic-style', variants: [, ] }, - { name: 'topic-tabletop', variants: [, ] }, - { name: 'topic-technology', variants: [, ] }, - { name: 'topic-television', variants: [, ] }, - { name: 'topic-traumasupport', variants: [, ] }, - { name: 'topic-travel', variants: [, ] }, - { name: 'topic-videogaming', variants: [, ] }, - { name: 'topic-womensfashion', variants: [, ] }, - { name: 'topic-womenshealth', variants: [, ] } -] -}; - -The following is a list of icons available for use within Custom Posts. To use, include the `Name` of the icon in either a ` -
- - ); - }, -}); -``` -[View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) - -### Example - -Something you might run into when adding assets is that the images could be large enough to push some content outside of the visible frame. If you notice that happening, you can adjust the container height and image width using `height`, `grow`, and `resizeMode`. See the following example: - -```tsx - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut sed malesuada tortor. Phasellus - velit eros, fermentum vitae cursus ut, condimentum nec tellus. - - ​ - ​ - {/* Footer */} - - - - - - -``` diff --git a/versioned_docs/version-0.12/blocks/overview.mdx b/versioned_docs/version-0.12/blocks/overview.mdx deleted file mode 100644 index f149f7c2..00000000 --- a/versioned_docs/version-0.12/blocks/overview.mdx +++ /dev/null @@ -1,96 +0,0 @@ -# Overview - -Blocks is a cross-platform layout system, optimized for building apps (not web pages, not documents). The goal is to let anyone build simple apps that conform to our design system and best practices for apps, without it being rocket science. If you want to get started right away try a [Devvit Blocks template](../examples/template-library.md#devvit-blocks). - -## Available blocks - -We support the following elements: - -### Containers - -- **Blocks** -- [**HStack**](./stacks.mdx) -- [**VStack**](./stacks.mdx) -- [**ZStack**](./stacks.mdx) - -### Objects - -- [**Text**](./text.mdx) -- [**Button**](./button.mdx) -- [**Spacer**](./spacer.mdx) -- [**Image**](./image.mdx) -- [**Icon**](./icon.mdx) - -Further elements (components) may be derived from these blocks, and obey the same rules. - -## Sizing - -### Paddings and gaps - -- We're operating in a [border-box](https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing) model, where the padding is counted as part of the size of an element. -- Padding is incompressible. -- Gaps are implemented as if we're injecting spacers between all children. - -### Units - -There are two supported units: - -- `px`: device-independent pixels -- `%`: percent of parent container's available content area (i.e. subtracting the parent's padding and gaps) - -### Intrinsic size - -All elements have an _intrinsic size_. This is the size that they would be if there were no sizing modifiers applied to them. - -- **HStack**: Sum of the intrinsic widths of the children × the max of the intrinsic heights of the children (+ gaps and padding) -- **VStack**: Max of the intrinsic widths of the children × the sum of the intrinsic heights of the children (+ gaps and padding) -- **ZStack**: Max of the intrinsic sizes of the children (+ padding) -- **Text**: Size of the text without wrapping or truncation -- **Button**: Size of the text without wrapping or truncation (+ padding) -- **Spacer**: Size in pixels, as specified -- **Image**: imageWidth × imageHeight - -This size provides a baseline, which can be modified by attributes. There are a few sizing attributes: - -- `width` / `height` -- `minWidth` / `minHeight` -- `maxWidth` / `maxHeight` -- `grow` (operates in the _current direction_). - -:::note -Setting both `width` and `grow` simultaneously is not recommended, because then `grow` would become a no-op (overridden by `width`). -::: - -### Preferred size - -The preferred size is calculated based on the intrinsic size and the modifier attributes. The modifiers can conflict, in which case the precedence order is: - -`(most important) minWidth > maxWidth > width > aspect-ratio > grow > intrinsic width (least important)` - -Here, `grow` attempts to set `width="100%"`. Unlike actually setting `width="100%"`, `grow` can be flexibly adjusted later. Examples: - -- `` will always have a preferred size of 50px. (width overrides `grow`) -- `` will always take at least 50px, and will attempt to consume the available `width`. - -### Adjusted size - -Grow elements are flexible. Whenever the full width (or height) of a parent element is not fully utilized, a grow element will expand to fit the parent element, assuming that the other constraints permit. Grow is prioritized lower than the other sizing attributes, e.g. an element will never grow beyond its maxWidth. - -### Direction - -All elements inherit a direction for the purposes of growing. Things only grow in one direction at a time. - -| Element | Self Direction | Child Direction | -| ---------------------- | -------------- | --------------- | -| Blocks | N/A | Vertical | -| [HStack](./stacks.mdx) | Inherit | Horizontal | -| [VStack](./stacks.mdx) | Inherit | Vertical | -| [ZStack](./stacks.mdx) | Inherit | Inherit | -| [Text](./text.mdx) | Horizontal | N/A | -| [Button](./button.mdx) | Horizontal | N/A | -| [Spacer](./spacer.mdx) | Inherit | N/A | -| [Image](./image.mdx) | Inherit | N/A | - -### Overflow - -All containers clip overflown content. diff --git a/versioned_docs/version-0.12/blocks/spacer.mdx b/versioned_docs/version-0.12/blocks/spacer.mdx deleted file mode 100644 index e663797a..00000000 --- a/versioned_docs/version-0.12/blocks/spacer.mdx +++ /dev/null @@ -1,84 +0,0 @@ -import AttributeMinHeight from './attributes/_minHeight.md'; -import AttributeMaxHeight from './attributes/_maxHeight.md'; -import AttributeMinWidth from './attributes/_minWidth.md'; -import AttributeMaxWidth from './attributes/_maxWidth.md'; -import AttributeGrow from './attributes/_grow.md'; -import AttributeWidth from './attributes/_width.mdx'; -import AttributeHeight from './attributes/_height.mdx'; - -# Spacer - -The `` element is used to create adjustable space between UI elements. - -## Attributes - -### `size` - -Sets the spacer size. Possible values: - -- `xsmall` -- `small`: The default if the attribute is not used. -- `medium` -- `large` - -### `grow` - - - -### `shape` - -Possible values: - -- `invisible` -- `thin` -- `square` - -### `width` - - - -### `minWidth` - - - -### `maxWidth` - - - -### `height` - - - -### `minHeight` - - - -### `maxHeight` - - - -## Examples - -```tsx - - - - - -``` - -```tsx - - - - - -``` - -```tsx - - - - - -``` diff --git a/versioned_docs/version-0.12/blocks/stacks.mdx b/versioned_docs/version-0.12/blocks/stacks.mdx deleted file mode 100644 index fa9d3abb..00000000 --- a/versioned_docs/version-0.12/blocks/stacks.mdx +++ /dev/null @@ -1,297 +0,0 @@ -import AttributeMinHeight from './attributes/_minHeight.md'; -import AttributeMaxHeight from './attributes/_maxHeight.md'; -import AttributeMinWidth from './attributes/_minWidth.md'; -import AttributeMaxWidth from './attributes/_maxWidth.md'; -import AttributeGrow from './attributes/_grow.md'; -import AttributeAlignment from './attributes/_alignment.md'; -import AttributeOnPress from './attributes/_onPress.md'; -import AttributeWidth from './attributes/_width.mdx'; -import AttributeHeight from './attributes/_height.mdx'; -import AttributeColor from './attributes/_color.md'; - -# Stacks - -The stack elements (``, ``, and ``) are flexible containers used to arrange child elements in different orientations and layers. Stacks can be nested, styled, and clicked if given an `onPress` handler. Stacks clip any overflowing content. - -## Directions - -### `` - -Vertically stacks elements on top of each other. Perfect for forms, lists, or any vertical grouping of elements. - -### `` - -Horizontally arranges elements side by side. Ideal for creating horizontal layouts, like navigation menus or toolbars. - -### `` - -Layers elements on top of each other along the z-axis. Useful for overlaying elements like modals, badges, or custom stacking layouts. - -## Attributes - -### `padding` - -An `enum` that defines the padding inside the stack. Possible values: - -- `none`: The default if the attribute is not used. -- `xsmall`: 4px -- `small`: 8px -- `medium`: 16px -- `large`: 32px - -![app_image_fixed_size](../assets/blocks/docs-blocks-stacks-padding.png) - -#### Examples - -A padded row of buttons with a gap between: - -```tsx - - - - -``` - -### `gap` - -An `enum` that determines the space between child elements. Possible values: - -- `none`: The default if the attribute is not used. -- `small`: 8px -- `medium`: 16px -- `large`: 32px - -Has no effect on ``. - -![app_image_fixed_size](../assets/blocks/docs-blocks-stacks-gap.png) - -#### Examples - -Two left aligned buttons with a gap between: - -```tsx - - - - -``` - -The gap is automatically subtracted from the available space children can occupy. - -```tsx - - - - -``` - -### `alignment` - - - -#### Examples - -Align content to the top left corner - -```tsx - - - -``` - -Align content to the bottom right corner - -```tsx - - - -``` - -### `reverse` - -A `boolean` that reverses the order of child elements if set to true. Possible values: - -- `true` -- `false`: The default if the attribute is not used. - -![Showing the reverse attribute](../assets/blocks/docs-blocks-stacks-reverse.png) - -### `grow` - - - -#### Examples - -In a `hstack` children grow horizontally. - -```tsx - - - - -``` - -In a `vstack` children grow vertically. - -```tsx - - - - -``` - -Has no effect on children within a ``. - -### `width` - - - -#### Examples - -Two colorful squares, taking up 50% of the width 100px each, with a height of 20px. - -```tsx - - - - -``` - -### `minWidth` - - - -### `maxWidth` - - - -### `height` - - - -#### Examples - -Sets the height as an absolute value - -```tsx - ... -``` - -Sets the height as a percentage of the parent. - -```tsx - ... -``` - -### `minHeight` - - - -### `maxHeight` - - - -### `backgroundColor` - - - -Defaults to `transparent`. - -### `borderWidth` - -An `enum` for setting the border width of the stack. The border sits between edge of the element and the content area. Possible values: - -- `none`: The default if the attribute is not used. -- `thin`: 1px -- `thick`: 2px - -![Showing all the borderWidth options](../assets/blocks/docs-blocks-stacks-border-width.png) - -### `borderColor` - - - -Defaults to `neutral-border-weak`. - -### `cornerRadius` - -En `enum` that rounds all 4 corners of the stack. Clips content. Possible values: - -- `none`: The default if the attribute is not used. -- `small`: 8px. -- `medium`: 16px. -- `large`: 24px. -- `full`: 50%. - -![Showing all the cornerRadius options](../assets/blocks/docs-blocks-stacks-corner-radius.png) - -## Functions - -### `onPress` - - - -#### Examples - -```tsx - console.log('clicked')} -> -``` - -## Examples - -An ``. Open in [Play](https://developers.reddit.com/play#pen/N4IgdghgtgpiBcIQBoQGcBOBjBICWUADgPYYAuABMACIwBudeZAvhQGYbFQUDkAAgBN6jMgHpCAVwBGAGzxYAtBEJ4eAHTAbaDJgDoIAgQGEJaMlwAKxMwBUAnoRgAKYBooVIseLwBy0GDzIbhQYMGBCGN4A+ljEYGQwAB6UALwAfFTB7qFkEhhgFE5Z7hQAPLLEWADWaGnFJWUAFmYQ1RQQcgDmYLDxKWogWGEJGBRQeIYyMAMUncr9ILACeBJQM4QGy2CdC0srayAUUq1VnZwS4UbEMqQLAMRsAAxPTwN1BQ2fpQnJFGh4AC8YAsZBAMJ1pocAO4wPCdRpkBZSa4CN4AUUS0EIUwoNiYU1Koh+ZHeny+aA2Q1GZ2IUIoolJZJK5QkZHMBXkcQWphgGBm-yBuxgy1WM2UjjBEDAQwWhAwBDBdjeAFU0LzCVJWezGWTCc0yCcdczRBVqrVigBKYLMDTMK2aMBJEjkChCNgQCQySjaEQoEB0Xn-OIIACMzCAA). - -```tsx - - - - Example Title - - - - - -``` - -A `` example. Open in [Play](https://developers.reddit.com/play#pen/N4IgdghgtgpiBcIQBoQGcBOBjBICWUADgPYYAuABMACIwBudeZAvhQGYbFQUDkAAgBN6jMgHpCAVwBGAGzxYAtBEJ4eAHTAbaDJgDoIAgQGEJaMlwAKxMwBUAnoRgAKYBooVIseLwBy0GDzIbhQYMGBCGN4A+ljEYGQwAB6UALwAfFTB7qFkEhhgFE5Z7hQAPLLEWADWaGnFJWV0ZhDVFBByAOZgsPEpauhkEOQUWGEJGP0UHcp96FDtMpOEBgJ4YB2zsKsSUP11BQ2HpQnJFGh4AF4wmzDbuyAUAO4weB0AFmSzUsQyAnsA4v4KAB5QhkPBxNClUQnMj7Q5HKQSMjmArnK43O6TZSOIYQMCjWZoGCxcJDOx7UHguIUACM0KRKLi8IRJXKyNRZ0u136WzwO2xhFxGHxhP6xNJAnJlLBEIKACYGRzmfVEcq0dzMfz7m0hTA8QSeegSXEpRgKSA0lS5RQAMxKplgFkI6FNQbVZ1s0QVaq1YoASmCzA0zEDmjASRIwyEbAgEhklG0IhQIDoMAw5ziCFpzCAA). - -```tsx - - - - Game Options - - - - - - -``` - -A `` example. Open in [Play](https://developers.reddit.com/play#pen/N4IgdghgtgpiBcIQBoQGcBOBjBICWUADgPYYAuABMACIwBudeZAvhQGYbFQUDkAAgBN6jMgHpCAVwBGAGzxYAtBEJ4eAHTAbaDJgDoIAgQGEJaMlwAKxMwBUAnoRgAKYBooVIseLwBy0GDzIbhQYMGBCGN4A+ljEYGQwAB6UALwAfFTB7qFkEhhgFE5Z7hQAPLLEWADWaGnFJWUAXmYQ1RQQcgDmYLDxKWogWGEJGBRQeIYyMAMUAO4TZAAWKTwAzAAM64SJPBSLMHidi2QrAEyb27tSrVWdnBLhRsQypP0gAMQA7AAcAJzrvyMAzqBQaYNKCWSFDQeEaMDeiUSMggGE60xAcwORxOAykzwEM1iLwwb3eADEKZSZmY7FM3vsDHgwJ1gQAhACSAHEKDYAKIADRspVEkLIILB4NF0Nh8IGiTQUA6MhmsyxxzeoU6EmRGEJz1eA3JlIp1LItNlIDxAjsMw6hx6wzeeLI5m4Q3iMF1IHFErBAHkCuZCBRiGxoYqZDIKKL6uCRUkxbH3MLmmQbj6GsKKtVasUAJTBZgaZgFzRgJIkcgUIRsCDayjaEQoEB0T0wuIIACMzCAA). - -```tsx - - - - BIG TEXT - - - On top of small text - - - -``` diff --git a/versioned_docs/version-0.12/blocks/text.mdx b/versioned_docs/version-0.12/blocks/text.mdx deleted file mode 100644 index e44596d0..00000000 --- a/versioned_docs/version-0.12/blocks/text.mdx +++ /dev/null @@ -1,193 +0,0 @@ -import AttributeMinHeight from './attributes/_minHeight.md'; -import AttributeMaxHeight from './attributes/_maxHeight.md'; -import AttributeMinWidth from './attributes/_minWidth.md'; -import AttributeMaxWidth from './attributes/_maxWidth.md'; -import AttributeGrow from './attributes/_grow.md'; -import AttributeAlignment from './attributes/_alignment.md'; -import AttributeOnPress from './attributes/_onPress.md'; -import AttributeWidth from './attributes/_width.mdx'; -import AttributeHeight from './attributes/_height.mdx'; -import AttributeColor from './attributes/_color.md'; - -# Text - -The `` element enables you add and format text in your UI. - -By default, text blocks are limited to a single line. If you have a lot of text, it grows horizontally and the overflow gets clipped. There are two properties to make working with multi-line text easier: `wrap` and `overflow`. For text to wrap onto a new line, there needs to be a width defined. - -## Attributes - -### `style` - -An `enum` for defining the text style. Shorthand for setting `size`, `weight`, and `color` at the same time. Possible values: - -- `metadata` -- `body`: The default if the attribute is not used. -- `heading` - -### `size` - -An `enum` for setting the text size and line height. Overrides the `style` attribute if both are set. Possible values: - -- `xsmall` - - Text size: 10px - - Line height: 16px -- `small` - - Text size: 12px - - Line height: 16px -- `medium`: The default if the attribute is not used. - - Text size: 14px - - Line height: 20px -- `large` - - Text size: 16px - - Line height: 20px -- `xlarge` - - Text size: 18px - - Line height: 24px -- `xxlarge` - - Text size: 24px - - Line height: 28px - -![Text sizes](../assets/blocks/docs-blocks-text-size.png) - -### `weight` - -An `enum` for setting the text weight. Possible values: - -- `regular`: The default if the attribute is not used. -- `bold` - -![Text weights](../assets/blocks/docs-blocks-text-weights.png) - -### `color` - - - -Defaults to `neutral-content`. - -### `alignment` - - - -### `grow` - - - -### `outline` - -An `enum` for adding an outline to text. The outline is either `white` or `black`, depending on which color provides the most contrast against the text color. Possible values: - -- `none`: The default if the attribute is not used. -- `thin`: 1px -- `thick`: 2px - -### `selectable` - -A `boolean` for determing if users can select the text. Possible values: - -- `true`: The default if the attribute is not used. -- `false` - -### `wrap` - -A `boolean` for toggling text wrapping, enabling text to wrap onto multiple lines. Only wraps if the width is constrained. Possible values: - -- `true` -- `false`: The default if the attribute is not used. - -### `overflow` - -An `enum` for determing how text overflow is handled. Possible values: - -- `clip`: The default if the attribute is not used. -- `ellipsis` - -### `width` - - - -### `minWidth` - - - -### `maxWidth` - - - -### `height` - - - -### `minHeight` - - - -### `maxHeight` - - - -## Functions - -### `onPress` - - - -#### Examples - -```tsx - console.log('world')}>Hello -``` - -## Examples - -### Text wrapping - -Wrap the text onto a new line once the available width has been filled. The number of lines is a function of the font size and the text string length. - -![default](../assets/text_formats/text_wrapping.png) - -```tsx - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam consectetur rutrum tempor. - -``` - -### Changing the overflow symbol - -Set the overflow attribute to “ellipsis” provides a visual cue to users that there is additional text. - -![default](../assets/text_formats/overflow.png) - -```tsx - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam consectetur rutrum tempor. Donec - lacinia nulla dolor, eget iaculis quam imperdiet quis. Duis nec velit dignissim, lobortis ligula - eu, mattis arcu. - -``` - -### Constricting height and overflow indicator - -Define the height to limit the number of lines occupied by the text. - -![default](../assets/text_formats/constricting_overflow.png) - -```tsx - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam consectetur rutrum tempor. Donec - lacinia nulla dolor, eget iaculis quam imperdiet quis. Duis nec velit dignissim, lobortis ligula - eu, mattis arcu. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam consectetur - rutrum tempor. Donec lacinia nulla dolor, eget iaculis quam imperdiet quis. Duis nec velit - dignissim, lobortis ligula eu, mattis arcu. - -``` - -### Constricting height with limited content - -Avoid UI shift by setting a fixed height. - -![default](../assets/text_formats/constricting_limited.png) - -```tsx -Hello world -``` diff --git a/versioned_docs/version-0.12/capabilities/blocks/app_image_assets.md b/versioned_docs/version-0.12/capabilities/blocks/app_image_assets.md deleted file mode 100644 index ec8e5c46..00000000 --- a/versioned_docs/version-0.12/capabilities/blocks/app_image_assets.md +++ /dev/null @@ -1,116 +0,0 @@ -# Adding images - -Add images to your interactive post. - -You can add things like a logo for the community, a special artistic expression, or new characters to unlock in your game. - -:::note -The [Reddit Content Policy](https://www.redditinc.com/policies/content-policy) applies to images in your app. All project assets are subject to an app review and screened by automated Reddit systems at upload. Images that violate the content policy will be removed. -::: - -## What’s supported? - -- File types: JPG/JPEG, PNG, or GIF -- Image size: 20MB for JPG/PNG; 40MB for GIF -- Folder size: 1GB per app version - -The image file name does not have any constraints, but needs to reside in the “assets” folder. The file type is determined by inspecting its contents. - -## How it works - -Add an image to your app’s `assets` folder. The image is uploaded to Reddit and accessible in your app source code when you upload your app. New projects should have an `assets` folder in the root directory. If you’re updating an existing project, you’ll need to add the `assets` folder. - -Once the images are uploaded to Reddit, you can show the image in one of two ways: - -1. Use the Blocks UI component to embed your image in a post (to learn more about how to style images, check out our [blocks gallery](https://www.reddit.com/r/Devvit/post-viewer/1545cls/custom_post_block_kit_gallery/)). - -:::note -The imageWidth and imageHeight attributes are in device independent pixels (DIPs). -::: - -```tsx - -``` - -2. Use the URL string to get the image’s public URL from the code. - -```ts -context.assets.getURL('imageName.jpg'); -``` - -:::note -The image name is case-sensitive. Additionally, you can use nested folders to organize your assets. For example, -`assets/images/imageName.jpg` will be uploaded to Reddit as `images/imageName.jpg`. -::: - -## Use an image in a post - -1. Locate the `assets` folder at the root of your project directory (or create one if it’s not there). -2. Create your image and add it to the `assets` folder. -3. Update your code to import the new asset. Here’s an example: - -```tsx -Devvit.configure({ - redditAPI: true, -}); - -const render: Devvit.CustomPostComponent = () => { - return ( - - - Hello! - - - - ); -}; - -// addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. -Devvit.addCustomPostType({ - name: 'My custom post', - description: 'Test custom post for showing a custom asset!', - render, -}); - -Devvit.addMenuItem({ - location: 'subreddit', - label: 'Make custom post with image asset', - onPress: async (event, context) => { - const subreddit = await context.reddit.getSubredditById(context.subredditId); - - await context.reddit.submitPost({ - subredditName: subreddit.name, - title: 'Custom post!', - preview: render(context), - }); - }, -}); -``` -[View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) - -4. Run `devvit upload`. -5. Install or update your app on your subreddit. -6. From the subreddit menu, click "Make custom post with image asset". This will create a new post with the image embedded in it. - -![image_in_a_post](../../assets/docs-app-image-assets-1.png) - -## Show an image URL in a toast - -1. Locate the `assets` folder at the root of your project directory (or create one if it’s not there). -2. Create your image and add it to the `assets` folder. -3. Update your code to import the new asset. Here’s an example: - -```ts -Devvit.addMenuItem({ - location: 'subreddit', - label: 'Get image URL', - onPress: async (event, context) => { - const url = await context.assets.getURL('hello.png'); - context.ui.showToast(url); // should show 'https://i.redd.it/.png' - // and if you go to the URL it showed, it should be your art - // Note, it doesn't display the image this way, just the URL as text! - }, -}); -``` - -![image_in_a_post](../../assets/docs-app-image-assets-2.png) diff --git a/versioned_docs/version-0.12/capabilities/blocks/blocks_payments.md b/versioned_docs/version-0.12/capabilities/blocks/blocks_payments.md deleted file mode 100644 index 961ba400..00000000 --- a/versioned_docs/version-0.12/capabilities/blocks/blocks_payments.md +++ /dev/null @@ -1,253 +0,0 @@ -# Adding payments - -You can use the payments template to build your app or add payment functionality to an existing app. - -:::note -[Devvit Web](../../capabilities/devvit-web/devvit_web_overview.mdx) is the recommended approach for all interactive experiences. We recommend [migrating your app](../../earn-money/payments/payments_migrate.mdx) to Devvit Web payments. -::: - -To start with a template, select the payments template when you create a new project or run: - -```bash -devvit new -``` - -To add payments functionality to an existing app, run: - -```bash -npm install @devvit/payments -``` - -:::note -Make sure you’re on Devvit 0.11.3 or higher. See the [quickstart](https://developers.reddit.com/docs/next/quickstart) to get up and running. -::: - -## Register products - -Register products in the src/products.json file in your local app. To add products to your app, run the following command: - -```bash -devvit products add -``` - -Registered products are updated every time an app is uploaded, including when you use [Devvit playtest](../../guides/tools/playtest.md). - -
- Click here for instructions on how to add products manually to your products.json file. -The JSON schema for the file format is available at https://developers.reddit.com/schema/products.json. - -Each product in the products field has the following attributes: -| **Attribute** | **Description** | -| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `sku` | A product identifier that can be used to group orders or organize your products. Each sku must be unique for each product in your app. | -| `displayName` | The official name of the product that is displayed in purchase confirmation screens. The name must be fewer than 50 characters, including spaces. | -| `description` | A text string that describes the product and is displayed in purchase confirmation screens. The description must be fewer than 150 characters, including spaces. | -| `price` | An predefined integer that sets the product price in Reddit Gold. See details below. | -| `image.icon` | **(optional)** The path to the icon that represents your product in your assets folder. | -| `metadata` | **(optional)** An optional object that contains additional attributes you want to use to group and filter products. Keys and values must be alphanumeric (a - Z, 0 - 9, and - ) and contain 30 characters or less. You can add up to 10 metadata keys. Metadata keys cannot start with "devvit-". | -| `accountingType` | Categories for how buyers consume your products. Possible values are:
  • `INSTANT` for purchased items that are used immediately and disappear.
  • `DURABLE` for purchased items that are permanently applied to the account and can be used any number of times
  • `CONSUMABLE` for items that can be used at a later date but are removed once they are used.
  • `VALID_FOR_` values indicate a product can be used throughout a period of time after it is purchased.
| - -
- -## Price products - -Product prices are predefined and must be one of the following gold values: - -- 5 gold ($0.10) -- 25 gold ($0.50) -- 50 gold ($1) -- 100 gold ($2) -- 150 gold ($3) -- 250 gold ($5) -- 500 gold ($10) -- 1000 gold ($20) -- 2500 gold ($50) - -:::note -Actual payments will not be processed until your products are approved. While your app is under development, you can use sandbox payments to [simulate purchases](../../earn-money/payments/payments_test#simulate-purchases). -::: - -## Design guidelines - -You’ll need to clearly identify paid products or services. Here are some best practices to follow: - -- Use a short name, description, and image for each product. -- Don’t overwhelm users with too many items. -- Try to keep purchases in a consistent location or use a consistent visual pattern. -- Only use the gold icon to indicate purchases for Reddit Gold. - -### Product image - -Product images need to meet the following requirements: - -- Minimum size: 256x256 -- Supported file type: .png - -If you don’t provide an image, the default Reddit product image is used. - -![default image](../../assets/default_product_image.png) - -**Example** - -```json -{ - "$schema": "https://developers.reddit.com/schema/products.json", - "products": [ - { - "sku": "god_mode", - "displayName": "God mode", - "description": "God mode gives you superpowers (in theory)", - "price": 25, - "images": { - "icon": "products/extra_life_icon.png" - }, - "metadata": { - "category": "powerup" - }, - "accountingType": "CONSUMABLE" - } - ] -} -``` - -### Purchase buttons (required) - -#### Blocks - -The `ProductButton` is a Devvit blocks component designed to render a product with a purchase button. It can be customized to match your app's look and feel. - -**Usage:** - -```tsx - payments.purchase(p.sku)} - appearance="tile" -/> -``` - -##### `ProductButtonProps` - -| **Prop Name** | **Type** | **Description** | -| ------------------ | ----------------------------------------------- | ------------------------------------------------------------------------------------ | -| `product` | `Product` | The product object containing details such as `sku`, `price`, and `metadata`. | -| `onPress` | `(product: Product) => void` | Callback function triggered when the button is pressed. | -| `showIcon` | `boolean` | Determines whether the product icon is displayed on the button. Defaults to `false`. | -| `appearance` | `'compact'` | `'detailed'` | `'tile'` | Defines the visual style of the button. Defaults to `compact`. | -| `buttonAppearance` | `string` | Optional [button appearance](../../blocks/button.mdx#appearance). | -| `textColor` | `string` | Optional [text color](../../blocks/text.mdx#color). | - -#### Webviews - -Use Reddit’s primary, secondary, or bordered button component and gold icon in one of the following formats: - -![default image](../../assets/payments_button_purchase.png) - -Use a consistent and clear product component to display paid goods or services to your users. Product components can be customized to fit your app, like the examples below. - -![default image](../../assets/payments_component_button.png) - -![default image](../../assets/payments_component_list.png) - -![default image](../../assets/payments_component_tile.png) - -## Complete the payment flow - -Use `addPaymentHandler` to specify the function that is called during the order flow. This customizes how your app fulfills product orders and provides the ability for you to reject an order. - -Errors thrown within the payment handler automatically reject the order. To provide a custom error message to the frontend of your application, you can return {success: false, reason: } with a reason for the order rejection. - -This example shows how to issue an "extra life" to a user when they purchase the "extra_life" product. - -```ts -import { type Context } from "@devvit/public-api"; -import { addPaymentHandler } from "@devvit/payments"; -import { Devvit, useState } from "@devvit/public-api"; - -Devvit.configure({ - redis: true, - redditAPI: true, -}); - -const GOD_MODE_SKU = "god_mode"; - -addPaymentHandler({ - fulfillOrder: async (order, ctx) => { - if (!order.products.some(({ sku }) => sku === GOD_MODE_SKU)) { - throw new Error("Unable to fulfill order: sku not found"); - } - if (order.status !== "PAID") { - throw new Error("Becoming a god has a cost (in Reddit Gold)"); - } - - const redisKey = godModeRedisKey(ctx.postId, ctx.userId); - await ctx.redis.set(redisKey, "true"); - }, -}); -``` - -## Implement payments - -The frontend and backend of your app coordinate order processing. - -![Order workflow diagram](../../assets/payments_order_flow_diagram.png) - -To launch the payment flow, create a hook with `usePayments()` followed by `hook.purchase()` to initiate the purchase from the frontend. - -This triggers a native payment flow on all platforms (web, iOS, Android) that works with the Reddit backend to process the order. The `fulfillOrder()` hook calls your app during this process. - -Your app can acknowledge or reject the order. For example, for goods with limited quantities, your app may reject an order once the product is sold out. - -### Get your product details - -Use the `useProducts` hook or `getProducts` function to fetch details about products. - -```tsx -import { useProducts } from "@devvit/payments"; - -export function ProductsList(context: Devvit.Context): JSX.Element { - // Only query for products with the metadata "category" of value "powerup". - // The metadata field can be empty - if it is, useProducts will not filter on metadata. - const { products } = useProducts(context, { - metadata: { - category: "powerup", - }, - }); - - return ( - - {products.map((product) => ( - - {product.name} - {product.price} - - ))} - - ); -} -``` - -You can also fetch all products using custom-defined metadata or by an array of skus. Only one is required; if you provide both then they will be AND’d. - -```tsx -import { getProducts } from '@devvit/payments'; -const products = await getProducts({, -}); -``` - -### Initiate orders - -Provide the product sku to trigger a purchase. This automatically populates the most recently-approved product metadata for that product id. - -**Example** - -```tsx -import { usePayments } from '@devvit/payments'; - -// handles purchase results -const payments = usePayments((result: OnPurchaseResult) => { console.log('Tried to buy:', result.sku, '; result:', result.status); }); - -// for each sku in products: - -``` diff --git a/versioned_docs/version-0.12/capabilities/blocks/dimensions.md b/versioned_docs/version-0.12/capabilities/blocks/dimensions.md deleted file mode 100644 index 78651f00..00000000 --- a/versioned_docs/version-0.12/capabilities/blocks/dimensions.md +++ /dev/null @@ -1,76 +0,0 @@ -# Dimensions - -Create responsive interactive posts. - -Dimensions provides a way for you to create responsive interactive posts by giving you the dimensions of the root node as part of the context object. This lets you write responsive interfaces based on the space available within the context object. - -Dimensions are dynamic and update in real time if the device or viewport changes. You can also resize your screen as you develop to see how your posts respond in real time. This is the same post displayed on a phone (left) and a tablet (right): - -![A post that is aware of its size](../../assets/custom-posts/custom-posts-dimensions.png) - -You can use dimensions to: - -- Show a different experience on mobile compared to desktop (e.g., one column on mobile and two columns on desktop) -- Use pixel-based calculations for precision where percentages won’t work. - -## Getting dimensions - -:::warning Deprecation notice -`addCustomPostType` is deprecated and will be unsupported. It will not work after June 30. [View announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) -::: - -Dimension information is specified in density-independent pixels. These pixel units are located on the context object. - -| Dimension | Description | -| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Height | The pixel unit height of the interactive post. This is a fixed value that will not change based on the height property provided on `Devvit.addCustomPostType`. | -| Width | The pixel width of the containing box for your interactive post. | -| Scale | The [pixel scale](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio) that determines the resolution for how your custom post renders on the device. | - -## Example - -This example shows a custom post that specifies dimensions. - -```tsx -// addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. -Devvit.addCustomPostType({ - name: 'Dimensions app', - render: (context) => { - return context.dimensions?.width < 300 ? : ; - }, -}); -``` -[View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) - -This example shows that dimensions always references the root element, even if it’s in a child element. Although `MyHeader` is a component within the root element, this does not change the custom post dimensions provided in the context object. - -```tsx -export const MyHeader: Devvit.BlockComponent = (props, context) => { - // Dimensions reflect the custom post, not the Header component - const dimensions = context.dimensions; - return ( - - Header - - ); -}; - -// addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. -Devvit.addCustomPostType({ - name: 'Name', - render: (_context) => { - return ; - }, -}); -``` -[View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) - -## Limitations - -- Dimensions are only for the custom post box. -- Dimensions for specific elements within the custom post box are not supported. -- Dimensions for specific device screen sizes (phone, tablet, desktop) are not supported. diff --git a/versioned_docs/version-0.12/capabilities/blocks/optimize_performance.md b/versioned_docs/version-0.12/capabilities/blocks/optimize_performance.md deleted file mode 100644 index 031c699a..00000000 --- a/versioned_docs/version-0.12/capabilities/blocks/optimize_performance.md +++ /dev/null @@ -1,406 +0,0 @@ -# Optimizing performance - -You can optimize your app to make it run faster, use resources more efficiently, and create a better user experience. Here’s how. - -## Always use the latest public-api - -You can check your current Devvit version on the apps version panel. To update your app to use the latest version of the public API: - -1. Run `npm install -s devvit` in your project folder to update the Devvit CLI locally. - -2. In your project directory, run the following commands: - -- `npx devvit update app` -- `npm install` - -:::note -You must update the CLI before you upgrade @devvit/public-api in your project. -::: - -## Write performant requests - -These best practices will optimize your app. - -### Get as much data as possible from the context - -If you only need the name of the current user: - -- Avoid requests like `context.reddit.getCurrentUser()` or `context.reddit.getUserById(context.userId)`. -- Use `context.reddit.getCurrentUsername()` instead. - -If you only need the subreddit name: - -- Don’t request the whole Subreddit model with `context.reddit.getCurrentSubreddit()`. -- Use `context.reddit.getCurrentSubredditName()` instead. - -### Cache requests to RedditAPI or external resources - -Use `context.cache` to reduce the amount of requests to optimize performance and running costs of your application. - -### Leverage scheduled jobs to fetch or update data - -Use [scheduler](../server/scheduler.mdx) to make large data requests in the background and store it in [Redis](../server/redis.mdx) for later use. You can also [fetch data for multiple users](#how-to-cache-data). - -### Batch API calls to make parallel requests - -Every request defined in `useState` is blocking the render function. You can improve app performance by [making parallel requests](#how-to-make-parallel-requests). - -## Ensure your app has a lightweight first view - -The faster the first view appears on the user’s screen, the better the user experience. You can minimize and delay the data loading necessary for your app to display the first view. To do this: - -- Use setCustomPostPreview to make a dynamic, compelling preview that loads quickly. -- Use [useAsync](./working_with_useasync.md) to load the necessary data without blocking the rendering process. -- Import [useState](./working_with_usestate.md) or [useAsync](./working_with_useasync.md) to load the data needed for the specific component only when that component is rendered. - -## How to: make parallel requests - -In Devvit, the first render happens on the server side. Parallel fetch requests will speed up the first render. - -### Before optimization: individual fetch requests - -In the render function of this interactive post, the app fetches data about the post, the user, the weather, and the leaderboard stats. - -```tsx -import { Devvit, useState } from "@devvit/public-api"; - -render: (context) => { - const [postInfo] = useState(async () => { - return await getThreadInfo(context); - }); - - const [user] = useState(async () => { - return await getUser(context); - }); - - const [weather] = useState(async () => { - return await getTheWeather(context); - }); - - const [leaderboardStats] = useState(async () => { - return await getLeaderboard(context); - }); - - // the rest of the render function -}; -``` - -If each request takes roughly 250 ms, then four requests will take around 1 second to resolve. If we change the example to make those requests in parallel, it would take 250 ms to resolve all four! - -### After optimization: parallel fetch requests - -You can do this in two ways: - -- Use [useAsync](./working_with_useasync.md) to make everything non-blocking. -- Use [Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) inside one [useState](./working_with_usestate.md) method to get all of the information at once. - -:::note -The main difference between these two methods is that `useState` blocks render until it is resolved, and `useAsync` allows the app to render immediately and requires loading states while the requests resolve. -::: - -#### useAsync - -This is the best choice for performance because it allows you to render parts of your application while others may still be loading. Here’s how the same example looks for useAsync: - -```tsx -import { Devvit, useAsync } from "@devvit/public-api"; - -const { data: postInfo, loading: postInfoLoading } = useAsync(async () => { - return await getThreadInfo(context); -}); - -const { data: user, loading: userLoading } = useAsync(async () => { - return await getUser(context); -}); - -const { data: weather, loading: weatherLoading } = useAsync(async () => { - return await getTheWeather(context); -}); - -const { data: leaderboardStats, loading: leaderboardStatsLoading } = useAsync( - async () => { - return await getLeaderboard(context); - }, -); -``` - -#### useState - -This is the same example using useState. - -```tsx -import { Devvit, useState } from "@devvit/public-api"; - -render: (context) => { - const [appState, setAppState] = useState(async () => { - const [postInfo, user, weather, leaderboardStats] = await Promise.all([ - getThreadInfo(context), - getUser(context), - getTheWeather(context), - getLeaderboard(context), - ]); - return { - postInfo, - user, - weather, - leaderboardStats, - }; - }); - - const { postInfo, user, weather, leaderboardStats } = appState; - - // the rest of the render function -}; -``` - -You can see the app gets the same variables from the state object, which means that you won’t need to change the way you access the data from the state in the rest of the app. - -:::note -If you need to update one of the state props, you’ll need to do `setAppState({...appState, postInfo: newPostInfo})` instead of `setPostInfo(newPostInfo)`. -::: - -## How to: cache data - -The following example shows how unoptimized code for fetching data from an external resource, like a weather API, looks: - -```tsx -import { Devvit, useState } from "@devvit/public-api"; - -// naive, non-optimal way of fetching that kind of data -const [externalData] = useState(async () => { - const response = await fetch("https://external.weather.com"); - - return await response.json(); -}); -``` - -### Problem: request overload - -In this case, state is initialized for each user that sees the app. This means that in a large subreddit where thousands of users can see the post at the same time, your app would make thousands of requests to the external resource. If you request the data in an interval, like to get a game score or stock market information, then the load repeats on each interval tick. This is not ideal. - -### Solution: make one request - -You can use a cache helper to make one request for data, save the response, and provide this response to all users requesting the same data. The cache lives at the subreddit level (not the app level). - -**Example: fetch weather data every 2 hours with cache helper** - -```tsx -import { Devvit, useState } from "@devvit/public-api"; - -// optimized, performant way of fetching that kind of data -const [externalData] = useState(async () => { - return context.cache( - async () => { - const response = await fetch("https://external.weather.com"); - return await response.json(); - }, - { - key: `weather_data`, - ttl: 2 * 60 * 60 * 1000, // 2 hours in milliseconds - }, - ); -}); -``` - -:::note -Do not cache sensitive information. Cache helper randomly selects one user to make the real request and saves the response to the cache for others to use. You should only use cache helper for non-personalized fetches, since the same response is available to all users. -::: - -### Solution: schedule a job - -Alternatively, you can use [scheduler](../server/scheduler.mdx) to make the request in background, save the response to [Redis](../server/redis.mdx), and avoid unnecessary requests to the external resource. - -**Example: fetch weather data every 2 hours with a scheduled job** - -```tsx -import { Devvit } from "@devvit/public-api"; - -Devvit.addSchedulerJob({ - name: "fetch_weather_data", - onRun: async (_event, context) => { - const response = await fetch("https://external.weather.com"); - const responseData = await response.json(); - await context.redis.set("weather_data", JSON.stringify(responseData)); - }, -}); - -Devvit.addTrigger({ - event: "AppInstall", - onEvent: async (_event, context) => { - await context.scheduler.runJob({ - cron: "0 */2 * * *", // runs at the top of every second hour - name: "fetch_weather_data", - }); - }, -}); - -// inside the render method -const [externalData] = useState(async () => { - return context.redis.get("fetch_weather_data"); -}); - -export default Devvit; -``` - -## How to: update the client state without intervals​ - -If you have a game with a leaderboard, scores need to be updated immediately for all active sessions. - -One way to achieve this is to set an interval to fetch leaderboard stats as often as possible, but making a request in the interval would switch the execution to server environment and affect the performance of the app. In addition, each time a user viewed the app, it would spam the leaderboard database in an attempt to get the latest data. - -To optimize performance, use [realtime](../realtime/overview.md) to send the leaderboard stats to all users directly. - -### Without realtime​ - -Before using realtime, the leaderboard fetching code looked like this: - -```tsx -const getLeaderboard = async () => - await context.redis.zRange("leaderboard", 0, 5, { - reverse: true, - by: "rank", - }); - -const [leaderboard, setLeaderboard] = useState(async () => { - return await getLeaderboard(); -}); - -const leaderboardInterval = useInterval(async () => { - const newLeaderboard = await getLeaderboard(); - setLeaderboard(newLeaderboard); -}, 1000); - -leaderboardInterval.start(); -``` - -And code for updating the leaderboard looked like this: - -```tsx -await context.redis.zAdd("leaderboard", { member: username, score: gameScore }); -``` - -### With realtime​ - -Using realtime, you can fetch the leaderboard during the initial render and emit the new leaderboard state when the user completes the game. - -This is the updated game completion code: - -```tsx -// stays as is -await context.redis.zAdd("leaderboard", { member: username, score: gameScore }); -// new code -context.realtime.send("leaderboard_updates", { - member: username, - score: gameScore, -}); -``` - -Now replace the interval with the realtime subscription: - -```tsx -const [leaderboard, setLeaderboard] = useState(async () => { - return await getLeaderboard(); -}); // stays as is - -const channel = useChannel({ - name: "leaderboard_updates", - onMessage: (newLeaderboardEntry) => { - const newLeaderboard = [...leaderboard, newLeaderboardEntry] // append new entry - .sort((a, b) => b.score - a.score) // sort by score - .slice(0, 5); // leave top 5 - setLeaderboard(newLeaderboard); // update the state - }, -}); - -channel.subscribe(); -``` - -Using realtime ensures that extra requests will not impact your app’s performance, and the app only emits the event when the data has changed. - -## How to: measure your app’s performance - -You can use `console.log` to calculate the operation time of your app. - -This example shows the render function of a basic post that fetches the number of subreddit members. - -```tsx -const [subscriberCount] = useState(async () => { - const devvitSubredditInfo = await context.reddit.getSubredditInfoByName('devvit'); - return devvitSubredditInfo.subscribersCount || 0; -}); - -return ( - // app markup goes here -); -``` - -### Add a console log - -Before the above post can be rendered, two pieces of data need to be requested: -subreddit subscribers count and user avatar url. You can measure the amount of time it takes to request data inside the `useState` hook. - -To do this, you can add: - -- A variable that stores the timestamp of the operation start. -- A console log between the operation end and the return statement that prints the difference between the start and end in milliseconds. - -```tsx -const [subscriberCount] = useState(async () => { - const startSubscribersRequest = Date.now(); // a reference point for the request start - const devvitSubredditInfo = - await context.reddit.getSubredditInfoByName("devvit"); - - console.log( - `subscribers request took: ${Date.now() - startSubscribersRequest} milliseconds`, - ); - - return devvitSubredditInfo.subscribersCount || 0; -}); -``` - -Alternatively, you can measure the whole data collection step. On the first line of the render method you can declare a state variable: - -```tsx -const [performanceStartRender] = useState(Date.now()); // a reference point for the render start -``` - -Add a console.log before the return statement: - -```tsx -console.log( - `Getting the data took: ${Date.now() - performanceStartRender} milliseconds`, -); -``` - -All of that put together will look like this: - -```tsx -const [performanceStartRender] = useState(Date.now()); // a reference point for the render start - -const [subscriberCount] = useState(async () => { - const startSubscribersRequest = Date.now(); // a reference point for the request start - const devvitSubredditInfo = await context.reddit.getSubredditInfoByName('devvit'); - - console.log(`subscribers request took: ${Date.now() - startSubscribersRequest} milliseconds`); - - return devvitSubredditInfo.subscribersCount || 0; -}); - -console.log(`Getting the data took: ${Date.now() - performanceStartRender} milliseconds`); - -return ( - // app markup goes here -); -``` - -### Review your data - -Once you’ve set up your data collection, you can expect something like that in your logs: - -``` -subscribers request took: 106 milliseconds -getting user avatar url took: 203 milliseconds -getting the data took: 310 milliseconds -``` - -This will help you find the operations that affect your app’s performance. diff --git a/versioned_docs/version-0.12/capabilities/blocks/overview.md b/versioned_docs/version-0.12/capabilities/blocks/overview.md deleted file mode 100644 index b6cda57a..00000000 --- a/versioned_docs/version-0.12/capabilities/blocks/overview.md +++ /dev/null @@ -1,114 +0,0 @@ -# Overview - -:::warning - -With the introduction of [inline webviews](../server/launch_screen_and_entry_points/view_modes_entry_points), Devvit Web is now the recommended approach for all interactive experiences that need in-feed interactions or pop-out views. - -The Blocks documentation below remains to support developers maintaining existing apps built with Blocks. - -::: - -Devvit Blocks is a framework that allows you to build apps with Reddit native components. Blocks is optimized for speed and ease of use, but is not recommended for games due to technical constraints and limitations. - -:::warning -With the introduction of [0.12.2](../../changelog.md#devvit-0122-inline-mode-launch-screensexpanded-app-experiences-and-developer-logs), you can now render apps directly within the feed using Devvit Web. This makes Devvit Web the recommended path for all new projects. -::: - -## Examples - -### [r/WallStreetBets](https://www.reddit.com/r/wallstreetbets) - -Wall Street Bets's daily thread tracks stock performance and discussion on r/wallstreetbets. It's able to render in the feed quickly, and refresh automatically as new data is available. - -## Available blocks - -We support the following elements: - -### Containers - -- **Blocks** -- [**HStack**](../../blocks/stacks.mdx) -- [**VStack**](../../blocks/stacks.mdx) -- [**ZStack**](../../blocks/stacks.mdx) - -### Objects - -- [**Text**](../../blocks/text.mdx) -- [**Button**](../../blocks/button.mdx) -- [**Spacer**](../../blocks/spacer.mdx) -- [**Image**](../../blocks/image.mdx) -- [**Icon**](../../blocks/icon.mdx) - -Further elements (components) may be derived from these blocks, and obey the same rules. - -## Sizing - -### Paddings and gaps - -- We're operating in a [border-box](https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing) model, where the padding is counted as part of the size of an element. -- Padding is incompressible. -- Gaps are implemented as if we're injecting spacers between all children. - -### Units - -There are two supported units: - -- `px`: device-independent pixels -- `%`: percent of parent container's available content area (i.e. subtracting the parent's padding and gaps) - -### Intrinsic size - -All elements have an _intrinsic size_. This is the size that they would be if there were no sizing modifiers applied to them. - -- **HStack**: Sum of the intrinsic widths of the children × the max of the intrinsic heights of the children (+ gaps and padding) -- **VStack**: Max of the intrinsic widths of the children × the sum of the intrinsic heights of the children (+ gaps and padding) -- **ZStack**: Max of the intrinsic sizes of the children (+ padding) -- **Text**: Size of the text without wrapping or truncation -- **Button**: Size of the text without wrapping or truncation (+ padding) -- **Spacer**: Size in pixels, as specified -- **Image**: imageWidth × imageHeight - -This size provides a baseline, which can be modified by attributes. There are a few sizing attributes: - -- `width` / `height` -- `minWidth` / `minHeight` -- `maxWidth` / `maxHeight` -- `grow` (operates in the _current direction_). - -:::note -Setting both `width` and `grow` simultaneously is not recommended, because then `grow` would become a no-op (overridden by `width`). -::: - -### Preferred size - -The preferred size is calculated based on the intrinsic size and the modifier attributes. The modifiers can conflict, in which case the precedence order is: - -`(most important) minWidth > maxWidth > width > aspect-ratio > grow > intrinsic width (least important)` - -Here, `grow` attempts to set `width="100%"`. Unlike actually setting `width="100%"`, `grow` can be flexibly adjusted later. Examples: - -- `` will always have a preferred size of 50px. (width overrides `grow`) -- `` will always take at least 50px, and will attempt to consume the available `width`. - -### Adjusted size - -Grow elements are flexible. Whenever the full width (or height) of a parent element is not fully utilized, a grow element will expand to fit the parent element, assuming that the other constraints permit. Grow is prioritized lower than the other sizing attributes, e.g. an element will never grow beyond its maxWidth. - -### Direction - -All elements inherit a direction for the purposes of growing. Things only grow in one direction at a time. - -| Element | Self Direction | Child Direction | -|-----------------------------------| -------------- | --------------- | -| Blocks | N/A | Vertical | -| [HStack](../../blocks/stacks.mdx) | Inherit | Horizontal | -| [VStack](../../blocks/stacks.mdx) | Inherit | Vertical | -| [ZStack](../../blocks/stacks.mdx) | Inherit | Inherit | -| [Text](../../blocks/text.mdx) | Horizontal | N/A | -| [Button](../../blocks/button.mdx) | Horizontal | N/A | -| [Spacer](../../blocks/spacer.mdx) | Inherit | N/A | -| [Image](../../blocks/image.mdx) | Inherit | N/A | - -### Overflow - -All containers clip overflown content. diff --git a/versioned_docs/version-0.12/capabilities/blocks/working_with_useasync.md b/versioned_docs/version-0.12/capabilities/blocks/working_with_useasync.md deleted file mode 100644 index c74afd4b..00000000 --- a/versioned_docs/version-0.12/capabilities/blocks/working_with_useasync.md +++ /dev/null @@ -1,176 +0,0 @@ -# Working with useAsync - -:::note -This feature is experimental, which means the design is not final but it's still available for you to use. -::: - -`useAsync` is a hook that allows your app to perform server side calls like `redis.get` or `reddit.getCurrentUser` without blocking the render process. - -## Blocking versus non-blocking - -The code you write in Javascript can be blocking or non-blocking. To keep applications speedy, non-blocking code is preferred. - -Blocking code produces a waterfall of actions. One line must happen after another, so the speed in which a program can render takes a large hit. We want to avoid waterfalls to provide a nice experience for users. - -### Without useAsync (blocking) example - -```tsx -const App = () => { - // This will block the render until data is fetched - const [message] = useState(async () => await redis.get('welcomeMessage')); - - return {message}; -}; -``` - -### With useAsync (non-blocking) example - -```tsx -const App = () => { - const { data: message, loading, error } = useAsync(async () => await redis.get('welcomeMessage')); - - return ( - - {loading && Loading...} - {error && Error fetching message} - {message && {message}} - - ); -}; -``` - -This example displays “Loading…” immediately while fetching the data. - -## Understanding useAsync - -### Syntax - -```ts -const { data, loading, error } = useAsync(asyncFunction, { depends: {JSON object}, finally: () => { function } }); -``` - -- asyncFunction: an asynchronous function that must return a valid JSON value. Note that setState is not allowed in this function. Use the finally parameter if you need to use setState (see the example below). -- depends (optional): a JSON object or array of JSON objects that, when changed, will cause the asyncFunction to re-execute. -- finally (optional): a callback function that runs after the async operation completes regardless of success or failure. Ideal for state updates (i.e. calls to setState) and side effects. - -### Return values - -- data: the data returned from the initializer. -- loading: a boolean that denotes if it is loading or not. -- error: an error if the request failed. - -**The initializer for `useAsync` (the first argument of the function) must return a valid JSON value.** This differs from React due to how Devvit components work across server and client boundaries. - -### Example: fetching the time and setting state - -```ts -useAsync( - async () => { - const response = await fetch(`https://date.api/today?timezone=${timezone}`); - return response.json(); - }, - { - depends: [timezone], - finally: (data, error) => { - if (error) { - console.error('Failed to load date data:', error); - } else { - setTodayDate(data['currentDate']); - } - }, - } -); -``` - -### Example: fetching user data - -```ts -// A normal useAsync function -const { - data: username, - loading: usernameLoading, - error: usernameError, -} = useAsync(async () => { - const user = await ctx.reddit.getCurrentUser(); - - return user?.username ?? null; -}); - -// A dependent useAsync function -const { - data: userDetails, - loading: userDetailsLoading, - error: userDetailsError, -} = useAsync( - // This will run every time the value in depends changes - async () => { - if (!username) return null; - - const resp = await fetch(`https://some-api/get-user-details/${username}`); - return await resp.json(); - }, - { - // NOTE: This will be deep equality for objects and arrays! - depends: username, - } -); -``` - -### Example: complete application - -This a simple application that leverages useAsync to fetch data in a non-blocking way and updates the app whenever the page changes. - -```ts -import { useAsync, useState } from '@devvit/public-api'; - -const App = () => { - const [count, setCount] = useState(1); - - const { data, loading, error } = useAsync( - async () => { - const response = await fetch(`https://xkcd.com/${count}/info.0.json`); - if (!response.ok) throw new Error(`HTTP error ${response.status}: ${response.statusText}`); - return await response.json(); - }, - { depends: [count] } - ); - - return ( - - XKCD Titles - - {loading && Loading...} - {error && ( - - {error.message} - - )} - {data && {data.title}} - - Comic Number: {count} - - - ); -}; - -//add your custom post -// addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. -Devvit.addCustomPostType({ - name: 'AppName', - description: 'Using useAsync with XKCD API', - render: App, -}); -``` -[View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) - -## When to use useAsync and useState - -In most cases, you'll want to use `useAsync` over `useState` to keep your app snappy. One downside is that you need to handle `loading` and `error` when using `useAsync` or `useState`. - -One situation where `useState` could be preferable is if your app only has one request and it must be resolved in order to show any part of the app. - -Another time to consider `useState` is if you need to update the value you fetched and display the new result to the user. `useState` provides a better API for that, but keep in mind you still need to persist the updates to Redis. We will be working on making synced updates easier in the future. - -:::note -There isn't an easy way to update `useAsync` data based off of an action in the app at the moment. We are working on a better way to allow this in the future. -::: diff --git a/versioned_docs/version-0.12/capabilities/blocks/working_with_useinterval.md b/versioned_docs/version-0.12/capabilities/blocks/working_with_useinterval.md deleted file mode 100644 index 3b7a436c..00000000 --- a/versioned_docs/version-0.12/capabilities/blocks/working_with_useinterval.md +++ /dev/null @@ -1,80 +0,0 @@ -# Working with useInterval - -Update live interactive posts in real time. - -:::note -This feature is experimental. There are known issues with interval timing especially when using server side calls, like calling Redis, within the useInterval function. This would result in intervals longer than what a developer specifies. -::: - -The `useInterval` method lets you build live apps that automatically update in real time. You can use this method to trigger a render of your interactive post to do things like add a countdown timer, update a scoreboard with new play information, or set up a clock. - -![Countdown timer](../../assets/custom-posts/custom-posts-useinterval.png) - -## How it works - -The `useInterval` method lets you request that your app be re-rendered at a specified interval. Intervals are set in milliseconds, with a 1000ms minimum. - -```tsx -import { Devvit, useState, useInterval } from '@devvit/public-api'; - -const [counter, setCounter] = useState(1000); - -const updateInterval = useInterval(() => { - setCounter((counter) => counter - 1); -}, 1000); - -updateInterval.start(); -``` - -## Key limitations - -- You can only use one active, running `useInterval` per app. -- The minimum allowed interval time is 1 second. -- The timing is specific to the app. Timing begins when a user engages with the app, and the time interval triggers a refresh. For example: - - If you build a stopwatch and add 1000 ms each time the interval runs, the actual elapsed time would include the time between interval triggers and would be something like 1020 ms. - - If you build a countdown timer, the time interval trigger subtracts the current time at render, so it will match the elapsed time. -- An interval runs while the post is visible on screen. If a user navigates away, it is suspended. - -## useInterval vs scheduler - -The `useInterval` method is different from the scheduler, in a few important ways: - -- `useInterval`executes on the client and triggers updates to your app locally (it’s similar to setInterval on the web). It can re-render on the user's device to do things like create animations or refresh the screen. -- Scheduler executes on the server and can be used to run background processes. - -## Example clock app - -### [Playground link](https://developers.reddit.com/play#pen/N4IgdghgtgpiBcIQBoQGcBOBjBICWUADgPYYAuABMACIwBudeZAvhQGYbFQUDkAAgBN6jMgHpCAVwBGAGzxYAtBEJ4eAHTAa2EsFjJ5iYCgHMYZAMISMGGGDIAVAjAAUASioaKFLIbSUwxADuFAC8FGAwwdQQZC6unt6+lAAWxFZooRQAymQYeGDGzgGBAHSmZAASaRhobq4lhBACORDkzgBMyLwADDzxRolgfhRQ+RKxGWE5eQVFQWVmALJjE3UNTS1tnT19CT5DlGgw+wKT2bn5hcULZFnHhqdrjc1krWQdXTy9-V42ZFZGAAGABJgKl0sx4KDRmBxjA0JDQUcTgjARpmBoNLQGEwSk0BJY-FwAArEPz2ACehBcwASkFg8F45hkxCwAGseMgEjYwEIMIz9rEAB6UEIAPg8Ay8+2GAG19LAukcHE4ALqZQUwEUlCRHFqxZxuUIS8qWay2FWwOoJaVJCj6dmZI3iijKxxW50msxmnmWuI-LyDYVkHVHACSdhgGDoEBkzgdbK6AEZuqn6n43m5MVKKH8ARRnDbAwAeOgZx2xvDGMCwOwhHhYC1RkZ4AQCGQwHgUZIwKvJMghNQgFPdACkQ7FRcDFGLwddeAAXjB60KhTJWqYeGLgAqYMxi6Jg5Oc14D2XXuzj9OAxiwMx+hotSRyBQhGwIBIZJRsSINCgQHQUZoAYYAIEmzBAA) - -### Code sample - -```tsx -import { Devvit, useState, useInterval } from '@devvit/public-api'; - -function getCurrentTime() { - const now = new Date(); - const hours = String(now.getHours()).padStart(2, '0'); - const minutes = String(now.getMinutes()).padStart(2, '0'); - const seconds = String(now.getSeconds()).padStart(2, '0'); - return `${hours}:${minutes}:${seconds}`; -} - -// addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. -Devvit.addCustomPostType({ - name: 'Clock', - render: (context) => { - const [time, setTime] = useState(() => getCurrentTime()); - const tick = () => setTime(() => getCurrentTime()); - useInterval(tick, 1000).start(); - - return ( - - {time} - - ); - }, -}); - -export default Devvit; -``` -[View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) \ No newline at end of file diff --git a/versioned_docs/version-0.12/capabilities/blocks/working_with_usestate.md b/versioned_docs/version-0.12/capabilities/blocks/working_with_usestate.md deleted file mode 100644 index f156f630..00000000 --- a/versioned_docs/version-0.12/capabilities/blocks/working_with_usestate.md +++ /dev/null @@ -1,177 +0,0 @@ -# Working with useState - -`useState` is a hook that gives your component reactive memory. It allows you to preserve information across renders and trigger updates to your app when state is updated. - -## Arguments - -`useState` takes an initial state as an argument, and returns an array of two items: - -- a state variable -- a setter function to update the state variable - -The initializer for `useState` (the first argument of the function) can be a static value or a function. **Regardless of how it is initialized, it must return a valid JSON value.** This differs from React due to how Devvit components work across server and client boundaries. - -```ts -// A static value -const [variable, setVariable] = useState('initialState'); - -// An synchronous function -const [variable, setVariable] = useState(() => 'initialState'); - -// An async function -const [count, setCount] = useState(async () => await redis.get('count')); -``` - -:::info -If the initializer function is async it will block render under the value is resolved. This is a common performance pitfall so please use it sparingly or group async calls together into one state variable using [`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all). - -A non-blocking equivalent for fetching data is [useAsync](./working_with_useasync.md). -::: - -## Updating state - -You’ll want to retrieve and update state at various points in your app. There are a few common patterns you’ll see for doing this. - -```tsx -import { Devvit, useState } from '@devvit/public-api'; - -const MyComponent = (_event, context) => { - // Setting up state - const [count, setCount] = useState(async () => { - return await context.redis.get('count'); - }); - - return ( - - Count: {count} - {/* Update set count by using a setter function */} - - - {/* Update set count by setting the value directly */} - - - ); -}; -``` - -## Using state hooks - -Leverage `useState` across components to preserve values and trigger reactive updates across your app. - -In this example, state hooks are used to: - -- Navigate between two app "pages" -- Add a simple counter that increments a value - -```tsx -//define types of props to pass to components -interface Props { - navigate: (page: PageType) => void; - setCount: (count: number) => void; - count: number; -} - -enum PageType { - HOMEPAGE, - COUNTPAGE, -} - -//useState is available as a global import from public API. -const App: Devvit.CustomPostComponent = ({ useState }: Devvit.Context) => { - // set state components - const [page, navigate] = useState(PageType.HOMEPAGE); - const [count, setCount] = useState(0); - - // pass state into a Props object. This can be passed to components for passing state across components in your app - const props: Props = { - navigate, - setCount, - count, - }; - - // pass props into components - if (page === PageType.COUNTPAGE) { - return ; - } else { - return ; - } -}; - -//'HomePage' is a component that returns this app's default UI -const HomePage: Devvit.BlockComponent = ({ navigate }) => { - //defines how to handle 'onPress' button event on page - const countPage: Devvit.Blocks.OnPressEventHandler = () => { - navigate(PageType.COUNTPAGE); - }; - - //UI blocks comprising a page - return ( - - - {'This app will teach you how to count!'} - - - - - - ); -}; - -//'CountPage'component -const CountPage: Devvit.BlockComponent = ({ navigate, setCount, count }, { redis }) => { - const incrementCount: Devvit.Blocks.OnPressEventHandler = () => { - setCount((count) => count + 1); - // note: to preserve the value of 'count' longerterm - // you would need to add a separate method here. For example: - // await redis.set("count", count) - }; - - return ( - - - {'Press the button to add +1'} - - {count} - - - - - ); -}; - -//add your custom post -// addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. -Devvit.addCustomPostType({ - name: 'AppName', - description: 'Navigate between pages and count!', - render: App, -}); -``` -[View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) - -In this example, when a user session ends, state will no longer be available to the app. To save and persist data between sessions you will need to [store data server-side via the Redis](../server/redis). - -## Redis vs useState - -The `redis` [plugin](../server/redis) serves as your app’s long-term, server-side memory, while `useState` serves as its short-term, client-side memory. - -`useState` will keep your custom posts performant and responsive. UI that relies on too many server-side calls will slow down your app. - -The `redis` plugin should be used for storing data that needs to persist across sessions. Make these updates to your app in the background i.e. separate from the UI updates you are making. This way, your app does not need to wait on expensive calls to render new components. You should retrieve `redis` data once on render. - -Use state hooks for single session memory, like changing tabs, selecting a checkbox within the UI, or changing values in the user’s view during their app session. - -## User sessions - -`useState` persists through a single user session. If a user scrolls away from the page, navigates away from the page, or disconnects, the session will end and the state is invalidated. - -## Differences compared to React - -The Devvit `useState` hook is inspired by [React](https://react.dev/reference/react/useState) with a few key differences. - -- `useState` can only return something that can be serialized to JSON. That is booleans, numbers, strings, arrays, and objects. This is due to how the hook persists across server/client boundaries under the hood. -- `useState` can be initialized with an async function. This allows you to get remote information from Redis, Reddit, or by using fetch to place on to state. Please use this sparingly, as using an async initializer blocks render until the promise resolves. This can make your app feel slow. diff --git a/versioned_docs/version-0.12/capabilities/client/forms.mdx b/versioned_docs/version-0.12/capabilities/client/forms.mdx index b1801026..8516131b 100644 --- a/versioned_docs/version-0.12/capabilities/client/forms.mdx +++ b/versioned_docs/version-0.12/capabilities/client/forms.mdx @@ -230,7 +230,7 @@ For forms that open from a menu item, you can use menu responses. This is useful ## Form object -The form object enables you to customize the form container and the [list of form fields](#supported-fields-types) included. The form object structure is the same for both Devvit Web and Devvit Blocks. +The form object enables you to customize the form container and the [list of form fields](#supported-fields-types) included. #### Usage diff --git a/versioned_docs/version-0.12/capabilities/client/overview.mdx b/versioned_docs/version-0.12/capabilities/client/overview.mdx index 5d645d5d..c81dc705 100644 --- a/versioned_docs/version-0.12/capabilities/client/overview.mdx +++ b/versioned_docs/version-0.12/capabilities/client/overview.mdx @@ -33,11 +33,11 @@ if (result) { ## Available client effects -| Effect | Description | Devvit Web | Devvit Blocks | -|--------|-------------|------------|---------------| -| **Toast** | Show temporary notification messages | `showToast()` | `context.ui.showToast()` | -| **Form** | Display interactive forms with promise-based responses | `showForm()` | `context.ui.showForm()` | -| **Navigation** | Redirect to Reddit content or external URLs | `navigateTo()` | `context.ui.navigateTo()` | +| Effect | Description | API | +|--------|-------------|-----| +| **Toast** | Show temporary notification messages | `showToast()` | +| **Form** | Display interactive forms with promise-based responses | `showForm()` | +| **Navigation** | Redirect to Reddit content or external URLs | `navigateTo()` | :::note When to use client library functions You should only use client library functions in response to a user-initiated action. @@ -45,7 +45,7 @@ You should only use client library functions in response to a user-initiated act ## Menu responses -In Devvit Web, menu items can respond with client effects after server processing. +Menu items can respond with client effects after server processing. Menu responses allow you to: - Process data on the server before showing client effects diff --git a/versioned_docs/version-0.12/capabilities/creating_custom_post.md b/versioned_docs/version-0.12/capabilities/creating_custom_post.md index 9ce4960e..66ef675b 100644 --- a/versioned_docs/version-0.12/capabilities/creating_custom_post.md +++ b/versioned_docs/version-0.12/capabilities/creating_custom_post.md @@ -43,7 +43,7 @@ export const createPost = async () => { | `entry` | Key of the entrypoint defined in `devvit.json` | | `postData` | [Updates post data after creation](../capabilities/server/post-data) | | `textFallback` | [Specifies alternative text content](../capabilities/server/text_fallback) | -| `userGeneratedContent` | [Enables user-generated content](..capabilities/server/userActions) | +| `userGeneratedContent` | [Enables user-generated content](../capabilities/server/userActions) | | `styles` | Controls post appearance in the Reddit UI. See [Custom Post Styles](#custom-post-styles) | ## **Custom Post Styles** diff --git a/versioned_docs/version-0.12/capabilities/devvit-web/devvit_web_configuration.md b/versioned_docs/version-0.12/capabilities/devvit-web/devvit_web_configuration.md index 09a71ebe..57d71804 100644 --- a/versioned_docs/version-0.12/capabilities/devvit-web/devvit_web_configuration.md +++ b/versioned_docs/version-0.12/capabilities/devvit-web/devvit_web_configuration.md @@ -24,7 +24,6 @@ Additionally, you must include at least one of: - **`post`**: For web view apps - **`server`**: For Node.js server apps -- **`blocks`**: For Blocks ## Configuration sections @@ -37,11 +36,10 @@ Additionally, you must include at least one of: ### App components -| Property | Type | Description | Required | -| -------- | ------ | ---------------------------------- | ------------------------- | -| `post` | object | Custom post/web view configuration | One of post/server/blocks | -| `server` | object | Node.js server configuration | One of post/server/blocks | -| `blocks` | object | Blocks | One of post/server/blocks | +| Property | Type | Description | Required | +| -------- | ------ | ---------------------------------- | ------------------ | +| `post` | object | Custom post/web view configuration | One of post/server | +| `server` | object | Node.js server configuration | One of post/server | ### Permissions & capabilities @@ -316,23 +314,13 @@ Configure development settings: - `subreddit` (string): Default development subreddit (can be overridden by `DEVVIT_SUBREDDIT` env var) -## Migration from `devvit.yaml` - -1. Create a new `devvit.json` file in the project root. -2. Copy over the `name` property from `devvit.yaml`. -3. Delete `devvit.yaml`. -4. Move configuration from `Devvit.configure()` calls to `permissions`. For example, if the app called `Devvit.configure({redis: true})` set `permissions.redis` to `true` in `devvit.json`. -5. If the app has a web view, set `post` in `devvit.json` and either configure `post.entry` to `webroot/` or to your build output directory. Optionally, delete calls to `Devvit.addCustomPostType()` and `Devvit.addMenuItem()`. -6. If the app has a Node.js server, set `server` in `devvit.json`. -7. (Optional) Set `blocks.entry` to `src/main.tsx` (or `src.main.ts`) to continue using `@devvit/public-api` legacy APIs. - ## Validation rules The `devvit.json` configuration is validated against the JSON Schema at build time. Many IDEs will also underline errors as you write. Common validation errors include: - **JSON Syntax:** Adding comments or trailing commas (unsupported by JSON) - **Required Properties:** Missing the required `name` property -- **App Components:** Missing at least one of `post`, `server`, or `blocks` +- **App Components:** Missing at least one of `post` or `server` - **Dependencies:** Missing `server` when `triggers` is specified - **File References:** Missing files referenced in `devvit.json` - **Permissions:** Missing required permissions for used features diff --git a/versioned_docs/version-0.12/capabilities/devvit-web/devvit_web_overview.mdx b/versioned_docs/version-0.12/capabilities/devvit-web/devvit_web_overview.mdx index 03388204..b208d4e6 100644 --- a/versioned_docs/version-0.12/capabilities/devvit-web/devvit_web_overview.mdx +++ b/versioned_docs/version-0.12/capabilities/devvit-web/devvit_web_overview.mdx @@ -66,7 +66,7 @@ Devvit Web templates all have the same file structure: └── devvit.json # the devvit config file ``` -Now, instead of passing messages with postMessage (old way), you’ll define `/api/endpoints` (new way). +Your client talks to the server by calling `/api/` endpoints you define with `fetch()`. ### Client folder diff --git a/versioned_docs/version-0.12/capabilities/realtime/overview.md b/versioned_docs/version-0.12/capabilities/realtime/overview.md index d7613b20..9539fcec 100644 --- a/versioned_docs/version-0.12/capabilities/realtime/overview.md +++ b/versioned_docs/version-0.12/capabilities/realtime/overview.md @@ -6,9 +6,7 @@ Create live and event-driven interactive posts. Realtime provides a set of primi - **Event-driven**. Posts render automatically in response to server events. - **Synced**. Using realtime with [Redis](../server/redis.mdx) lets you build persistent community experiences that are backed by high performance data synchronization. -Realtime is supported in both [Devvit Web](../devvit-web/devvit_web_overview.mdx) and [Devvit Blocks](../blocks/overview.md) applications. - -Follow this guide, [Realtime in Devvit Blocks](./realtime_in_devvit_blocks.md), for instructions on using realtime in Devvit Blocks. +Realtime is supported in [Devvit Web](../devvit-web/devvit_web_overview.mdx) applications. # Realtime in Devvit Web diff --git a/versioned_docs/version-0.12/capabilities/realtime/realtime_in_devvit_blocks.md b/versioned_docs/version-0.12/capabilities/realtime/realtime_in_devvit_blocks.md deleted file mode 100644 index de7de7cb..00000000 --- a/versioned_docs/version-0.12/capabilities/realtime/realtime_in_devvit_blocks.md +++ /dev/null @@ -1,144 +0,0 @@ -# Realtime in Devvit Blocks - -This guide walks through step-by-step instructions on how to set up [Realtime](./overview.md) in a [Devvit Blocks](../blocks/overview.md) application - -## Create a live interactive post - -#### 1. Configure realtime - -```tsx -Devvit.configure({ - realtime: true, -}); -``` - -#### 2. Create and subscribe to a channel - -`useChannel` hook allows interactive posts to subscribe and send to an event stream. - -A new channel can be setup with function handlers containing custom logic to update state: - -- `onMessage` - called every time a message is received on a channel -- `onSubscribed` - optional hook to be informed when channel has connected -- `onUnsubscribed` - optional hook to be informed when channel has disconnected - -```tsx -import { Devvit, useChannel } from '@devvit/public-api'; - -// Defined within render function of an interactive post - -// Choose a channel name that works for you - -// You have the flexibility to define the message data shape to be published -// via channel.send - same shape will be received in the onMessage handler - -const channel = useChannel({ - name: 'events', - onMessage: (data) => { - // modify local state - }, - onSubscribed: () => { - // handle connection setup - }, - onUnsubscribed: () => { - // handle network degradation with fallback scenarios - }, -}); - -// subscribe to the channel to receive messages -channel.subscribe(); -``` - -#### 3. Send messages to a channel - -`channel.send` is recommended for peer-to-peer synchronization across clients. See [Mini Place](#mini-place) and [Snoo Club](#snoo-club) - -```tsx - - ); - }, - }); - ``` - [View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) - + :::warning @@ -361,41 +277,18 @@ To update post data after creation, fetch the post and use the `setPostData()` m ## Accessing post data Post data is available through `context.postData` in both client and server contexts. - - - ```tsx title="client/index.tsx" - import { context } from '@devvit/web/client'; - - export const App = () => { - return ( -
- Post Data: - {JSON.stringify(context.postData, null, 2) ?? 'undefined'} -
- ); - } - ``` -
- - ```tsx title="main.tsx" - import { Devvit } from '@devvit/public-api'; - - // addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. - Devvit.addCustomPostType({ - name: 'MyCustomPost', - render: (context) => { - return ( - - context.postData: - {JSON.stringify(context.postData, null, 2)} - - ); - }, - }); - ``` - [View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) - -
+```tsx title="client/index.tsx" +import { context } from '@devvit/web/client'; + +export const App = () => { + return ( +
+
Post Data:
+
{JSON.stringify(context.postData, null, 2) ?? 'undefined'}
+
+ ); +} +``` ## Limitations Post data supports: diff --git a/versioned_docs/version-0.12/capabilities/server/settings-and-secrets.mdx b/versioned_docs/version-0.12/capabilities/server/settings-and-secrets.mdx index 7b70c20f..18b3361a 100644 --- a/versioned_docs/version-0.12/capabilities/server/settings-and-secrets.mdx +++ b/versioned_docs/version-0.12/capabilities/server/settings-and-secrets.mdx @@ -15,140 +15,78 @@ Local environment variables and `.env` files are read during playtesting only. ## Defining settings -Settings are defined differently depending on whether you're using Devvit Web or Devvit Blocks. - - - - Define settings in your `devvit.json` file under the `settings` object. Settings are organized by scope: `global` for app-wide settings and secrets, `subreddit` for installation-specific settings. - - ```json title="devvit.json" - { - "settings": { - "global": { - "apiKey": { - "type": "string", - "label": "API Key", - "defaultValue": "", - "isSecret": true - }, - "environment": { - "type": "select", - "label": "Environment", - "options": [ - { - "label": "Production", - "value": "production" - }, - { - "label": "Development", - "value": "development" - } - ], - "defaultValue": "production" - } +Define settings in your `devvit.json` file under the `settings` object. Settings are organized by scope: `global` for app-wide settings and secrets, `subreddit` for installation-specific settings. + +```json title="devvit.json" +{ + "settings": { + "global": { + "apiKey": { + "type": "string", + "label": "API Key", + "defaultValue": "", + "isSecret": true }, - "subreddit": { - "welcomeMessage": { - "type": "string", - "label": "Welcome Message", - "validationEndpoint": "/internal/settings/validate-message", - "defaultValue": "Welcome to our community!" - }, - "enabledFeatures": { - "type": "multiSelect", - "label": "Enabled Features", - "options": [ - { - "label": "Auto-moderation", - "value": "automod" - }, - { - "label": "Welcome posts", - "value": "welcome" - }, - { - "label": "Statistics tracking", - "value": "stats" - } - ], - "defaultValue": ["welcome"] - } + "environment": { + "type": "select", + "label": "Environment", + "options": [ + { + "label": "Production", + "value": "production" + }, + { + "label": "Development", + "value": "development" + } + ], + "defaultValue": "production" } - } - } - ``` - - :::note - After defining settings in `devvit.json`, you must build your app (`npm run dev`) before you can set secrets via the CLI. - ::: - - - Use `Devvit.addSettings` to define settings with the appropriate scope. - - ```tsx title="main.tsx" - import { Devvit, SettingScope } from '@devvit/public-api'; - - Devvit.addSettings([ - // Global secret - { - type: 'string', - name: 'apiKey', - label: 'API Key', - isSecret: true, - scope: SettingScope.App, // or 'app' - }, - // Global setting - { - type: 'select', - name: 'environment', - label: 'Environment', - options: [ - { label: 'Production', value: 'production' }, - { label: 'Development', value: 'development' }, - ], - scope: 'app', }, - // Subreddit setting - { - type: 'string', - name: 'welcomeMessage', - label: 'Welcome Message', - scope: SettingScope.Installation, // or 'installation' - onValidate: async ({ value }) => { - if (!value || value.length < 5) { - return 'Message must be at least 5 characters'; - } + "subreddit": { + "welcomeMessage": { + "type": "string", + "label": "Welcome Message", + "validationEndpoint": "/internal/settings/validate-message", + "defaultValue": "Welcome to our community!" }, - }, - // Subreddit multi-select - { - type: 'select', - name: 'enabledFeatures', - label: 'Enabled Features', - options: [ - { label: 'Auto-moderation', value: 'automod' }, - { label: 'Welcome posts', value: 'welcome' }, - { label: 'Statistics tracking', value: 'stats' }, - ], - multiSelect: true, - scope: 'installation', - }, - ]); - ``` - - + "enabledFeatures": { + "type": "multiSelect", + "label": "Enabled Features", + "options": [ + { + "label": "Auto-moderation", + "value": "automod" + }, + { + "label": "Welcome posts", + "value": "welcome" + }, + { + "label": "Statistics tracking", + "value": "stats" + } + ], + "defaultValue": ["welcome"] + } + } + } +} +``` + +:::note +After defining settings in `devvit.json`, you must build your app (`npm run dev`) before you can set secrets via the CLI. +::: ## Setting types -Both frameworks support the following setting types: +The following setting types are supported: - **string**: Text input field - **boolean**: Toggle switch - **number**: Numeric input - **select**: Dropdown selection (single choice) -- **multiSelect** (Web) / **select with multiSelect: true** (Blocks): Multiple choice dropdown -- **paragraph**: Multi-line text input (Blocks only) -- **group**: Grouped settings for organization (Blocks only) +- **multiSelect**: Multiple choice dropdown ## Managing secrets @@ -181,255 +119,175 @@ Successfully added app settings for apiKey! ``` :::warning -At least one app installation is required before you can store secrets via the CLI. You can run `npx devvit playtest` (or `npm run dev` in Devvit Web) to start your first installation. +At least one app installation is required before you can store secrets via the CLI. Run `npm run dev` to start your first installation. ::: ## Accessing settings in your app Settings can be retrieved from within your app. - - - - + + + +```tsx title="server/index.ts" +import { settings } from '@devvit/web/server'; - ```tsx title="server/index.ts" - import { settings } from '@devvit/web/server'; +type ProcessResponse = { success: true }; - type ProcessResponse = { success: true }; +// Get a single setting +const apiKey = await settings.get('apiKey'); - // Get a single setting +// Get multiple settings +const [welcomeMessage, features] = await Promise.all([ + settings.get('welcomeMessage'), + settings.get('enabledFeatures') +]); + +// Use in an endpoint +app.post('/api/process', async (c) => { const apiKey = await settings.get('apiKey'); - - // Get multiple settings - const [welcomeMessage, features] = await Promise.all([ - settings.get('welcomeMessage'), - settings.get('enabledFeatures') - ]); + const environment = await settings.get('environment'); - // Use in an endpoint - app.post('/api/process', async (c) => { - const apiKey = await settings.get('apiKey'); - const environment = await settings.get('environment'); - - const response = await fetch('https://api.example.com/endpoint', { - headers: { - 'Authorization': `Bearer ${apiKey}`, - 'X-Environment': environment - } - }); - - return c.json({ success: true }); + const response = await fetch('https://api.example.com/endpoint', { + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'X-Environment': environment + } }); - ``` - - + return c.json({ success: true }); +}); +``` + + + - ```tsx title="server/index.ts" - import { settings } from '@devvit/web/server'; +```tsx title="server/index.ts" +import { settings } from '@devvit/web/server'; - type ProcessResponse = { success: true }; +type ProcessResponse = { success: true }; - // Get a single setting +// Get a single setting +const apiKey = await settings.get('apiKey'); + +// Get multiple settings +const [welcomeMessage, features] = await Promise.all([ + settings.get('welcomeMessage'), + settings.get('enabledFeatures') +]); + +// Use in an endpoint +router.post('/api/process', async (req, res) => { const apiKey = await settings.get('apiKey'); - - // Get multiple settings - const [welcomeMessage, features] = await Promise.all([ - settings.get('welcomeMessage'), - settings.get('enabledFeatures') - ]); + const environment = await settings.get('environment'); - // Use in an endpoint - router.post('/api/process', async (req, res) => { - const apiKey = await settings.get('apiKey'); - const environment = await settings.get('environment'); - - const response = await fetch('https://api.example.com/endpoint', { - headers: { - 'Authorization': `Bearer ${apiKey}`, - 'X-Environment': environment - } - }); - - res.json({ success: true }); - }); - ``` - - - - - - ```tsx title="main.tsx" - import { Devvit } from '@devvit/public-api'; - - Devvit.addMenuItem({ - label: 'Process with API', - location: 'subreddit', - onPress: async (_event, context) => { - // Get individual settings - const apiKey = await context.settings.get('apiKey'); - const welcomeMessage = await context.settings.get('welcomeMessage'); - - // Get all settings - const allSettings = await context.settings.getAll(); - - // Use the settings - const response = await fetch('https://api.example.com/endpoint', { - headers: { - 'Authorization': `Bearer ${apiKey}`, - } - }); - - context.ui.showToast(`Processed with message: ${welcomeMessage}`); - }, + const response = await fetch('https://api.example.com/endpoint', { + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'X-Environment': environment + } }); - // Access in custom post type - // addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. - Devvit.addCustomPostType({ - name: 'ConfigurablePost', - render: (context) => { - const [features, setFeatures] = useState([]); - - useEffect(async () => { - const enabledFeatures = await context.settings.get('enabledFeatures'); - setFeatures(enabledFeatures || []); - }, []); - - return ( - - Enabled features: {features.join(', ')} - - ); - }, - }); - ``` - [View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) - + res.json({ success: true }); +}); +``` + + ## Input validation -Validate user input to ensure it meets your requirements before saving. - - - - Define a validation endpoint in your `devvit.json` and implement it in your server: - - ```json title="devvit.json" - { - "settings": { - "subreddit": { - "minimumAge": { - "type": "number", - "label": "Minimum Account Age (days)", - "validationEndpoint": "/internal/settings/validate-age", - "defaultValue": 7 - } +Validate user input to ensure it meets your requirements before saving. Define a validation endpoint in your `devvit.json` and implement it in your server: + +```json title="devvit.json" +{ + "settings": { + "subreddit": { + "minimumAge": { + "type": "number", + "label": "Minimum Account Age (days)", + "validationEndpoint": "/internal/settings/validate-age", + "defaultValue": 7 } } } - ``` +} +``` + + + + +```tsx title="server/index.ts" +import type { SettingsValidationRequest, SettingsValidationResponse } from '@devvit/web/shared'; + +app.post('/internal/settings/validate-age', async (c) => { + const { value } = await c.req.json>(); + + if (!value || value < 0) { + return c.json({ + success: false, + error: 'Age must be a positive number', + }); + } + + if (value > 365) { + return c.json({ + success: false, + error: 'Maximum age is 365 days', + }); + } + + return c.json({ success: true }); +}); +``` - - + + - ```tsx title="server/index.ts" - import type { SettingsValidationRequest, SettingsValidationResponse } from '@devvit/web/shared'; +```tsx title="server/index.ts" +import type { SettingsValidationRequest, SettingsValidationResponse } from '@devvit/web/shared'; - app.post('/internal/settings/validate-age', async (c) => { - const { value } = await c.req.json>(); +router.post>( + '/internal/settings/validate-age', + async (req, res): Promise => { + const { value } = req.body; if (!value || value < 0) { - return c.json({ + res.json({ success: false, error: 'Age must be a positive number', }); + return; } if (value > 365) { - return c.json({ + res.json({ success: false, error: 'Maximum age is 365 days', }); + return; } - return c.json({ success: true }); - }); - ``` - - - - - ```tsx title="server/index.ts" - import type { SettingsValidationRequest, SettingsValidationResponse } from '@devvit/web/shared'; - - router.post>( - '/internal/settings/validate-age', - async (req, res): Promise => { - const { value } = req.body; - - if (!value || value < 0) { - res.json({ - success: false, - error: 'Age must be a positive number', - }); - return; - } - - if (value > 365) { - res.json({ - success: false, - error: 'Maximum age is 365 days', - }); - return; - } + res.json({ success: true }); + } +); +``` - res.json({ success: true }); - } - ); - ``` - - - - - - Add an `onValidate` handler to your setting definition: - - ```tsx title="main.tsx" - Devvit.addSettings([ - { - type: 'number', - name: 'minimumAge', - label: 'Minimum Account Age (days)', - scope: 'installation', - onValidate: async ({ value }) => { - if (!value || value < 0) { - return 'Age must be a positive number'; - } - if (value > 365) { - return 'Maximum age is 365 days'; - } - }, - }, - ]); - ``` - + ## Subreddit settings UI @@ -444,210 +302,117 @@ Moderators will see all non-secret settings defined for the subreddit scope and Here's a complete example showing both secrets and subreddit settings in action: - - - ```json title="devvit.json" - { - "settings": { - "global": { - "openaiApiKey": { - "type": "string", - "label": "OpenAI API Key", - "isSecret": true, - "defaultValue": "" - } +```json title="devvit.json" +{ + "settings": { + "global": { + "openaiApiKey": { + "type": "string", + "label": "OpenAI API Key", + "isSecret": true, + "defaultValue": "" + } + }, + "subreddit": { + "aiModel": { + "type": "select", + "label": "AI Model", + "options": [ + { "label": "GPT-4", "value": "gpt-4" }, + { "label": "GPT-3.5", "value": "gpt-3.5-turbo" } + ], + "defaultValue": "gpt-3.5-turbo" }, - "subreddit": { - "aiModel": { - "type": "select", - "label": "AI Model", - "options": [ - { "label": "GPT-4", "value": "gpt-4" }, - { "label": "GPT-3.5", "value": "gpt-3.5-turbo" } - ], - "defaultValue": "gpt-3.5-turbo" - }, - "maxTokens": { - "type": "number", - "label": "Max Response Tokens", - "validationEndpoint": "/internal/settings/validate-tokens", - "defaultValue": 150 - } + "maxTokens": { + "type": "number", + "label": "Max Response Tokens", + "validationEndpoint": "/internal/settings/validate-tokens", + "defaultValue": 150 } } } - ``` - - - - - ```tsx title="server/index.ts" - import type { JsonObject, JsonValue } from '@devvit/web/shared'; - import { settings } from '@devvit/web/server'; - - type GenerateRequest = { messages: JsonValue }; - type GenerateResponse = JsonObject; - - app.post('/api/generate', async (c) => { - const [apiKey, model, maxTokens] = await Promise.all([ - settings.get('openaiApiKey'), - settings.get('aiModel'), - settings.get('maxTokens') - ]); - const { messages } = await c.req.json(); - - const response = await fetch('https://api.openai.com/v1/chat/completions', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${apiKey}`, - }, - body: JSON.stringify({ - model, - max_tokens: maxTokens, - messages, - }), - }); +} +``` - const data = (await response.json()) as GenerateResponse; - return c.json(data); - }); - ``` - - - - - ```tsx title="server/index.ts" - import type { JsonObject, JsonValue } from '@devvit/web/shared'; - import { settings } from '@devvit/web/server'; - - type GenerateRequest = { messages: JsonValue }; - type GenerateResponse = JsonObject; - - router.post('/api/generate', async (req, res) => { - const [apiKey, model, maxTokens] = await Promise.all([ - settings.get('openaiApiKey'), - settings.get('aiModel'), - settings.get('maxTokens') - ]); - - const response = await fetch('https://api.openai.com/v1/chat/completions', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${apiKey}`, - }, - body: JSON.stringify({ - model, - max_tokens: maxTokens, - messages: req.body.messages, - }), - }); + + + +```tsx title="server/index.ts" +import type { JsonObject, JsonValue } from '@devvit/web/shared'; +import { settings } from '@devvit/web/server'; + +type GenerateRequest = { messages: JsonValue }; +type GenerateResponse = JsonObject; + +app.post('/api/generate', async (c) => { + const [apiKey, model, maxTokens] = await Promise.all([ + settings.get('openaiApiKey'), + settings.get('aiModel'), + settings.get('maxTokens') + ]); + const { messages } = await c.req.json(); - const data = (await response.json()) as GenerateResponse; - res.json(data); + const response = await fetch('https://api.openai.com/v1/chat/completions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${apiKey}`, + }, + body: JSON.stringify({ + model, + max_tokens: maxTokens, + messages, + }), }); - ``` - - - - - ```tsx title="main.tsx" - import { Devvit } from '@devvit/public-api'; + const data = (await response.json()) as GenerateResponse; + return c.json(data); +}); +``` - Devvit.configure({ - http: true, - }); + + - Devvit.addSettings([ - { - name: 'openaiApiKey', - label: 'OpenAI API Key', - type: 'string', - isSecret: true, - scope: 'app', - }, - { - name: 'aiModel', - label: 'AI Model', - type: 'select', - options: [ - { label: 'GPT-4', value: 'gpt-4' }, - { label: 'GPT-3.5', value: 'gpt-3.5-turbo' }, - ], - scope: 'installation', - }, - { - name: 'maxTokens', - label: 'Max Response Tokens', - type: 'number', - scope: 'installation', - onValidate: async ({ value }) => { - if (value < 1 || value > 500) { - return 'Tokens must be between 1 and 500'; - } - }, - }, - ]); +```tsx title="server/index.ts" +import type { JsonObject, JsonValue } from '@devvit/web/shared'; +import { settings } from '@devvit/web/server'; - async function generateResponse(context: Devvit.Context, prompt: string): Promise { - const [apiKey, model, maxTokens] = await Promise.all([ - context.settings.get('openaiApiKey'), - context.settings.get('aiModel'), - context.settings.get('maxTokens'), - ]); - - const response = await fetch('https://api.openai.com/v1/chat/completions', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${apiKey}`, - }, - body: JSON.stringify({ - model, - max_tokens: maxTokens, - messages: [{ role: 'user', content: prompt }], - }), - }); +type GenerateRequest = { messages: JsonValue }; +type GenerateResponse = JsonObject; - const data = await response.json(); - return data.choices[0]?.message?.content || 'No response'; - } +router.post('/api/generate', async (req, res) => { + const [apiKey, model, maxTokens] = await Promise.all([ + settings.get('openaiApiKey'), + settings.get('aiModel'), + settings.get('maxTokens') + ]); - // addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. - Devvit.addCustomPostType({ - name: 'AI Assistant', - render: (context) => { - const [response, setResponse] = useState(''); - - return ( - - - {response && {response}} - - ); + const response = await fetch('https://api.openai.com/v1/chat/completions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${apiKey}`, }, + body: JSON.stringify({ + model, + max_tokens: maxTokens, + messages: req.body.messages, + }), }); - export default Devvit; - ``` - [View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) - + const data = (await response.json()) as GenerateResponse; + res.json(data); +}); +``` + + ## Limitations diff --git a/versioned_docs/version-0.12/changelog.md b/versioned_docs/version-0.12/changelog.md index e5009a26..a843eacb 100644 --- a/versioned_docs/version-0.12/changelog.md +++ b/versioned_docs/version-0.12/changelog.md @@ -93,10 +93,6 @@ This release brings new guidance to help you build more engaging community games The newly updated [Building Community Games](https://developers.reddit.com/docs/guides/best-practices/community_games#player-retention) guide includes new tips for creating engaging gameplay that thrives on Reddit! Learn which mechanics drive long-term engagement and how to improve your chances of [getting featured](https://developers.reddit.com/docs/guides/launch/feature-guide). -**Deprecating Devvit Blocks** - -As we announced last month, [Devvit Blocks will be deprecated](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) effective June 30, 2026. In this release, we added a deprecation notice to `Devvit.addCustomPostType`. New apps that use this API will now be rejected during app review. - **Other Fixes** - **Improved modmail validation and logging**: Added subreddit ID validation for Reddit Modmail requests and improved error logging. - **Fixed image uploads in comments created with RichTextBuilder**: Resolved an issue where comment creation failed when adding images due to media processing conflicts. RichTextBuilder now accepts media URLs instead of media IDs, and the Devvit runtime converts them during processing to ensure compatibility with native post and comment media handling. @@ -201,9 +197,6 @@ In the last release of 2025, we’ve made a slew of minor updates (they're still In this release, we’re excited to bring payment support to Devvit Web. If you’re looking to add payments to your app, check out our [updated docs](./earn-money/payments/payments_overview.md). -Devvit Web has reached full feature parity with blocks, and we strongly recommend using Devvit Web for all new apps. If you want to convert your existing blocks apps (including mod apps) to Devvit Web, check out the [migration guide](./guides/migrate/devvit-singleton.md). - -To keep things clear (and friendlier to AI-assisted IDEs), we're moving all [blocks documentation](./capabilities/blocks/overview.md) into its own dedicated section. ## Devvit 0.12.4: Ins and Outs @@ -311,7 +304,7 @@ This release also includes a handful of other fixes including: - Added a method mergePostData() to append to postData. - Fixed reddit.setPostFlair() method. -- Added a new triggers field that fixed the issue where triggers within the blocks entrypoint weren’t working. The migration guide has been updated. +- Added a new triggers field that fixed an entrypoint triggers issue. - Added error handling when trying to `devvit new`on an already existing app name. - Added disconnectRealtime() and isRealtimeConnected() as helper methods for the realtime plugin. @@ -341,8 +334,6 @@ There's also a new [web-based creation flow](https://developers.reddit.com/new/) - **Client/server architecture**: Clear separation between frontend (@devvit/web/client) and backend (@devvit/web/server) - **Full platform access**: Continued access to Redis, Reddit API, and Devvit's hosting services -- **Flexible development**: Use Devvit Web alongside existing Blocks - choose the right tool for each feature - **Current Limitations** - Serverless endpoints only (no long-running connections or streaming) @@ -353,10 +344,6 @@ There's also a new [web-based creation flow](https://developers.reddit.com/new/) **Getting Started** - **New apps**: Go to developers.reddit.com/new to start building new apps -- **Existing apps**: Migration is optional - your current apps continue to work on 0.11, but we recommend using these migration guides to move your app over to Devvit Web. - - [Devvit Web Experimental to Devvit Web](./guides/migrate/devvit-web-experimental.md) - - [useWebView to Devvit Web](./guides/migrate/inline-web-view.md) - - [Blocks app to Devvit Web](./guides/migrate/devvit-singleton.md) **Support & Feedback** diff --git a/versioned_docs/version-0.12/earn-money/payments/payments_add.mdx b/versioned_docs/version-0.12/earn-money/payments/payments_add.mdx index 5071336f..8db52d08 100644 --- a/versioned_docs/version-0.12/earn-money/payments/payments_add.mdx +++ b/versioned_docs/version-0.12/earn-money/payments/payments_add.mdx @@ -5,10 +5,6 @@ import TabItem from "@theme/TabItem"; The Devvit payments API is available in Devvit Web. Keep reading to learn how to configure your products and accept payments. -:::note -Devvit Web is recommended for payments. Check out how to [migrate blocks apps](./payments_migrate.mdx) if you're app is currently using a blocks version of payments. -::: - To start with a template, select the payments template when you create a new project or run: ```bash @@ -255,13 +251,7 @@ If you don’t provide an image, the default Reddit product image is used. ### Purchase buttons (required) -#### Devvit Web - -In Devvit Web, use your own UI (e.g. a button or product card) and call `purchase(sku)` from `@devvit/web/client` when the user chooses a product. Follow the [design guidelines](#design-guidelines) (e.g. gold icon, clear labeling). - -#### Blocks (legacy) - -If your app still uses Devvit Blocks, you can use the `ProductButton` component and [migrate to Devvit Web](./payments_migrate.mdx) when ready. The `ProductButton` renders a product with a purchase button; use `payments.purchase(p.sku)` in the `onPress` callback (from `@devvit/payments`). +Use your own UI (e.g. a button or product card) and call `purchase(sku)` from `@devvit/web/client` when the user chooses a product. Follow the [design guidelines](#design-guidelines) (e.g. gold icon, clear labeling). #### Webviews diff --git a/versioned_docs/version-0.12/earn-money/payments/payments_migrate.mdx b/versioned_docs/version-0.12/earn-money/payments/payments_migrate.mdx deleted file mode 100644 index ab33351a..00000000 --- a/versioned_docs/version-0.12/earn-money/payments/payments_migrate.mdx +++ /dev/null @@ -1,78 +0,0 @@ -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -# Migrate Blocks payments - -If you already have payments set up on a Blocks app, use the following steps to migrate your payments functionality. - -1. Update devvit.json - -Reference your `products.json` and declare endpoints. - -```tsx title="devvit.json" -{ - "permissions": { "payments": true }, - "payments": { - "productsFile": "./products.json", - "endpoints": { - "fulfillOrder": "/internal/payments/fulfill", - "refundOrder": "/internal/payments/refund" - } - } -} - -``` - -2. Replace payment hooks with endpoints - -- Blocks: `addPaymentHandler({ fulfillOrder, refundOrder })` -- Devvit Web: implement `/internal/payments/fulfill` and `/internal/payments/refund` - - - - -```tsx -import type { PaymentHandlerResponse, Order } from "@devvit/web/server"; - -app.post("/internal/payments/fulfill", async (c) => { - const order = await c.req.json(); - // migrate your old fulfillOrder logic here - return c.json({ success: true }); -}); -``` - - - - -```tsx -import type { PaymentHandlerResponse, Order } from "@devvit/web/server"; - -router.post( - "/internal/payments/fulfill", - async (req, res) => { - const order = req.body; - // migrate your old fulfillOrder logic here - res.json({ success: true }); - }, -); -``` - - - - -3. Update client purchase calls - -- Blocks: `usePayments().purchase(sku)` -- Devvit Web: `purchase(sku)` from`@devvit/web/client` - -4. Update products and orders APIs - -- Blocks: `useProducts`, `useOrders` -- Devvit Web: server‑side `payments.getProducts()`, `payments.getOrders()`; expose via `/api/` if needed by the client diff --git a/versioned_docs/version-0.12/earn-money/payments/support_this_app.md b/versioned_docs/version-0.12/earn-money/payments/support_this_app.md index 3b9eb5f1..cc26a0a5 100644 --- a/versioned_docs/version-0.12/earn-money/payments/support_this_app.md +++ b/versioned_docs/version-0.12/earn-money/payments/support_this_app.md @@ -62,32 +62,6 @@ Example client code: ```tsx title="client/index.ts" import { purchase, OrderResultStatus } from "@devvit/web/client"; -<<<<<<< HEAD -// addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. -Devvit.addCustomPostType({ - render: (context) => { - const { products } = useProducts(context); - const payments = usePayments((result: OnPurchaseResult) => { - if (result.status === OrderResultStatus.Success) { - context.ui.showToast({ - appearance: 'success', - text: 'Thanks for your support!', - }); - } else { - context.ui.showToast( - `Purchase failed! Please try again.` - ); - } - }); - const supportProduct = products.find(products.find((p) => p.sku === 'support-app'); - return ( - payments.purchase(p.sku)} - /> - ); -}) -======= async function handleSupportApp() { const result = await purchase("support-app"); if (result.status === OrderResultStatus.STATUS_SUCCESS) { @@ -96,9 +70,7 @@ async function handleSupportApp() { // show error or retry (result.errorMessage may be set) } } ->>>>>>> 64da331 (DR-370 update payments docs referencing Ddevvit singleton) ``` -[View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) ## Example diff --git a/versioned_docs/version-0.12/examples/template-library.md b/versioned_docs/version-0.12/examples/template-library.md index d49a28a8..0af2702b 100644 --- a/versioned_docs/version-0.12/examples/template-library.md +++ b/versioned_docs/version-0.12/examples/template-library.md @@ -8,7 +8,7 @@ Here are some starter projects and templates for your Devvit projects - [React starter](https://github.com/reddit/devvit-template-react) - a boilerplate project scaffolding to help you kickstart your React project. It includes preinstalled libraries for Devvit, Vite, React, Express, Tailwind, and TypeScript for faster development. -- [Three.js starter](https://github.com/reddit/devvit-template-threejs) - a tower blocks example that shows you how to create 3D graphics in the browser. It includes preinstalled libraries for Devvit, Vite, Three.js, Express, and TypeScript. This template is great for visualizations and games. +- [Three.js starter](https://github.com/reddit/devvit-template-threejs) - a 3D stacking game example that shows you how to create 3D graphics in the browser. It includes preinstalled libraries for Devvit, Vite, Three.js, Express, and TypeScript. This template is great for visualizations and games. - [Phaser starter](https://github.com/reddit/devvit-template-phaser) - a feature-rich HTML5 game framework for building 2D games in the browser. It includes preinstalled libraries for Devvit, Vite, Phaser, Express, and TypeScript. This template is good for handling physics, animations, input, sound, and asset management. @@ -18,14 +18,7 @@ Here are some starter projects and templates for your Devvit projects - [Hello world](https://github.com/reddit/devvit-template-hello-world/tree/main) - a simple template to build a counter app with no frameworks or opinions. - -## Devvit Blocks - -[Devvit Blocks](../capabilities/blocks/overview.md) lets you build applications that run inside of a Reddit post, using Reddit's own design system: optimized for performance and cross-platform compatibility. - -- [Blocks Empty Template](https://github.com/reddit/devvit-template-blocks) - an empty project leveraging Devvit Blocks - -- [Payments Template](https://github.com/reddit/devvit-template-payments) - a template that contains all boilerplate code to enable [Devvit Payments](../earn-money/payments/payments_overview.md) in your Blocks app. +- [Payments Template](https://github.com/reddit/devvit-template-payments) - a template that contains all boilerplate code to enable [Devvit Payments](../earn-money/payments/payments_overview.md) in your app. ## Mod Tools diff --git a/versioned_docs/version-0.12/guides/faq.mdx b/versioned_docs/version-0.12/guides/faq.mdx index 81890681..bacc0cd9 100644 --- a/versioned_docs/version-0.12/guides/faq.mdx +++ b/versioned_docs/version-0.12/guides/faq.mdx @@ -93,7 +93,7 @@ One other common gotcha: Devvit projects use `vite build --watch` rather than a Prefer updating the project-local CLI dependency instead of relying on a global install. The current docs recommend updating `devvit` in your project, then running `npx devvit update app` so your `@devvit` packages match the CLI. -For the current commands and workflow, see [Devvit CLI](./tools/devvit_cli.mdx), [Optimizing performance](../capabilities/blocks/optimize_performance.md), and the update notes in [Changelog](../changelog.md). +For the current commands and workflow, see [Devvit CLI](./tools/devvit_cli.mdx) and the update notes in [Changelog](../changelog.md). @@ -173,30 +173,6 @@ Devvit Web is the current client/server app model for building Devvit apps with - -
-Should I use Devvit Web or Devvit Blocks for a new game? - -Use Devvit Web for new games. The current docs say Devvit Web has reached feature parity with Blocks for new apps, strongly recommend Devvit Web for all new apps, and note that `Devvit.addCustomPostType()` is deprecated and new apps using it will be rejected during review. The clearest sources are [Devvit Web overview](../capabilities/devvit-web/devvit_web_overview.mdx) and [Changelog](../changelog.md). - -
- - -
-Can I combine Devvit Blocks and Devvit Web? - -Legacy and partially migrated apps can still mix pieces while they transition, and the migration guides explicitly say apps can be partially migrated. But for new game-style interactive posts, the better direction is to move the experience to Devvit Web instead of designing a new Blocks-plus-Web architecture around deprecated custom post APIs. See [Migrating from useWebView to Devvit Web](./migrate/inline-web-view.md) and [Migrating Blocks/Mod Tools to Devvit Web](./migrate/devvit-singleton.md). - -
- - -
-How do I migrate from Blocks or useWebView to Devvit Web? - -There are separate migration guides depending on what you are starting from: Blocks/Mod Tools, `useWebView`, and Devvit Web experimental. In all cases, the main shift is toward `devvit.json`, explicit client/server folders, and direct server endpoints instead of older message-passing or singleton-based setup. Start with [Blocks/Mod Tools to Devvit Web](./migrate/devvit-singleton.md), [useWebView to Devvit Web](./migrate/inline-web-view.md), or [Devvit Web Experimental to Devvit Web](./migrate/devvit-web-experimental.md). - -
-
Where do I customize the first screen or launch screen? @@ -227,7 +203,7 @@ There is no single canonical viewport size for all devices. Design responsively,
How do I add images that ship with my app? -Put static images in the root `assets/` directory when they should ship with the app version, such as logos, backgrounds, or other bundled art. The docs cover both `` usage and `context.assets.getURL()` in [App image assets](../capabilities/blocks/app_image_assets.md). +Put static images in your client `dist/` directory when they should ship with the app version, such as logos, backgrounds, or other bundled art. Reference them directly from your web client (for example via ``).
@@ -245,7 +221,7 @@ See [Forms](../capabilities/client/forms.mdx) and [Media uploads](../capabilitie
What image URLs can I use in a Devvit app? -The documented display paths are bundled asset filenames, Reddit-hosted URLs, and SVG data URLs, depending on the UI surface. If your source image lives somewhere else on the web, do not assume you can hotlink it directly into the UI. Upload it first so Reddit hosts the image, then use the returned URL. See [Image](../blocks/image.mdx), [App image assets](../capabilities/blocks/app_image_assets.md), and [Media uploads](../capabilities/server/media-uploads.mdx). +The documented display paths are bundled client assets, Reddit-hosted URLs, and SVG data URLs, depending on the UI surface. If your source image lives somewhere else on the web, do not assume you can hotlink it directly into the UI. Upload it first so Reddit hosts the image, then use the returned URL. See [Media uploads](../capabilities/server/media-uploads.mdx).
@@ -271,7 +247,7 @@ Set `postData` when creating the post, then read it from `context.postData` on e The standard Devvit pattern is to build leaderboards with Redis sorted sets. Use [Redis](../capabilities/server/redis.mdx) for score storage and ranking, use [Scheduler](../capabilities/server/scheduler.mdx) for daily or periodic resets, and prefer realtime updates over constant polling when you need a live leaderboard. -Keep the FAQ answer short: Redis is the right storage layer, but the exact schema depends on whether you need daily, weekly, or all-time rankings. The best starting points are [Redis](../capabilities/server/redis.mdx) and the leaderboard/performance guidance in [Optimizing performance](../capabilities/blocks/optimize_performance.md). +Keep the FAQ answer short: Redis is the right storage layer, but the exact schema depends on whether you need daily, weekly, or all-time rankings. [Redis](../capabilities/server/redis.mdx) is the best starting point.
@@ -327,14 +303,12 @@ If you need to react to flair changes, Devvit documents an [`onPostFlairUpdate`]
How do I get a user's username or snoovatar? -If you only need the current user's name, prefer `getCurrentUsername()`. The [performance guide](../capabilities/blocks/optimize_performance.md) recommends this over fetching the full user object. +If you only need the current user's name, prefer `getCurrentUsername()` over fetching the full user object. If you need more profile information, use `getCurrentUser()` from the [Reddit API client](../api/redditapi/RedditAPIClient/classes/RedditAPIClient.md). To get a snoovatar URL, use `reddit.getSnoovatarUrl(username)` or `user.getSnoovatarUrl()` on the [User](../api/redditapi/models/classes/User.md) model. Some handlers also expose experimental identity fields such as `username` and `snoovatar` on [BaseContext](../api/public-api/type-aliases/BaseContext.md), but the Reddit client methods are the clearest documented path. -If you are building with Devvit Blocks, the API also exposes an [`avatar`](../api/public-api/@devvit/namespaces/Devvit/namespaces/Blocks/type-aliases/AvatarProps.md) component that renders from a `thingId`. -
@@ -359,7 +333,7 @@ There is no single limits page today, but these are the most commonly referenced - [Devvit Web](../capabilities/devvit-web/devvit_web_overview.mdx): 30 second max request time, 4 MB max payload size, and 10 MB max response size. - [Post data](../capabilities/server/post-data.mdx): 2 KB per post. - [Settings and secrets](../capabilities/server/settings-and-secrets.mdx): 2 KB per setting value. -- [Realtime](../capabilities/realtime/realtime_in_devvit_blocks.md): 1 MB maximum message payload and 100 messages per second per installation. +- [Realtime](../capabilities/realtime/overview.md): 1 MB maximum message payload and 100 messages per second per installation. - [Scheduler](../capabilities/server/scheduler.mdx): up to 10 live recurring actions per installation, plus `runJob()` limits documented on that page. If you need data to survive app updates, do not rely on browser `localStorage`. The [Devvit Web overview](../capabilities/devvit-web/devvit_web_overview.mdx) recommends Redis for persistent storage across versions. @@ -372,7 +346,7 @@ If you need data to survive app updates, do not rely on browser `localStorage`. The docs do not publish one fixed pixel size for every device or view mode. Instead, build responsive layouts and test them across the views supported by the [UI Simulator](./tools/ui_simulator.mdx): mobile, desktop, and fullscreen. -For Devvit Web, inline and expanded mode behave differently: expanded is the larger experience, with more room for rich interaction, while inline should stay lightweight and feed-friendly. For Devvit Blocks, layout sizing is based on [device-independent pixels and percentages](../blocks/overview.mdx), not a single universal viewport. +Inline and expanded mode behave differently: expanded is the larger experience, with more room for rich interaction, while inline should stay lightweight and feed-friendly. If you are tuning layout behavior, start with mobile-first assumptions and validate in the simulator before hard-coding dimensions. The best references are [View modes and entry points](../capabilities/server/launch_screen_and_entry_points/view_modes_entry_points.md), [Launch screen customization](../capabilities/server/launch_screen_and_entry_points/launch_screen_customization.md), and [UI Simulator](./tools/ui_simulator.mdx). @@ -384,7 +358,7 @@ If you are tuning layout behavior, start with mobile-first assumptions and valid There is no separate NSFW platform guide in these docs, so the main source of truth is [Devvit Rules](../devvit_rules.md) plus Reddit's linked platform policies. -The rules explicitly require labels or age-gating before exposing users to graphic, sexually explicit, or otherwise mature content. Static assets and uploaded media are also subject to the same safety checks and policy review described in [App image assets](../capabilities/blocks/app_image_assets.md) and [Media uploads](../capabilities/server/media-uploads.mdx). +The rules explicitly require labels or age-gating before exposing users to graphic, sexually explicit, or otherwise mature content. Static assets and uploaded media are also subject to the same safety checks and policy review described in [Media uploads](../capabilities/server/media-uploads.mdx). If your app idea depends on adult content or NSFW communities, review those rules first and get clarification in the [Devvit Discord](https://developers.reddit.com/discord) before you ship. diff --git a/versioned_docs/version-0.12/guides/migrate/devvit-singleton.md b/versioned_docs/version-0.12/guides/migrate/devvit-singleton.md deleted file mode 100644 index 5c34a769..00000000 --- a/versioned_docs/version-0.12/guides/migrate/devvit-singleton.md +++ /dev/null @@ -1,64 +0,0 @@ -# Migrating Blocks/Mod Tools to Devvit Web - -This guide covers migrating traditional Devvit apps (using only Blocks or Mod Tools, without web views) to the Devvit Web setup. This is a straightforward migration that requires minimal changes. - -## Overview - -The migration primarily involves switching from `devvit.yaml` to `devvit.json` configuration. Your existing Blocks and Mod Tools code will continue to work with minimal changes. - -## Migration steps - -### 1. Create devvit.json - -Create a `devvit.json` file in your project root with your app configuration: - -```json -{ - "name": "your-app-name", - "blocks": { - "entry": "src/main.tsx", - "triggers": ["onPostCreate"] - } -} -``` - -Replace: - -- `"your-app-name"` with your actual app name -- `"src/main.tsx"` with the path to your main Blocks entry point (where you export your Devvit instance) -- Include any triggers used in your src/main.tsx in the triggers array (or remove the parameter) - -### 2. Remove devvit.yaml - -Delete the `devvit.yaml` file from your project root. All configuration is now handled by `devvit.json`. - -### 3. Handle static assets - -If your app uses static assets (images, fonts, etc.) from an `assets` folder, you'll need to define this in update your `devvit.json` to point to these assets: - -```json -{ - "name": "your-app-name", - "blocks": { - "entry": "src/main.tsx", - "triggers": ["onPostCreate"] - }, - "media": { - "dir": "assets/" - } -} -``` - -### 4. Test your app - -Run your app locally to ensure everything works: - -```bash -devvit playtest -``` - -## That's it! - -Your Blocks and Mod Tools code should work as intended without any other changes. The Devvit runtime handles the compatibility layer automatically. - -While your app will work with just these changes, we recommend exploring the additional capabilities available in Devvit Web over time. diff --git a/versioned_docs/version-0.12/guides/migrate/devvit-web-experimental.md b/versioned_docs/version-0.12/guides/migrate/devvit-web-experimental.md deleted file mode 100644 index 9fa9983c..00000000 --- a/versioned_docs/version-0.12/guides/migrate/devvit-web-experimental.md +++ /dev/null @@ -1,190 +0,0 @@ -# Migrating from Devvit Web Experimental to Devvit Web - -This guide will help you migrate from the experimental version of Devvit Web to the official Devvit Web setup. You must complete this migration to publish and grow your app. - -> **Note**: Apps can be partially migrated, you don't need to re-write everything! - -## How to identify if you're using the experimental version - -You're using Devvit Web experimental if: - -- Your project is based on either of these templates: - - https://github.com/reddit/devvit-bolt-starter-experimental - - https://github.com/reddit/devvit-template-react -- You have a `defineConfig` function in `src/devvit/main.tsx` - -## What's changing - -### Before (experimental) - -- Uses `defineConfig` function in blocks -- Multiple `@devvit/X` packages for different capabilities -- Webroot-based dist outputs - -### After (final version) - -- Uses `devvit.json` for all configuration -- Single `@devvit/web` package with submodule imports -- Cleaner dist folder structure -- Clear separation of client and server code - -## Migration steps - -### 1. Install the latest @devvit/web - -```bash -npm install @devvit/web@latest -``` - -### 2. Remove individual @devvit packages - -Remove all individual capability packages: - -```bash -npm uninstall @devvit/redis @devvit/server @devvit/client -``` - -### 3. Create your devvit.json - -Create a `devvit.json` file in your project root. This replaces all configuration previously done through `defineConfig`: - -```json -{ - "post": { - "client": { - "dir": "dist/client", - "entry": "dist/client/index.html" - } - }, - "blocks": { - "entry": "src/devvit/main.tsx" - }, - "server": { - "entry": "dist/server/index.cjs" - } -} -``` - -> **Note**: Output directories no longer need to go to `webroot`. Use `dist/client` and `dist/server` for cleaner organization. - -### 4. Update your imports - -Change all imports from individual packages to the unified `@devvit/web` package: - -#### Server-side imports - -```typescript -// Before -import { redis } from '@devvit/redis'; -import { createServer, context } from '@devvit/server'; - -// After -import { redis } from '@devvit/web/server'; -import { createServer, context } from '@devvit/web/server'; -``` - -#### Client-side imports - -```typescript -// Before -import { navigateTo } from '@devvit/client'; - -// After -import { navigateTo } from '@devvit/web/client'; -``` - -### 5. Remove defineConfig from main.tsx - -In your `src/devvit/main.tsx`, remove the `defineConfig` function and any configuration it contained. This configuration now lives in `devvit.json`. - -```typescript -// Before in src/devvit/main.tsx -import { defineConfig } from '@devvit/server'; - -export default defineConfig({ - // ... configuration -}); - -// After -// Simply export your Devvit instance or any Devvit.addX functions -import { Devvit } from '@devvit/web'; - -// Your Devvit setup code here -export default Devvit; -``` - -### 6. Reorganize your project structure - -We recommend using a clean folder structure: - -``` -your-app/ -├── src/ -│ ├── client/ # Your web app (React, etc.) -│ │ └── index.tsx -│ ├── server/ # Your server endpoints -│ │ └── index.ts -│ └── devvit/ # Blocks-related code (optional now) -│ └── main.tsx -├── dist/ # Built assets -│ ├── client/ -│ └── server/ -├── devvit.json -└── package.json -``` - -### 7. Update your build configuration - -Ensure your bundler outputs to the correct directories specified in `devvit.json`: - -#### Server Vite config example - -```typescript -export default defineConfig({ - ssr: { - noExternal: true, - }, - build: { - emptyOutDir: false, - ssr: 'index.ts', - outDir: '../../dist/server', - target: 'node22', - sourcemap: true, - rollupOptions: { - external: [...builtinModules], - output: { - format: 'cjs', - entryFileNames: 'index.cjs', - inlineDynamicImports: true, - }, - }, - }, -}); -``` - -#### Client Vite config example - -```typescript -export default defineConfig({ - build: { - outDir: '../../dist/client', // No longer webroot - }, -}); -``` - -## Quick migration path - -For the fastest migration: - -1. **Start with a new template**: Clone https://github.com/reddit/devvit-template-react -2. **Move your server endpoints**: Copy your server code to the `src/server` folder -3. **Move your client app**: Copy your React/web code to the `src/client` folder -4. **Update imports**: Find and replace all `@devvit/X` imports with `@devvit/web/server` or `@devvit/web/client` -5. **Configure devvit.json**: Set up your entrypoints as shown above and update your app name -6. **Test locally**: Run `npm run dev` to ensure everything works - -## Additional considerations - -- All capabilities previously available through the experimental API are still available in the final version -- The context object and Redis access work the same way, just with different import paths -- Your app logic can still be split between client and server as before diff --git a/versioned_docs/version-0.12/guides/migrate/inline-web-view.md b/versioned_docs/version-0.12/guides/migrate/inline-web-view.md deleted file mode 100644 index e1700e0f..00000000 --- a/versioned_docs/version-0.12/guides/migrate/inline-web-view.md +++ /dev/null @@ -1,202 +0,0 @@ -# Migrating from useWebView to Devvit Web - -This guide will migrate your legacy webview implementation (using useWebView inside of Blocks) to the official Devvit Web setup. - -:::note -Apps can be partially migrated, you don't need to re-write everything! -::: - -# Before - -- Use postMessage for message passing -- App logic is isomorphic (server/client) in Blocks -- No client effects available - -# After - -- No postMessage required -- Use web native fetch() to server endpoints directly -- App logic is either on the client, or the server, with clear deliniation -- Client effects are available directly from web views - -## Setting up devvit.json - -The first thing you need to do is setup `devvit.json`. - -Schema here: https://developers.reddit.com/schema/config-file.v1.json - -`devvit.json` supports all capabilities previously available in the `Devvit` singleton, e.g. `Devvit.addCustomPostType()`. For the purposes of this guide, only the post rendering logic will be migrated. - -### Understanding entrypoints - -Your `devvit.json` must have entrypoints that point to **outputs** of your code. It is assumed that you have installed a bundler or can otherwise prepare static assets to appear in your dist folders. - -```js -{ - "post": { - "client": { // The output of your client app, probably /src/webroot - "dir": "dist/client", - "entry": "dist/client/index.html" - } - }, - "blocks": { // point to where you export Devvit singleton, probably src/main.tsx - "entry": "src/devvit/main.tsx" - }, - "server": { // new folder which will contain your Node server - "entry": "dist/server/index.cjs" - }, -} -``` - -> You'll notice that the `blocks` entrypoint points to your TypeScript source file (`src/devvit/main.tsx`). This is because the Devvit CLI handles bundling for Blocks automatically. For your `client` and `server` entrypoints, however, you are responsible for bundling your code and pointing to the final output files in your `dist` directory. - -### Building your client and server - -The `devvit.json` configuration for `client` and `server` points to files in a `dist` directory. This means you're responsible for building your web and server assets. You can use any bundler you like, such as `vite`. - -For example, your `package.json` might include scripts to output your assets to the `dist` folder. - -Sample server vite config - -```ts title="src/server/vite.config.ts -import { defineConfig } from 'vite'; -import { builtinModules } from 'node:module'; - -export default defineConfig({ - ssr: { - noExternal: true, - }, - build: { - ssr: 'index.ts', - outDir: '../../dist/server', - target: 'node22', - sourcemap: true, - rollupOptions: { - external: [...builtinModules], - - output: { - format: 'cjs', - entryFileNames: 'index.cjs', - inlineDynamicImports: true, - }, - }, - }, -}); -``` - -Sample client Vite config (for React) - -```ts title="src/client/vite.config.ts -import { defineConfig } from 'vite'; -import tailwind from '@tailwindcss/vite'; -import react from '@vitejs/plugin-react'; - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react(), tailwind()], - build: { - outDir: '../../dist/client', - sourcemap: true, - chunkSizeWarningLimit: 1500, - }, -}); -``` - -## Setting up your server endpoints - -You can use any Node server for your server endpoints. This guide will use [Express](https://expressjs.com/). - -1. Install Express - -``` -npm i express -``` - -2. Create a server index file - -```ts title='src/server/index.ts' -import express from 'express'; -// The `@devvit/server` package provides the tools to create a server, -// and gives you access to the request context. -import { createServer, context, getServerPort, redis } from '@devvit/web/server'; - -const app = express(); - -// Middleware for JSON body parsing -app.use(express.json()); -// Middleware for URL-encoded body parsing -app.use(express.urlencoded({ extended: true })); -// Middleware for plain text body parsing -app.use(express.text()); - -const router = express.Router(); - -// The `context` object is automatically populated with useful information, -// like the current user's ID. Devvit's services, like redis, are also -// available via named imports from `@devvit/server`. -router.get<{ postId: string }, { message: string }>( - '/api/hello', - async (_req, res): Promise => { - const { userId } = context; - res.status(200).json({ - message: `Hello ${userId}`, - }); - } -); - -router.get('/api/init', async (_req, res): Promise => { - res.json({ initialState: await redis.get('initialState') }); -}); - -// Use router middleware -app.use(router); - -// Get port from environment variable with fallback -const port = getServerPort(); - -const server = createServer(app); -server.on('error', (err) => console.error(`server error; ${err.stack}`)); -server.listen(port, () => console.log(`http://localhost:${port}`)); -``` - -### Calling your server endpoints - -Now - -Instead of using `postMessage`, your client-side code can now directly fetch the initial state from the `/api/init` endpoint we defined in the server. - -```ts title=/src/client/app.ts -const res = await fetch('/api/init'); -const data = await res.json(); -console.log(data.initialState); // Logs the state from Redis -``` - -## Client effects - -Previously, client effects were not available to your webview app. You had to pass a custom postMessage and handle that message in Blocks. Now, all client effects are available directly in the web-view through `@devvit/client`. - -Before - -```ts title=/src/devvit/main.tsx -const BlocksComponent = () => { - const wv = useWebView({ - onMessage: (message) => { - if (message.type === 'navigate_to') { - ui.navigateTo(message.data.destination); - } - }, - }); -}; -``` - -```js title=webroot/app.js -window.postMessage({ type: 'navigate_to', destination: 'reddit.com' }); -``` - -Now - -```ts title=client/app.ts -import { navigateTo } from '@devvit/web/client'; - -navigateTo('reddit.com'); -``` diff --git a/versioned_docs/version-0.12/guides/tools/devvit_cli.mdx b/versioned_docs/version-0.12/guides/tools/devvit_cli.mdx index 3ff2308a..fe985951 100644 --- a/versioned_docs/version-0.12/guides/tools/devvit_cli.mdx +++ b/versioned_docs/version-0.12/guides/tools/devvit_cli.mdx @@ -34,25 +34,13 @@ $ npx devvit create icons "src/my-icons.ts" #### Using the SVG files in app code -```tsx -import { Devvit } from '@devvit/public-api'; +```tsx title="src/client/App.tsx" import Icons from './my-icons.ts'; -// addCustomPostType() is deprecated and will be unsupported. It will not work after June 30. View the announcement below this example. -Devvit.addCustomPostType({ - name: 'my-custom-post', - render: (_context) => { - return ( - - - - ); - }, -}); - -export default Devvit; +export const App = () => ( + +); ``` -[View `addCustomPostType` deprecation announcement.](https://www.reddit.com/r/Devvit/comments/1r3xcm2/devvit_web_and_the_future_of_devvit/) ### devvit help diff --git a/versioned_docs/version-0.12/quickstart/quickstart-mod-tool.md b/versioned_docs/version-0.12/quickstart/quickstart-mod-tool.md index b3ae9b65..b907e557 100644 --- a/versioned_docs/version-0.12/quickstart/quickstart-mod-tool.md +++ b/versioned_docs/version-0.12/quickstart/quickstart-mod-tool.md @@ -34,135 +34,120 @@ Cutting the template to the target directory... This tutorial lets you build your own version of [Comment Mop](https://developers.reddit.com/apps/comment-nuke). This tool allows moderators to remove and/or lock a full comment tree with a single menu action, avoiding repetitive mechanical tasks for community moderators. -### Create a menu action for moderators +### Declare a menu action for moderators -The template leverages [Menu Actions](../capabilities/client/menu-actions) to enable moderators to Delete/Lock child comments of a post or comment. Menu Actions appear in the moderator menu: -![menu actions](../assets/quickstart/quickstart-mod-tool-1.png) +Menu items are declared in `devvit.json`. Each entry points at a server endpoint that runs when a moderator clicks it. The template leverages [Menu Actions](../capabilities/client/menu-actions.mdx) to enable moderators to Delete/Lock child comments of a post or comment. -The following code adds a menu action to comments. Once the app is installed on a subreddit, all moderators of the subreddit will see this option appear in the moderator menu for all comments. +![menu actions](../assets/quickstart/quickstart-mod-tool-1.png) -```ts -Devvit.addMenuItem({ - label: 'Mop comments', - description: 'Remove this comment and all child comments. This might take a few seconds to run.', - location: 'comment', - forUserType: 'moderator', - onPress: (_event, context) => { - context.ui.showForm(nukeForm); +```json title="devvit.json" +{ + "menu": { + "items": [ + { + "label": "Mop comments", + "description": "Remove this comment and all child comments. This might take a few seconds to run.", + "forUserType": "moderator", + "location": ["comment"], + "endpoint": "/internal/menu/mop-comments" + } + ] }, -}); + "forms": { + "mopForm": "/internal/form/mop-submit" + }, + "permissions": { + "reddit": { "enable": true, "scope": "moderator" } + } +} ``` -You'll notice that the `onPress` handler of this Menu Item action invokes a form with `context.ui.showForm()`. This will be explained in the next step. +### Show a form from the menu action -### Devvit forms - -Optionally, some moderator tools might need to request some additional information from the moderator before they can execute. In these cases we can leverage [Devvit Forms](../capabilities/client/forms). Comment Mop will display a form with some options regarding the action to be taken: +Some moderator tools need additional information before they execute. The menu endpoint can respond with a form using [menu responses](../capabilities/client/menu-actions.mdx). Comment Mop displays a form with options for the action to be taken: ![forms](../assets/quickstart/quickstart-mod-tool-2.png) -The code that defines this form is: +```ts title="server/index.ts" +import type { MenuItemRequest, UiResponse } from '@devvit/web/shared'; + +app.post('/internal/menu/mop-comments', async (c) => { + const _input = await c.req.json(); + return c.json({ + showForm: { + name: 'mopForm', + form: { + title: 'Mop Comments', + acceptLabel: 'Mop', + cancelLabel: 'Cancel', + fields: [ + { name: 'remove', label: 'Remove comments', type: 'boolean', defaultValue: true }, + { name: 'lock', label: 'Lock comments', type: 'boolean', defaultValue: false }, + { + name: 'skipDistinguished', + label: 'Skip distinguished comments', + type: 'boolean', + defaultValue: false, + }, + ], + }, + }, + }); +}); +``` -```ts -// Define form fields -const nukeFields: FormField[] = [ - { - name: 'remove', - label: 'Remove comments', - type: 'boolean', - defaultValue: true, - }, - { - name: 'lock', - label: 'Lock comments', - type: 'boolean', - defaultValue: false, - }, - { - name: 'skipDistinguished', - label: 'Skip distinguished comments', - type: 'boolean', - defaultValue: false, - }, -] as const; - -// Create form -const nukeForm = Devvit.createForm( - () => { - return { - fields: nukeFields, - title: 'Mop Comments', - acceptLabel: 'Mop', - cancelLabel: 'Cancel', - }; - }, - // Form confirmation handler - async ({ values }, context) => { - if (!values.lock && !values.remove) { - context.ui.showToast('You must select either lock or remove.'); - return; - } +### Handle the form submission with the Reddit API - if (context.commentId) { - // ... - // mop comments here - // ... - } - } -); -``` +Devvit apps can use the Reddit API to act on comments and posts. The form submission endpoint receives the moderator's selections and traverses the comment tree: -The code that handles mopping the comment has been redacted from the sample above. It uses the Reddit API to traverse through the comments and perform the necessary actions. It will be explained in the next step. +```ts title="server/index.ts" +import { reddit, context } from '@devvit/web/server'; +import type { UiResponse } from '@devvit/web/shared'; -### Reddit API +type MopFormRequest = { + remove: boolean; + lock: boolean; + skipDistinguished: boolean; +}; -Apps made with Devvit can leverage the Reddit API to perform actions on comments, posts, get information about the current session, etc. The following code uses the Reddit API to find the child comments of the selected comment and delete all of them: +app.post('/internal/form/mop-submit', async (c) => { + const { remove, lock, skipDistinguished } = await c.req.json(); + const { commentId } = context; -```ts -Devvit.configure({ - redditAPI: true, -}); + if (!remove && !lock) { + return c.json({ showToast: 'You must select either lock or remove.' }); + } + + if (!commentId) { + return c.json({ showToast: 'This action must be run on a comment.' }); + } -export async function handleNuke(props: NukeProps, context: Devvit.Context) { try { - // Get Comment and User from Reddit API - const comment = await context.reddit.getCommentById(props.commentId); - const user = await context.reddit.getCurrentUser(); - - // Get Comments for Removal - const comments: Comment[] = []; - for await (const eachComment of getAllCommentsInThread(comment, skipDistinguished)) { - comments.push(eachComment); - } + const rootComment = await reddit.getCommentById(commentId); - // Remove all comments - await Promise.all(comments.map((comment) => comment.removed || comment.remove())); - - // Add to Mod Log - try { - await context.modLog.add({ - action: 'removecomment', - target: props.commentId, - details: 'comment-mop app', - description: `u/${user.username} used comment-mop to ${verbage} this comment and all child comments.`, - }); - } catch (e: unknown) { - console.error(`Failed to add modlog for comment: ${props.commentId}.`, (e as Error).message); + for await (const comment of walkReplies(rootComment, skipDistinguished)) { + if (remove && !comment.removed) await comment.remove(); + if (lock && !comment.locked) await comment.lock(); } - // Show Toast with Result - context.ui.showToast('Comments removed! Refresh the page to see the cleanup.'); - } catch (err: unknown) { - context.ui.showToast('Mop failed! Please try again later.'); + return c.json({ + showToast: 'Comments mopped! Refresh the page to see the cleanup.', + }); + } catch (err) { console.error(err); + return c.json({ showToast: 'Mop failed! Please try again later.' }); } -} +}); -// Helper Function - Depth-first traversal to get all comments in a thread -async function* getAllCommentsInThread(comment: Comment): AsyncGenerator { +async function* walkReplies( + comment: Awaited>, + skipDistinguished: boolean, +): AsyncGenerator { + if (skipDistinguished && comment.distinguishedBy) return; + yield comment; const replies = await comment.replies.all(); for (const reply of replies) { - yield* getAllCommentsInThread(reply); + yield* walkReplies(reply, skipDistinguished); } } ``` @@ -186,6 +171,6 @@ Now you have a mod tool running from the code that you deployed yourself. Feel f ## Further reading - Use our [launch guide](../guides/launch/launch-guide.md) to guide you where to get your first users. -- [Devvit Forms](../capabilities/client/forms) -- [Menu Actions](../capabilities/client/menu-actions) +- [Devvit Forms](../capabilities/client/forms.mdx) +- [Menu Actions](../capabilities/client/menu-actions.mdx) - [Reddit Developer Funds](../earn-money/reddit_developer_funds) diff --git a/versioned_sidebars/version-0.12-sidebars.json b/versioned_sidebars/version-0.12-sidebars.json index b72dfc6b..73bdc650 100644 --- a/versioned_sidebars/version-0.12-sidebars.json +++ b/versioned_sidebars/version-0.12-sidebars.json @@ -156,7 +156,6 @@ "items": [ "earn-money/payments/payments_overview", "earn-money/payments/payments_add", - "earn-money/payments/payments_migrate", "earn-money/payments/payments_test", "earn-money/payments/payments_publish", "earn-money/payments/payments_manage", @@ -284,9 +283,6 @@ "type": "category", "label": "Migration Guides", "items": [ - "guides/migrate/devvit-singleton", - "guides/migrate/devvit-web-experimental", - "guides/migrate/inline-web-view", { "type": "category", "label": "Splash Screens", @@ -317,33 +313,6 @@ "className": "sidebar-divider", "defaultStyle": false }, - { - "type": "category", - "label": "Devvit Blocks", - "items": [ - "capabilities/blocks/overview", - "capabilities/blocks/blocks_payments", - "capabilities/blocks/dimensions", - "capabilities/blocks/working_with_usestate", - "capabilities/blocks/working_with_useinterval", - "capabilities/blocks/working_with_useasync", - "capabilities/blocks/app_image_assets", - "capabilities/realtime/realtime_in_devvit_blocks", - { - "type": "category", - "label": "Blocks Reference", - "items": [ - "blocks/stacks", - "blocks/text", - "blocks/icon", - "blocks/button", - "blocks/image", - "blocks/spacer", - "blocks/colors" - ] - } - ] - }, { "type": "doc", "label": "Changelog",