Skip to content

Commit 46ff1b8

Browse files
committed
fix(buttons): fix line styling & rework action binding
1 parent 98ce6dc commit 46ff1b8

File tree

8 files changed

+183
-88
lines changed

8 files changed

+183
-88
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ Neomorphic ui library for svelte 5
33

44
## TODO
55
- [x] Buttons
6-
- @media any-pointer:coarse any-hover:none
7-
- shallow
6+
- [ ] @media any-pointer:coarse any-hover:none
87
- [x] Tabs
9-
- @media any-pointer:coarse any-hover:none
8+
- [ ] @media any-pointer:coarse any-hover:none
9+
- [ ] tabs panels
1010
- paper ?
1111
- card
1212
- border loading

src/lib/buttons/NeoButton.svelte

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import type { NeoButtonProps } from '~/buttons/neo-button.model.js';
55
66
import IconCircleLoading from '~/icons/IconCircleLoading.svelte';
7-
import { emptyFn } from '~/utils/action.utils.js';
7+
import { toAction, toActionProps, toTransition, toTransitionProps } from '~/utils/action.utils.js';
88
99
/* eslint-disable prefer-const -- necessary for binding checked */
1010
let {
@@ -42,15 +42,12 @@
4242
onkeyup,
4343
4444
// Transition
45-
in: inFn,
46-
inProps,
47-
out: outFn,
48-
outProps,
49-
transition: transitionFn,
50-
transitionProps,
45+
in: inAction,
46+
out: outAction,
47+
transition: transitionAction,
5148
49+
// Actions
5250
use,
53-
useProps,
5451
5552
// Other props
5653
...rest
@@ -99,9 +96,13 @@
9996
onkeyup?.(e);
10097
};
10198
102-
const _inFn = $derived(inFn ?? transitionFn ?? emptyFn);
103-
const _outFn = $derived(outFn ?? transitionFn ?? emptyFn);
104-
const _useFn = $derived(use ?? (() => {}));
99+
const inFn = $derived(toTransition(inAction ?? transitionAction));
100+
const inProps = $derived(toTransitionProps(inAction ?? transitionAction));
101+
const outFn = $derived(toTransition(outAction ?? transitionAction));
102+
const outProps = $derived(toTransitionProps(outAction ?? transitionAction));
103+
104+
const useFn = $derived(toAction(use));
105+
const useProps = $derived(toActionProps(use));
105106
</script>
106107

107108
<svelte:element
@@ -123,9 +124,9 @@
123124
class:rounded
124125
class:rotate
125126
class:empty
126-
use:_useFn={useProps}
127-
out:_outFn={outProps ?? transitionProps}
128-
in:_inFn={inProps ?? transitionProps}
127+
use:useFn={useProps}
128+
out:outFn={outProps}
129+
in:inFn={inProps}
129130
onkeydown={onKeydownEnter}
130131
onkeyup={onKeyUpEnter}
131132
onclick={onClick}

src/lib/buttons/NeoButtonGroup.svelte

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script lang="ts">
22
import type { NeoButtonGroup } from '~/buttons/neo-button-group.model.js';
33
4-
import { emptyFn } from '~/utils/action.utils.js';
4+
import { toAction, toActionProps, toTransition, toTransitionProps } from '~/utils/action.utils.js';
55
66
const {
77
// Snippets
@@ -23,19 +23,24 @@
2323
vertical,
2424
2525
// Transition
26-
in: inFn,
27-
inProps,
28-
out: outFn,
29-
outProps,
30-
transition: transitionFn,
31-
transitionProps,
26+
in: inAction,
27+
out: outAction,
28+
transition: transitionAction,
29+
30+
// Actions
31+
use,
3232
3333
// Other props
3434
...rest
3535
}: NeoButtonGroup = $props();
3636
37-
const _inFn = $derived(inFn ?? transitionFn ?? emptyFn);
38-
const _outFn = $derived(outFn ?? transitionFn ?? emptyFn);
37+
const inFn = $derived(toTransition(inAction ?? transitionAction));
38+
const inProps = $derived(toTransitionProps(inAction ?? transitionAction));
39+
const outFn = $derived(toTransition(outAction ?? transitionAction));
40+
const outProps = $derived(toTransitionProps(outAction ?? transitionAction));
41+
42+
const useFn = $derived(toAction(use));
43+
const useProps = $derived(toActionProps(use));
3944
</script>
4045

