@uni-design-system/uni-angular 0.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,270 @@
1
+ // noinspection JSUnusedGlobalSymbols
2
+
3
+ import { computed, inject, Injectable, linkedSignal, Signal, signal } from '@angular/core';
4
+ import { css } from '@emotion/css';
5
+ import { LocalStorageService, Options } from '../cdk';
6
+ import {
7
+ LightTheme,
8
+ type NullableSize,
9
+ type ThemeName,
10
+ type Typeface,
11
+ type UniTheme,
12
+ Z_INDEX,
13
+ type ZIndexableElements,
14
+ ColorToken,
15
+ ComponentName,
16
+ ComponentTheme,
17
+ ContainerColorToken,
18
+ ContentColorToken,
19
+ NullableStyleExpression,
20
+ OptionalSize,
21
+ Size,
22
+ TextRole,
23
+ Thickness,
24
+ Variant,
25
+ type ColorKey,
26
+ type Radius,
27
+ type Border,
28
+ type Shadow,
29
+ } from '@uni-design-system/uni-core';
30
+
31
+ import { UNI_THEMES } from './theme.token';
32
+ import { safeParseInt } from '../cdk/helpers/number.helper';
33
+
34
+ @Injectable({
35
+ providedIn: 'root',
36
+ })
37
+ export class ThemeService {
38
+ private themes = inject(UNI_THEMES);
39
+ private localStorage = inject(LocalStorageService);
40
+
41
+ theme = signal<UniTheme>(LightTheme);
42
+ themeOptions = signal<Options<ThemeName>>([]);
43
+
44
+ components = computed(() => this.theme().components);
45
+ component = <T>(componentName: ComponentName): Signal<ComponentTheme<T>> =>
46
+ computed(() => (this.components()[componentName] as ComponentTheme<T>) || {});
47
+ colors = computed(() => this.theme().colors);
48
+ typeFaces = computed(() => this.theme().typefaces);
49
+ spacing = computed(() => this.theme().spacing);
50
+ thicknesses = computed(() => this.theme().thicknesses);
51
+ radii = computed(() => this.theme().radii);
52
+ borders = computed(() => this.theme().borders);
53
+ shadows = computed(() => this.theme().shadows);
54
+ icons = computed(() => this.theme().icons);
55
+
56
+ constructor() {
57
+ this.themeOptions.set(
58
+ Object.keys(this.themes).map((key) => {
59
+ return { label: this.themes[key].name, value: key };
60
+ })
61
+ );
62
+
63
+ this.selectTheme(this.localStorage.getItem('theme') || Object.keys(this.themes)[0] || 'base');
64
+ }
65
+
66
+ public selectTheme(themeName: ThemeName): void {
67
+ this.selectedThemeKey.set(themeName);
68
+ if (this.themes[themeName]) this.theme.set(this.themes[themeName]);
69
+ this.localStorage.setItem('theme', themeName);
70
+ }
71
+
72
+ public selectedThemeName = computed(() => this.theme().name);
73
+ public selectedThemeKey = signal<string>('');
74
+
75
+ textClass = (textRole: TextRole, textColor?: ContentColorToken) => {
76
+ return css([
77
+ {
78
+ ...this.typeFaces()[textRole],
79
+ },
80
+ textColor && {
81
+ color: this.colors()[textColor],
82
+ },
83
+ ]);
84
+ };
85
+
86
+ componentStyle = (componentName: ComponentName, variant: Variant, size: Size) =>
87
+ computed(() => {
88
+ const component = this.component(componentName)();
89
+ const { fixed, colors, sizes } = component;
90
+ const colorStyle = colors && colors[variant];
91
+ const sizeStyle = sizes && sizes[size];
92
+ return { ...fixed, ...colorStyle, ...sizeStyle };
93
+ });
94
+
95
+ getSpacing = (size: NullableSize) => {
96
+ return size === 'none' ? 'none' : this.spacing()[size];
97
+ };
98
+
99
+ getThickness = (thickness: Thickness) => this.theme().thicknesses[thickness];
100
+
101
+ getContentColor = (token: ContainerColorToken, useVariant?: boolean) =>
102
+ useVariant
103
+ ? this.colors()[`on-${token}-variant` as ColorToken]
104
+ : this.colors()[`on-${token}` as ColorToken];
105
+
106
+ colorPair = (token?: ContainerColorToken, colorVariant?: boolean): NullableStyleExpression => {
107
+ if (!token) return;
108
+ const backgroundColor = this.colors()[token];
109
+ const color = this.getContentColor(token, colorVariant);
110
+ return { color, backgroundColor };
111
+ };
112
+
113
+ backgroundColor = (token?: ColorKey): NullableStyleExpression => {
114
+ return !token ? undefined : { backgroundColor: this.colors()[token] };
115
+ };
116
+
117
+ backgroundImage = (url?: string): NullableStyleExpression => {
118
+ return !url ? undefined : { backgroundImage: `url(${url})` };
119
+ };
120
+
121
+ getContainerColors = (color: Variant, useVariant?: boolean) => {
122
+ const token = (color + '-container') as ContainerColorToken;
123
+ return this.colorPair(token, useVariant);
124
+ };
125
+
126
+ typeface = (typeface?: Typeface) => typeface && this.typeFaces()[typeface];
127
+
128
+ colorPalette = () => this.colors();
129
+
130
+ color(color?: ColorKey): NullableStyleExpression {
131
+ return !color ? undefined : { color: this.colors()[color] };
132
+ }
133
+
134
+ getDashedBorder(
135
+ color: ColorKey | undefined,
136
+ radius: Radius | undefined
137
+ ): NullableStyleExpression {
138
+ if (!color) return;
139
+
140
+ const r = radius && this.radii()[radius];
141
+ const borderRadius = r ? safeParseInt(r) : 0;
142
+ const colors = this.colors()[color];
143
+ const strokeColor = colors?.replace('#', '%23');
144
+
145
+ return {
146
+ backgroundImage: `url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='${borderRadius}' ry='${borderRadius}' stroke='${strokeColor}' stroke-width='4' stroke-dasharray='6%2c 14' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e")`,
147
+ borderRadius,
148
+ };
149
+ }
150
+
151
+ radius(size: Radius | undefined): NullableStyleExpression {
152
+ return !size ? undefined : { borderRadius: this.radii()[size] };
153
+ }
154
+
155
+ getRadiusLeft(size: Radius | undefined): NullableStyleExpression {
156
+ if (!size) return;
157
+ return {
158
+ borderBottomLeftRadius: this.radii()[size],
159
+ borderTopLeftRadius: this.radii()[size],
160
+ };
161
+ }
162
+
163
+ getRadiusRight(size: Radius | undefined): NullableStyleExpression {
164
+ if (!size) return;
165
+ return {
166
+ borderBottomRightRadius: this.radii()[size],
167
+ borderTopRightRadius: this.radii()[size],
168
+ };
169
+ }
170
+
171
+ getRadiusTop(size: Radius | undefined): NullableStyleExpression {
172
+ if (!size) return;
173
+ return {
174
+ borderTopLeftRadius: this.radii()[size],
175
+ borderTopRightRadius: this.radii()[size],
176
+ };
177
+ }
178
+
179
+ getRadiusBottom(size: Radius | undefined): NullableStyleExpression {
180
+ if (!size) return;
181
+ return {
182
+ borderBottomLeftRadius: this.radii()[size],
183
+ borderBottomRightRadius: this.radii()[size],
184
+ };
185
+ }
186
+
187
+ padding(size: OptionalSize): NullableStyleExpression {
188
+ return !size ? undefined : { padding: this.spacing()[size] };
189
+ }
190
+
191
+ horizontalPadding(size: OptionalSize): NullableStyleExpression {
192
+ return !size ? undefined : { paddingInline: this.spacing()[size] };
193
+ }
194
+
195
+ verticalPadding(size: OptionalSize): NullableStyleExpression {
196
+ return !size ? undefined : { paddingBlock: this.spacing()[size] };
197
+ }
198
+
199
+ paddingLeft(size: OptionalSize): NullableStyleExpression {
200
+ return !size ? undefined : { paddingLeft: this.spacing()[size] };
201
+ }
202
+
203
+ paddingRight(size: OptionalSize): NullableStyleExpression {
204
+ return !size ? undefined : { paddingRight: this.spacing()[size] };
205
+ }
206
+
207
+ paddingTop(size: OptionalSize): NullableStyleExpression {
208
+ return !size ? undefined : { paddingTop: this.spacing()[size] };
209
+ }
210
+
211
+ paddingBottom(size: OptionalSize): NullableStyleExpression {
212
+ return !size ? undefined : { paddingBottom: this.spacing()[size] };
213
+ }
214
+
215
+ border(border: Border | undefined): NullableStyleExpression {
216
+ return !border ? undefined : { border: this.borders()[border] };
217
+ }
218
+
219
+ borderTop(border: Border | undefined): NullableStyleExpression {
220
+ return !border ? undefined : { borderTop: this.borders()[border] };
221
+ }
222
+
223
+ borderBottom(border: Border | undefined): NullableStyleExpression {
224
+ return !border ? undefined : { borderBottom: this.borders()[border] };
225
+ }
226
+
227
+ borderLeft(border: Border | undefined): NullableStyleExpression {
228
+ return !border ? undefined : { borderLeft: this.borders()[border] };
229
+ }
230
+
231
+ borderRight(border: Border | undefined): NullableStyleExpression {
232
+ return !border ? undefined : { borderRight: this.borders()[border] };
233
+ }
234
+
235
+ boxShadow(shadow: Shadow | undefined) {
236
+ return !shadow ? undefined : { boxShadow: this.shadows()[shadow] };
237
+ }
238
+
239
+ gap(gap: OptionalSize): NullableStyleExpression {
240
+ return !gap || gap === 'none' ? undefined : { gap: this.spacing()[gap] };
241
+ }
242
+
243
+ zIndex(element: ZIndexableElements | undefined): NullableStyleExpression {
244
+ return !element ? undefined : { zIndex: Z_INDEX[element] };
245
+ }
246
+
247
+ borderColor(borderColor: ColorToken): NullableStyleExpression {
248
+ return { borderColor: this.colors()[borderColor] };
249
+ }
250
+
251
+ getComponentTheme<T>(componentName: ComponentName) {
252
+ return this.component<T>(componentName);
253
+ }
254
+
255
+ // Used to get an "always-defined" options object from a component theme.
256
+ getComponentOptions = <T>(componentName: ComponentName) =>
257
+ linkedSignal({
258
+ source: this.getComponentTheme<T>(componentName),
259
+ computation: () => {
260
+ return this.getComponentTheme<T>(componentName)().options || ({} as T);
261
+ },
262
+ });
263
+
264
+ componentOptions = (componentName: ComponentName) =>
265
+ computed(() => this.component(componentName)().options || {});
266
+
267
+ style(prop: string, value: string | number | undefined): NullableStyleExpression {
268
+ return !value ? undefined : { [prop]: value };
269
+ }
270
+ }
@@ -0,0 +1,7 @@
1
+ import { InjectionToken } from '@angular/core';
2
+ import { type UniTheme, UniThemes } from '@uni-design-system/uni-core';
3
+
4
+ export const UNI_THEMES = new InjectionToken<Record<string, UniTheme>>('', {
5
+ providedIn: 'root',
6
+ factory: () => UniThemes,
7
+ });
@@ -0,0 +1,21 @@
1
+ import { Controls, Source, Story, StoryProps } from '@storybook/addon-docs/blocks';
2
+ import { ReactElement } from 'react';
3
+
4
+ export function StoryUsage({
5
+ of,
6
+ exclude,
7
+ }: StoryProps & { exclude?: string | string[] }): ReactElement {
8
+ const commonControlExcludes = ['children', 'className', 'style'];
9
+ const excludedControls = [
10
+ ...commonControlExcludes,
11
+ ...(Array.isArray(exclude) ? exclude : exclude ? [exclude] : []),
12
+ ];
13
+
14
+ return (
15
+ <div>
16
+ <Story of={of} />
17
+ <Source of={of} dark={true} />
18
+ <Controls of={of} exclude={excludedControls} />
19
+ </div>
20
+ );
21
+ }
package/tsconfig.json CHANGED
@@ -1,17 +1,19 @@
1
1
  {
2
2
  "extends": "@uni-design-system/tsconfig/base.json",
3
3
  "compilerOptions": {
4
+ "moduleResolution": "bundler",
4
5
  "outDir": "./dist",
5
- "rootDir": "./src",
6
+ "rootDir": "..",
6
7
  "module": "ESNext",
7
8
  "target": "ES2022",
8
9
  "esModuleInterop": true,
9
10
  "types": ["node", "vite/client"],
11
+ "jsx": "react-jsx",
10
12
  "emitDecoratorMetadata": true,
11
13
  "experimentalDecorators": true,
12
14
  "useDefineForClassFields": false,
13
- "ignoreDeprecations": "6.0",
15
+ "ignoreDeprecations": "5.0"
14
16
  },
15
- "include": ["src/**/*", "vite.config.ts"],
16
- "exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.stories.ts"]
17
+ "include": ["src/**/*", "**/*"],
18
+ "exclude": ["../node_modules", "../dist", "**/*.spec.ts", "**/*.stories.ts"]
17
19
  }
