@uni-design-system/uni-angular 1.0.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.
- package/.storybook/main.ts +33 -16
- package/.storybook/manager.ts +24 -0
- package/.storybook/preview.ts +10 -3
- package/.storybook/tsconfig.json +7 -4
- package/.turbo/turbo-build.log +3 -3
- package/CHANGELOG.md +7 -0
- package/dist/fesm2022/uni-design-system-uni-angular.mjs +2 -2
- package/dist/fesm2022/uni-design-system-uni-angular.mjs.map +1 -1
- package/package.json +14 -10
- package/src/lib/button.component.ts +12 -5
- package/src/lib/cdk/datasource/base-datasource.ts +113 -0
- package/src/lib/cdk/datasource/datasource.types.ts +10 -0
- package/src/lib/cdk/datasource/record-datasource.ts +115 -0
- package/src/lib/cdk/datasource/server-side-datasource.ts +172 -0
- package/src/lib/cdk/helpers/memoize.helper.ts +15 -0
- package/src/lib/cdk/helpers/number.helper.ts +2 -0
- package/src/lib/cdk/index.ts +3 -0
- package/src/lib/cdk/local-storage/local-storage.service.ts +124 -0
- package/src/lib/cdk/option/option.model.ts +6 -0
- package/src/lib/cdk/timer/timer.ts +66 -0
- package/src/lib/components/text/text.component.ts +57 -0
- package/src/lib/components/text/text.mdx +15 -0
- package/src/lib/components/text/text.stories.ts +39 -0
- package/src/lib/theming/theme.service.ts +270 -0
- package/src/lib/theming/theme.token.ts +7 -0
- package/src/stories/blocks/StoryUsage.tsx +21 -0
- package/tsconfig.json +6 -4
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function memoize<T extends (...args: any[]) => any>(fn: T): T {
|
|
2
|
+
const cache = new Map<string, ReturnType<T>>();
|
|
3
|
+
|
|
4
|
+
return ((...args: Parameters<T>): ReturnType<T> => {
|
|
5
|
+
const key = JSON.stringify(args);
|
|
6
|
+
|
|
7
|
+
if (cache.has(key)) {
|
|
8
|
+
return cache.get(key)!;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const result = fn(...args);
|
|
12
|
+
cache.set(key, result);
|
|
13
|
+
return result;
|
|
14
|
+
}) as T;
|
|
15
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { memoize } from '../helpers/memoize.helper';
|
|
3
|
+
|
|
4
|
+
@Injectable({
|
|
5
|
+
providedIn: 'root',
|
|
6
|
+
})
|
|
7
|
+
export class LocalStorageService {
|
|
8
|
+
private isLocalStorageAvailable = memoize((): boolean => {
|
|
9
|
+
try {
|
|
10
|
+
const test = '__localStorage_test__';
|
|
11
|
+
localStorage.setItem(test, test);
|
|
12
|
+
localStorage.removeItem(test);
|
|
13
|
+
return true;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
setItem<T>(key: string, value: T): boolean {
|
|
20
|
+
if (!this.isLocalStorageAvailable()) {
|
|
21
|
+
console.warn('LocalStorage is not available');
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
|
|
27
|
+
localStorage.setItem(key, stringValue);
|
|
28
|
+
return true;
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error('Error saving to localStorage:', error);
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getItem<T>(key: string): T | null {
|
|
36
|
+
if (!this.isLocalStorageAvailable()) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const item = localStorage.getItem(key);
|
|
42
|
+
if (item === null) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(item) as T;
|
|
48
|
+
} catch {
|
|
49
|
+
return item as T;
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error('Error reading from localStorage:', error);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
removeItem(key: string): boolean {
|
|
58
|
+
if (!this.isLocalStorageAvailable()) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
localStorage.removeItem(key);
|
|
64
|
+
return true;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('Error removing from localStorage:', error);
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
clear(): boolean {
|
|
72
|
+
if (!this.isLocalStorageAvailable()) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
localStorage.clear();
|
|
78
|
+
return true;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('Error clearing localStorage:', error);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
hasKey(key: string): boolean {
|
|
86
|
+
if (!this.isLocalStorageAvailable()) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return localStorage.getItem(key) !== null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getAllKeys(): string[] {
|
|
94
|
+
if (!this.isLocalStorageAvailable()) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
return Object.keys(localStorage);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error('Error getting localStorage keys:', error);
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
getSize(): number {
|
|
107
|
+
if (!this.isLocalStorageAvailable()) {
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
let total = 0;
|
|
113
|
+
for (const key in localStorage) {
|
|
114
|
+
if (Object.prototype.hasOwnProperty.call(localStorage, key)) {
|
|
115
|
+
total += localStorage[key].length + key.length;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return total;
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error('Error calculating localStorage size:', error);
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { computed, DestroyRef, inject, signal } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
export function useTimer() {
|
|
4
|
+
const destroyRef = inject(DestroyRef);
|
|
5
|
+
|
|
6
|
+
const msRemaining = signal<number>(0);
|
|
7
|
+
const isPaused = signal<boolean>(false);
|
|
8
|
+
const isActive = computed(() => msRemaining() > 0);
|
|
9
|
+
|
|
10
|
+
let intervalId: any = null;
|
|
11
|
+
let endTime = 0;
|
|
12
|
+
let onCompleteCallback: (() => void) | undefined;
|
|
13
|
+
|
|
14
|
+
const stop = () => {
|
|
15
|
+
if (intervalId) clearInterval(intervalId);
|
|
16
|
+
intervalId = null;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const tick = () => {
|
|
20
|
+
const remaining = Math.max(0, endTime - Date.now());
|
|
21
|
+
msRemaining.set(remaining);
|
|
22
|
+
|
|
23
|
+
if (remaining <= 0) {
|
|
24
|
+
stop();
|
|
25
|
+
if (onCompleteCallback) onCompleteCallback();
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const start = (durationMs: number, onComplete?: () => void) => {
|
|
30
|
+
stop();
|
|
31
|
+
onCompleteCallback = onComplete;
|
|
32
|
+
isPaused.set(false);
|
|
33
|
+
msRemaining.set(durationMs);
|
|
34
|
+
endTime = Date.now() + durationMs;
|
|
35
|
+
intervalId = setInterval(tick, 100);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const pause = () => {
|
|
39
|
+
if (!isActive() || isPaused()) return;
|
|
40
|
+
stop();
|
|
41
|
+
isPaused.set(true);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const resume = () => {
|
|
45
|
+
if (!isActive() || !isPaused()) return;
|
|
46
|
+
isPaused.set(false);
|
|
47
|
+
endTime = Date.now() + msRemaining();
|
|
48
|
+
intervalId = setInterval(tick, 100);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
destroyRef.onDestroy(() => stop());
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
start,
|
|
55
|
+
pause,
|
|
56
|
+
resume,
|
|
57
|
+
stop: () => {
|
|
58
|
+
stop();
|
|
59
|
+
msRemaining.set(0);
|
|
60
|
+
},
|
|
61
|
+
msRemaining,
|
|
62
|
+
isPaused,
|
|
63
|
+
isActive,
|
|
64
|
+
secondsRemaining: computed(() => Math.ceil(msRemaining() / 1000)),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Component, HostBinding, inject, input } from '@angular/core';
|
|
2
|
+
import { css } from '@emotion/css';
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
ColorKey,
|
|
6
|
+
OptionalDisplay,
|
|
7
|
+
OptionalTextAlign,
|
|
8
|
+
Typeface,
|
|
9
|
+
} from '@uni-design-system/uni-core';
|
|
10
|
+
import { ThemeService } from '../../theming/theme.service';
|
|
11
|
+
|
|
12
|
+
@Component({
|
|
13
|
+
selector: 'uni-text, Text',
|
|
14
|
+
standalone: true,
|
|
15
|
+
imports: [],
|
|
16
|
+
template: '<ng-content></ng-content>',
|
|
17
|
+
})
|
|
18
|
+
export class UniTextComponent {
|
|
19
|
+
theme = inject(ThemeService);
|
|
20
|
+
|
|
21
|
+
typeface = input<Typeface>('title-small');
|
|
22
|
+
color = input<ColorKey>();
|
|
23
|
+
display = input<OptionalDisplay>();
|
|
24
|
+
align = input<OptionalTextAlign>();
|
|
25
|
+
nowrap = input<boolean>();
|
|
26
|
+
maxWidth = input<number>();
|
|
27
|
+
ellipsis = input<boolean>(false);
|
|
28
|
+
|
|
29
|
+
@HostBinding('class') get className() {
|
|
30
|
+
return css([
|
|
31
|
+
{
|
|
32
|
+
...this.theme.typeface(this.typeface()),
|
|
33
|
+
...this.theme.color(this.color()),
|
|
34
|
+
display: this.display(),
|
|
35
|
+
},
|
|
36
|
+
this.align() && {
|
|
37
|
+
textAlign: this.align(),
|
|
38
|
+
},
|
|
39
|
+
this.nowrap() && {
|
|
40
|
+
whiteSpace: 'nowrap',
|
|
41
|
+
},
|
|
42
|
+
this.maxWidth() && {
|
|
43
|
+
maxWidth: this.maxWidth(),
|
|
44
|
+
overflow: 'hidden',
|
|
45
|
+
whiteSpace: 'nowrap',
|
|
46
|
+
textOverflow: 'ellipsis',
|
|
47
|
+
display: 'inline-block',
|
|
48
|
+
},
|
|
49
|
+
this.ellipsis() && {
|
|
50
|
+
whiteSpace: 'nowrap',
|
|
51
|
+
overflow: 'hidden',
|
|
52
|
+
textOverflow: 'ellipsis',
|
|
53
|
+
minWidth: 0,
|
|
54
|
+
},
|
|
55
|
+
]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Meta, Title } from '@storybook/addon-docs/blocks';
|
|
2
|
+
import * as Stories from './text.stories';
|
|
3
|
+
import { StoryUsage } from '../../../stories/blocks/StoryUsage';
|
|
4
|
+
|
|
5
|
+
<Meta of={Stories} name="Overview" />
|
|
6
|
+
<Title />
|
|
7
|
+
`import { UniTextComponent } from '@uni-design-system/uni-angular';`
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
The Text Component is used to format text using typefaces and colors defined in the loaded theme.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
<StoryUsage of={Stories.DisplayLarge} />
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { argsToTemplate, Meta, StoryObj } from '@storybook/angular';
|
|
2
|
+
import { UniTextComponent } from './text.component';
|
|
3
|
+
import type { Typeface, ColorKey } from '@uni-design-system/uni-core';
|
|
4
|
+
|
|
5
|
+
// 1. Create a unified argument type for the story file
|
|
6
|
+
type StoryArgs = UniTextComponent & { ngContent?: string };
|
|
7
|
+
|
|
8
|
+
// 2. Pass the combined type directly to Meta so it recognizes 'ngContent' in argTypes
|
|
9
|
+
const meta: Meta<StoryArgs> = {
|
|
10
|
+
title: 'Components/Text',
|
|
11
|
+
component: UniTextComponent as any, // Cast to any prevents the mapper from breaking on the intersection
|
|
12
|
+
render: (args) => {
|
|
13
|
+
const { ngContent, ...componentProps } = args;
|
|
14
|
+
return {
|
|
15
|
+
props: componentProps,
|
|
16
|
+
template: `<uni-text ${argsToTemplate(componentProps)}>${ngContent || ''}</uni-text>`,
|
|
17
|
+
};
|
|
18
|
+
},
|
|
19
|
+
argTypes: {
|
|
20
|
+
ngContent: {
|
|
21
|
+
control: 'text',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default meta;
|
|
27
|
+
|
|
28
|
+
// 3. Keep the matching type structure on your StoryObj
|
|
29
|
+
type Story = StoryObj<StoryArgs>;
|
|
30
|
+
|
|
31
|
+
export const DisplayLarge: Story = {
|
|
32
|
+
args: {
|
|
33
|
+
ngContent: 'The quick brown fox jumps over the lazy dog.',
|
|
34
|
+
color: 'primary' as ColorKey,
|
|
35
|
+
typeface: 'title-large' as Typeface,
|
|
36
|
+
display: 'block',
|
|
37
|
+
align: 'center',
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -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,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": "
|
|
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": "
|
|
15
|
+
"ignoreDeprecations": "5.0"
|
|
14
16
|
},
|
|
15
|
-
"include": ["src/**/*", "
|
|
16
|
-
"exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.stories.ts"]
|
|
17
|
+
"include": ["src/**/*", "**/*"],
|
|
18
|
+
"exclude": ["../node_modules", "../dist", "**/*.spec.ts", "**/*.stories.ts"]
|
|
17
19
|
}
|