Skip to content

Commit 52d67f6

Browse files
committed
fix: unify input width
1 parent 691f18c commit 52d67f6

12 files changed

Lines changed: 405 additions & 14 deletions

File tree

apps/docs/src/containers/theme-studio/editor-fields.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,30 @@ export function swatchTextStyle(background: string, foreground: string): React.C
1010
}
1111

1212
function parseSliderValue(value: string, unit?: 'px' | 'em' | 'rem'): number | undefined {
13+
const trimmed = value.trim();
14+
15+
// CSS zero is unit-agnostic. Presets may serialize it as `0px` while the
16+
// editor slider expects `rem`, so normalize all zero forms to numeric 0.
17+
if (/^-?0(?:\.0+)?(?:px|em|rem)?$/.test(trimmed)) {
18+
return 0;
19+
}
20+
1321
if (unit === 'px') {
14-
const match = /^(-?\d+(?:\.\d+)?)px$/.exec(value.trim());
22+
const match = /^(-?\d+(?:\.\d+)?)px$/.exec(trimmed);
1523
return match ? Number(match[1]) : undefined;
1624
}
1725

1826
if (unit === 'em') {
19-
const match = /^(-?\d+(?:\.\d+)?)em$/.exec(value.trim());
27+
const match = /^(-?\d+(?:\.\d+)?)em$/.exec(trimmed);
2028
return match ? Number(match[1]) : undefined;
2129
}
2230

2331
if (unit === 'rem') {
24-
const match = /^(-?\d+(?:\.\d+)?)rem$/.exec(value.trim());
32+
const match = /^(-?\d+(?:\.\d+)?)rem$/.exec(trimmed);
2533
return match ? Number(match[1]) : undefined;
2634
}
2735

28-
const parsed = Number(value);
36+
const parsed = Number(trimmed);
2937
return Number.isNaN(parsed) ? undefined : parsed;
3038
}
3139

apps/docs/src/containers/theme-studio/theme-document-adapter.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ export function buildThemeDocumentFromDraft(draft: ThemeEditorDraft): ThemeDocum
156156
'card.header-color': fields.cardForeground,
157157
'card.body-padding': fields.cardPadding,
158158
'card.header-padding': fields.cardPadding,
159-
'input.bg': fields.base,
159+
'input.bg': fields.input,
160160
'input.color': fields.baseForeground,
161161
'input.border': fields.input,
162162
'input.border.hover': fields.ring,
@@ -169,7 +169,7 @@ export function buildThemeDocumentFromDraft(draft: ThemeEditorDraft): ThemeDocum
169169
'input.padding-inline-sm': fields.fieldPaddingSm,
170170
'input.padding-inline-md': fields.fieldPaddingMd,
171171
'input.padding-inline-lg': fields.fieldPaddingLg,
172-
'select.bg': fields.base,
172+
'select.bg': fields.input,
173173
'select.color': fields.baseForeground,
174174
'select.border': fields.input,
175175
'select.border.hover': fields.ring,
@@ -186,7 +186,7 @@ export function buildThemeDocumentFromDraft(draft: ThemeEditorDraft): ThemeDocum
186186
'select.option.active-bg': fields.muted,
187187
'select.option.selected-bg': fields.accent,
188188
'table.radius': fields.radius,
189-
'picker.input-bg': fields.base,
189+
'picker.input-bg': fields.input,
190190
'picker.input-border': fields.input,
191191
'picker.input-border-hover': fields.ring,
192192
'picker.input-border-focus': fields.ring,
@@ -245,9 +245,21 @@ export function buildThemeDocumentFromDraft(draft: ThemeEditorDraft): ThemeDocum
245245
'cascader.height.sm': fields.fieldHeightSm,
246246
'cascader.height.md': fields.fieldHeightMd,
247247
'cascader.height.lg': fields.fieldHeightLg,
248+
'cascader.bg': fields.input,
249+
'cascader.border': fields.input,
250+
'cascader.border-hover': fields.ring,
251+
'cascader.border-focus': fields.ring,
252+
'cascader.shadow-focus': fields.shadowFocus,
253+
'cascader.radius': fields.inputRadius,
248254
'cascader.padding.sm': `0 calc(${fields.fieldPaddingSm} + 20px) 0 ${fields.fieldPaddingSm}`,
249255
'cascader.padding.md': `0 calc(${fields.fieldPaddingMd} + 20px) 0 ${fields.fieldPaddingMd}`,
250256
'cascader.padding.lg': `0 calc(${fields.fieldPaddingLg} + 20px) 0 ${fields.fieldPaddingLg}`,
257+
'native-select.bg': fields.input,
258+
'native-select.border': fields.input,
259+
'native-select.border-hover': fields.ring,
260+
'native-select.border-focus': fields.ring,
261+
'native-select.shadow-focus': fields.shadowFocus,
262+
'native-select.radius': fields.inputRadius,
251263
'checkbox.bg': fields.base,
252264
'checkbox.border': fields.input,
253265
'checkbox.border.hover': fields.ring,