@@ -1,28 +0,0 @@
1
- [14:23:36.870] [INFO] storybook v10.3.6
2
- [14:23:37.740] [DEBUG] Getting package.json info for /Users/george/git/gaenglish/uni/packages/angular/package.json...
3
- [14:23:38.002] [INFO] Starting...
4
- [14:23:38.138] [DEBUG] Starting preview..
5
- [14:23:38.145] [INFO] Addon-docs: using MDX3
6
- [14:23:38.157] [INFO] Using implicit CSS loaders
7
- [14:23:38.465] [ERROR] Failed to build the preview
8
- [14:23:38.466] [ERROR] SB_FRAMEWORK_ANGULAR_0001 (AngularLegacyBuildOptionsError): Your Storybook startup script uses a solution that is not supported anymore.
9
- You must use Angular builder to have an explicit configuration on the project used in angular.json.
10
-
11
- Please run 'npx storybook automigrate' to automatically fix your config.
12
-
13
- More info:
14
- - https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#angular-drop-support-for-calling-storybook-directly
15
- - https://github.com/storybookjs/storybook/tree/next/code/frameworks/angular#how-do-i-migrate-to-an-angular-storybook-builder
16
- 
17
- at checkForLegacyBuildOptions (file:///Users/george/git/gaenglish/uni/node_modules/.pnpm/@storybook+angular@10.3.6_51f2ba54bcdf51a50f118aadc673eb3e/node_modules/@storybook/angular/dist/server/framework-preset-angular-cli.js:159:11)
18
- at Object.webpackFinal (file:///Users/george/git/gaenglish/uni/node_modules/.pnpm/@storybook+angular@10.3.6_51f2ba54bcdf51a50f118aadc673eb3e/node_modules/@storybook/angular/dist/server/framework-preset-angular-cli.js:93:3)
19
- at async Object.webpack (file:///Users/george/git/gaenglish/uni/node_modules/.pnpm/@storybook+builder-webpack5@10.3.6_esbuild@0.27.7_storybook@10.3.6_@testing-library+dom_db6b670199b912be7a72b23934922527/node_modules/@storybook/builder-webpack5/dist/presets/custom-webpack-preset.js:282:28)
20
- at async starter (file:///Users/george/git/gaenglish/uni/node_modules/.pnpm/@storybook+builder-webpack5@10.3.6_esbuild@0.27.7_storybook@10.3.6_@testing-library+dom_db6b670199b912be7a72b23934922527/node_modules/@storybook/builder-webpack5/dist/index.js:753:16)
21
- at async Module.start (file:///Users/george/git/gaenglish/uni/node_modules/.pnpm/@storybook+builder-webpack5@10.3.6_esbuild@0.27.7_storybook@10.3.6_@testing-library+dom_db6b670199b912be7a72b23934922527/node_modules/@storybook/builder-webpack5/dist/index.js:857:14)
22
- at async storybookDevServer (file:///Users/george/git/gaenglish/uni/node_modules/.pnpm/storybook@10.3.6_@testing-library+dom@10.4.1_react-dom@19.2.5_react@19.2.5__react@19.2.5/node_modules/storybook/dist/core-server/index.js:7503:83)
23
- at async buildOrThrow (file:///Users/george/git/gaenglish/uni/node_modules/.pnpm/storybook@10.3.6_@testing-library+dom@10.4.1_react-dom@19.2.5_react@19.2.5__react@19.2.5/node_modules/storybook/dist/core-server/index.js:4508:12)
24
- at async buildDevStandalone (file:///Users/george/git/gaenglish/uni/node_modules/.pnpm/storybook@10.3.6_@testing-library+dom@10.4.1_react-dom@19.2.5_react@19.2.5__react@19.2.5/node_modules/storybook/dist/core-server/index.js:8033:41)
25
- at async withTelemetry (file:///Users/george/git/gaenglish/uni/node_modules/.pnpm/storybook@10.3.6_@testing-library+dom@10.4.1_react-dom@19.2.5_react@19.2.5__react@19.2.5/node_modules/storybook/dist/_node-chunks/chunk-UYONHJJP.js:225:12)
26
- at async dev (file:///Users/george/git/gaenglish/uni/node_modules/.pnpm/storybook@10.3.6_@testing-library+dom@10.4.1_react-dom@19.2.5_react@19.2.5__react@19.2.5/node_modules/storybook/dist/bin/core.js:2736:3)
27
- [14:23:38.472] [WARN] Broken build, fix the error above.
28
- You may need to refresh the browser.