@insymetri/styleguide 0.1.27 → 0.1.28

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.
@@ -39,7 +39,7 @@
39
39
  const isDisabled = $derived(disabled || loading)
40
40
 
41
41
  const baseClasses =
42
- 'inline-flex items-center justify-center gap-8 whitespace-nowrap cursor-default transition-colors duration-base ease-in-out no-underline disabled:cursor-not-allowed'
42
+ 'inline-flex items-center justify-center gap-8 whitespace-nowrap cursor-default transition-colors duration-base ease-in-out no-underline outline-none focus-visible:border-accent focus-visible:ring-3 focus-visible:ring-primary disabled:cursor-not-allowed'
43
43
 
44
44
  const variantClasses = {
45
45
  primary:
@@ -47,9 +47,9 @@
47
47
  {/if}
48
48
  <DatePicker.Input
49
49
  class={cn(
50
- 'flex items-center gap-4 px-12 border bg-input-bg border-input-border transition-all duration-fast hover:border-input-border-hover [&:has(:focus)]:border-input-border-hover motion-reduce:transition-none',
50
+ 'flex items-center gap-4 px-12 border bg-input-bg border-input-border transition-all duration-fast hover:border-input-border-hover [&:has(:focus)]:border-accent [&:has(:focus)]:ring-3 [&:has(:focus)]:ring-primary motion-reduce:transition-none',
51
51
  densityClasses[density.value],
52
- showError && 'border-input-border-error [&:has(:focus)]:border-input-border-error',
52
+ showError && 'border-input-border-error [&:has(:focus)]:border-input-border-error [&:has(:focus)]:ring-error',
53
53
  disabled && 'bg-input-bg-disabled cursor-not-allowed'
54
54
  )}
55
55
  >
@@ -30,7 +30,7 @@
30
30
  const isDisabled = $derived(disabled || loading)
31
31
 
32
32
  const baseClasses =
33
- 'inline-flex items-center justify-center cursor-default transition-colors duration-base ease-in-out disabled:cursor-not-allowed'
33
+ 'inline-flex items-center justify-center cursor-default transition-colors duration-base ease-in-out outline-none focus-visible:ring-3 focus-visible:ring-primary disabled:cursor-not-allowed'
34
34
 
35
35
  const variantClasses = {
36
36
  primary:
@@ -71,7 +71,7 @@
71
71
  {#if hasAddons}
72
72
  <div
73
73
  class={cn(
74
- 'flex items-center bg-input-bg border border-input-border transition-all duration-fast hover:border-input-border-hover focus-within:border-input-border-hover motion-reduce:transition-none',
74
+ 'flex items-center bg-input-bg border border-button-secondary transition-all duration-fast hover:border-button-secondary-hover focus-within:border-button-secondary-hover motion-reduce:transition-none',
75
75
  densityClasses[density.value],
76
76
  showError && 'border-input-border-error focus-within:border-input-border-error',
77
77
  shouldShake && 'animate-shake',
@@ -117,7 +117,7 @@
117
117
  {type}
118
118
  {disabled}
119
119
  class={cn(
120
- 'py-5 px-12 font-[family-name:var(--font-family)] text-input-text bg-input-bg border border-input-border transition-all duration-fast outline-none w-full box-border placeholder:text-input-placeholder hover:border-input-border-hover focus:border-input-border-hover disabled:bg-input-bg-disabled disabled:text-input-text-disabled disabled:cursor-not-allowed motion-reduce:transition-none motion-reduce:animate-none',
120
+ 'py-5 px-12 font-[family-name:var(--font-family)] text-input-text bg-input-bg border border-button-secondary transition-all duration-fast outline-none w-full box-border placeholder:text-input-placeholder hover:border-button-secondary-hover focus:border-accent focus:ring-3 focus:ring-primary disabled:bg-input-bg-disabled disabled:text-input-text-disabled disabled:cursor-not-allowed motion-reduce:transition-none motion-reduce:animate-none',
121
121
  densityClasses[density.value],
122
122
  showError && 'border-input-border-error focus:border-input-border-error focus:ring-error',
123
123
  shouldShake && 'animate-shake'
@@ -12,49 +12,103 @@
12
12
  items: Item[]
13
13
  value: string
14
14
  onValueChange?: (value: string) => void
15
- size?: 'sm' | 'md'
16
15
  class?: string
17
16
  }
18
17
 
19
- let {items, value = $bindable(), onValueChange, size = 'sm', class: className}: Props = $props()
18
+ let {items, value = $bindable(), onValueChange, class: className}: Props = $props()
20
19
 
21
20
  const density = useDensity()
22
21
 
23
- const densityRadius = {
24
- compact: 'rounded-control',
25
- default: 'rounded-control',
26
- comfortable: 'rounded-control',
27
- mobile: 'rounded-control',
22
+ const densityHeight = {
23
+ compact: 'h-28',
24
+ default: 'h-32',
25
+ comfortable: 'h-40',
26
+ mobile: 'h-48',
28
27
  } as const
29
28
 
30
- const sizeClasses = {
31
- sm: 'px-12 py-4 text-small',
32
- md: 'px-16 py-6 text-default',
29
+ const densityClasses = {
30
+ compact: 'px-8 text-tiny',
31
+ default: 'px-8 text-small',
32
+ comfortable: 'px-12 text-small',
33
+ mobile: 'px-8 text-small',
33
34
  } as const
34
35
 
36
+ const thumbInset = {
37
+ compact: 2,
38
+ default: 2,
39
+ comfortable: 3,
40
+ mobile: 3,
41
+ } as const
42
+
43
+ const activeIndex = $derived(items.findIndex((item) => item.value === value))
44
+
35
45
  function select(v: string) {
36
46
  value = v
37
47
  onValueChange?.(v)
38
48
  }
39
49
 
40
- function getActiveClass(item: Item): string {
41
- if (item.variant === 'danger') return 'bg-error text-inverse'
42
- return 'bg-primary text-inverse'
50
+ function cycle(direction: 1 | -1) {
51
+ const nextIndex = (activeIndex + direction + items.length) % items.length
52
+ select(items[nextIndex].value)
53
+ }
54
+
55
+ function handleKeydown(e: KeyboardEvent) {
56
+ if (e.key === ' ' || e.key === 'Enter') {
57
+ e.preventDefault()
58
+ cycle(1)
59
+ } else if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
60
+ e.preventDefault()
61
+ cycle(1)
62
+ } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
63
+ e.preventDefault()
64
+ cycle(-1)
65
+ }
66
+ }
67
+
68
+ function getThumbColor(item: Item | undefined): string {
69
+ if (item?.variant === 'danger') return 'var(--color-error, #ef4444)'
70
+ return 'var(--ii-primary-15, rgba(17, 115, 212, 0.15))'
43
71
  }
44
72
  </script>
45
73
 
46
- <div class={cn('inline-flex border border-primary overflow-hidden', densityRadius[density.value], className)} role="group">
74
+ <div
75
+ class={cn(
76
+ 'relative inline-flex bg-input-bg border border-button-secondary rounded-control outline-none focus-visible:ring-3 focus-visible:ring-primary cursor-default',
77
+ densityHeight[density.value],
78
+ className
79
+ )}
80
+ role="radiogroup"
81
+ tabindex="0"
82
+ onkeydown={handleKeydown}
83
+ style="padding: {thumbInset[density.value]}px;"
84
+ >
85
+ <!-- Sliding thumb -->
86
+ <div
87
+ class="absolute"
88
+ style:border-radius="calc(var(--ii-radius-control) - {thumbInset[density.value]}px)"
89
+ style="
90
+ top: {thumbInset[density.value]}px;
91
+ bottom: {thumbInset[density.value]}px;
92
+ left: calc({thumbInset[density.value]}px + {activeIndex} * (100% - {thumbInset[density.value] * 2}px) / {items.length});
93
+ width: calc((100% - {thumbInset[density.value] * 2}px) / {items.length});
94
+ background: {getThumbColor(items[activeIndex])};
95
+ transition: left 300ms cubic-bezier(0.4, 0, 0.2, 1);
96
+ "
97
+ ></div>
98
+
47
99
  {#each items as item (item.value)}
48
100
  {@const isActive = value === item.value}
49
101
  <button
50
102
  type="button"
103
+ tabindex="-1"
51
104
  class={cn(
52
- 'border-none font-medium cursor-default whitespace-nowrap text-center not-first:border-l not-first:border-primary',
53
- sizeClasses[size],
54
- isActive ? getActiveClass(item) : 'bg-surface text-secondary hover:bg-background'
105
+ 'relative z-10 flex-1 border-none font-medium cursor-default whitespace-nowrap text-center outline-none bg-transparent',
106
+ densityClasses[density.value],
107
+ isActive ? 'text-primary' : 'text-secondary hover:text-body'
55
108
  )}
56
- style="transition: all var(--transition-fast)"
57
- aria-pressed={isActive}
109
+ style="transition: color var(--transition-fast)"
110
+ role="radio"
111
+ aria-checked={isActive}
58
112
  onclick={() => select(item.value)}
59
113
  >
60
114
  {item.label}
@@ -7,7 +7,6 @@ type Props = {
7
7
  items: Item[];
8
8
  value: string;
9
9
  onValueChange?: (value: string) => void;
10
- size?: 'sm' | 'md';
11
10
  class?: string;
12
11
  };
13
12
  declare const IISegmentedControl: import("svelte").Component<Props, {}, "value">;
@@ -62,32 +62,16 @@
62
62
  />
63
63
  </section>
64
64
 
65
- <!-- Sizes -->
65
+ <!-- Density-responsive -->
66
66
  <section>
67
- <h2 class="text-default-emphasis text-primary mb-8">Sizes</h2>
68
- <div class="flex items-center gap-16">
69
- <div class="flex flex-col items-start gap-4">
70
- <span class="text-tiny text-secondary">sm (default)</span>
71
- <IISegmentedControl
72
- size="sm"
73
- items={[
74
- {label: 'On', value: 'on'},
75
- {label: 'Off', value: 'off'},
76
- ]}
77
- value="on"
78
- />
79
- </div>
80
- <div class="flex flex-col items-start gap-4">
81
- <span class="text-tiny text-secondary">md</span>
82
- <IISegmentedControl
83
- size="md"
84
- items={[
85
- {label: 'On', value: 'on'},
86
- {label: 'Off', value: 'off'},
87
- ]}
88
- value="on"
89
- />
90
- </div>
91
- </div>
67
+ <h2 class="text-default-emphasis text-primary mb-8">Density Responsive</h2>
68
+ <p class="text-small text-secondary mb-12">Sizing follows the density provider context.</p>
69
+ <IISegmentedControl
70
+ items={[
71
+ {label: 'On', value: 'on'},
72
+ {label: 'Off', value: 'off'},
73
+ ]}
74
+ value="on"
75
+ />
92
76
  </section>
93
77
  </div>
@@ -42,7 +42,7 @@
42
42
  </span>
43
43
  {/if}
44
44
  <Switch.Root
45
- class="group relative w-44 h-24 bg-muted rounded-full cursor-default transition-all duration-fast shrink-0 appearance-none border-none data-[state=checked]:bg-primary data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 motion-reduce:transition-none"
45
+ class="group relative w-44 h-24 bg-muted rounded-full cursor-default transition-all duration-fast shrink-0 appearance-none border-none outline-none focus-visible:ring-3 focus-visible:ring-primary data-[state=checked]:bg-primary data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 motion-reduce:transition-none"
46
46
  {checked}
47
47
  onCheckedChange={newChecked => {
48
48
  checked = newChecked
@@ -55,7 +55,7 @@
55
55
  {disabled}
56
56
  aria-label={ariaLabel}
57
57
  class={cn(
58
- 'inline-flex items-center justify-center cursor-default transition-colors duration-base ease-in-out text-secondary hover:bg-button-ghost-hover hover:text-button-ghost-hover data-[state=on]:bg-button-ghost-hover data-[state=on]:text-body disabled:opacity-50 disabled:cursor-not-allowed',
58
+ 'inline-flex items-center justify-center cursor-default transition-colors duration-base ease-in-out outline-none focus-visible:ring-3 focus-visible:ring-primary text-secondary hover:bg-button-ghost-hover hover:text-button-ghost-hover data-[state=on]:bg-button-ghost-hover data-[state=on]:text-body disabled:opacity-50 disabled:cursor-not-allowed',
59
59
  resolvedSizeClasses,
60
60
  iconSizeClasses[size],
61
61
  className
@@ -4,16 +4,27 @@
4
4
  import StepIndicator from './StepIndicator.svelte'
5
5
 
6
6
  let step: 'email' | 'otp' = $state('email')
7
+ let transitioning = $state(false)
8
+
9
+ const FADE_DURATION = 200
10
+
11
+ function transitionTo(newStep: 'email' | 'otp') {
12
+ transitioning = true
13
+ setTimeout(() => {
14
+ step = newStep
15
+ transitioning = false
16
+ }, FADE_DURATION)
17
+ }
7
18
  </script>
8
19
 
9
20
  {#if step === 'email'}
10
- <LoginScreenBare onContinue={() => (step = 'otp')} hideStepIndicator>
21
+ <LoginScreenBare onContinue={() => transitionTo('otp')} hideStepIndicator {transitioning}>
11
22
  {#snippet stepIndicator()}
12
23
  <StepIndicator current={step === 'email' ? 0 : 1} total={2} />
13
24
  {/snippet}
14
25
  </LoginScreenBare>
15
26
  {:else}
16
- <OTPScreenBare onBack={() => (step = 'email')} hideStepIndicator>
27
+ <OTPScreenBare onBack={() => transitionTo('email')} hideStepIndicator {transitioning}>
17
28
  {#snippet stepIndicator()}
18
29
  <StepIndicator current={step === 'email' ? 0 : 1} total={2} />
19
30
  {/snippet}