4146
<div
@@ -51,8 +56,9 @@
5156
class:coalesce
5257
class:skeleton
5358
class:vertical
54-
out:_outFn={outProps ?? transitionProps}
55-
in:_inFn={inProps ?? transitionProps}
59+
use:useFn={useProps}
60+
out:outFn={outProps}
61+
in:inFn={inProps}
5662
{...rest}
5763
>
5864
{@render children?.({

src/lib/nav/NeoTab.svelte

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import NeoButton from '~/buttons/NeoButton.svelte';
99
import IconClose from '~/icons/IconClose.svelte';
1010
import { getTabContext } from '~/nav/neo-tabs-context.svelte.js';
11+
import { toAction, toActionProps } from '~/utils/action.utils.js';
1112
import { defaultTransitionDuration, enterTransition } from '~/utils/transition.utils.js';
1213
1314
const {
@@ -55,21 +56,23 @@
5556
onclose?.(tabId, value, ref);
5657
context?.onClose(tabId, value, ref);
5758
};
59+
60+
const useFn = $derived(toAction(tabProps?.use));
61+
const useProps = $derived(toActionProps(tabProps?.use));
5862
</script>
5963

60-
<div bind:this={ref} class="neo-tab" class:active class:slide transition:transition={enterTransition} {...tabProps}>
61-
<NeoButton
62-
role="tab"
63-
data-tab-id={tabId}
64-
data-active={active}
65-
toggle
66-
readonly
67-
checked={active}
68-
onclick={onClick}
69-
empty={!children}
70-
{...rest}
71-
{disabled}
72-
>
64+
<div
65+
bind:this={ref}
66+
class="neo-tab"
67+
data-tab-id={tabId}
68+
data-active={active}
69+
class:active
70+
class:slide
71+
transition:transition={enterTransition}
72+
{...tabProps}
73+
use:useFn={useProps}
74+
>
75+
<NeoButton role="tab" toggle readonly checked={active} onclick={onClick} empty={!children} {...rest} {disabled}>
7376
{@render children?.({ active, tabId, value })}
7477
{#if closeable}
7578
<button
@@ -93,6 +96,7 @@
9396
:global(.neo-button:hover) {
9497
:global(.icon-close) {
9598
opacity: 1;
99+
pointer-events: auto;
96100
}
97101
}
98102
@@ -129,6 +133,7 @@
129133
opacity 0.2s ease-in,
130134
color 0.5s ease,
131135
background-color 0.5s ease;
136+
pointer-events: none;
132137
}
133138
134139
&:focus-visible :global(.icon-close) {

src/lib/nav/NeoTabs.svelte

Lines changed: 70 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
44
import { untrack } from 'svelte';
55
6-
import type { OnChange, TabsProps } from '~/nav/neo-tabs.model.js';
6+
import type { NeoTabsContext, OnChange, TabsProps } from '~/nav/neo-tabs.model.js';
77
88
import NeoButton from '~/buttons/NeoButton.svelte';
99
import NeoButtonGroup from '~/buttons/NeoButtonGroup.svelte';
1010
import IconAdd from '~/icons/IconAdd.svelte';
1111
import { type NeoTabContextPositions, setTabContext } from '~/nav/neo-tabs-context.svelte.js';
12+
import { toAction, toActionProps } from '~/utils/action.utils.js';
1213
1314
/* eslint-disable prefer-const -- necessary for binding checked */
1415
let {
@@ -66,18 +67,20 @@
6667
6768
const style = $derived([tabsProps?.style, position].filter(Boolean).join('; '));
6869
69-
$inspect(position).with((...args) => console.info('position', ...args));
70-
7170
// reflect component active to context
7271
$effect(() => {
73-
console.info('active', active, context.active);
7472
if (active === context.active) return;
7573
untrack(() => context.onChange(active));
7674
});
7775
7876
$effect(() => {
79-
context.onOption({ slide, line, closeable: close, disabled, vertical: rest.vertical });
77+
context.onOption({ line, slide, closeable: close, disabled, vertical: rest.vertical });
8078
});
79+
80+
const childContext: NeoTabsContext = $derived({ active, disabled, slide, close, add, vertical: rest.vertical });
81+
82+
const useFn = $derived(toAction(tabsProps?.use));
83+
const useProps = $derived(toActionProps(tabsProps?.use));
8184
</script>
8285

8386
{#snippet icon()}
@@ -94,11 +97,13 @@
9497
class:text={rest.text}
9598
class:vertical={rest.vertical}
9699
class:rounded={rest.rounded}
100+
class:shallow={rest.shallow}
97101
{...tabsProps}
102+
use:useFn={useProps}
98103
{style}
99104
>
100105
<NeoButtonGroup {...rest}>
101-
{@render children?.({ active, disabled, slide, close, add, vertical: rest.vertical })}
106+
{@render children?.(childContext)}
102107
{#if add}
103108
<div transition:transition={{ duration: 200, css: `overflow: hidden; white-space: nowrap` }}>
104109
<NeoButton onclick={onadd} {icon} />
@@ -136,9 +141,27 @@
136141
padding-bottom: 0.5rem;
137142
}
138143
139-
&.line :global(.neo-tab.active::before) {
140-
--neo-tab-full-width: 2px;
141-
--neo-tab-old-width: 2px;
144+
&.line {
145+
:global(.neo-tab::before) {
146+
--neo-tab-width: 2px;
147+
--neo-tab-old-width: 2px;
148+
--neo-tab-old-max-height: calc(var(--neo-tab-old-height, 100%) - 1rem);
149+
--neo-tab-max-height: calc(var(--neo-tab-height, 100%) - 1rem);
150+
151+
top: 0;
152+
width: 2px;
153+
height: 0;
154+
max-height: var(--neo-tab-max-height);
155+
margin-left: 0.3rem;
156+
transition:
157+
box-shadow 0.3s ease,
158+
height 0.3s var(--transition-bezier);
159+
margin-block: 0.5rem;
160+
}
161+
162+
:global(.neo-tab.active::before) {
163+
height: var(--neo-tab-height, 100%);
164+
}
142165
}
143166
}
144167
@@ -147,11 +170,12 @@
147170
}
148171
149172
&.slide {
150-
--neo-tab-full-width: 100%;
151-
--neo-tab-full-height: 100%;
173+
--neo-tab-width: 100%;
174+
--neo-tab-height: 100%;
152175
153176
:global(.neo-tab .neo-button) {
154-
box-shadow: var(--box-shadow-flat);
177+
color: var(--neo-btn-text-color, inherit);
178+
box-shadow: var(--box-shadow-flat) !important;
155179
transition: none;
156180
}
157181
@@ -165,8 +189,8 @@
165189
top: calc(0 - var(--border-width, 1px));
166190
left: calc(0 - var(--border-width, 1px));
167191
z-index: var(--z-index-in-front, 1);
168-
width: var(--neo-tab-full-width, 100%);
169-
height: var(--neo-tab-full-height, 100%);
192+
width: var(--neo-tab-width, 100%);
193+
height: var(--neo-tab-height, 100%);
170194
border: var(--border-width, 1px) var(--neo-tab-border-color, transparent) solid;
171195
border-radius: var(--neo-tab-border-radius, var(--border-radius));
172196
box-shadow: var(--box-shadow-flat);
@@ -178,17 +202,30 @@
178202
179203
&.line :global(.neo-tab.active::before) {
180204
bottom: 0;
181-
height: 2px;
182205
background-color: var(--color-primary, var(--text-color));
183206
box-shadow: var(--box-shadow-flat);
184-
transition:
185-
box-shadow 0.3s ease,
186-
width 0.3s var(--transition-bezier);
187207
}
188208
189-
&.line:not(.vertical) :global(.neo-tab.active::before) {
190-
--neo-tab-full-height: 2px;
191-
--neo-tab-old-height: 2px;
209+
&.line:not(.vertical) {
210+
:global(.neo-tab::before) {
211+
--neo-tab-height: 2px;
212+
--neo-tab-old-height: 2px;
213+
--neo-tab-old-max-width: calc(var(--neo-tab-old-width, 100%) - 1.5rem);
214+
--neo-tab-max-width: calc(var(--neo-tab-width, 100%) - 1.5rem);
215+
216+
width: 0;
217+
max-width: var(--neo-tab-max-width);
218+
height: 2px;
219+
margin-bottom: 0.125rem;
220+
transition:
221+
box-shadow 0.3s ease,
222+
width 0.3s var(--transition-bezier);
223+
margin-inline: 0.75rem;
224+
}
225+
226+
:global(.neo-tab.active::before) {
227+
width: var(--neo-tab-width, 100%);
228+
}
192229
}
193230
194231
:global(.neo-tab.active::before) {
@@ -201,15 +238,19 @@
201238
202239
@keyframes slide {
203240
0% {
204-
width: var(--neo-tab-old-width, var(--neo-tab-full-width, 100%));
205-
height: var(--neo-tab-old-height, var(--neo-tab-full-height, 100%));
241+
width: var(--neo-tab-old-width, var(--neo-tab-width, 100%));
242+
max-width: var(--neo-tab-old-max-width);
243+
height: var(--neo-tab-old-height, var(--neo-tab-height, 100%));
244+
max-height: var(--neo-tab-old-max-height);
206245
box-shadow: var(--box-shadow-inset-2);
207246
transform: var(--transform);
208247
}
209248
210249
100% {
211-
width: var(--neo-tab-full-width, 100%);
212-
height: var(--neo-tab-full-height, 100%);
250+
width: var(--neo-tab-width, 100%);
251+
max-width: var(--neo-tab-max-width);
252+
height: var(--neo-tab-height, 100%);
253+
max-height: var(--neo-tab-max-height);
213254
box-shadow: var(--box-shadow-inset-2);
214255
transform: translate(0, 0);
215256
}
@@ -225,6 +266,10 @@
225266
}
226267
}
227268
269+
&.shallow {
270+
--box-shadow-inset-2: var(--box-shadow-inset-1);
271+
}
272+
228273
&.rounded :global(.neo-button-group .neo-tab::before) {
229274
border-radius: var(--neo-tab-border-radius, var(--border-radius-lg));
230275
}

src/lib/nav/neo-tab.model.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Snippet } from 'svelte';
22
import type { HTMLAttributes } from 'svelte/elements';
33
import type { NeoButtonProps } from '~/buttons/neo-button.model.js';
44
import type { OnChange } from '~/nav/neo-tabs.model.js';
5+
import type { HTMLUseProps } from '~/utils/action.utils.js';
56

67
export type TabId = string | number | symbol;
78
export type NeoTabProps<T = unknown> = {
@@ -43,5 +44,5 @@ export type NeoTabProps<T = unknown> = {
4344
/**
4445
* Optional props to pass to the tab container.
4546
*/
46-
tabProps?: Partial<HTMLAttributes<HTMLDivElement>>;
47+
tabProps?: Partial<HTMLAttributes<HTMLDivElement>> & HTMLUseProps;
4748
} & Omit<NeoButtonProps, 'value' | 'children'>;

0 commit comments

Comments
 (0)