@ibis-design/svelte 0.2.0 → 0.6.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.
Files changed (73) hide show
  1. package/README.md +12 -2
  2. package/dist/components/buttons/Button.svelte +106 -0
  3. package/dist/components/buttons/Button.svelte.d.ts +69 -0
  4. package/dist/components/buttons/DropdownButton.svelte +91 -0
  5. package/dist/components/buttons/DropdownButton.svelte.d.ts +33 -0
  6. package/dist/components/buttons/PillTab.svelte +84 -0
  7. package/dist/components/buttons/PillTab.svelte.d.ts +55 -0
  8. package/dist/components/buttons/button.css +193 -0
  9. package/dist/components/buttons/dropdownButton.css +82 -0
  10. package/dist/components/buttons/pillTab.css +59 -0
  11. package/dist/components/containers/Banner.svelte +73 -0
  12. package/dist/components/containers/Banner.svelte.d.ts +16 -0
  13. package/dist/components/containers/Card.svelte +34 -0
  14. package/dist/components/containers/Card.svelte.d.ts +14 -0
  15. package/dist/components/containers/PillTabs.svelte +22 -0
  16. package/dist/components/containers/PillTabs.svelte.d.ts +6 -0
  17. package/dist/components/containers/TipIndicator.svelte +78 -0
  18. package/dist/components/containers/TipIndicator.svelte.d.ts +28 -0
  19. package/dist/components/containers/Toaster.svelte +75 -0
  20. package/dist/components/containers/Toaster.svelte.d.ts +16 -0
  21. package/dist/components/containers/Tooltip.svelte.d.ts +26 -0
  22. package/dist/components/containers/banner.css +155 -0
  23. package/dist/components/containers/tipIndicator.css +79 -0
  24. package/dist/components/containers/toaster.css +137 -0
  25. package/dist/components/containers/tooltip.css +0 -0
  26. package/dist/components/inputs/.gitkeep +0 -0
  27. package/dist/components/inputs/Checkbox.svelte +95 -0
  28. package/dist/components/inputs/Checkbox.svelte.d.ts +33 -0
  29. package/dist/components/inputs/Chips.svelte +104 -0
  30. package/dist/components/inputs/Chips.svelte.d.ts +48 -0
  31. package/dist/components/inputs/Dropdown.svelte +83 -0
  32. package/dist/components/inputs/Dropdown.svelte.d.ts +15 -0
  33. package/dist/components/inputs/Radio.svelte +109 -0
  34. package/dist/components/inputs/Radio.svelte.d.ts +49 -0
  35. package/dist/components/inputs/Switch.svelte +45 -0
  36. package/dist/components/inputs/Switch.svelte.d.ts +21 -0
  37. package/dist/components/inputs/TextArea.svelte +65 -0
  38. package/dist/components/inputs/TextArea.svelte.d.ts +14 -0
  39. package/dist/components/inputs/TextInput.svelte +273 -0
  40. package/dist/components/inputs/TextInput.svelte.d.ts +140 -0
  41. package/dist/components/inputs/TextLink.svelte +102 -0
  42. package/dist/components/inputs/TextLink.svelte.d.ts +21 -0
  43. package/dist/components/inputs/checkbox.css +103 -0
  44. package/dist/components/inputs/chips.css +76 -0
  45. package/dist/components/inputs/dropdown.css +106 -0
  46. package/dist/components/inputs/radio.css +108 -0
  47. package/dist/components/inputs/switch.css +68 -0
  48. package/dist/components/inputs/textInput.css +152 -0
  49. package/dist/components/inputs/textarea.css +91 -0
  50. package/dist/components/inputs/textlink.css +163 -0
  51. package/dist/index.d.ts +21 -8
  52. package/dist/index.js +36 -2000
  53. package/dist/layouts/AppLayout.svelte +83 -0
  54. package/dist/layouts/AppLayout.svelte.d.ts +20 -0
  55. package/dist/layouts/AuthLayout.svelte +63 -0
  56. package/dist/layouts/AuthLayout.svelte.d.ts +18 -0
  57. package/dist/layouts/DashboardLayout.svelte +88 -0
  58. package/dist/layouts/DashboardLayout.svelte.d.ts +20 -0
  59. package/dist/types/button.js +5 -0
  60. package/dist/types/index.d.ts +2 -2
  61. package/dist/types/index.js +5 -0
  62. package/dist/types/layout.d.ts +1 -1
  63. package/dist/types/layout.js +1 -0
  64. package/package.json +49 -44
  65. package/dist/index.css +0 -1
  66. package/dist/index.js.map +0 -1
  67. package/dist/lib/components/buttons/Button.svelte.d.ts +0 -1
  68. package/dist/lib/components/buttons/DropdownButton.svelte.d.ts +0 -1
  69. package/dist/lib/components/containers/Card.svelte.d.ts +0 -1
  70. package/dist/lib/layouts/AppLayout.svelte.d.ts +0 -1
  71. package/dist/lib/layouts/AuthLayout.svelte.d.ts +0 -1
  72. package/dist/lib/layouts/DashboardLayout.svelte.d.ts +0 -1
  73. /package/dist/{types/input.d.ts → components/containers/Tooltip.svelte} +0 -0
