@idealyst/mcp-server 1.2.102 → 1.2.103
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/dist/{chunk-SRNAGZJL.js → chunk-74TE7CY7.js} +7740 -2803
- package/dist/chunk-74TE7CY7.js.map +1 -0
- package/dist/index.cjs +7634 -2586
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +176 -41
- package/dist/index.js.map +1 -1
- package/dist/tools/index.cjs +7763 -2802
- package/dist/tools/index.cjs.map +1 -1
- package/dist/tools/index.d.cts +99 -7
- package/dist/tools/index.d.ts +99 -7
- package/dist/tools/index.js +49 -1
- package/package.json +7 -6
- package/dist/chunk-SRNAGZJL.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
storageGuides,
|
|
8
8
|
toolDefinitions,
|
|
9
9
|
translateGuides
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-74TE7CY7.js";
|
|
11
11
|
|
|
12
12
|
// src/index.ts
|
|
13
13
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -467,6 +467,48 @@ UnistylesRegistry
|
|
|
467
467
|
});
|
|
468
468
|
\`\`\`
|
|
469
469
|
|
|
470
|
+
## useTheme Hook
|
|
471
|
+
|
|
472
|
+
Access the current theme in your components with full TypeScript inference:
|
|
473
|
+
|
|
474
|
+
\`\`\`typescript
|
|
475
|
+
import { useTheme } from '@idealyst/theme';
|
|
476
|
+
|
|
477
|
+
function MyComponent() {
|
|
478
|
+
const theme = useTheme();
|
|
479
|
+
|
|
480
|
+
return (
|
|
481
|
+
<View style={{ backgroundColor: theme.colors.surface.primary }}>
|
|
482
|
+
<Text style={{ color: theme.colors.text.primary }}>
|
|
483
|
+
Hello
|
|
484
|
+
</Text>
|
|
485
|
+
</View>
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
\`\`\`
|
|
489
|
+
|
|
490
|
+
The hook returns the properly typed theme based on your registered theme type:
|
|
491
|
+
|
|
492
|
+
\`\`\`typescript
|
|
493
|
+
const theme = useTheme();
|
|
494
|
+
|
|
495
|
+
// Access intent colors
|
|
496
|
+
const primaryColor = theme.intents.primary.primary;
|
|
497
|
+
const successLight = theme.intents.success.light;
|
|
498
|
+
|
|
499
|
+
// Access sizes
|
|
500
|
+
const buttonPadding = theme.sizes.button.md.paddingHorizontal;
|
|
501
|
+
|
|
502
|
+
// Access radii
|
|
503
|
+
const borderRadius = theme.radii.md;
|
|
504
|
+
|
|
505
|
+
// Access surface colors
|
|
506
|
+
const screenBg = theme.colors.surface.screen;
|
|
507
|
+
|
|
508
|
+
// Access text colors
|
|
509
|
+
const textColor = theme.colors.text.primary;
|
|
510
|
+
\`\`\`
|
|
511
|
+
|
|
470
512
|
## Platform-Specific Styles
|
|
471
513
|
|
|
472
514
|
\`\`\`typescript
|
|
@@ -804,12 +846,12 @@ import { View, Card, Text } from '@idealyst/components';
|
|
|
804
846
|
### Form Input Spacing
|
|
805
847
|
|
|
806
848
|
\`\`\`tsx
|
|
807
|
-
import {
|
|
849
|
+
import { TextInput, Checkbox, View } from '@idealyst/components';
|
|
808
850
|
|
|
809
851
|
// Use margin to space form fields
|
|
810
852
|
<View>
|
|
811
|
-
<
|
|
812
|
-
<
|
|
853
|
+
<TextInput placeholder="Email" marginVertical="sm" />
|
|
854
|
+
<TextInput placeholder="Password" marginVertical="sm" />
|
|
813
855
|
<Checkbox label="Remember me" marginVertical="md" />
|
|
814
856
|
</View>
|
|
815
857
|
\`\`\`
|
|
@@ -2144,6 +2186,8 @@ type TabBarScreenOptions = {
|
|
|
2144
2186
|
} & ScreenOptions;
|
|
2145
2187
|
\`\`\`
|
|
2146
2188
|
|
|
2189
|
+
> **Nested navigators:** When a Stack is nested inside a Tab, the **stack's root route** still uses \`TabBarScreenOptions\` for its \`options\` (it needs tabBarIcon/tabBarLabel for the tab bar). Type it as \`options: TabBarScreenOptions\` on the stack wrapper route.
|
|
2190
|
+
|
|
2147
2191
|
Example:
|
|
2148
2192
|
\`\`\`tsx
|
|
2149
2193
|
{
|
|
@@ -2258,6 +2302,45 @@ Full paths are automatically computed:
|
|
|
2258
2302
|
- \`/dashboard/reports\`
|
|
2259
2303
|
- \`/settings\`
|
|
2260
2304
|
|
|
2305
|
+
## Adding a New Screen to an Existing App
|
|
2306
|
+
|
|
2307
|
+
When adding a new screen to a scaffolded project, you must complete ALL three steps:
|
|
2308
|
+
|
|
2309
|
+
### Step 1: Create the screen component
|
|
2310
|
+
\`\`\`tsx
|
|
2311
|
+
// packages/shared/src/screens/TodoListScreen.tsx
|
|
2312
|
+
import { View, Text } from '@idealyst/components';
|
|
2313
|
+
|
|
2314
|
+
export function TodoListScreen() {
|
|
2315
|
+
return <View padding="md"><Text typography="h5">Todo List</Text></View>;
|
|
2316
|
+
}
|
|
2317
|
+
\`\`\`
|
|
2318
|
+
|
|
2319
|
+
### Step 2: Register in the route config
|
|
2320
|
+
\`\`\`tsx
|
|
2321
|
+
// In your AppRouter.ts or route configuration file
|
|
2322
|
+
import { TodoListScreen } from '../screens/TodoListScreen';
|
|
2323
|
+
|
|
2324
|
+
const appRouter: RouteParam = {
|
|
2325
|
+
path: "/",
|
|
2326
|
+
type: 'navigator',
|
|
2327
|
+
layout: 'stack',
|
|
2328
|
+
routes: [
|
|
2329
|
+
{ path: "", type: 'screen', component: HomeScreen },
|
|
2330
|
+
{ path: "todos", type: 'screen', component: TodoListScreen }, // Add this
|
|
2331
|
+
]
|
|
2332
|
+
};
|
|
2333
|
+
\`\`\`
|
|
2334
|
+
|
|
2335
|
+
### Step 3: Navigate to the new screen
|
|
2336
|
+
\`\`\`tsx
|
|
2337
|
+
// In any existing screen
|
|
2338
|
+
const { navigate } = useNavigator();
|
|
2339
|
+
<Button onPress={() => navigate({ path: '/todos' })}>View Todos</Button>
|
|
2340
|
+
\`\`\`
|
|
2341
|
+
|
|
2342
|
+
> **CRITICAL:** A screen component that is NOT registered in the route config is unreachable. Writing a screen without adding it to routes is a common mistake.
|
|
2343
|
+
|
|
2261
2344
|
## Best Practices
|
|
2262
2345
|
|
|
2263
2346
|
1. **Use relative paths** for child routes
|
|
@@ -2528,7 +2611,7 @@ header={{
|
|
|
2528
2611
|
height: 80,
|
|
2529
2612
|
content: (
|
|
2530
2613
|
<View style={{ flexDirection: 'row', justifyContent: 'space-between', padding: 16 }}>
|
|
2531
|
-
<Text
|
|
2614
|
+
<Text typography="h6" weight="bold">Dashboard</Text>
|
|
2532
2615
|
<UserMenu />
|
|
2533
2616
|
</View>
|
|
2534
2617
|
)
|
|
@@ -2723,7 +2806,7 @@ export const DashboardLayout: React.FC<StackLayoutProps> = ({
|
|
|
2723
2806
|
alignItems: 'center',
|
|
2724
2807
|
padding: 16
|
|
2725
2808
|
}}>
|
|
2726
|
-
<Text
|
|
2809
|
+
<Text typography="h5" weight="bold">Dashboard</Text>
|
|
2727
2810
|
<View style={{ flexDirection: 'row', gap: 16 }}>
|
|
2728
2811
|
<NotificationBell />
|
|
2729
2812
|
<UserAvatar />
|
|
@@ -2868,15 +2951,21 @@ navigator.navigate({ path: '/new-location', replace: true });
|
|
|
2868
2951
|
|
|
2869
2952
|
## useParams Hook
|
|
2870
2953
|
|
|
2871
|
-
Access current route path parameters
|
|
2954
|
+
Access current route path parameters. Returns \`Record<string, string>\`.
|
|
2955
|
+
|
|
2956
|
+
> **WARNING:** \`useParams()\` does NOT accept generic type arguments. Do NOT write \`useParams<{ id: string }>()\` \u2014 this causes TS2558. Access params by key from the returned record instead.
|
|
2872
2957
|
|
|
2873
2958
|
\`\`\`tsx
|
|
2874
2959
|
import { useParams } from '@idealyst/navigation';
|
|
2875
2960
|
|
|
2876
2961
|
function UserScreen() {
|
|
2962
|
+
// CORRECT \u2014 no type argument
|
|
2877
2963
|
const params = useParams();
|
|
2878
2964
|
const userId = params.id; // Path param from /user/:id
|
|
2879
2965
|
|
|
2966
|
+
// WRONG \u2014 useParams does NOT accept generics
|
|
2967
|
+
// const params = useParams<{ id: string }>(); // TS2558 error!
|
|
2968
|
+
|
|
2880
2969
|
return <Text>User ID: {userId}</Text>;
|
|
2881
2970
|
}
|
|
2882
2971
|
\`\`\`
|
|
@@ -3232,7 +3321,7 @@ const { canGoBack, goBack } = useNavigator();
|
|
|
3232
3321
|
|
|
3233
3322
|
{canGoBack() && (
|
|
3234
3323
|
<Button
|
|
3235
|
-
|
|
3324
|
+
leftIcon="arrow-left"
|
|
3236
3325
|
onPress={goBack}
|
|
3237
3326
|
>
|
|
3238
3327
|
Back
|
|
@@ -3309,11 +3398,11 @@ import { NavigatorParam, NotFoundComponentProps } from '@idealyst/navigation';
|
|
|
3309
3398
|
const NotFoundPage = ({ path, params }: NotFoundComponentProps) => (
|
|
3310
3399
|
<Screen>
|
|
3311
3400
|
<View style={{ alignItems: 'center', padding: 24 }}>
|
|
3312
|
-
<Icon name="alert-circle" size=
|
|
3313
|
-
<Text
|
|
3401
|
+
<Icon name="alert-circle" size="xl" intent="danger" />
|
|
3402
|
+
<Text typography="h5">Page Not Found</Text>
|
|
3314
3403
|
<Text color="secondary">The path "{path}" doesn't exist.</Text>
|
|
3315
3404
|
{params && Object.keys(params).length > 0 && (
|
|
3316
|
-
<Text
|
|
3405
|
+
<Text typography="caption">Params: {JSON.stringify(params)}</Text>
|
|
3317
3406
|
)}
|
|
3318
3407
|
</View>
|
|
3319
3408
|
</Screen>
|
|
@@ -3352,7 +3441,7 @@ const NotFoundPage = ({ path, params }: NotFoundComponentProps) => {
|
|
|
3352
3441
|
return (
|
|
3353
3442
|
<Screen>
|
|
3354
3443
|
<View style={{ padding: 16, gap: 24 }}>
|
|
3355
|
-
<Text
|
|
3444
|
+
<Text typography="h5">404 - Page Not Found</Text>
|
|
3356
3445
|
<Text>Attempted: {path}</Text>
|
|
3357
3446
|
{params?.id && <Text>User ID: {params.id}</Text>}
|
|
3358
3447
|
<Button onPress={() => navigate({ path: '/', replace: true })}>
|
|
@@ -3418,7 +3507,7 @@ Each navigator can have its own 404 handling:
|
|
|
3418
3507
|
const SettingsNotFound = ({ path }: NotFoundComponentProps) => (
|
|
3419
3508
|
<Screen>
|
|
3420
3509
|
<View style={{ alignItems: 'center' }}>
|
|
3421
|
-
<Icon name="cog-off" size=
|
|
3510
|
+
<Icon name="cog-off" size="lg" intent="warning" />
|
|
3422
3511
|
<Text>Settings page not found: {path}</Text>
|
|
3423
3512
|
</View>
|
|
3424
3513
|
</Screen>
|
|
@@ -3510,17 +3599,17 @@ const GlobalNotFound = ({ path, params }: NotFoundComponentProps) => {
|
|
|
3510
3599
|
return (
|
|
3511
3600
|
<Screen>
|
|
3512
3601
|
<View style={{ padding: 24, gap: 24, alignItems: 'center', flex: 1, justifyContent: 'center' }}>
|
|
3513
|
-
<Icon name="alert-circle-outline" size=
|
|
3514
|
-
<Text
|
|
3602
|
+
<Icon name="alert-circle-outline" size="xl" intent="danger" />
|
|
3603
|
+
<Text typography="h5" weight="bold">Page Not Found</Text>
|
|
3515
3604
|
<Text color="secondary">The page you're looking for doesn't exist.</Text>
|
|
3516
3605
|
|
|
3517
3606
|
<Card style={{ marginTop: 16, padding: 16 }}>
|
|
3518
|
-
<Text
|
|
3519
|
-
<Text
|
|
3607
|
+
<Text typography="caption" weight="semibold">Attempted path:</Text>
|
|
3608
|
+
<Text typography="caption" color="secondary">{path}</Text>
|
|
3520
3609
|
{params && Object.keys(params).length > 0 && (
|
|
3521
3610
|
<>
|
|
3522
|
-
<Text
|
|
3523
|
-
<Text
|
|
3611
|
+
<Text typography="caption" weight="semibold" style={{ marginTop: 8 }}>Params:</Text>
|
|
3612
|
+
<Text typography="caption" color="secondary">{JSON.stringify(params)}</Text>
|
|
3524
3613
|
</>
|
|
3525
3614
|
)}
|
|
3526
3615
|
</Card>
|
|
@@ -3539,9 +3628,9 @@ const AdminNotFound = ({ path }: NotFoundComponentProps) => {
|
|
|
3539
3628
|
|
|
3540
3629
|
return (
|
|
3541
3630
|
<Screen>
|
|
3542
|
-
<View padding=
|
|
3543
|
-
<Icon name="shield-off" size=
|
|
3544
|
-
<Text
|
|
3631
|
+
<View padding="md" style={{ alignItems: 'center' }}>
|
|
3632
|
+
<Icon name="shield-off" size="lg" intent="warning" />
|
|
3633
|
+
<Text typography="h6">Admin page not found</Text>
|
|
3545
3634
|
<Button
|
|
3546
3635
|
type="outlined"
|
|
3547
3636
|
size="sm"
|
|
@@ -3736,7 +3825,7 @@ export function StackLayout({ options, currentPath }: StackLayoutProps) {
|
|
|
3736
3825
|
{/* Title - centered or left-aligned */}
|
|
3737
3826
|
<View style={{ flex: 1 }}>
|
|
3738
3827
|
{typeof options?.headerTitle === 'string' ? (
|
|
3739
|
-
<Text
|
|
3828
|
+
<Text typography="h6" weight="bold">{options.headerTitle}</Text>
|
|
3740
3829
|
) : (
|
|
3741
3830
|
options?.headerTitle
|
|
3742
3831
|
)}
|
|
@@ -3821,16 +3910,17 @@ export function TabLayout({ routes, currentPath }: TabLayoutProps) {
|
|
|
3821
3910
|
})}
|
|
3822
3911
|
{options?.tabBarBadge && (
|
|
3823
3912
|
<Badge
|
|
3824
|
-
count={options.tabBarBadge}
|
|
3825
3913
|
style={{ position: 'absolute', top: -4, right: -8 }}
|
|
3826
|
-
|
|
3914
|
+
>
|
|
3915
|
+
{options.tabBarBadge}
|
|
3916
|
+
</Badge>
|
|
3827
3917
|
)}
|
|
3828
3918
|
</View>
|
|
3829
3919
|
|
|
3830
3920
|
{/* Label */}
|
|
3831
3921
|
{options?.tabBarLabel && (
|
|
3832
3922
|
<Text
|
|
3833
|
-
|
|
3923
|
+
typography="caption"
|
|
3834
3924
|
style={{
|
|
3835
3925
|
marginTop: 4,
|
|
3836
3926
|
color: isActive ? '#007AFF' : '#8E8E93',
|
|
@@ -3882,7 +3972,7 @@ export function DrawerLayout({ routes, currentPath, options }: StackLayoutProps)
|
|
|
3882
3972
|
}}>
|
|
3883
3973
|
{/* Logo/Header */}
|
|
3884
3974
|
<View style={{ height: 56, justifyContent: 'center', paddingHorizontal: 16 }}>
|
|
3885
|
-
{!isCollapsed && <Text
|
|
3975
|
+
{!isCollapsed && <Text typography="h6" weight="bold">My App</Text>}
|
|
3886
3976
|
</View>
|
|
3887
3977
|
|
|
3888
3978
|
{/* Menu Items */}
|
|
@@ -3918,7 +4008,7 @@ export function DrawerLayout({ routes, currentPath, options }: StackLayoutProps)
|
|
|
3918
4008
|
onPress={() => setIsCollapsed(!isCollapsed)}
|
|
3919
4009
|
style={{ padding: 12, marginTop: 'auto' }}
|
|
3920
4010
|
>
|
|
3921
|
-
<Icon name={isCollapsed ? 'chevron-right' : 'chevron-left'} size=
|
|
4011
|
+
<Icon name={isCollapsed ? 'chevron-right' : 'chevron-left'} size="sm" />
|
|
3922
4012
|
</Pressable>
|
|
3923
4013
|
</View>
|
|
3924
4014
|
|
|
@@ -3946,7 +4036,7 @@ export function AppLayout({ options, routes, currentPath, children }: StackLayou
|
|
|
3946
4036
|
height: 56,
|
|
3947
4037
|
content: (
|
|
3948
4038
|
<View style={{ flexDirection: 'row', alignItems: 'center', flex: 1 }}>
|
|
3949
|
-
<Text
|
|
4039
|
+
<Text typography="h6" weight="bold">{options?.headerTitle || 'App'}</Text>
|
|
3950
4040
|
<View style={{ marginLeft: 'auto' }}>{options?.headerRight}</View>
|
|
3951
4041
|
</View>
|
|
3952
4042
|
),
|
|
@@ -4091,15 +4181,59 @@ Idealyst uses Material Design Icons (@mdi/react) with **7,447 icons** available.
|
|
|
4091
4181
|
|
|
4092
4182
|
## Icon Usage
|
|
4093
4183
|
|
|
4094
|
-
###
|
|
4184
|
+
### TypeScript Type \u2014 CRITICAL
|
|
4185
|
+
|
|
4186
|
+
Icon names are typed as \`IconName\`, NOT \`string\`. Use **bare names** like \`'home'\`, \`'check-circle'\` \u2014 do NOT add an \`'mdi:'\` prefix.
|
|
4095
4187
|
|
|
4096
|
-
|
|
4188
|
+
When you create helper functions that return icon names, you MUST type them as \`IconName\`:
|
|
4189
|
+
|
|
4190
|
+
\`\`\`tsx
|
|
4191
|
+
import type { IconName } from '@idealyst/components';
|
|
4192
|
+
|
|
4193
|
+
// CORRECT: return type is IconName
|
|
4194
|
+
function getStatusIcon(status: string): IconName {
|
|
4195
|
+
switch (status) {
|
|
4196
|
+
case 'active': return 'check-circle';
|
|
4197
|
+
case 'pending': return 'clock-outline';
|
|
4198
|
+
default: return 'help-circle';
|
|
4199
|
+
}
|
|
4200
|
+
}
|
|
4201
|
+
|
|
4202
|
+
// WRONG: return type 'string' will cause TS2322 error when passed to icon props
|
|
4203
|
+
// function getStatusIcon(status: string): string { ... } // \u274C
|
|
4204
|
+
|
|
4205
|
+
// For icon lookup maps, type the values as IconName:
|
|
4206
|
+
const FILE_TYPE_ICONS: Record<string, IconName> = {
|
|
4207
|
+
'image': 'file-image',
|
|
4208
|
+
'video': 'file-video',
|
|
4209
|
+
'audio': 'file-music',
|
|
4210
|
+
'document': 'file-document',
|
|
4211
|
+
'default': 'file',
|
|
4212
|
+
};
|
|
4213
|
+
|
|
4214
|
+
function getFileIcon(type: string): IconName {
|
|
4215
|
+
return FILE_TYPE_ICONS[type] ?? FILE_TYPE_ICONS['default'];
|
|
4216
|
+
}
|
|
4217
|
+
\`\`\`
|
|
4218
|
+
|
|
4219
|
+
### When to Import \`Icon\` vs Use \`IconName\` Type
|
|
4220
|
+
|
|
4221
|
+
- Import the \`Icon\` component only when rendering a standalone icon as JSX: \`<Icon name="home" />\`
|
|
4222
|
+
- For component props like \`leftIcon\` on Button, \`leading\`/\`trailing\` on ListItem \u2014 pass bare string names. Do NOT import the \`Icon\` component for this use.
|
|
4223
|
+
- Import ONLY the \`IconName\` type (not the component) when you need a typed variable for an icon name:
|
|
4224
|
+
|
|
4225
|
+
\`\`\`tsx
|
|
4226
|
+
import type { IconName } from '@idealyst/components';
|
|
4227
|
+
const myIcon: IconName = 'home'; // Correct: type-only import
|
|
4228
|
+
\`\`\`
|
|
4229
|
+
|
|
4230
|
+
### In Components
|
|
4097
4231
|
|
|
4098
4232
|
\`\`\`tsx
|
|
4099
4233
|
import { Button, Icon, List, Badge } from '@idealyst/components';
|
|
4100
4234
|
|
|
4101
|
-
// Button with icon
|
|
4102
|
-
<Button
|
|
4235
|
+
// Button with icon (use leftIcon, NOT icon)
|
|
4236
|
+
<Button leftIcon="check">Save</Button>
|
|
4103
4237
|
|
|
4104
4238
|
// Standalone Icon
|
|
4105
4239
|
<Icon name="home" size="md" color="primary" />
|
|
@@ -4115,7 +4249,7 @@ import { Button, Icon, List, Badge } from '@idealyst/components';
|
|
|
4115
4249
|
|
|
4116
4250
|
\`\`\`tsx
|
|
4117
4251
|
<Icon
|
|
4118
|
-
name="home" // Icon name (required)
|
|
4252
|
+
name="home" // Icon name (required) \u2014 type: IconName
|
|
4119
4253
|
size="xs|sm|md|lg|xl|number" // Icon size
|
|
4120
4254
|
color="primary|secondary|..." // Theme color
|
|
4121
4255
|
/>
|
|
@@ -4340,11 +4474,12 @@ Icons follow naming patterns:
|
|
|
4340
4474
|
|
|
4341
4475
|
### Button Icons
|
|
4342
4476
|
\`\`\`tsx
|
|
4343
|
-
<Button
|
|
4344
|
-
<Button
|
|
4345
|
-
<Button
|
|
4346
|
-
<Button
|
|
4477
|
+
<Button leftIcon="content-save">Save</Button>
|
|
4478
|
+
<Button leftIcon="delete" intent="danger">Delete</Button>
|
|
4479
|
+
<Button leftIcon="pencil">Edit</Button>
|
|
4480
|
+
<Button leftIcon="plus">Add New</Button>
|
|
4347
4481
|
\`\`\`
|
|
4482
|
+
> **Note:** Button uses \`leftIcon\` and \`rightIcon\` \u2014 NOT \`icon\`.
|
|
4348
4483
|
|
|
4349
4484
|
### List Icons
|
|
4350
4485
|
\`\`\`tsx
|
|
@@ -4356,9 +4491,9 @@ Icons follow naming patterns:
|
|
|
4356
4491
|
|
|
4357
4492
|
### Navigation Icons
|
|
4358
4493
|
\`\`\`tsx
|
|
4359
|
-
<Button
|
|
4360
|
-
<Button
|
|
4361
|
-
<Button
|
|
4494
|
+
<Button leftIcon="arrow-left" type="text">Back</Button>
|
|
4495
|
+
<Button leftIcon="menu" type="text">Menu</Button>
|
|
4496
|
+
<Button leftIcon="close" type="text">Close</Button>
|
|
4362
4497
|
\`\`\`
|
|
4363
4498
|
|
|
4364
4499
|
### Status Icons
|