Skip to content

Commit dc07552

Browse files
committed
feat: added a doc on modifiers
1 parent 895ffe1 commit dc07552

File tree

9 files changed

+1456
-1172
lines changed

9 files changed

+1456
-1172
lines changed

astro.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export default defineConfig({
7777
SkipLink: './src/components/starlight/SkipLink.astro',
7878
},
7979
customCss: ['./src/tailwind.css'],
80-
sidebar,
80+
sidebar: sidebar,
8181
expressiveCode: {
8282
plugins: [pluginLineNumbers()],
8383
defaultProps: {

package.json

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,26 @@
1313
},
1414
"dependencies": {
1515
"@astrojs/check": "^0.9.4",
16-
"@astrojs/react": "^3.6.2",
17-
"@astrojs/starlight": "^0.28.3",
16+
"@astrojs/react": "^3.6.3",
17+
"@astrojs/starlight": "^0.28.6",
1818
"@astrojs/starlight-tailwind": "^2.0.3",
19-
"@astrojs/tailwind": "^5.1.2",
19+
"@astrojs/tailwind": "^5.1.3",
2020
"@expressive-code/plugin-line-numbers": "^0.37.1",
2121
"@iconify-json/mdi": "^1.2.1",
22-
"@types/react": "^18.3.12",
23-
"@types/react-dom": "^18.3.1",
24-
"astro": "^4.16.7",
22+
"@types/react": "^18.3.14",
23+
"@types/react-dom": "^18.3.2",
24+
"astro": "^4.16.17",
2525
"astro-embed": "^0.7.4",
26-
"astro-icon": "^1.1.1",
27-
"firebase-tools": "^13.22.1",
28-
"prettier": "^3.3.3",
26+
"astro-icon": "^1.1.4",
27+
"firebase-tools": "^13.28.0",
28+
"prettier": "^3.4.2",
2929
"react": "^18.3.1",
3030
"react-dom": "^18.3.1",
3131
"rehype-external-links": "^3.0.0",
3232
"sharp": "^0.32.6",
33-
"starlight-links-validator": "^0.12.3",
33+
"starlight-links-validator": "^0.12.4",
3434
"starlight-showcases": "^0.2.0",
35-
"tailwindcss": "^3.4.14",
36-
"typescript": "^5.6.3"
35+
"tailwindcss": "^3.4.16",
36+
"typescript": "^5.7.2"
3737
}
3838
}

pnpm-lock.yaml

Lines changed: 1160 additions & 1155 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sidebar.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ export const sidebar = [
3030
'guides/cms/designing-content-schemas',
3131
'guides/cms/custom-content-type',
3232
'guides/cms/custom-layout',
33+
{
34+
slug: 'guides/cms/custom-modifier',
35+
badge: { text: 'New', variant: 'note' },
36+
},
3337
'guides/cms/custom-action',
3438
'guides/cms/custom-api-content',
3539
'guides/cms/conditions',

src/content/docs/guides/cms/core-elements.mdx

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: Core Elements
33
description: The 5 core elements for building CMS-driven UI Experiences
44
---
55

6-
import { Aside } from '@astrojs/starlight/components'
6+
import { Aside, Badge } from '@astrojs/starlight/components'
77

88
Applications have become much more sophisticated in the past few years and will
99
continue to advance the State of the Art. Many of them are moving into the realm
@@ -12,14 +12,15 @@ that we would like to play as a framework.
1212

1313
In this article let's explore the five core elements of building such
1414
experiences. This is a distilled version of the essentials for any App and are
15-
the fundamental building blocks of a CMS-driven experience. These 5 elements
15+
the fundamental building blocks of a CMS-driven experience. These 6 elements
1616
are:
1717

1818
1. Route
1919
2. Content Item
2020
3. Layout
21-
4. Action
22-
5. Condition
21+
4. Modifiers <Badge text="New" />
22+
5. Action
23+
6. Condition
2324

2425
![Core elements and their relationships](images/core-elements.png)
2526

@@ -102,6 +103,37 @@ specified on the CMS.
102103

103104
</Aside>
104105

106+
## Modifiers <sup><Badge text="New" /></sup>
107+
108+
Modifiers are additional visual behaviors that can be attached to the output of
109+
the layout in order to produce something interesting. This could change the
110+
theme of the contained widget, provide more localization context, or do things
111+
like in coach marks.
112+
113+
Modifiers are an interesting way of extending the visual behaviors of the layout
114+
without having to write all of those behaviors for each specific content item.
115+
116+
The input to a modifier is the built `Widget` of the content item, which is then
117+
optionally wrapped in a `Widget` by each modifier in order to produce the final
118+
output.
119+
120+
Thus, the output of a modifier is also a `Widget`. For modifiers that don't make
121+
any change, it is very natural to return the same widget that was passed in.
122+
This could be the case of a simple pass-through modifier. Other modifiers can do
123+
a wrapping of additional providers or `InheritedWidget`s to provide additional
124+
context for the contained widget
125+
126+
<Aside type="tip" title={'Modifiers are composable and chained'}>
127+
128+
You can have multiple modifiers applied to a single content item. This allows
129+
you to build complex visual behaviors by composing and chaining simpler ones.
130+
The order is particularly important as the output of one modifier is the input
131+
to the next.
132+
133+
As expected, you can configure all modifiers in the CMS.
134+
135+
</Aside>
136+
105137
## Action
106138

107139
Actions allow the user to participate and interact with the interface. There can
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
---
2+
title: Custom Modifier
3+
description:
4+
Leverage the power of modifiers to modify the visual behavior of the content
5+
item
6+
---
7+
8+
import { Aside } from '@astrojs/starlight/components'
9+
import { Image } from 'astro:assets'
10+
import darkCardImage from './images/dark-card-theme-modifier.png'
11+
import cmsThemeModifierImage from './images/cms-theme-modifier.png'
12+
13+
We have seen earlier that custom layouts can be used to modify the appearance of
14+
a content item. You can create several layouts, exposed by different features
15+
across the application.
16+
17+
That is great for creating different layouts, but what if you want to apply the
18+
same visual behavior across all instances of a content item? Well, that is where
19+
**modifiers** come in.
20+
21+
`Modifiers` allow you to attach a visual behavior to every instance of a content
22+
item, and you can configure that from the CMS with the corresponding
23+
implementation in Flutter.
24+
25+
## Chain of Modifiers
26+
27+
Unlike layouts, there can be multiple modifiers for a content item. These can be
28+
applied as a chain with each modifier getting the output of the previous
29+
modifier.
30+
31+
By chaining modifiers you can get a pretty rich visual output for a specific
32+
content item. We'll see an example of how to use a `theme-modifier` to modify
33+
the theme for the entire widget tree.
34+
35+
![Chain of Modifiers](images/chain-of-modifiers.png)
36+
37+
The input to the modifier is the output of the _layout_ for the content-item,
38+
which is the `Widget`. Each modifier can modify the `Widget` and pass it on to
39+
the next modifier in the chain. The output of the last modifier is the final
40+
`Widget` that is rendered on the screen.
41+
42+
<Aside type="tip" title={'Creative combination of Modifiers, DI, Layout within Vyuh'}>
43+
44+
You can combine the power of Modifiers, Dependency Injection, and Layouts to
45+
create a rich visual experience for your content items. This is where the true
46+
power of Vyuh lies.
47+
48+
The modifiers can construct a tree that can leverage State-management via
49+
Dependency Injection, `InheritedWidget`s, custom `Layout` of the `ContentItem`,
50+
and other Flutter features to create a powerful visual experience.
51+
52+
The core abstractions of Vyuh are designed to be composable and extensible for
53+
achieving any of your visual requirements.
54+
55+
</Aside>
56+
57+
## Implementing a Custom `ThemeModifier`
58+
59+
Flutter allows us to change the themes of certain widget trees by using a
60+
`Theme` widget at the right places. This allows us to have a different theme
61+
altogether for a widget subtree compared to the one for the entire application.
62+
63+
This is the perfect use case for a Modifier that can come in and wrap the layout
64+
widget for the content item with a Theme widget. The code for this is pretty
65+
simple and can be listed entirely over here.
66+
67+
```dart title="theme_modifier.dart" showLineNumbers {26-31}
68+
import 'package:flutter/material.dart';
69+
import 'package:json_annotation/json_annotation.dart';
70+
import 'package:vyuh_core/vyuh_core.dart';
71+
import 'package:vyuh_feature_system/vyuh_feature_system.dart';
72+
73+
part 'theme_modifier.g.dart';
74+
75+
@JsonSerializable()
76+
final class ThemeModifier extends ContentModifierConfiguration {
77+
static const schemaName = 'vyuh.content.modifier.theme';
78+
79+
static final typeDescriptor = TypeDescriptor(
80+
fromJson: ThemeModifier.fromJson,
81+
schemaType: schemaName,
82+
title: 'Theme Modifier',
83+
);
84+
85+
final ThemeMode mode;
86+
87+
ThemeModifier({this.mode = ThemeMode.light}) : super(schemaType: schemaName);
88+
89+
factory ThemeModifier.fromJson(Map<String, dynamic> json) =>
90+
_$ThemeModifierFromJson(json);
91+
92+
@override
93+
Widget build(BuildContext context, Widget child, ContentItem content) {
94+
final service = vyuh.di.get<ThemeService>();
95+
final themeData = service.theme(mode);
96+
97+
return themeData != null ? Theme(data: themeData, child: child) : child;
98+
}
99+
}
100+
101+
```
102+
103+
This is a simple modifier that takes a `ThemeMode` as input and wraps the child
104+
widget with a `Theme` widget. The `Theme` widget is constructed using a
105+
`ThemeData` object that is fetched from a `ThemeService`. Somewhere else in the
106+
app, you would have a `ThemeService` that is pre-configured for the light and
107+
dark `ThemeMode`.
108+
109+
You can see how we are combining the output of the layout with the modifier to
110+
create a themed-widget-tree. In the process, we also leverage Dependency
111+
Injection to fetch the `ThemeService` that provides the `ThemeData` object.
112+
113+
> This modifier can be configured from the CMS with the `mode` parameter.
114+
115+
## Specifying Modifiers in the `FeatureDescriptor`
116+
117+
For the modifiers to show up in your CMS and also work on the Flutter side, you
118+
need to specify them inside a `FeatureDescriptor`. This can be seen below.
119+
120+
### CMS Configuration
121+
122+
On the CMS side, we have configured the theme-modifier with `light`, `dark`, and
123+
a `system` mode.
124+
125+
```typescript title="content-modifiers/theme.ts"
126+
import { defineType } from 'sanity'
127+
import { FaPalette as Icon } from 'react-icons/fa6'
128+
129+
export const themeModifier = defineType({
130+
name: 'vyuh.content.modifier.theme',
131+
type: 'object',
132+
title: 'Theme',
133+
icon: Icon,
134+
fields: [
135+
{
136+
name: 'mode',
137+
title: 'Mode',
138+
type: 'string',
139+
initialValue: 'light',
140+
options: {
141+
list: [
142+
{ title: 'Light', value: 'light' },
143+
{ title: 'Dark', value: 'dark' },
144+
{ title: 'System', value: 'system' },
145+
],
146+
},
147+
},
148+
],
149+
preview: {
150+
select: {
151+
mode: 'mode',
152+
},
153+
prepare(selection: any) {
154+
return {
155+
title: 'Theme',
156+
subtitle: `Mode: ${selection.mode ?? 'light'}`,
157+
}
158+
},
159+
},
160+
})
161+
```
162+
163+
We then include this in the `FeatureDescriptor` like so:
164+
165+
```typescript title="feature.ts" showLineNumbers {11}
166+
import { FeatureDescriptor } from '@vyuh/sanity-schema-core'
167+
import { themeModifier } from './content-modifiers/theme'
168+
169+
export const system = new FeatureDescriptor({
170+
name: 'system',
171+
title: 'System',
172+
description: 'Core System feature of the framework',
173+
174+
// rest of the configuration
175+
176+
contentModifiers: [themeModifier],
177+
})
178+
```
179+
180+
### Flutter Configuration
181+
182+
On the Flutter side, we have its counterpart in the
183+
`ThemeModifier.typeDescriptor`.
184+
185+
```dart title="feature.dart" showLineNumbers {16-18}
186+
import 'theme_modifier.dart';
187+
188+
final feature = FeatureDescriptor(
189+
name: 'system',
190+
title: 'System',
191+
description: 'The core building blocks of the framework',
192+
icon: Icons.hub,
193+
init: () async {
194+
vyuh.di.register(ThemeService());
195+
},
196+
197+
// Rest of the configuration
198+
199+
extensions: [
200+
ContentExtensionDescriptor(
201+
contentModifiers: [
202+
ThemeModifier.typeDescriptor,
203+
],
204+
),
205+
],
206+
);
207+
208+
```
209+
210+
With these two configuration in place, the `ThemeModifier` will work its magic
211+
on the content items. The resulting rendered version can be seen in the
212+
following section.
213+
214+
## In Action
215+
216+
<Image src={cmsThemeModifierImage} alt={'Dark Theme for Card via a Modifier'} />
217+
218+
You can see the configuration for the `ThemeModifier` in the CMS. This is a very
219+
simple example of how you can use a modifier to change the theme of a widget
220+
tree for a content item. In this case, we are changing the theme of a `Card` to
221+
a dark theme.
222+
223+
While the rest of the App might be using a light theme, this content item will
224+
always render with a dark theme. This is a powerful way to apply a targeted
225+
visual change to a content item without disturbing the rest of the application
226+
UI.
227+
228+
<Image
229+
src={darkCardImage}
230+
alt={'Dark Theme for Card via a Modifier'}
231+
width={300}
232+
/>
233+
234+
## Summary
235+
236+
Modifiers are a powerful way to apply a visual behavior to a content item. They
237+
can be chained together to create a rich visual experience for the content item.
238+
You can use them to apply a theme, localization, or any other visual behavior
239+
that you can think of.
240+
241+
The power of modifiers lies in their ability to be configured from the CMS and
242+
applied to the content item in Flutter. This allows you to create a rich visual
243+
experience for your content items without having to write a lot of Flutter code.
Loading
Loading
Loading

0 commit comments

Comments
 (0)