@uni-design-system/uni-angular 1.0.0 → 2.0.1

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
  }