@mkatogui/uds-svelte 0.2.1

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 (36) hide show
  1. package/README.md +100 -0
  2. package/package.json +27 -0
  3. package/src/components/Accordion.svelte +99 -0
  4. package/src/components/Alert.svelte +67 -0
  5. package/src/components/Avatar.svelte +62 -0
  6. package/src/components/Badge.svelte +50 -0
  7. package/src/components/Breadcrumb.svelte +60 -0
  8. package/src/components/Button.svelte +53 -0
  9. package/src/components/Checkbox.svelte +62 -0
  10. package/src/components/CodeBlock.svelte +90 -0
  11. package/src/components/CommandPalette.svelte +133 -0
  12. package/src/components/DataTable.svelte +140 -0
  13. package/src/components/DatePicker.svelte +73 -0
  14. package/src/components/DropdownMenu.svelte +113 -0
  15. package/src/components/FeatureCard.svelte +63 -0
  16. package/src/components/FileUpload.svelte +120 -0
  17. package/src/components/Footer.svelte +72 -0
  18. package/src/components/FormInput.svelte +102 -0
  19. package/src/components/HeroSection.svelte +65 -0
  20. package/src/components/Modal.svelte +67 -0
  21. package/src/components/NavigationBar.svelte +56 -0
  22. package/src/components/Pagination.svelte +115 -0
  23. package/src/components/PricingTable.svelte +89 -0
  24. package/src/components/ProgressIndicator.svelte +86 -0
  25. package/src/components/Radio.svelte +64 -0
  26. package/src/components/Select.svelte +85 -0
  27. package/src/components/SideNavigation.svelte +130 -0
  28. package/src/components/Skeleton.svelte +53 -0
  29. package/src/components/SocialProofBar.svelte +90 -0
  30. package/src/components/Tabs.svelte +100 -0
  31. package/src/components/TestimonialCard.svelte +71 -0
  32. package/src/components/Toast.svelte +83 -0
  33. package/src/components/ToggleSwitch.svelte +61 -0
  34. package/src/components/Tooltip.svelte +67 -0
  35. package/src/index.d.ts +553 -0
  36. package/src/index.js +32 -0