packages/react/src/cascader/style/index.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
.#{$prefix}-cascader {
44
position: relative;
55
display: inline-block;
6-
min-width: var(--ty-cascader-min-width);
6+
width: 100%;
7+
min-width: 0;
78

89
&_disabled {
910
opacity: var(--ty-cascader-opacity-disabled);

packages/react/src/date-picker/style/index.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ $dp: #{$prefix}-date-picker;
55
.#{$dp} {
66
display: inline-flex;
77
position: relative;
8+
width: 100%;
89
font-size: var(--ty-picker-input-font-size);
910

1011
// ---- Input ----

packages/react/src/input-number/__tests__/input-number.test.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,9 @@ describe('<InputNumber />', () => {
3131
expect(container.querySelector('.ty-input-number__up')).toBeInTheDocument();
3232
expect(container.querySelector('.ty-input-number__down')).toBeInTheDocument();
3333
});
34+
35+
it('should not crash when controlled value is undefined', () => {
36+
const { container } = render(<InputNumber value={undefined as unknown as number} />);
37+
expect(container.querySelector('input')).toHaveValue(null);
38+
});
3439
});

packages/react/src/input-number/input-number.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ const isValid = (val: string | number): boolean => {
1313
return !isNaN(+val);
1414
};
1515

16+
const isFiniteNumber = (val: unknown): val is number => {
17+
return typeof val === 'number' && Number.isFinite(val);
18+
};
19+
1620
const getDecimalPrecision = (num: number): number => {
1721
const str = String(num);
1822
const dotIndex = str.indexOf('.');
@@ -46,9 +50,13 @@ const InputNumber = React.forwardRef<HTMLDivElement, InputNumberProps>((props, r
4650
[`${prefixCls}_always-controls`]: controls,
4751
});
4852
const resolvedPrecision = precision ?? Math.max(getDecimalPrecision(step), getDecimalPrecision(defaultValue));
49-
const [value, setValue] = useState<number>(
53+
const [value, setValue] = useState<number | undefined>(
5054
'value' in props ? (props.value as number) : defaultValue
5155
);
56+
const hasNumericValue = isFiniteNumber(value);
57+
const displayValue = hasNumericValue
58+
? (resolvedPrecision > 0 ? value.toFixed(resolvedPrecision) : String(value))
59+
: '';
5260

5361
const inputOnChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
5462
const raw = Number(e.target.value.trim());
@@ -60,7 +68,8 @@ const InputNumber = React.forwardRef<HTMLDivElement, InputNumberProps>((props, r
6068
const plusOnClick = (e: MouseEvent<HTMLSpanElement>): void => {
6169
e.stopPropagation();
6270
if (!disabled && isValid(step)) {
63-
const val = toPrecision(+value + +step, resolvedPrecision);
71+
const nextBase = hasNumericValue ? value : 0;
72+
const val = toPrecision(nextBase + +step, resolvedPrecision);
6473
if (val <= max) {
6574
!('value' in props) && setValue(val);
6675
onChange && onChange(val, e);
@@ -71,7 +80,8 @@ const InputNumber = React.forwardRef<HTMLDivElement, InputNumberProps>((props, r
7180
const minusOnClick = (e: MouseEvent<HTMLSpanElement>): void => {
7281
e.stopPropagation();
7382
if (!disabled && isValid(step)) {
74-
const val = toPrecision(+value - +step, resolvedPrecision);
83+
const nextBase = hasNumericValue ? value : 0;
84+
const val = toPrecision(nextBase - +step, resolvedPrecision);
7585
if (val >= min) {
7686
!('value' in props) && setValue(val);
7787
onChange && onChange(val, e);
@@ -88,14 +98,14 @@ const InputNumber = React.forwardRef<HTMLDivElement, InputNumberProps>((props, r
8898
<input
8999
autoComplete="off"
90100
disabled={disabled}
91-
value={resolvedPrecision > 0 ? value.toFixed(resolvedPrecision) : value}
101+
value={displayValue}
92102
type="number"
93103
className={`${prefixCls}__input`}
94104
max={max}
95105
min={min}
96106
step={step}
97107
onChange={inputOnChange}
98-
aria-valuenow={value}
108+
aria-valuenow={hasNumericValue ? value : undefined}
99109
aria-valuemax={max}
100110
aria-valuemin={min}
101111
aria-disabled={disabled}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import React from 'react';
2+
import { Menu, Tag } from '@tiny-design/react';
3+
import './mega-navigation.scss';
4+
5+
type FeatureItem = {
6+
key: string;
7+
title: string;
8+
description: string;
9+
badge?: string;
10+
};
11+
12+
const componentItems: FeatureItem[] = [
13+
{
14+
key: 'alert-dialog',
15+
title: 'Alert Dialog',
16+
description: 'A focused modal for urgent decisions that require a clear response.',
17+
},
18+
{
19+
key: 'hover-card',
20+
title: 'Hover Card',
21+
description: 'Preview linked content, people, or metadata without leaving the page.',
22+
},
23+
{
24+
key: 'progress',
25+
title: 'Progress',
26+
description: 'Show task, upload, or workflow completion with clear visual feedback.',
27+
badge: 'Updated',
28+
},
29+
{
30+
key: 'scroll-area',
31+
title: 'Scroll Area',
32+
description: 'Create more intentional scrolling regions for dense panels and inspectors.',
33+
},
34+
{
35+
key: 'tabs',
36+
title: 'Tabs',
37+
description: 'Split complex views into clear parallel sections for dashboards and settings.',
38+
},
39+
{
40+
key: 'tooltip',
41+
title: 'Tooltip',
42+
description: 'Add lightweight explanations and hints without interrupting the main flow.',
43+
},
44+
];
45+
46+
const resourceItems: FeatureItem[] = [
47+
{
48+
key: 'guides',
49+
title: 'Design Guides',
50+
description: 'Patterns for information architecture, interaction semantics, and theme strategy.',
51+
},
52+
{
53+
key: 'templates',
54+
title: 'Page Templates',
55+
description: 'Ready-made shells for authentication, product consoles, and marketing surfaces.',
56+
badge: 'New',
57+
},
58+
{
59+
key: 'tokens',
60+
title: 'Token System',
61+
description: 'A shared language for color, radius, elevation, spacing, and typography.',
62+
},
63+
{
64+
key: 'cli',
65+
title: 'CLI & MCP',
66+
description: 'Scaffolding, theme export, and AI workflow integration for faster delivery.',
67+
},
68+
];
69+
70+
function FeatureCard({ item }: { item: FeatureItem }) {
71+
return (
72+
<div className="menu-mega-nav__card">
73+
<div className="menu-mega-nav__card-heading">
74+
<span className="menu-mega-nav__card-title">{item.title}</span>
75+
{item.badge ? (
76+
<Tag variant="soft" color="info">
77+
{item.badge}
78+
</Tag>
79+
) : null}
80+
</div>
81+
<p className="menu-mega-nav__card-description">{item.description}</p>
82+
</div>
83+
);
84+
}
85+
86+
export default function MegaNavigationDemo() {
87+
return (
88+
<div className="menu-mega-nav">
89+
<Menu
90+
defaultSelectedKeys={['components']}
91+
overlayClassName="menu-mega-nav__popup"
92+
className="menu-mega-nav__bar">
93+
<Menu.SubMenu
94+
index="getting-started"
95+
title="Getting started"
96+
extra={<span className="menu-mega-nav__nav-pill">New</span>}>
97+
<Menu.Item index="getting-started-install">Installation</Menu.Item>
98+
<Menu.Item index="getting-started-theme">Theme Setup</Menu.Item>
99+
<Menu.Item index="getting-started-layout">Layout Principles</Menu.Item>
100+
<Menu.Item index="getting-started-figma">Design Assets</Menu.Item>
101+
</Menu.SubMenu>
102+
103+
<Menu.SubMenu
104+
index="components"
105+
title="Components"
106+
extra={<Tag variant="soft" color="info">28</Tag>}>
107+
{componentItems.map((item) => (
108+
<Menu.Item key={item.key} index={`components-${item.key}`}>
109+
<FeatureCard item={item} />
110+
</Menu.Item>
111+
))}
112+
</Menu.SubMenu>
113+
114+
<Menu.SubMenu index="docs" title="Docs">
115+
{resourceItems.map((item) => (
116+
<Menu.Item key={item.key} index={`docs-${item.key}`}>
117+
<FeatureCard item={item} />
118+
</Menu.Item>
119+
))}
120+
</Menu.SubMenu>
121+
</Menu>
122+
</div>
123+
);
124+
}

0 commit comments

Comments
 (0)