package/README.md CHANGED
@@ -115,16 +115,26 @@ With inline snippets:
115
115
 
116
116
  ## Build
117
117
 
118
+ This package is built with **`@sveltejs/package`** (`svelte-package`), the supported Svelte library pipeline. It preprocesses Svelte, transpiles TypeScript, and emits **`.d.ts` next to outputs** so consumers get correct component prop types (not a separate Vite “library mode” + `vite-plugin-dts` flow).
119
+
118
120
  From the package directory:
119
121
 
120
122
  ```bash
121
123
  npm run build
122
124
  ```
123
125
 
126
+ This runs `svelte-kit sync` (generates `.svelte-kit` types for the minimal Kit shell) then `svelte-package`, writing output to `dist/`.
127
+
128
+ Watch mode during development:
129
+
130
+ ```bash
131
+ npm run dev
132
+ ```
133
+
124
134
  From the monorepo root:
125
135
 
126
136
  ```bash
127
- npm run build --workspaces
137
+ npm run build -w @ibis-design/svelte
128
138
  ```
129
139
 
130
140
  ## Type checking
@@ -133,4 +143,4 @@ npm run build --workspaces
133
143
  npm run check
134
144
  ```
135
145
 
136
- Runs `svelte-check` with the package `tsconfig.json`.
146
+ Runs `svelte-kit sync` then `svelte-check` with the package `tsconfig.json`.
@@ -0,0 +1,106 @@
1
+ <script lang="ts">
2
+ import './button.css';
3
+ import type { Snippet } from 'svelte';
4
+ import type { ButtonVariant, ButtonSize } from '../../types/button';
5
+ import type { HTMLButtonAttributes } from 'svelte/elements';
6
+
7
+ interface Props extends HTMLButtonAttributes {
8
+ /**
9
+ * Native button variants.
10
+ *
11
+ * Determines the visual style of the button, affecting its colors and borders.
12
+ *
13
+ * @default "primary"
14
+ * @example
15
+ * - `primary`: <Button variant="primary">Save</Button>
16
+ * - `secondary`: <Button variant="secondary">Cancel</Button>
17
+ */
18
+ variant?: ButtonVariant;
19
+ /**
20
+ * Native button sizes.
21
+ *
22
+ * Determines the size of the button, affecting its padding and font size.
23
+ *
24
+ * @default "md"
25
+ * @example
26
+ * - `sm`: <Button size="sm">Small</Button>
27
+ * - `md`: <Button size="md">Medium</Button>
28
+ * - `lg`: <Button size="lg">Large</Button>
29
+ */
30
+ size?: ButtonSize;
31
+ /**
32
+ * Native button behavior types.
33
+ *
34
+ * Determines how the button behaves when clicked.
35
+ *
36
+ * @default "button"
37
+ * @remarks
38
+ * Use `submit` for form submission and `reset` to clear form fields.
39
+ * Avoid using `submit` for non-form actions
40
+ */
41
+ type?: 'button' | 'submit' | 'reset';
42
+ /**
43
+ * Disables the button, preventing user interaction and applying disabled styles.
44
+ *
45
+ * @default false
46
+ */
47
+ disabled?: boolean;
48
+ /**
49
+ * Shows a loading spinner and disables the button to indicate an ongoing action.
50
+ *
51
+ * @default false
52
+ */
53
+ loading?: boolean;
54
+ /**
55
+ * Applies a skeleton loading state to the button, showing a placeholder style while content is loading.
56
+ *
57
+ * @default false`
58
+ */
59
+ skeleton?: boolean;
60
+ /**
61
+ * If true, the button is styled for icon-only content, removing extra padding and centering the icon.
62
+ *
63
+ * @default false
64
+ */
65
+ iconOnly?: boolean;
66
+ label?: string;
67
+ children?: Snippet;
68
+ }
69
+
70
+ let {
71
+ variant = 'primary',
72
+ size = 'md',
73
+ type = 'button',
74
+ disabled = false,
75
+ loading = false,
76
+ skeleton = false,
77
+ iconOnly = false,
78
+ label,
79
+ children,
80
+ ...rest
81
+ }: Props = $props();
82
+ </script>
83
+
84
+ <button
85
+ {...rest}
86
+ {type}
87
+ {disabled}
88
+ aria-busy={loading}
89
+ class="ibis-button
90
+ ibis-button--{variant}
91
+ ibis-button--{size}
92
+ {loading ? 'ibis-button--loading' : ''}
93
+ {skeleton ? 'ibis-button--skeleton' : ''}
94
+ {iconOnly ? 'ibis-button--icon-only' : ''}"
95
+ >
96
+ <span class="ibis-button__content {loading || skeleton ? 'ibis-button__content--hidden' : ''}">
97
+ {#if children}
98
+ {@render children?.()}
99
+ {:else if label}
100
+ {label}
101
+ {/if}
102
+ </span>
103
+ {#if loading}
104
+ <span class="ibis-button__loader" aria-hidden="true"></span>
105
+ {/if}
106
+ </button>
@@ -0,0 +1,69 @@
1
+ import './button.css';
2
+ import type { Snippet } from 'svelte';
3
+ import type { ButtonVariant, ButtonSize } from '../../types/button';
4
+ import type { HTMLButtonAttributes } from 'svelte/elements';
5
+ interface Props extends HTMLButtonAttributes {
6
+ /**
7
+ * Native button variants.
8
+ *
9
+ * Determines the visual style of the button, affecting its colors and borders.
10
+ *
11
+ * @default "primary"
12
+ * @example
13
+ * - `primary`: <Button variant="primary">Save</Button>
14
+ * - `secondary`: <Button variant="secondary">Cancel</Button>
15
+ */
16
+ variant?: ButtonVariant;
17
+ /**
18
+ * Native button sizes.
19
+ *
20
+ * Determines the size of the button, affecting its padding and font size.
21
+ *
22
+ * @default "md"
23
+ * @example
24
+ * - `sm`: <Button size="sm">Small</Button>
25
+ * - `md`: <Button size="md">Medium</Button>
26
+ * - `lg`: <Button size="lg">Large</Button>
27
+ */
28
+ size?: ButtonSize;
29
+ /**
30
+ * Native button behavior types.
31
+ *
32
+ * Determines how the button behaves when clicked.
33
+ *
34
+ * @default "button"
35
+ * @remarks
36
+ * Use `submit` for form submission and `reset` to clear form fields.
37
+ * Avoid using `submit` for non-form actions
38
+ */
39
+ type?: 'button' | 'submit' | 'reset';
40
+ /**
41
+ * Disables the button, preventing user interaction and applying disabled styles.
42
+ *
43
+ * @default false
44
+ */
45
+ disabled?: boolean;
46
+ /**
47
+ * Shows a loading spinner and disables the button to indicate an ongoing action.
48
+ *
49
+ * @default false
50
+ */
51
+ loading?: boolean;
52
+ /**
53
+ * Applies a skeleton loading state to the button, showing a placeholder style while content is loading.
54
+ *
55
+ * @default false`
56
+ */
57
+ skeleton?: boolean;
58
+ /**
59
+ * If true, the button is styled for icon-only content, removing extra padding and centering the icon.
60
+ *
61
+ * @default false
62
+ */
63
+ iconOnly?: boolean;
64
+ label?: string;
65
+ children?: Snippet;
66
+ }
67
+ declare const Button: import("svelte").Component<Props, {}, "">;
68
+ type Button = ReturnType<typeof Button>;
69
+ export default Button;
@@ -0,0 +1,91 @@
1
+ <script lang="ts">
2
+ import './dropdownButton.css';
3
+ import Button from './Button.svelte';
4
+ import type { Snippet } from 'svelte';
5
+
6
+ interface Props {
7
+ /**
8
+ * The label shown in the trigger button when an option is selected.
9
+ */
10
+ label?: string;
11
+ /**
12
+ * Placeholder text shown in the trigger button when no option is selected.
13
+ *
14
+ * @default "Select..."
15
+ */
16
+ placeholder?: string;
17
+ /**
18
+ * Custom trigger content via Snippet.
19
+ * Falls back to label or placeholder if not provided.
20
+ */
21
+ trigger?: Snippet;
22
+ /**
23
+ * Custom menu content via Snippet.
24
+ * Use this to pass menu items.
25
+ */
26
+ menu?: Snippet<[close: () => void]>;
27
+ /**
28
+ * Disables the dropdown, preventing interaction.
29
+ *
30
+ * @default false
31
+ */
32
+ disabled?: boolean;
33
+ }
34
+
35
+ let { label, placeholder = 'Select...', trigger, menu, disabled = false }: Props = $props();
36
+
37
+ let open = $state(false);
38
+ let dropdownRef = $state<HTMLDivElement | null>(null);
39
+
40
+ const menuId = `ibis-dropdown-button-${Math.random().toString(36).slice(2)}`;
41
+
42
+ const toggle = () => {
43
+ if (disabled) return;
44
+ open = !open;
45
+ };
46
+
47
+ const handleKeydown = (e: KeyboardEvent) => {
48
+ if (e.key === 'Escape') open = false;
49
+ };
50
+
51
+ const handleClickOutside = (e: MouseEvent) => {
52
+ if (dropdownRef && !dropdownRef.contains(e.target as Node)) {
53
+ open = false;
54
+ }
55
+ };
56
+ </script>
57
+
58
+ <svelte:window onclick={handleClickOutside} />
59
+
60
+ <div
61
+ class="dropdown-button {open ? 'dropdown-button--open' : ''}"
62
+ bind:this={dropdownRef}
63
+ onkeydown={handleKeydown}
64
+ role="combobox"
65
+ tabindex="0"
66
+ aria-expanded={open}
67
+ aria-haspopup="listbox"
68
+ aria-controls={menuId}
69
+ >
70
+ <!-- Trigger -->
71
+ <Button onclick={toggle} {disabled}>
72
+ {#if trigger}
73
+ {@render trigger()}
74
+ {:else}
75
+ {label || placeholder}
76
+ {/if}
77
+
78
+ <svg class="caret {open ? 'caret--open' : ''}" viewBox="0 0 16 16" aria-hidden="true">
79
+ <path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="2" fill="none" />
80
+ </svg>
81
+ </Button>
82
+
83
+ <!-- Menu -->
84
+ {#if open}
85
+ <ul class="dropdown-menu" role="listbox">
86
+ {#if menu}
87
+ {@render menu(() => (open = false))}
88
+ {/if}
89
+ </ul>
90
+ {/if}
91
+ </div>
@@ -0,0 +1,33 @@
1
+ import './dropdownButton.css';
2
+ import type { Snippet } from 'svelte';
3
+ interface Props {
4
+ /**
5
+ * The label shown in the trigger button when an option is selected.
6
+ */
7
+ label?: string;
8
+ /**
9
+ * Placeholder text shown in the trigger button when no option is selected.
10
+ *
11
+ * @default "Select..."
12
+ */
13
+ placeholder?: string;
14
+ /**
15
+ * Custom trigger content via Snippet.
16
+ * Falls back to label or placeholder if not provided.
17
+ */
18
+ trigger?: Snippet;
19
+ /**
20
+ * Custom menu content via Snippet.
21
+ * Use this to pass menu items.
22
+ */
23
+ menu?: Snippet<[close: () => void]>;
24
+ /**
25
+ * Disables the dropdown, preventing interaction.
26
+ *
27
+ * @default false
28
+ */
29
+ disabled?: boolean;
30
+ }
31
+ declare const DropdownButton: import("svelte").Component<Props, {}, "">;
32
+ type DropdownButton = ReturnType<typeof DropdownButton>;
33
+ export default DropdownButton;
@@ -0,0 +1,84 @@
1
+ <script lang="ts">
2
+ import './pillTab.css';
3
+ import type { HTMLInputAttributes } from 'svelte/elements';
4
+
5
+ interface Props extends HTMLInputAttributes {
6
+ /**
7
+ * The visible text label displayed inside the pill tab.
8
+ *
9
+ * @example
10
+ * <PillTab label="Overview" value="overview" />
11
+ */
12
+ label?: string;
13
+ /**
14
+ * The unique identifier for the input element.
15
+ *
16
+ * If not provided, a random ID is generated automatically.
17
+ */
18
+ id?: string;
19
+ /**
20
+ * The currently selected value in the radio group.
21
+ *
22
+ * When `group` matches `value`, the pill tab appears active.
23
+ * Bind this to a shared variable across all tabs in the same group.
24
+ *
25
+ * @example
26
+ * <PillTab bind:group={activeTab} value="overview" label="Overview" />
27
+ */
28
+ group?: string | number;
29
+ /**
30
+ * The value this pill tab represents in the radio group.
31
+ *
32
+ * When `group === value`, this tab is considered active.
33
+ *
34
+ * @example
35
+ * <PillTab value="settings" label="Settings" />
36
+ */
37
+ value: string | number;
38
+ /**
39
+ * The name attribute shared across all radio inputs in the same group.
40
+ *
41
+ * Required for native radio group behavior — all tabs in the same
42
+ * group must share the same `name`.
43
+ *
44
+ * @example
45
+ * <PillTab name="main-nav" value="overview" label="Overview" />
46
+ */
47
+ name?: string;
48
+ /**
49
+ * Disables the pill tab, preventing user interaction and applying disabled styles.
50
+ *
51
+ * @default false
52
+ */
53
+ disabled?: boolean;
54
+ }
55
+
56
+ let { id, label, group = $bindable(), value, name, disabled = false, ...rest }: Props = $props();
57
+
58
+ const fallbackId = `ibis-pill-tab-${Math.random().toString(36).slice(2)}`;
59
+ const inputId = $derived(id ?? fallbackId);
60
+
61
+ const checked = $derived(group === value);
62
+ </script>
63
+
64
+ <label
65
+ class="ibis-pill-tab
66
+ {checked ? 'ibis-pill-tab--active' : ''}
67
+ {disabled ? 'ibis-pill-tab--disabled' : ''}"
68
+ for={inputId}
69
+ >
70
+ <input
71
+ {...rest}
72
+ id={inputId}
73
+ type="radio"
74
+ {name}
75
+ bind:group
76
+ {value}
77
+ {disabled}
78
+ class="ibis-pill-tab__input"
79
+ />
80
+
81
+ <span class="ibis-pill-tab__content">
82
+ {label}
83
+ </span>
84
+ </label>
@@ -0,0 +1,55 @@
1
+ import './pillTab.css';
2
+ import type { HTMLInputAttributes } from 'svelte/elements';
3
+ interface Props extends HTMLInputAttributes {
4
+ /**
5
+ * The visible text label displayed inside the pill tab.
6
+ *
7
+ * @example
8
+ * <PillTab label="Overview" value="overview" />
9
+ */
10
+ label?: string;
11
+ /**
12
+ * The unique identifier for the input element.
13
+ *
14
+ * If not provided, a random ID is generated automatically.
15
+ */
16
+ id?: string;
17
+ /**
18
+ * The currently selected value in the radio group.
19
+ *
20
+ * When `group` matches `value`, the pill tab appears active.
21
+ * Bind this to a shared variable across all tabs in the same group.
22
+ *
23
+ * @example
24
+ * <PillTab bind:group={activeTab} value="overview" label="Overview" />
25
+ */
26
+ group?: string | number;
27
+ /**
28
+ * The value this pill tab represents in the radio group.
29
+ *
30
+ * When `group === value`, this tab is considered active.
31
+ *
32
+ * @example
33
+ * <PillTab value="settings" label="Settings" />
34
+ */
35
+ value: string | number;
36
+ /**
37
+ * The name attribute shared across all radio inputs in the same group.
38
+ *
39
+ * Required for native radio group behavior — all tabs in the same
40
+ * group must share the same `name`.
41
+ *
42
+ * @example
43
+ * <PillTab name="main-nav" value="overview" label="Overview" />
44
+ */
45
+ name?: string;
46
+ /**
47
+ * Disables the pill tab, preventing user interaction and applying disabled styles.
48
+ *
49
+ * @default false
50
+ */
51
+ disabled?: boolean;
52
+ }
53
+ declare const PillTab: import("svelte").Component<Props, {}, "group">;
54
+ type PillTab = ReturnType<typeof PillTab>;
55
+ export default PillTab;
@@ -0,0 +1,193 @@
1
+ .ibis-button {
2
+ font-family: var(--font-family-sans);
3
+ font-weight: var(--font-weight-normal);
4
+ border: var(--border-width-default) solid transparent;
5
+ cursor: pointer;
6
+ transition:
7
+ filter var(--transition-duration-fast) var(--transition-timing-default),
8
+ transform var(--transition-duration-fast) var(--transition-timing-default),
9
+ box-shadow var(--transition-duration-fast) var(--transition-timing-default);
10
+ display: inline-flex;
11
+ align-items: center;
12
+ justify-content: center;
13
+ }
14
+
15
+ .ibis-button :global(svg) {
16
+ width: 1em;
17
+ height: 1em;
18
+ display: inline-block;
19
+ vertical-align: middle;
20
+ gap: var(--spacing-1);
21
+ }
22
+
23
+ /* Content Layout */
24
+ .ibis-button__content {
25
+ display: inline-flex;
26
+ align-items: center;
27
+ justify-content: center;
28
+ gap: var(--spacing-1);
29
+ }
30
+
31
+ .ibis-button__content--hidden {
32
+ visibility: hidden;
33
+ }
34
+
35
+ /* Icon Only */
36
+ .ibis-button--icon-only {
37
+ padding: var(--spacing-2);
38
+ }
39
+
40
+ .ibis-button--icon-only .ibis-button__content {
41
+ gap: 0;
42
+ }
43
+
44
+ /* Disabled State */
45
+ .ibis-button:disabled {
46
+ opacity: var(--opacity-disabled);
47
+ cursor: not-allowed;
48
+ }
49
+
50
+ /* Loading State */
51
+ .ibis-button--loading {
52
+ position: relative;
53
+ cursor: not-allowed;
54
+ pointer-events: none;
55
+ }
56
+
57
+ /* Primary Variant */
58
+ .ibis-button--primary {
59
+ background: linear-gradient(180deg, #9665df 0%, #7945c8 100%); /* Hardcoded */
60
+ color: var(--color-white);
61
+
62
+ box-shadow: var(--component-button-primary-default-shadow);
63
+ border: 0.5px solid #bd91ff; /* Hardcoded */
64
+ }
65
+
66
+ .ibis-button--primary:not(:disabled):hover,
67
+ .ibis-button--primary:not(:disabled):active {
68
+ box-shadow: none;
69
+ }
70
+
71
+ .ibis-button--primary:not(:disabled):hover {
72
+ background: var(--color-primary-800);
73
+ border-color: none;
74
+ border-width: 0.7 px; /* Hardcoded */
75
+ }
76
+
77
+ .ibis-button--primary:not(:disabled):active {
78
+ background: var(--color-primary-800);
79
+ border-color: var(--color-primary-400);
80
+ border-width: var(--border-width-thin);
81
+ }
82
+
83
+ .ibis-button--primary:disabled {
84
+ background: var(--color-neutral-400);
85
+ color: var(--color-neutral-600);
86
+ border-color: var(--color-neutral-600);
87
+ }
88
+
89
+ /* Secondary Variant */
90
+ .ibis-button--secondary {
91
+ background-color: transparent;
92
+ color: var(--color-primary-pink-500);
93
+ border-color: var(--color-primary-pink-700);
94
+ }
95
+
96
+ .ibis-button--secondary:not(:disabled):hover {
97
+ background-color: var(--color-primary-pink-100);
98
+ }
99
+
100
+ /* Variant Sizes */
101
+ .ibis-button--sm {
102
+ padding: var(--spacing-1) var(--spacing-2);
103
+ font-size: var(--font-size-normal-text-sm);
104
+ border-radius: var(--component-button-size-sm-border-radius);
105
+ }
106
+
107
+ .ibis-button--md {
108
+ padding: var(--spacing-2) var(--spacing-4);
109
+ font-size: var(--font-size-normal-text-DEFAULT);
110
+ border-radius: var(--component-button-size-md-border-radius);
111
+ }
112
+
113
+ .ibis-button--lg {
114
+ padding: var(--spacing-3) var(--spacing-6);
115
+ font-size: var(--font-size-normal-text-lg);
116
+ border-radius: var(--component-button-size-lg-border-radius);
117
+ }
118
+
119
+ /* Loader Spinner */
120
+ .ibis-button__loader {
121
+ position: absolute;
122
+ width: 1em;
123
+ height: 1em;
124
+ border: var(--border-width-default) solid var(--color-black);
125
+ border-top-color: var(--color-white);
126
+ border-radius: 50%;
127
+ animation: ibis-spin 0.8s linear infinite;
128
+ top: 50%;
129
+ left: 50%;
130
+ transform: translate(-50%, -50%);
131
+ }
132
+
133
+ @keyframes ibis-spin {
134
+ from {
135
+ transform: translate(-50%, -50%) rotate(0deg);
136
+ }
137
+ to {
138
+ transform: translate(-50%, -50%) rotate(360deg);
139
+ }
140
+ }
141
+
142
+ /* Skeleton State */
143
+ .ibis-button--skeleton {
144
+ position: relative;
145
+ overflow: hidden;
146
+ background: var(--color-neutral-200);
147
+ color: transparent;
148
+ border-color: var(--color-neutral-500);
149
+ cursor: default;
150
+ pointer-events: none;
151
+ }
152
+
153
+ .ibis-button--skeleton::after {
154
+ content: '';
155
+ position: absolute;
156
+ top: 50%;
157
+ left: 50%;
158
+ transform: translate(-50%, -50%);
159
+ width: 80%;
160
+ height: 1em;
161
+ background-color: var(--color-neutral-400);
162
+ }
163
+
164
+ .ibis-button--icon-only--skeleton::after {
165
+ width: 1em;
166
+ height: 1em;
167
+ border-radius: 4px;
168
+ }
169
+
170
+ .ibis-button--skeleton::before {
171
+ content: '';
172
+ position: absolute;
173
+ inset: 0;
174
+ background: linear-gradient(
175
+ 90deg,
176
+ rgba(255, 255, 255, 0.25) 0%,
177
+ rgba(255, 255, 255, 0.6) 50%,
178
+ rgba(255, 255, 255, 0.25) 100%
179
+ );
180
+ transform: translateX(-100%);
181
+ animation: ibis-shimmer 2s infinite;
182
+ border-radius: inherit;
183
+ }
184
+
185
+ @keyframes ibis-shimmer {
186
+ 0% {
187
+ transform: translateX(-100%);
188
+ }
189
+
190
+ 100% {
191
+ transform: translateX(100%);
192
+ }
193
+ }