@@ -0,0 +1,64 @@
1
+ <script lang="ts">
2
+ interface RadioOption {
3
+ value: string;
4
+ label: string;
5
+ disabled?: boolean;
6
+ }
7
+
8
+ interface Props {
9
+ variant?: 'standard' | 'card';
10
+ options?: RadioOption[];
11
+ name?: string;
12
+ value?: string;
13
+ legend?: string;
14
+ disabled?: boolean;
15
+ class?: string;
16
+ [key: string]: any;
17
+ }
18
+
19
+ let {
20
+ variant = 'standard',
21
+ options = [],
22
+ name = '',
23
+ value = $bindable(''),
24
+ legend = '',
25
+ disabled = false,
26
+ class: className = '',
27
+ ...rest
28
+ }: Props = $props();
29
+
30
+ let groupId = $derived(`uds-radio-${Math.random().toString(36).slice(2, 9)}`);
31
+
32
+ let classes = $derived(
33
+ [
34
+ 'uds-radio',
35
+ `uds-radio--${variant}`,
36
+ disabled && 'uds-radio--disabled',
37
+ className,
38
+ ]
39
+ .filter(Boolean)
40
+ .join(' ')
41
+ );
42
+ </script>
43
+
44
+ <fieldset class={classes} {...rest}>
45
+ {#if legend}
46
+ <legend class="uds-radio__legend">{legend}</legend>
47
+ {/if}
48
+ {#each options as option, i}
49
+ <div class="uds-radio__option" class:uds-radio__option--card={variant === 'card'}>
50
+ <input
51
+ class="uds-radio__input"
52
+ type="radio"
53
+ id="{groupId}-{i}"
54
+ {name}
55
+ value={option.value}
56
+ checked={value === option.value}
57
+ disabled={disabled || option.disabled}
58
+ aria-checked={value === option.value}
59
+ onchange={() => (value = option.value)}
60
+ />
61
+ <label class="uds-radio__label" for="{groupId}-{i}">{option.label}</label>
62
+ </div>
63
+ {/each}
64
+ </fieldset>
@@ -0,0 +1,85 @@
1
+ <script lang="ts">
2
+ interface SelectOption {
3
+ value: string;
4
+ label: string;
5
+ disabled?: boolean;
6
+ }
7
+
8
+ interface Props {
9
+ variant?: 'native' | 'custom';
10
+ size?: 'sm' | 'md' | 'lg';
11
+ options?: SelectOption[];
12
+ placeholder?: string;
13
+ required?: boolean;
14
+ disabled?: boolean;
15
+ label?: string;
16
+ errorText?: string;
17
+ value?: string;
18
+ id?: string;
19
+ class?: string;
20
+ [key: string]: any;
21
+ }
22
+
23
+ let {
24
+ variant = 'native',
25
+ size = 'md',
26
+ options = [],
27
+ placeholder = '',
28
+ required = false,
29
+ disabled = false,
30
+ label = '',
31
+ errorText = '',
32
+ value = $bindable(''),
33
+ id,
34
+ class: className = '',
35
+ ...rest
36
+ }: Props = $props();
37
+
38
+ let selectId = $derived(id || `uds-select-${Math.random().toString(36).slice(2, 9)}`);
39
+ let errorId = $derived(`${selectId}-error`);
40
+ let isError = $derived(!!errorText);
41
+
42
+ let classes = $derived(
43
+ [
44
+ 'uds-select',
45
+ `uds-select--${variant}`,
46
+ `uds-select--${size}`,
47
+ isError && 'uds-select--error',
48
+ disabled && 'uds-select--disabled',
49
+ className,
50
+ ]
51
+ .filter(Boolean)
52
+ .join(' ')
53
+ );
54
+ </script>
55
+
56
+ <div class={classes}>
57
+ {#if label}
58
+ <label class="uds-select__label" for={selectId}>
59
+ {label}
60
+ {#if required}
61
+ <span class="uds-select__required" aria-hidden="true">*</span>
62
+ {/if}
63
+ </label>
64
+ {/if}
65
+ <select
66
+ class="uds-select__field"
67
+ id={selectId}
68
+ bind:value
69
+ {required}
70
+ {disabled}
71
+ aria-invalid={isError || undefined}
72
+ aria-describedby={errorText ? errorId : undefined}
73
+ {...rest}
74
+ >
75
+ {#if placeholder}
76
+ <option value="" disabled selected>{placeholder}</option>
77
+ {/if}
78
+ {#each options as option}
79
+ <option value={option.value} disabled={option.disabled}>{option.label}</option>
80
+ {/each}
81
+ </select>
82
+ {#if errorText}
83
+ <p class="uds-select__error" id={errorId} role="alert">{errorText}</p>
84
+ {/if}
85
+ </div>
@@ -0,0 +1,130 @@
1
+ <script lang="ts">
2
+ interface NavItem {
3
+ id: string;
4
+ label: string;
5
+ href?: string;
6
+ icon?: string;
7
+ children?: NavItem[];
8
+ }
9
+
10
+ interface NavSection {
11
+ title: string;
12
+ items: NavItem[];
13
+ }
14
+
15
+ interface Props {
16
+ variant?: 'default' | 'collapsed' | 'with-sections';
17
+ collapsed?: boolean;
18
+ items?: NavItem[];
19
+ sections?: NavSection[];
20
+ activeItem?: string;
21
+ onNavigate?: (itemId: string) => void;
22
+ class?: string;
23
+ children?: import('svelte').Snippet;
24
+ [key: string]: any;
25
+ }
26
+
27
+ let {
28
+ variant = 'default',
29
+ collapsed = false,
30
+ items = [],
31
+ sections = [],
32
+ activeItem = '',
33
+ onNavigate,
34
+ class: className = '',
35
+ children,
36
+ ...rest
37
+ }: Props = $props();
38
+
39
+ let expandedItems = $state<Set<string>>(new Set());
40
+
41
+ let classes = $derived(
42
+ [
43
+ 'uds-side-nav',
44
+ `uds-side-nav--${variant}`,
45
+ collapsed && 'uds-side-nav--collapsed',
46
+ className,
47
+ ]
48
+ .filter(Boolean)
49
+ .join(' ')
50
+ );
51
+
52
+ function toggleExpand(id: string) {
53
+ if (expandedItems.has(id)) {
54
+ expandedItems.delete(id);
55
+ } else {
56
+ expandedItems.add(id);
57
+ }
58
+ expandedItems = new Set(expandedItems);
59
+ }
60
+
61
+ function handleNavigate(item: NavItem) {
62
+ onNavigate?.(item.id);
63
+ }
64
+ </script>
65
+
66
+ <nav class={classes} aria-label="Side navigation" {...rest}>
67
+ {#if variant === 'with-sections' && sections.length > 0}
68
+ {#each sections as section}
69
+ <div class="uds-side-nav__section">
70
+ <h3 class="uds-side-nav__section-title">{section.title}</h3>
71
+ <ul class="uds-side-nav__list">
72
+ {#each section.items as item}
73
+ <li class="uds-side-nav__item" class:uds-side-nav__item--active={activeItem === item.id}>
74
+ <a
75
+ class="uds-side-nav__link"
76
+ href={item.href || '#'}
77
+ aria-current={activeItem === item.id ? 'page' : undefined}
78
+ onclick={(e) => { if (!item.href) e.preventDefault(); handleNavigate(item); }}
79
+ >
80
+ {item.label}
81
+ </a>
82
+ </li>
83
+ {/each}
84
+ </ul>
85
+ </div>
86
+ {/each}
87
+ {:else}
88
+ <ul class="uds-side-nav__list">
89
+ {#each items as item}
90
+ <li class="uds-side-nav__item" class:uds-side-nav__item--active={activeItem === item.id}>
91
+ {#if item.children && item.children.length > 0}
92
+ <button
93
+ class="uds-side-nav__link uds-side-nav__link--expandable"
94
+ aria-expanded={expandedItems.has(item.id)}
95
+ onclick={() => toggleExpand(item.id)}
96
+ >
97
+ {item.label}
98
+ </button>
99
+ {#if expandedItems.has(item.id)}
100
+ <ul class="uds-side-nav__sublist">
101
+ {#each item.children as child}
102
+ <li class="uds-side-nav__item" class:uds-side-nav__item--active={activeItem === child.id}>
103
+ <a
104
+ class="uds-side-nav__link"
105
+ href={child.href || '#'}
106
+ aria-current={activeItem === child.id ? 'page' : undefined}
107
+ onclick={(e) => { if (!child.href) e.preventDefault(); handleNavigate(child); }}
108
+ >
109
+ {child.label}
110
+ </a>
111
+ </li>
112
+ {/each}
113
+ </ul>
114
+ {/if}
115
+ {:else}
116
+ <a
117
+ class="uds-side-nav__link"
118
+ href={item.href || '#'}
119
+ aria-current={activeItem === item.id ? 'page' : undefined}
120
+ onclick={(e) => { if (!item.href) e.preventDefault(); handleNavigate(item); }}
121
+ >
122
+ {item.label}
123
+ </a>
124
+ {/if}
125
+ </li>
126
+ {/each}
127
+ </ul>
128
+ {/if}
129
+ {@render children?.()}
130
+ </nav>
@@ -0,0 +1,53 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ variant?: 'text' | 'card' | 'avatar' | 'table';
4
+ size?: 'sm' | 'md' | 'lg';
5
+ lines?: number;
6
+ animated?: boolean;
7
+ class?: string;
8
+ [key: string]: any;
9
+ }
10
+
11
+ let {
12
+ variant = 'text',
13
+ size = 'md',
14
+ lines = 3,
15
+ animated = true,
16
+ class: className = '',
17
+ ...rest
18
+ }: Props = $props();
19
+
20
+ let classes = $derived(
21
+ [
22
+ 'uds-skeleton',
23
+ `uds-skeleton--${variant}`,
24
+ `uds-skeleton--${size}`,
25
+ animated && 'uds-skeleton--animated',
26
+ className,
27
+ ]
28
+ .filter(Boolean)
29
+ .join(' ')
30
+ );
31
+ </script>
32
+
33
+ <div class={classes} aria-hidden="true" {...rest}>
34
+ {#if variant === 'text'}
35
+ {#each Array(lines) as _, i}
36
+ <div class="uds-skeleton__line" style:width={i === lines - 1 ? '60%' : '100%'}></div>
37
+ {/each}
38
+ {:else if variant === 'card'}
39
+ <div class="uds-skeleton__image"></div>
40
+ <div class="uds-skeleton__line" style:width="80%"></div>
41
+ <div class="uds-skeleton__line" style:width="60%"></div>
42
+ {:else if variant === 'avatar'}
43
+ <div class="uds-skeleton__circle"></div>
44
+ {:else if variant === 'table'}
45
+ {#each Array(lines) as _}
46
+ <div class="uds-skeleton__row">
47
+ <div class="uds-skeleton__cell"></div>
48
+ <div class="uds-skeleton__cell"></div>
49
+ <div class="uds-skeleton__cell"></div>
50
+ </div>
51
+ {/each}
52
+ {/if}
53
+ </div>
@@ -0,0 +1,90 @@
1
+ <script lang="ts">
2
+ interface Logo {
3
+ src: string;
4
+ alt: string;
5
+ href?: string;
6
+ }
7
+
8
+ interface Stat {
9
+ value: string;
10
+ label: string;
11
+ }
12
+
13
+ interface Testimonial {
14
+ quote: string;
15
+ author: string;
16
+ }
17
+
18
+ interface Props {
19
+ variant?: 'logo-strip' | 'stats-counter' | 'testimonial-mini' | 'combined';
20
+ size?: 'standard' | 'compact';
21
+ logos?: Logo[];
22
+ stats?: Stat[];
23
+ testimonials?: Testimonial[];
24
+ animated?: boolean;
25
+ class?: string;
26
+ children?: import('svelte').Snippet;
27
+ [key: string]: any;
28
+ }
29
+
30
+ let {
31
+ variant = 'logo-strip',
32
+ size = 'standard',
33
+ logos = [],
34
+ stats = [],
35
+ testimonials = [],
36
+ animated = false,
37
+ class: className = '',
38
+ children,
39
+ ...rest
40
+ }: Props = $props();
41
+
42
+ let classes = $derived(
43
+ [
44
+ 'uds-social-proof',
45
+ `uds-social-proof--${variant}`,
46
+ `uds-social-proof--${size}`,
47
+ animated && 'uds-social-proof--animated',
48
+ className,
49
+ ]
50
+ .filter(Boolean)
51
+ .join(' ')
52
+ );
53
+ </script>
54
+
55
+ <div class={classes} {...rest}>
56
+ {#if logos.length > 0}
57
+ <div class="uds-social-proof__logos">
58
+ {#each logos as logo}
59
+ {#if logo.href}
60
+ <a href={logo.href} class="uds-social-proof__logo-link">
61
+ <img src={logo.src} alt={logo.alt} class="uds-social-proof__logo" />
62
+ </a>
63
+ {:else}
64
+ <img src={logo.src} alt={logo.alt} class="uds-social-proof__logo" />
65
+ {/if}
66
+ {/each}
67
+ </div>
68
+ {/if}
69
+ {#if stats.length > 0}
70
+ <div class="uds-social-proof__stats">
71
+ {#each stats as stat}
72
+ <div class="uds-social-proof__stat">
73
+ <span class="uds-social-proof__stat-value">{stat.value}</span>
74
+ <span class="uds-social-proof__stat-label">{stat.label}</span>
75
+ </div>
76
+ {/each}
77
+ </div>
78
+ {/if}
79
+ {#if testimonials.length > 0}
80
+ <div class="uds-social-proof__testimonials">
81
+ {#each testimonials as testimonial}
82
+ <blockquote class="uds-social-proof__testimonial">
83
+ <p>{testimonial.quote}</p>
84
+ <cite>{testimonial.author}</cite>
85
+ </blockquote>
86
+ {/each}
87
+ </div>
88
+ {/if}
89
+ {@render children?.()}
90
+ </div>
@@ -0,0 +1,100 @@
1
+ <script lang="ts">
2
+ interface TabItem {
3
+ id: string;
4
+ label: string;
5
+ disabled?: boolean;
6
+ }
7
+
8
+ interface Props {
9
+ variant?: 'line' | 'pill' | 'segmented';
10
+ size?: 'sm' | 'md' | 'lg';
11
+ tabs?: TabItem[];
12
+ activeTab?: string;
13
+ onChange?: (tabId: string) => void;
14
+ class?: string;
15
+ children?: import('svelte').Snippet;
16
+ [key: string]: any;
17
+ }
18
+
19
+ let {
20
+ variant = 'line',
21
+ size = 'md',
22
+ tabs = [],
23
+ activeTab = $bindable(''),
24
+ onChange,
25
+ class: className = '',
26
+ children,
27
+ ...rest
28
+ }: Props = $props();
29
+
30
+ let classes = $derived(
31
+ [
32
+ 'uds-tabs',
33
+ `uds-tabs--${variant}`,
34
+ `uds-tabs--${size}`,
35
+ className,
36
+ ]
37
+ .filter(Boolean)
38
+ .join(' ')
39
+ );
40
+
41
+ function handleTabClick(tabId: string) {
42
+ activeTab = tabId;
43
+ onChange?.(tabId);
44
+ }
45
+
46
+ function handleKeydown(event: KeyboardEvent, index: number) {
47
+ const enabledTabs = tabs.filter((t) => !t.disabled);
48
+ const currentIndex = enabledTabs.findIndex((t) => t.id === tabs[index].id);
49
+
50
+ let nextIndex: number | undefined;
51
+ if (event.key === 'ArrowRight') {
52
+ nextIndex = (currentIndex + 1) % enabledTabs.length;
53
+ } else if (event.key === 'ArrowLeft') {
54
+ nextIndex = (currentIndex - 1 + enabledTabs.length) % enabledTabs.length;
55
+ }
56
+
57
+ if (nextIndex !== undefined) {
58
+ event.preventDefault();
59
+ handleTabClick(enabledTabs[nextIndex].id);
60
+ const btn = (event.currentTarget as HTMLElement)
61
+ .parentElement?.querySelectorAll('[role="tab"]:not([disabled])')[nextIndex] as HTMLElement;
62
+ btn?.focus();
63
+ }
64
+ }
65
+ </script>
66
+
67
+ <div class={classes} {...rest}>
68
+ <div class="uds-tabs__list" role="tablist">
69
+ {#each tabs as tab, i}
70
+ <button
71
+ class="uds-tabs__tab"
72
+ class:uds-tabs__tab--active={activeTab === tab.id}
73
+ role="tab"
74
+ id="tab-{tab.id}"
75
+ aria-selected={activeTab === tab.id}
76
+ aria-controls="tabpanel-{tab.id}"
77
+ disabled={tab.disabled}
78
+ tabindex={activeTab === tab.id ? 0 : -1}
79
+ onclick={() => handleTabClick(tab.id)}
80
+ onkeydown={(e) => handleKeydown(e, i)}
81
+ >
82
+ {tab.label}
83
+ </button>
84
+ {/each}
85
+ </div>
86
+ {#each tabs as tab}
87
+ <div
88
+ class="uds-tabs__panel"
89
+ class:uds-tabs__panel--active={activeTab === tab.id}
90
+ role="tabpanel"
91
+ id="tabpanel-{tab.id}"
92
+ aria-labelledby="tab-{tab.id}"
93
+ hidden={activeTab !== tab.id}
94
+ >
95
+ {#if activeTab === tab.id}
96
+ {@render children?.()}
97
+ {/if}
98
+ </div>
99
+ {/each}
100
+ </div>
@@ -0,0 +1,71 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ variant?: 'quote-card' | 'video' | 'metric' | 'carousel';
4
+ size?: 'sm' | 'md' | 'lg';
5
+ quote?: string;
6
+ avatar?: string;
7
+ name?: string;
8
+ title?: string;
9
+ company?: string;
10
+ rating?: number;
11
+ class?: string;
12
+ children?: import('svelte').Snippet;
13
+ [key: string]: any;
14
+ }
15
+
16
+ let {
17
+ variant = 'quote-card',
18
+ size = 'md',
19
+ quote = '',
20
+ avatar,
21
+ name = '',
22
+ title: jobTitle = '',
23
+ company = '',
24
+ rating,
25
+ class: className = '',
26
+ children,
27
+ ...rest
28
+ }: Props = $props();
29
+
30
+ let classes = $derived(
31
+ [
32
+ 'uds-testimonial',
33
+ `uds-testimonial--${variant}`,
34
+ `uds-testimonial--${size}`,
35
+ className,
36
+ ]
37
+ .filter(Boolean)
38
+ .join(' ')
39
+ );
40
+ </script>
41
+
42
+ <blockquote class={classes} {...rest}>
43
+ {#if quote}
44
+ <p class="uds-testimonial__quote">{quote}</p>
45
+ {/if}
46
+ {@render children?.()}
47
+ <footer class="uds-testimonial__footer">
48
+ {#if avatar}
49
+ <img class="uds-testimonial__avatar" src={avatar} alt={name} />
50
+ {/if}
51
+ <div class="uds-testimonial__attribution">
52
+ {#if name}
53
+ <cite class="uds-testimonial__name">{name}</cite>
54
+ {/if}
55
+ {#if jobTitle || company}
56
+ <span class="uds-testimonial__role">
57
+ {jobTitle}{#if jobTitle && company}, {/if}{company}
58
+ </span>
59
+ {/if}
60
+ </div>
61
+ {#if rating != null}
62
+ <div class="uds-testimonial__rating" aria-label={`${rating} out of 5 stars`}>
63
+ {#each Array(5) as _, i}
64
+ <span class="uds-testimonial__star" class:uds-testimonial__star--filled={i < rating} aria-hidden="true">
65
+ {i < rating ? '\u2605' : '\u2606'}
66
+ </span>
67
+ {/each}
68
+ </div>
69
+ {/if}
70
+ </footer>
71
+ </blockquote>
@@ -0,0 +1,83 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ variant?: 'success' | 'error' | 'warning' | 'info' | 'neutral';
4
+ size?: 'sm' | 'md' | 'lg';
5
+ message?: string;
6
+ duration?: number;
7
+ action?: string;
8
+ onDismiss?: () => void;
9
+ onAction?: () => void;
10
+ visible?: boolean;
11
+ class?: string;
12
+ children?: import('svelte').Snippet;
13
+ [key: string]: any;
14
+ }
15
+
16
+ let {
17
+ variant = 'info',
18
+ size = 'md',
19
+ message = '',
20
+ duration = 5000,
21
+ action,
22
+ onDismiss,
23
+ onAction,
24
+ visible = true,
25
+ class: className = '',
26
+ children,
27
+ ...rest
28
+ }: Props = $props();
29
+
30
+ let state = $state<'entering' | 'visible' | 'exiting'>('entering');
31
+
32
+ let toastRole = $derived(
33
+ variant === 'error' || variant === 'warning' ? 'alert' : 'status'
34
+ );
35
+
36
+ let classes = $derived(
37
+ [
38
+ 'uds-toast',
39
+ `uds-toast--${variant}`,
40
+ `uds-toast--${size}`,
41
+ `uds-toast--${state}`,
42
+ className,
43
+ ]
44
+ .filter(Boolean)
45
+ .join(' ')
46
+ );
47
+
48
+ $effect(() => {
49
+ if (!visible) return;
50
+ const enterTimer = setTimeout(() => (state = 'visible'), 50);
51
+ let exitTimer: ReturnType<typeof setTimeout> | undefined;
52
+ if (duration > 0) {
53
+ exitTimer = setTimeout(() => {
54
+ state = 'exiting';
55
+ setTimeout(() => onDismiss?.(), 300);
56
+ }, duration);
57
+ }
58
+ return () => {
59
+ clearTimeout(enterTimer);
60
+ if (exitTimer) clearTimeout(exitTimer);
61
+ };
62
+ });
63
+
64
+ function handleDismiss() {
65
+ state = 'exiting';
66
+ setTimeout(() => onDismiss?.(), 300);
67
+ }
68
+ </script>
69
+
70
+ {#if visible}
71
+ <div class={classes} role={toastRole} {...rest}>
72
+ <p class="uds-toast__message">{message}</p>
73
+ {@render children?.()}
74
+ <div class="uds-toast__actions">
75
+ {#if action}
76
+ <button class="uds-toast__action" onclick={onAction}>{action}</button>
77
+ {/if}
78
+ <button class="uds-toast__dismiss" onclick={handleDismiss} aria-label="Dismiss notification">
79
+ <span aria-hidden="true">&times;</span>
80
+ </button>
81
+ </div>
82
+ </div>
83
+ {/if}