@meistrari/tela-build 1.25.3 → 1.27.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/components/tela/badge/badge.vue +3 -3
- package/components/tela/button/button.vue +1 -1
- package/components/tela/icon/custom.vue +6 -0
- package/components/tela/icon-button/icon-button.vue +2 -2
- package/components/tela/menubar/menubar-item.vue +1 -1
- package/components/tela/menubar/menubar-trigger.vue +1 -1
- package/components/tela/sidebar/sidebar-content.vue +10 -0
- package/components/tela/sidebar/sidebar-footer.vue +5 -0
- package/components/tela/sidebar/sidebar-header.vue +5 -0
- package/components/tela/sidebar/sidebar-item.vue +48 -0
- package/components/tela/sidebar/sidebar-logo.vue +21 -0
- package/components/tela/sidebar/sidebar-user.vue +47 -0
- package/components/tela/sidebar/sidebar.mdx +175 -0
- package/components/tela/sidebar/sidebar.stories.ts +190 -0
- package/components/tela/sidebar/sidebar.vue +5 -0
- package/components/tela/star-button.vue +1 -1
- package/components/tela/transparent-input.vue +18 -7
- package/package.json +1 -1
- package/types/custom-icon.ts +1 -1
- package/unocss.config.ts +11 -0
- package/utils/design-tokens.ts +5 -5
|
@@ -20,15 +20,15 @@ const tag = computed(() => props.to ? NuxtLink : 'div')
|
|
|
20
20
|
<component
|
|
21
21
|
:is="tag"
|
|
22
22
|
:class="cn(
|
|
23
|
-
'inline-block px-[5px] rounded-[5px] select-none',
|
|
24
|
-
variant === 'outline' && 'border-[0.5px]
|
|
23
|
+
'inline-block px-[5px] py-[3px] rounded-[5px] select-none',
|
|
24
|
+
variant === 'outline' && 'border-[0.5px] border-border',
|
|
25
25
|
variant === 'filled' && 'bg-lowered',
|
|
26
26
|
props.class,
|
|
27
27
|
)"
|
|
28
28
|
>
|
|
29
29
|
<span
|
|
30
30
|
:class="cn(
|
|
31
|
-
'flex items-center gap-[4px] text-[9px] whitespace-nowrap font-580 leading-[
|
|
31
|
+
'flex items-center gap-[4px] text-[9px] whitespace-nowrap font-580 leading-[10px] tracking-0.2px uppercase',
|
|
32
32
|
variant === 'outline' && 'text-tertiary',
|
|
33
33
|
variant === 'filled' && 'text-secondary tracking-normal',
|
|
34
34
|
textClass,
|
|
@@ -100,7 +100,7 @@ const iconSize = computed(() => {
|
|
|
100
100
|
<component
|
|
101
101
|
:is="tag"
|
|
102
102
|
:to="to"
|
|
103
|
-
:class="resolvedStyle"
|
|
103
|
+
:class="resolvedStyle" ease-out duration-40 flex items-center justify-center gap-6px
|
|
104
104
|
:disabled="disabled"
|
|
105
105
|
:cursor="disabled ? 'not-allowed' : 'pointer'"
|
|
106
106
|
:target="target"
|
|
@@ -354,5 +354,11 @@ const colorValue = computed(() => {
|
|
|
354
354
|
<template v-if="name === 'timeline-start'">
|
|
355
355
|
<circle cx="4.5" cy="4.5" r="4" fill="white" stroke="currentColor" />
|
|
356
356
|
</template>
|
|
357
|
+
|
|
358
|
+
<template v-if="name === 'level-right'">
|
|
359
|
+
<g transform="translate(3.3, 0.2)">
|
|
360
|
+
<path d="M0.805054 0.805054L3.34137 3.34137C4.54019 4.54019 4.60421 6.46316 3.4878 7.73906L0.805054 10.8051" stroke="currentColor" stroke-width="1.61015" stroke-linecap="round" />
|
|
361
|
+
</g>
|
|
362
|
+
</template>
|
|
357
363
|
</svg>
|
|
358
364
|
</template>
|
|
@@ -29,7 +29,7 @@ const emit = defineEmits<{
|
|
|
29
29
|
}>()
|
|
30
30
|
|
|
31
31
|
const style = computed(() => {
|
|
32
|
-
const base = 'cursor-pointer flex items-center justify-center rounded-lg p-0.5em
|
|
32
|
+
const base = 'cursor-pointer flex items-center justify-center rounded-lg p-0.5em ease-out duration-40'
|
|
33
33
|
const size = ({
|
|
34
34
|
'3xs': 'text-6px p-1',
|
|
35
35
|
'2xs': 'text-8px p-4px', // 16px
|
|
@@ -40,7 +40,7 @@ const style = computed(() => {
|
|
|
40
40
|
|
|
41
41
|
const color = ({
|
|
42
42
|
primary: 'bg-#01292F !text-white hover:bg-#153A40 b-#011D21 hover:b-#123137',
|
|
43
|
-
secondary: 'bg-transparent text-icon hover:bg-
|
|
43
|
+
secondary: 'bg-transparent text-icon-tertiary hover:bg-muted hover:text-gray-950 active:text-gray-800 disabled:bg-gray-100 disabled:!text-gray-300',
|
|
44
44
|
} as Record<typeof props.color, string>)[props.color]
|
|
45
45
|
|
|
46
46
|
const disabled = props.disabled && 'cursor-not-allowed !bg-#ECF0F2 !text-#617275 !b-0'
|
|
@@ -22,7 +22,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
|
22
22
|
<MenubarItem
|
|
23
23
|
v-bind="forwarded"
|
|
24
24
|
:class="cn(
|
|
25
|
-
'relative flex cursor-pointer select-none items-center rounded-xl px-3 py-1.5 text-body-14-medium font-460 outline-none focus:bg-
|
|
25
|
+
'relative flex cursor-pointer select-none items-center rounded-xl px-3 py-1.5 text-body-14-medium font-460 outline-none focus:bg-muted data-[disabled]:pointer-events-none data-[disabled]:opacity-40',
|
|
26
26
|
inset && 'pl-8',
|
|
27
27
|
props.class,
|
|
28
28
|
)"
|
|
@@ -17,7 +17,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|
|
17
17
|
v-bind="forwardedProps"
|
|
18
18
|
:class="
|
|
19
19
|
cn(
|
|
20
|
-
'flex items-center gap-2 cursor-pointer select-none px-2 py-1.5 text-sm font-medium outline-none rounded-lg hover:bg-
|
|
20
|
+
'flex items-center gap-2 cursor-pointer select-none px-2 py-1.5 text-sm font-medium outline-none rounded-lg hover:bg-lowered data-[state=open]:bg-lowered',
|
|
21
21
|
props.class,
|
|
22
22
|
)
|
|
23
23
|
"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
flex flex-col h-full overflow-y-auto no-scrollbar
|
|
4
|
+
class="[mask-image:linear-gradient(to_bottom,transparent,black_20px,black_calc(100%_-_20px),transparent)]"
|
|
5
|
+
>
|
|
6
|
+
<nav flex flex-col items-center gap-14px h-full px-10px pt-12px mb-48px>
|
|
7
|
+
<slot />
|
|
8
|
+
</nav>
|
|
9
|
+
</div>
|
|
10
|
+
</template>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { NuxtLink } from '#components'
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{
|
|
5
|
+
icon: string
|
|
6
|
+
label: string
|
|
7
|
+
to?: string
|
|
8
|
+
onClick?: () => void
|
|
9
|
+
isActive: boolean
|
|
10
|
+
}>()
|
|
11
|
+
|
|
12
|
+
const iconName = computed(() => props.isActive ? `${props.icon}-fill` : props.icon)
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<component
|
|
17
|
+
:is="to ? NuxtLink : 'button'"
|
|
18
|
+
:to="to"
|
|
19
|
+
:type="!to ? 'button' : undefined"
|
|
20
|
+
class="group"
|
|
21
|
+
flex="~ col" items-center justify-center gap-2px outline-none
|
|
22
|
+
:data-active="isActive"
|
|
23
|
+
v-bind="!to && onClick ? { onClick } : {}"
|
|
24
|
+
>
|
|
25
|
+
<div relative size-40px flex items-center justify-center rounded-10px>
|
|
26
|
+
<TelaIcon
|
|
27
|
+
:name="iconName"
|
|
28
|
+
size="20px"
|
|
29
|
+
relative z-1
|
|
30
|
+
:color="isActive ? 'icon' : 'icon-tertiary duration-150 ease-out group-hover:icon group-focus-within:icon'"
|
|
31
|
+
/>
|
|
32
|
+
<div
|
|
33
|
+
:class="cn(
|
|
34
|
+
'absolute inset-0 size-full rounded-[14px] z-0 border-[0.5px] border-transparent',
|
|
35
|
+
isActive ? 'bg-neutral-200 group-focus-within:border-strong' : 'bg scale-10 opacity-0 duration-150 ease-out origin-center group-hover:border-strong group-hover:scale-100 group-hover:opacity-100 group-focus-within:border-strong group-focus-within:scale-100 group-focus-within:opacity-100',
|
|
36
|
+
)"
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
<p
|
|
40
|
+
:class="cn(
|
|
41
|
+
'text-[11px] leading-[12px] -tracking-0.2px',
|
|
42
|
+
isActive ? 'text-primary font-550' : 'font-460 text-tertiary duration-150 ease-out group-hover:text-primary group-focus-within:text-primary',
|
|
43
|
+
)"
|
|
44
|
+
>
|
|
45
|
+
{{ label }}
|
|
46
|
+
</p>
|
|
47
|
+
</component>
|
|
48
|
+
</template>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { NuxtLink } from '#components'
|
|
3
|
+
|
|
4
|
+
defineProps<{
|
|
5
|
+
src?: string
|
|
6
|
+
alt: string
|
|
7
|
+
to?: string
|
|
8
|
+
}>()
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<component
|
|
13
|
+
:is="to ? NuxtLink : 'div'"
|
|
14
|
+
:to="to"
|
|
15
|
+
class="rounded-12px overflow-hidden outline-none focus-within:ring-0.5px focus-within:ring-border-strong"
|
|
16
|
+
>
|
|
17
|
+
<img v-if="src" :src="src" :alt="alt" w-40px h-40px object-cover>
|
|
18
|
+
<TelaInitials v-else-if="alt" size="md" :word="alt" />
|
|
19
|
+
<div v-else w-40px h-40px bg-black />
|
|
20
|
+
</component>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineProps<{
|
|
3
|
+
image?: string
|
|
4
|
+
name: string
|
|
5
|
+
email: string
|
|
6
|
+
actions: {
|
|
7
|
+
label: string
|
|
8
|
+
icon: string
|
|
9
|
+
color: 'positive' | 'caution' | 'negative'
|
|
10
|
+
onClick: () => void
|
|
11
|
+
}[]
|
|
12
|
+
}>()
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<TelaDropdownMenuRoot>
|
|
17
|
+
<TelaDropdownMenuTrigger as-child>
|
|
18
|
+
<button
|
|
19
|
+
:class="cn('group', image ? 'ring-transparent focus-within:ring-border-strong' : 'ring-border-strong focus-within:ring-border-accent')"
|
|
20
|
+
rounded-full overflow-hidden ring-0.5px
|
|
21
|
+
>
|
|
22
|
+
<img v-if="image" :src="image" :alt="name" w-32px h-32px object-cover>
|
|
23
|
+
<div v-else w-32px h-32px bg />
|
|
24
|
+
</button>
|
|
25
|
+
</TelaDropdownMenuTrigger>
|
|
26
|
+
<TelaDropdownMenuContent align="start" side="top" class="min-w-200px!">
|
|
27
|
+
<div px-12px py-8px>
|
|
28
|
+
<h5 heading-h5-semibold text-primary mb-2px>
|
|
29
|
+
{{ name }}
|
|
30
|
+
</h5>
|
|
31
|
+
<p body-12-regular text-secondary>
|
|
32
|
+
{{ email }}
|
|
33
|
+
</p>
|
|
34
|
+
</div>
|
|
35
|
+
<TelaDropdownMenuSeparator />
|
|
36
|
+
<TelaDropdownMenuItem
|
|
37
|
+
v-for="action in actions"
|
|
38
|
+
:key="action.label"
|
|
39
|
+
:icon="action.icon"
|
|
40
|
+
:color="action.color"
|
|
41
|
+
@click="action.onClick"
|
|
42
|
+
>
|
|
43
|
+
{{ action.label }}
|
|
44
|
+
</TelaDropdownMenuItem>
|
|
45
|
+
</TelaDropdownMenuContent>
|
|
46
|
+
</TelaDropdownMenuRoot>
|
|
47
|
+
</template>
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
|
|
2
|
+
import * as SidebarStories from './sidebar.stories.ts';
|
|
3
|
+
|
|
4
|
+
<Meta of={SidebarStories} />
|
|
5
|
+
|
|
6
|
+
# TelaSidebar
|
|
7
|
+
|
|
8
|
+
A composable sidebar navigation system built from focused sub-components. Fixed 80px wide and full-height, designed for icon-based navigation with labels.
|
|
9
|
+
|
|
10
|
+
## Examples
|
|
11
|
+
|
|
12
|
+
### Full Sidebar
|
|
13
|
+
|
|
14
|
+
```vue
|
|
15
|
+
<TelaSidebar>
|
|
16
|
+
<TelaSidebarHeader>
|
|
17
|
+
<TelaSidebarLogo src="/tela-logo-black.svg" alt="Tela Logo" />
|
|
18
|
+
</TelaSidebarHeader>
|
|
19
|
+
|
|
20
|
+
<TelaSidebarContent>
|
|
21
|
+
<TelaSidebarItem
|
|
22
|
+
v-for="item in items"
|
|
23
|
+
:key="item.label"
|
|
24
|
+
:icon="item.icon"
|
|
25
|
+
:label="item.label"
|
|
26
|
+
:to="item.path"
|
|
27
|
+
:is-active="isItemActive(item)"
|
|
28
|
+
:on-click="item.onClick"
|
|
29
|
+
/>
|
|
30
|
+
</TelaSidebarContent>
|
|
31
|
+
|
|
32
|
+
<TelaSidebarFooter>
|
|
33
|
+
<TelaSidebarItem
|
|
34
|
+
icon="i-ph-bell"
|
|
35
|
+
label="Activity"
|
|
36
|
+
:is-active="route.path === '/activity'"
|
|
37
|
+
/>
|
|
38
|
+
<TelaSidebarUser
|
|
39
|
+
:image="user.image"
|
|
40
|
+
:name="user.name"
|
|
41
|
+
:email="user.email"
|
|
42
|
+
:actions="[
|
|
43
|
+
{ label: 'Logout', icon: 'i-ph-sign-out', color: 'negative', onClick: handleLogout },
|
|
44
|
+
]"
|
|
45
|
+
/>
|
|
46
|
+
</TelaSidebarFooter>
|
|
47
|
+
</TelaSidebar>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
<Canvas of={SidebarStories.Default} />
|
|
51
|
+
|
|
52
|
+
### Activity Active
|
|
53
|
+
|
|
54
|
+
<Canvas of={SidebarStories.ActivityActive} />
|
|
55
|
+
|
|
56
|
+
### No Active Item
|
|
57
|
+
|
|
58
|
+
<Canvas of={SidebarStories.NoActiveItem} />
|
|
59
|
+
|
|
60
|
+
### Individual Item States
|
|
61
|
+
|
|
62
|
+
<Canvas of={SidebarStories.SingleItem} />
|
|
63
|
+
|
|
64
|
+
## Components
|
|
65
|
+
|
|
66
|
+
### `<TelaSidebar>`
|
|
67
|
+
|
|
68
|
+
Root wrapper. Renders as an `<aside>` with fixed 80px width, full height, and a right border. Place `TelaSidebarHeader`, `TelaSidebarContent`, and `TelaSidebarFooter` as direct children.
|
|
69
|
+
|
|
70
|
+
### `<TelaSidebarHeader>`
|
|
71
|
+
|
|
72
|
+
Top section of the sidebar. Stacks children vertically with 24px gap and vertical padding. Use for logos or workspace switchers.
|
|
73
|
+
|
|
74
|
+
### `<TelaSidebarLogo>`
|
|
75
|
+
|
|
76
|
+
Displays a logo or fallback inside the header. When `src` is provided, renders an image. When only `alt` is provided, falls back to `TelaInitials`. If neither is set, renders a placeholder div.
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
interface TelaSidebarLogoProps {
|
|
80
|
+
src?: string // Image source URL or path — when provided, renders an <img>
|
|
81
|
+
alt: string // Accessible alt text — used as the word for TelaInitials fallback
|
|
82
|
+
to?: string // Optional link — wraps the content in a NuxtLink when provided
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### `<TelaSidebarContent>`
|
|
87
|
+
|
|
88
|
+
Middle section that grows to fill available space. Scrollable with hidden scrollbar and a fade mask at the top and bottom edges for a smooth overflow effect. Place nav items directly as children — they stack vertically with 14px gap.
|
|
89
|
+
|
|
90
|
+
### `<TelaSidebarItem>`
|
|
91
|
+
|
|
92
|
+
An individual navigation item with an icon and label. Renders as a `NuxtLink` when `to` is provided, otherwise as a `<button>`.
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
interface TelaSidebarItemProps {
|
|
96
|
+
icon: string // Iconify class (e.g. "i-ph-house")
|
|
97
|
+
label: string // Text label shown below the icon
|
|
98
|
+
isActive: boolean // Highlights the item as the current route
|
|
99
|
+
to?: string // Route path — renders as a link
|
|
100
|
+
onClick?: () => void // Click handler when not using `to`
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
<ArgTypes of={SidebarStories} />
|
|
105
|
+
|
|
106
|
+
### `<TelaSidebarFooter>`
|
|
107
|
+
|
|
108
|
+
Bottom section of the sidebar. Always contains `TelaSidebarUser` for account controls. Optionally add secondary `TelaSidebarItem` actions (e.g. activity, notifications) above it.
|
|
109
|
+
|
|
110
|
+
Default — user only:
|
|
111
|
+
|
|
112
|
+
```vue
|
|
113
|
+
<TelaSidebarFooter>
|
|
114
|
+
<TelaSidebarUser
|
|
115
|
+
:avatar-url="user.avatarUrl"
|
|
116
|
+
:username="user.name"
|
|
117
|
+
:email="user.email"
|
|
118
|
+
:options="[
|
|
119
|
+
{ label: 'Logout', icon: 'i-ph-sign-out', color: 'negative', onClick: handleLogout },
|
|
120
|
+
]"
|
|
121
|
+
/>
|
|
122
|
+
</TelaSidebarFooter>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
With secondary actions:
|
|
126
|
+
|
|
127
|
+
```vue
|
|
128
|
+
<TelaSidebarFooter>
|
|
129
|
+
<TelaSidebarItem
|
|
130
|
+
icon="i-ph-bell"
|
|
131
|
+
label="Activity"
|
|
132
|
+
:is-active="route.path === '/activity'"
|
|
133
|
+
to="/activity"
|
|
134
|
+
/>
|
|
135
|
+
<TelaSidebarUser
|
|
136
|
+
:avatar-url="user.avatarUrl"
|
|
137
|
+
:username="user.name"
|
|
138
|
+
:email="user.email"
|
|
139
|
+
:options="[
|
|
140
|
+
{ label: 'Logout', icon: 'i-ph-sign-out', color: 'negative', onClick: handleLogout },
|
|
141
|
+
]"
|
|
142
|
+
/>
|
|
143
|
+
</TelaSidebarFooter>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### `<TelaSidebarUser>`
|
|
147
|
+
|
|
148
|
+
User account button in the footer. Clicking opens a dropdown menu with the user's name, email, and configurable action items.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
interface TelaSidebarUserProps {
|
|
152
|
+
image?: string // Optional — falls back to a placeholder if not provided
|
|
153
|
+
name: string
|
|
154
|
+
email: string
|
|
155
|
+
actions: {
|
|
156
|
+
label: string
|
|
157
|
+
icon: string
|
|
158
|
+
color: 'positive' | 'caution' | 'negative'
|
|
159
|
+
onClick: () => void
|
|
160
|
+
}[]
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Example:
|
|
165
|
+
|
|
166
|
+
```vue
|
|
167
|
+
<TelaSidebarUser
|
|
168
|
+
image="https://example.com/avatar.jpg"
|
|
169
|
+
name="Jane Doe"
|
|
170
|
+
email="jane@example.com"
|
|
171
|
+
:actions="[
|
|
172
|
+
{ label: 'Logout', icon: 'i-ph-sign-out', color: 'negative', onClick: handleLogout },
|
|
173
|
+
]"
|
|
174
|
+
/>
|
|
175
|
+
```
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
2
|
+
import TelaSidebar from './sidebar.vue'
|
|
3
|
+
import TelaSidebarHeader from './sidebar-header.vue'
|
|
4
|
+
import TelaSidebarContent from './sidebar-content.vue'
|
|
5
|
+
import TelaSidebarLogo from './sidebar-logo.vue'
|
|
6
|
+
import TelaSidebarItem from './sidebar-item.vue'
|
|
7
|
+
import TelaSidebarFooter from './sidebar-footer.vue'
|
|
8
|
+
import TelaSidebarUser from './sidebar-user.vue'
|
|
9
|
+
|
|
10
|
+
const meta: Meta<typeof TelaSidebar> = {
|
|
11
|
+
title: 'Patterns/Sidebar',
|
|
12
|
+
component: TelaSidebar,
|
|
13
|
+
parameters: {
|
|
14
|
+
layout: 'fullscreen',
|
|
15
|
+
docs: {
|
|
16
|
+
description: {
|
|
17
|
+
component: 'A composable sidebar navigation system built from focused sub-components. Fixed 80px wide and full-height, designed for icon-based navigation with labels. Compose `TelaSidebarHeader`, `TelaSidebarContent`, and `TelaSidebarFooter` inside the root `TelaSidebar` to build the full layout.',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default meta
|
|
24
|
+
|
|
25
|
+
type Story = StoryObj<typeof TelaSidebar>
|
|
26
|
+
|
|
27
|
+
const navItems = [
|
|
28
|
+
{ icon: 'i-ph-house', label: 'Home', path: '/' },
|
|
29
|
+
{ icon: 'i-ph-graph', label: 'Workflows', path: '/workflows' },
|
|
30
|
+
{ icon: 'i-ph-database', label: 'Data', path: '/data' },
|
|
31
|
+
{ icon: 'i-ph-gear', label: 'Settings', path: '/settings' },
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
const userActions = [
|
|
35
|
+
{ label: 'Logout', icon: 'i-ph-sign-out', color: 'negative' as const, onClick: () => {} },
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
const components = {
|
|
39
|
+
TelaSidebar,
|
|
40
|
+
TelaSidebarHeader,
|
|
41
|
+
TelaSidebarLogo,
|
|
42
|
+
TelaSidebarContent,
|
|
43
|
+
TelaSidebarItem,
|
|
44
|
+
TelaSidebarFooter,
|
|
45
|
+
TelaSidebarUser,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const Default: Story = {
|
|
49
|
+
render: () => ({
|
|
50
|
+
components,
|
|
51
|
+
setup() {
|
|
52
|
+
const items = navItems
|
|
53
|
+
const activePath = '/workflows'
|
|
54
|
+
const isItemActive = (item: { path: string }) => activePath === item.path
|
|
55
|
+
return { items, isItemActive, userActions }
|
|
56
|
+
},
|
|
57
|
+
template: `
|
|
58
|
+
<TelaSidebar>
|
|
59
|
+
<TelaSidebarHeader>
|
|
60
|
+
<TelaSidebarLogo src="/tela-logo-black.svg" alt="Tela Logo" />
|
|
61
|
+
</TelaSidebarHeader>
|
|
62
|
+
|
|
63
|
+
<TelaSidebarContent>
|
|
64
|
+
<TelaSidebarItem
|
|
65
|
+
v-for="item in items"
|
|
66
|
+
:key="item.label"
|
|
67
|
+
:icon="item.icon"
|
|
68
|
+
:label="item.label"
|
|
69
|
+
:to="item.path"
|
|
70
|
+
:is-active="isItemActive(item)"
|
|
71
|
+
/>
|
|
72
|
+
<TelaSidebarItem
|
|
73
|
+
icon="i-ph-bell"
|
|
74
|
+
label="Activity"
|
|
75
|
+
:is-active="false"
|
|
76
|
+
/>
|
|
77
|
+
</TelaSidebarContent>
|
|
78
|
+
|
|
79
|
+
<TelaSidebarFooter>
|
|
80
|
+
<TelaSidebarUser
|
|
81
|
+
name="Username"
|
|
82
|
+
email="user@example.com"
|
|
83
|
+
:actions="userActions"
|
|
84
|
+
/>
|
|
85
|
+
</TelaSidebarFooter>
|
|
86
|
+
</TelaSidebar>
|
|
87
|
+
`,
|
|
88
|
+
}),
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const NoActiveItem: Story = {
|
|
92
|
+
render: () => ({
|
|
93
|
+
components,
|
|
94
|
+
setup() {
|
|
95
|
+
return { items: navItems, userActions }
|
|
96
|
+
},
|
|
97
|
+
template: `
|
|
98
|
+
<TelaSidebar>
|
|
99
|
+
<TelaSidebarHeader>
|
|
100
|
+
<TelaSidebarLogo src="/tela-logo-black.svg" alt="Tela Logo" />
|
|
101
|
+
</TelaSidebarHeader>
|
|
102
|
+
|
|
103
|
+
<TelaSidebarContent>
|
|
104
|
+
<TelaSidebarItem
|
|
105
|
+
v-for="item in items"
|
|
106
|
+
:key="item.label"
|
|
107
|
+
:icon="item.icon"
|
|
108
|
+
:label="item.label"
|
|
109
|
+
:to="item.path"
|
|
110
|
+
:is-active="false"
|
|
111
|
+
/>
|
|
112
|
+
<TelaSidebarItem
|
|
113
|
+
icon="i-ph-bell"
|
|
114
|
+
label="Activity"
|
|
115
|
+
:is-active="false"
|
|
116
|
+
/>
|
|
117
|
+
</TelaSidebarContent>
|
|
118
|
+
|
|
119
|
+
<TelaSidebarFooter>
|
|
120
|
+
<TelaSidebarUser
|
|
121
|
+
name="Username"
|
|
122
|
+
email="user@example.com"
|
|
123
|
+
:actions="userActions"
|
|
124
|
+
/>
|
|
125
|
+
</TelaSidebarFooter>
|
|
126
|
+
</TelaSidebar>
|
|
127
|
+
`,
|
|
128
|
+
}),
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export const ActivityActive: Story = {
|
|
132
|
+
render: () => ({
|
|
133
|
+
components,
|
|
134
|
+
setup() {
|
|
135
|
+
return { items: navItems, userActions }
|
|
136
|
+
},
|
|
137
|
+
template: `
|
|
138
|
+
<TelaSidebar>
|
|
139
|
+
<TelaSidebarHeader>
|
|
140
|
+
<TelaSidebarLogo src="/tela-logo-black.svg" alt="Tela Logo" />
|
|
141
|
+
</TelaSidebarHeader>
|
|
142
|
+
|
|
143
|
+
<TelaSidebarContent>
|
|
144
|
+
<TelaSidebarItem
|
|
145
|
+
v-for="item in items"
|
|
146
|
+
:key="item.label"
|
|
147
|
+
:icon="item.icon"
|
|
148
|
+
:label="item.label"
|
|
149
|
+
:to="item.path"
|
|
150
|
+
:is-active="false"
|
|
151
|
+
/>
|
|
152
|
+
<TelaSidebarItem
|
|
153
|
+
icon="i-ph-bell"
|
|
154
|
+
label="Activity"
|
|
155
|
+
:is-active="true"
|
|
156
|
+
/>
|
|
157
|
+
</TelaSidebarContent>
|
|
158
|
+
|
|
159
|
+
<TelaSidebarFooter>
|
|
160
|
+
<TelaSidebarUser
|
|
161
|
+
name="Username"
|
|
162
|
+
email="user@example.com"
|
|
163
|
+
:actions="userActions"
|
|
164
|
+
/>
|
|
165
|
+
</TelaSidebarFooter>
|
|
166
|
+
</TelaSidebar>
|
|
167
|
+
`,
|
|
168
|
+
}),
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export const SingleItem: Story = {
|
|
172
|
+
parameters: {
|
|
173
|
+
layout: 'centered',
|
|
174
|
+
docs: {
|
|
175
|
+
description: {
|
|
176
|
+
story: 'Individual `TelaSidebarItem` in active and inactive states.',
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
render: () => ({
|
|
181
|
+
components: { TelaSidebarItem },
|
|
182
|
+
template: `
|
|
183
|
+
<div style="display: flex; gap: 24px; align-items: center;">
|
|
184
|
+
<TelaSidebarItem icon="i-ph-house" label="Home" :is-active="false" />
|
|
185
|
+
<TelaSidebarItem icon="i-ph-graph" label="Workflows" :is-active="true" />
|
|
186
|
+
<TelaSidebarItem icon="i-ph-bell" label="Activity" :is-active="false" />
|
|
187
|
+
</div>
|
|
188
|
+
`,
|
|
189
|
+
}),
|
|
190
|
+
}
|
|
@@ -32,7 +32,7 @@ watch(() => props.modelValue, (newValue, oldValue) => {
|
|
|
32
32
|
:icon="modelValue ? 'i-ph-star-fill' : 'i-ph-star-light'"
|
|
33
33
|
color="secondary"
|
|
34
34
|
:size="size"
|
|
35
|
-
:class="cn(props.class)"
|
|
35
|
+
:class="cn(modelValue && 'hover:bg-amber-100!', props.class)"
|
|
36
36
|
:icon-class="cn('relative z-1', modelValue ? 'text-amber-400!' : 'text-neutral-400!')"
|
|
37
37
|
@click.stop.prevent="emit('update:modelValue', !modelValue)"
|
|
38
38
|
/>
|
|
@@ -23,6 +23,16 @@ const initialValue = ref(props.modelValue)
|
|
|
23
23
|
|
|
24
24
|
const isEditing = ref(!props.displayIcon)
|
|
25
25
|
|
|
26
|
+
const autowidthOptions = computed(() => ({
|
|
27
|
+
comfortZone: props.displayIcon ? '2px' : '0px',
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
function recalcAutowidth() {
|
|
31
|
+
nextTick(() => {
|
|
32
|
+
inputRef.value?.dispatchEvent(new Event('input', { bubbles: true }))
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
function onFocus() {
|
|
27
37
|
if (props.modelValue === props.defaultValue) {
|
|
28
38
|
emit('update:modelValue', '')
|
|
@@ -83,6 +93,9 @@ function handleClickOutside(event: Event) {
|
|
|
83
93
|
|
|
84
94
|
onMounted(() => {
|
|
85
95
|
document.addEventListener('mousedown', handleClickOutside)
|
|
96
|
+
void document.fonts?.ready?.then(() => {
|
|
97
|
+
recalcAutowidth()
|
|
98
|
+
})
|
|
86
99
|
})
|
|
87
100
|
|
|
88
101
|
onUnmounted(() => {
|
|
@@ -93,26 +106,24 @@ onUnmounted(() => {
|
|
|
93
106
|
<template>
|
|
94
107
|
<div
|
|
95
108
|
ref="containerRef"
|
|
96
|
-
flex relative
|
|
109
|
+
flex relative min-w-0
|
|
97
110
|
:class="cn('group', containerClass, { 'font-580 text-18px': !disableDefaultStyling })"
|
|
98
111
|
outline-none
|
|
99
112
|
text="textcolor"
|
|
100
113
|
items-center
|
|
101
|
-
cursor-text
|
|
102
114
|
@click="onStartEditing"
|
|
103
115
|
>
|
|
104
116
|
<input
|
|
105
117
|
ref="inputRef"
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
118
|
+
v-autowidth="autowidthOptions"
|
|
119
|
+
w-fit
|
|
120
|
+
shrink-0
|
|
121
|
+
max-w-full
|
|
110
122
|
p-0
|
|
111
123
|
transition
|
|
112
124
|
:value="modelValue"
|
|
113
125
|
:placeholder="placeholder"
|
|
114
126
|
:disabled="disabled || (displayIcon && !isEditing)"
|
|
115
|
-
truncate
|
|
116
127
|
bg-transparent
|
|
117
128
|
:class="[
|
|
118
129
|
{ 'font-580 text-18px': !disableDefaultStyling },
|
package/package.json
CHANGED
package/types/custom-icon.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export type TelaCustomIconName = 'circle' | 'pause' | 'queued' | 'close' | 'circle-notch' | 'circle-minus' | 'stop' | 'circle-dashed' | 'warning' | 'check' | 'check-dashed' | 'semi-circle' | 'arrow-down' | 'reported' | 'circle-warning' | 'slice' | 'half-slice' | 'knot' | 'timeline-dot' | 'timeline-start'
|
|
1
|
+
export type TelaCustomIconName = 'circle' | 'pause' | 'queued' | 'close' | 'circle-notch' | 'circle-minus' | 'stop' | 'circle-dashed' | 'warning' | 'check' | 'check-dashed' | 'semi-circle' | 'arrow-down' | 'reported' | 'circle-warning' | 'slice' | 'half-slice' | 'knot' | 'timeline-dot' | 'timeline-start' | 'level-right'
|
package/unocss.config.ts
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import transformerDirectives from '@unocss/transformer-directives'
|
|
2
2
|
import { defineConfig, toEscapedSelector } from 'unocss'
|
|
3
|
+
import type { Extractor } from 'unocss'
|
|
3
4
|
import { DT } from './utils/design-tokens'
|
|
4
5
|
|
|
6
|
+
/* Enables fill variants for Phosphor icons used as a dynamic suffix (e.g. `${icon}-fill`)
|
|
7
|
+
* in components, which UnoCSS can't detect through static scanning */
|
|
8
|
+
const phosphorFillExtractor: Extractor = {
|
|
9
|
+
name: 'phosphor-fill-icons',
|
|
10
|
+
extract: ({ code }) => new Set(
|
|
11
|
+
[...code.matchAll(/['"`](i-ph-[\w-]+)['"`]/g)].map(([, icon]) => `${icon}-fill`),
|
|
12
|
+
),
|
|
13
|
+
}
|
|
14
|
+
|
|
5
15
|
export default defineConfig({
|
|
16
|
+
extractors: [phosphorFillExtractor],
|
|
6
17
|
theme: {
|
|
7
18
|
...DT,
|
|
8
19
|
animation: {
|
package/utils/design-tokens.ts
CHANGED
|
@@ -304,11 +304,11 @@ export const DT = {
|
|
|
304
304
|
warning: BASE_COLORS.amber[200],
|
|
305
305
|
},
|
|
306
306
|
text: {
|
|
307
|
-
primary: BASE_COLORS.
|
|
308
|
-
secondary: BASE_COLORS.
|
|
309
|
-
tertiary: BASE_COLORS.
|
|
310
|
-
subtle: BASE_COLORS.
|
|
311
|
-
contrast: BASE_COLORS.
|
|
307
|
+
primary: BASE_COLORS.neutral[800],
|
|
308
|
+
secondary: BASE_COLORS.neutral[500],
|
|
309
|
+
tertiary: BASE_COLORS.neutral[400],
|
|
310
|
+
subtle: BASE_COLORS.neutral[300],
|
|
311
|
+
contrast: BASE_COLORS.neutral[900],
|
|
312
312
|
success: BASE_COLORS.green[600],
|
|
313
313
|
error: BASE_COLORS.red[600],
|
|
314
314
|
warning: BASE_COLORS.amber[600],
|