@turnipxenon/pineapple 5.0.0 → 5.1.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/README.md +11 -11
- package/dist/ui/components/FourPartCard.svelte +4 -0
- package/dist/ui/components/SortDropdown.svelte +50 -0
- package/dist/ui/components/SortDropdown.svelte.d.ts +9 -0
- package/dist/ui/components/SortDropdown.svelte.d.ts.map +1 -0
- package/dist/ui/components/TagFilter.svelte +32 -0
- package/dist/ui/components/TagFilter.svelte.d.ts +9 -0
- package/dist/ui/components/TagFilter.svelte.d.ts.map +1 -0
- package/dist/ui/components/accordion/PinyaAccordion.svelte +5 -3
- package/dist/ui/components/accordion/PinyaAccordion.svelte.d.ts.map +1 -1
- package/dist/ui/elements/CodeBlock/CodeBlock.svelte +12 -6
- package/dist/ui/elements/PineappleSwitch.svelte +2 -2
- package/dist/ui/elements/PineappleSwitch.svelte.d.ts +1 -1
- package/dist/ui/elements/PineappleSwitch.svelte.d.ts.map +1 -1
- package/dist/ui/elements/pinya-combobox/PinyaCombobox.svelte +169 -26
- package/dist/ui/elements/pinya-combobox/PinyaCombobox.svelte.d.ts.map +1 -1
- package/dist/ui/elements/pinya-combobox/PinyaComboboxProps.d.ts +6 -7
- package/dist/ui/elements/pinya-combobox/PinyaComboboxProps.d.ts.map +1 -1
- package/dist/ui/modules/experience/ExampleJob1.svelte +79 -0
- package/dist/ui/modules/experience/ExampleJob1.svelte.d.ts +26 -0
- package/dist/ui/modules/experience/ExampleJob1.svelte.d.ts.map +1 -0
- package/dist/ui/modules/experience/ExampleJob2.svelte +74 -0
- package/dist/ui/modules/experience/ExampleJob2.svelte.d.ts +25 -0
- package/dist/ui/modules/experience/ExampleJob2.svelte.d.ts.map +1 -0
- package/dist/ui/modules/experience/index.d.ts +3 -0
- package/dist/ui/modules/experience/index.d.ts.map +1 -0
- package/dist/ui/modules/experience/index.js +2 -0
- package/dist/ui/modules/modals/general-settings/LanguagePicker.svelte +7 -9
- package/dist/ui/modules/modals/general-settings/LanguagePicker.svelte.d.ts.map +1 -1
- package/dist/ui/modules/projects/Hepcat.svelte +5 -1
- package/dist/ui/modules/projects/Hepcat.svelte.d.ts +4 -1
- package/dist/ui/modules/projects/Hepcat.svelte.d.ts.map +1 -1
- package/dist/ui/modules/projects/Pengi.svelte +4 -1
- package/dist/ui/modules/projects/Pengi.svelte.d.ts +4 -1
- package/dist/ui/modules/projects/Pengi.svelte.d.ts.map +1 -1
- package/dist/ui/modules/projects/Soulwork.svelte +4 -1
- package/dist/ui/modules/projects/Soulwork.svelte.d.ts +4 -1
- package/dist/ui/modules/projects/Soulwork.svelte.d.ts.map +1 -1
- package/dist/ui/modules/projects/ThisWebpage.svelte +4 -2
- package/dist/ui/modules/projects/ThisWebpage.svelte.d.ts +3 -1
- package/dist/ui/modules/projects/ThisWebpage.svelte.d.ts.map +1 -1
- package/dist/ui/modules/universal-overlay/UniversalOverlay.svelte +0 -1
- package/dist/ui/modules/universal-overlay/UniversalOverlay.svelte.d.ts.map +1 -1
- package/dist/ui/templates/SeaweedLayout/EntryGroup.svelte +177 -29
- package/dist/ui/templates/SeaweedLayout/EntryGroup.svelte.d.ts.map +1 -1
- package/dist/ui/templates/SeaweedLayout/ProjectGroupConfig.svelte +1 -3
- package/dist/ui/templates/SeaweedLayout/SeaweedLayout.svelte +188 -44
- package/dist/ui/templates/SeaweedLayout/SeaweedLayout.svelte.d.ts.map +1 -1
- package/dist/ui/templates/SeaweedLayout/props.d.ts +18 -2
- package/dist/ui/templates/SeaweedLayout/props.d.ts.map +1 -1
- package/dist/ui/templates/SeaweedLayout/props.js +8 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,13 +6,13 @@ NPM package: https://www.npmjs.com/package/@turnipxenon/pineapple
|
|
|
6
6
|
|
|
7
7
|
## Developing
|
|
8
8
|
|
|
9
|
-
Once you've created a project and installed dependencies with `
|
|
9
|
+
Once you've created a project and installed dependencies with `pnpm`, start a development server:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
|
|
12
|
+
pnpm dev
|
|
13
13
|
|
|
14
14
|
# or start the server and open the app in a new browser tab
|
|
15
|
-
|
|
15
|
+
pnpm dev -- --open
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
## Building
|
|
@@ -20,10 +20,10 @@ yarn dev -- --open
|
|
|
20
20
|
To create a production version of your app:
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
|
|
23
|
+
pnpm build
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
You can preview the production build with `
|
|
26
|
+
You can preview the production build with `pnpm preview`.
|
|
27
27
|
|
|
28
28
|
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
|
29
29
|
|
|
@@ -35,7 +35,7 @@ TODO: If you're curious how to install this on a fresh package or a package not
|
|
|
35
35
|
## Migration from v2 to v3
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
|
|
38
|
+
pnpm add @turnipxenon2/pineapple
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
**Manual steps**
|
|
@@ -119,13 +119,13 @@ kit: {
|
|
|
119
119
|
|
|
120
120
|
## Local linking
|
|
121
121
|
|
|
122
|
-
1. In pineapple, run `
|
|
123
|
-
2. In seaweed2, run `
|
|
122
|
+
1. In pineapple, run `pnpm link`
|
|
123
|
+
2. In seaweed2, run `pnpm link @turnipxenon/pineapple`
|
|
124
124
|
|
|
125
125
|
**To unlink:**
|
|
126
126
|
|
|
127
|
-
1. In seaweed2, run `
|
|
128
|
-
2. In pineapple, run `
|
|
127
|
+
1. In seaweed2, run `pnpm unlink @turnipxenon/pineapple`
|
|
128
|
+
2. In pineapple, run `pnpm unlink`
|
|
129
129
|
3. **If unlinking, remember to restart PC cause Windows symlinking is tricky**
|
|
130
130
|
|
|
131
|
-
To reinstall a single package: `
|
|
131
|
+
To reinstall a single package: `pnpm add @turnipxenon/pineapple --no-package-lock --no-save`
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import PinyaCombobox from "../elements/pinya-combobox/PinyaCombobox.svelte";
|
|
3
|
+
import type { GenericComboboxItem } from "../elements/pinya-combobox/PinyaComboboxProps";
|
|
4
|
+
import { SectionType } from "../templates/SeaweedLayout/props";
|
|
5
|
+
|
|
6
|
+
interface SortDropdownProps {
|
|
7
|
+
sortBy: string[]; // Current sort value (bindable)
|
|
8
|
+
sectionType: SectionType; // Determines available sort options
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let {
|
|
12
|
+
sortBy = $bindable(["default"]),
|
|
13
|
+
sectionType = SectionType.Projects
|
|
14
|
+
}: SortDropdownProps = $props();
|
|
15
|
+
|
|
16
|
+
const projectSortOptions: GenericComboboxItem<string>[] = [
|
|
17
|
+
{ label: "Default order", value: "default" },
|
|
18
|
+
{ label: "Most recently finished", value: "date-desc" },
|
|
19
|
+
{ label: "Oldest finished", value: "date-asc" },
|
|
20
|
+
{ label: "Longest projects", value: "duration-desc" },
|
|
21
|
+
{ label: "Shortest projects", value: "duration-asc" }
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const experienceSortOptions: GenericComboboxItem<string>[] = [
|
|
25
|
+
{ label: "Default order", value: "default" },
|
|
26
|
+
{ label: "Most recent first", value: "date-desc" },
|
|
27
|
+
{ label: "Oldest first", value: "date-asc" }
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const sortOptions = $derived(
|
|
31
|
+
sectionType === SectionType.Experience
|
|
32
|
+
? experienceSortOptions
|
|
33
|
+
: projectSortOptions
|
|
34
|
+
);
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<div class="sort-dropdown">
|
|
38
|
+
<PinyaCombobox
|
|
39
|
+
data={sortOptions}
|
|
40
|
+
bind:value={sortBy}
|
|
41
|
+
label="Sort by"
|
|
42
|
+
placeholder="Select sort order"
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<style>
|
|
47
|
+
.sort-dropdown {
|
|
48
|
+
min-width: 200px;
|
|
49
|
+
}
|
|
50
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SectionType } from "../templates/SeaweedLayout/props";
|
|
2
|
+
interface SortDropdownProps {
|
|
3
|
+
sortBy: string[];
|
|
4
|
+
sectionType: SectionType;
|
|
5
|
+
}
|
|
6
|
+
declare const SortDropdown: import("svelte").Component<SortDropdownProps, {}, "sortBy">;
|
|
7
|
+
type SortDropdown = ReturnType<typeof SortDropdown>;
|
|
8
|
+
export default SortDropdown;
|
|
9
|
+
//# sourceMappingURL=SortDropdown.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SortDropdown.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/ui/components/SortDropdown.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,WAAW,EAAE,MAAM,uCAAuC,CAAC;AAGnE,UAAU,iBAAiB;IAC1B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;CACzB;AAyCF,QAAA,MAAM,YAAY,6DAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import PinyaCombobox from "../elements/pinya-combobox/PinyaCombobox.svelte";
|
|
3
|
+
|
|
4
|
+
interface TagFilterProps {
|
|
5
|
+
allTags: string[]; // All available tags
|
|
6
|
+
selectedTags: string[]; // Currently selected tags (bindable)
|
|
7
|
+
label?: string; // Optional label
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
allTags,
|
|
12
|
+
selectedTags = $bindable([]),
|
|
13
|
+
label = "Filter by tags"
|
|
14
|
+
}: TagFilterProps = $props();
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<div class="tag-filter">
|
|
18
|
+
<PinyaCombobox
|
|
19
|
+
bind:value={selectedTags}
|
|
20
|
+
data={allTags.map(t => ({value: t, label: t}))}
|
|
21
|
+
multiple={true}
|
|
22
|
+
{label}
|
|
23
|
+
/>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<style>
|
|
27
|
+
.tag-filter {
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
gap: 0.5rem;
|
|
31
|
+
}
|
|
32
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface TagFilterProps {
|
|
2
|
+
allTags: string[];
|
|
3
|
+
selectedTags: string[];
|
|
4
|
+
label?: string;
|
|
5
|
+
}
|
|
6
|
+
declare const TagFilter: import("svelte").Component<TagFilterProps, {}, "selectedTags">;
|
|
7
|
+
type TagFilter = ReturnType<typeof TagFilter>;
|
|
8
|
+
export default TagFilter;
|
|
9
|
+
//# sourceMappingURL=TagFilter.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TagFilter.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/ui/components/TagFilter.svelte.ts"],"names":[],"mappings":"AAMC,UAAU,cAAc;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAoBF,QAAA,MAAM,SAAS,gEAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { Accordion } from "melt/builders";
|
|
5
5
|
import { setContext } from "svelte";
|
|
6
6
|
import type { SvelteSet } from "svelte/reactivity";
|
|
7
|
-
import {
|
|
7
|
+
import { type AccordionContext, accordionContextKey } from "./accordionContext";
|
|
8
8
|
import type { PinyaAccordionProps } from "./PinyaAccordionProps";
|
|
9
9
|
|
|
10
10
|
let {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
multiple = true,
|
|
14
14
|
...props
|
|
15
15
|
}: PinyaAccordionProps = $props();
|
|
16
|
+
|
|
16
17
|
let accordion = $derived(new Accordion({
|
|
17
18
|
value: (() => {
|
|
18
19
|
if (openItems) {
|
|
@@ -33,10 +34,11 @@
|
|
|
33
34
|
} else {
|
|
34
35
|
openItems = [];
|
|
35
36
|
}
|
|
36
|
-
},
|
|
37
|
+
},
|
|
38
|
+
multiple,
|
|
37
39
|
}));
|
|
38
40
|
setContext<AccordionContext>(accordionContextKey, (key) => accordion.getItem(key));
|
|
39
|
-
setContext<string[]>(
|
|
41
|
+
setContext<string[]>("accordionOpenItems", openItems);
|
|
40
42
|
</script>
|
|
41
43
|
|
|
42
44
|
<div {...props} class={`pinya-accordion-root ${props.class ?? ''}`} {...accordion.root}>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PinyaAccordion.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/ui/components/accordion/PinyaAccordion.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"PinyaAccordion.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/ui/components/accordion/PinyaAccordion.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAqDjE,QAAA,MAAM,cAAc,kEAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
|
|
@@ -95,7 +95,11 @@
|
|
|
95
95
|
font-style: var(--shiki-dark-font-style) !important;
|
|
96
96
|
font-weight: var(--shiki-dark-font-weight) !important;
|
|
97
97
|
-webkit-text-decoration: var(--shiki-dark-text-decoration) !important;
|
|
98
|
-
text-decoration: var(--shiki-dark-text-decoration) !important
|
|
98
|
+
text-decoration: var(--shiki-dark-text-decoration) !important;
|
|
99
|
+
|
|
100
|
+
span[style*="color:#4C4F69"] {
|
|
101
|
+
color: oklch(0.835 0.043 279.325) !important;
|
|
102
|
+
}
|
|
99
103
|
}
|
|
100
104
|
|
|
101
105
|
html.dark .shiki.has-diff span.diff.add {
|
|
@@ -119,15 +123,17 @@
|
|
|
119
123
|
padding-inline-end: 0;
|
|
120
124
|
|
|
121
125
|
code {
|
|
122
|
-
display:
|
|
123
|
-
flex-direction: column;
|
|
124
|
-
gap: 0.2lh;
|
|
126
|
+
display: block;
|
|
125
127
|
|
|
126
128
|
& > span {
|
|
127
|
-
display:
|
|
128
|
-
flex-wrap: wrap;
|
|
129
|
+
display: block;
|
|
129
130
|
padding-inline-start: 2em;
|
|
130
131
|
padding-inline-end: 1em;
|
|
132
|
+
margin-block-end: -1lh;
|
|
133
|
+
|
|
134
|
+
&:last-child {
|
|
135
|
+
margin-block-end: 0;
|
|
136
|
+
}
|
|
131
137
|
}
|
|
132
138
|
}
|
|
133
139
|
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
|
|
6
6
|
interface Props {
|
|
7
7
|
checked?: boolean;
|
|
8
|
-
name
|
|
8
|
+
name?: string;
|
|
9
9
|
onChange?: ((val: boolean) => void);
|
|
10
10
|
children?: Snippet;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
let {
|
|
14
14
|
checked = $bindable(false),
|
|
15
|
-
name,
|
|
15
|
+
name = undefined,
|
|
16
16
|
onChange = undefined,
|
|
17
17
|
children = undefined
|
|
18
18
|
}: Props = $props();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PineappleSwitch.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/ui/elements/PineappleSwitch.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGrC,UAAU,KAAK;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"PineappleSwitch.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/ui/elements/PineappleSwitch.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGrC,UAAU,KAAK;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC,CAAC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AA8BF,QAAA,MAAM,eAAe,kDAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
|
|
@@ -1,42 +1,132 @@
|
|
|
1
|
-
<!-- TODO: Documentation: consider documentation showcase -->
|
|
2
|
-
|
|
3
1
|
<script lang="ts" generics="T extends string">
|
|
4
2
|
import type { PinyaComboboxProps } from "./PinyaComboboxProps";
|
|
5
3
|
import { Combobox } from "melt/builders";
|
|
4
|
+
import { SvelteSet } from "svelte/reactivity";
|
|
6
5
|
|
|
7
6
|
let {
|
|
8
7
|
// todo: fix this
|
|
9
|
-
|
|
10
|
-
value = $bindable(),
|
|
8
|
+
value = $bindable([]),
|
|
11
9
|
onValueChange = () => {
|
|
12
10
|
},
|
|
13
|
-
|
|
11
|
+
multiple = false,
|
|
12
|
+
name = "",
|
|
14
13
|
...props
|
|
15
14
|
}: PinyaComboboxProps<T> = $props();
|
|
16
15
|
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
console.log("change", e);
|
|
20
|
-
onValueChange({
|
|
21
|
-
value: e,
|
|
22
|
-
items: props.data
|
|
23
|
-
});
|
|
24
|
-
};
|
|
16
|
+
const uid = $props.id();
|
|
17
|
+
const _name = $derived(name ? name : `combobox-${uid}`);
|
|
25
18
|
|
|
26
|
-
const combobox = new Combobox
|
|
27
|
-
value:
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
const combobox = $derived(new Combobox({
|
|
20
|
+
value: (() => {
|
|
21
|
+
if (value) {
|
|
22
|
+
if (multiple) {
|
|
23
|
+
return value;
|
|
24
|
+
} else if (value.length > 0) {
|
|
25
|
+
return value[0];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return undefined;
|
|
30
|
+
})(),
|
|
31
|
+
// todo: investigate later why typescript says it's the wrong type?
|
|
32
|
+
onValueChange: (t: T | SvelteSet<T> | undefined) => {
|
|
33
|
+
if (t instanceof SvelteSet) {
|
|
34
|
+
if (t.size > 0) {
|
|
35
|
+
value = [...t];
|
|
36
|
+
} else {
|
|
37
|
+
value = [];
|
|
38
|
+
}
|
|
39
|
+
} else if (t) {
|
|
40
|
+
value = [t];
|
|
41
|
+
} else {
|
|
42
|
+
value = [];
|
|
43
|
+
}
|
|
44
|
+
onValueChange?.(value);
|
|
30
45
|
},
|
|
31
|
-
|
|
32
|
-
|
|
46
|
+
inputValue: (() => {
|
|
47
|
+
if (value.length === props.data.length) {
|
|
48
|
+
return "All";
|
|
49
|
+
}
|
|
50
|
+
if (value.length === 0) {
|
|
51
|
+
return "---";
|
|
52
|
+
}
|
|
53
|
+
return props.data.filter(d => value.includes(d.value))
|
|
54
|
+
.map(d => d.label)
|
|
55
|
+
.join(", ");
|
|
56
|
+
})(),
|
|
57
|
+
multiple
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
let inputValue = $state("");
|
|
61
|
+
let wrapperEl: HTMLDivElement | undefined = $state();
|
|
62
|
+
|
|
63
|
+
const onfocusin = (event: FocusEvent) => {
|
|
64
|
+
// Don't clear filter when navigating to/between options
|
|
65
|
+
const target = event.target as HTMLElement;
|
|
66
|
+
if (!target?.hasAttribute('data-melt-combobox-option')) {
|
|
67
|
+
inputValue = "";
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const onblur = (event: FocusEvent) => {
|
|
72
|
+
// Don't reset if focus moved to another element within the wrapper
|
|
73
|
+
if (wrapperEl && event.relatedTarget instanceof Node && wrapperEl.contains(event.relatedTarget)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (value.length === props.data.length) {
|
|
78
|
+
inputValue = "All";
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (value.length === 0) {
|
|
82
|
+
inputValue = "---";
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
inputValue = props.data.filter(d => value.includes(d.value))
|
|
87
|
+
.map(d => d.label)
|
|
88
|
+
.join(", ");
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const onOptionKeydown = (event: KeyboardEvent, index: number) => {
|
|
92
|
+
const options = wrapperEl?.querySelectorAll<HTMLElement>('[data-melt-combobox-option]');
|
|
93
|
+
if (!options) return;
|
|
94
|
+
|
|
95
|
+
if (event.key === 'ArrowDown') {
|
|
96
|
+
event.preventDefault();
|
|
97
|
+
options[index + 1]?.focus();
|
|
98
|
+
} else if (event.key === 'ArrowUp') {
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
if (index === 0) {
|
|
101
|
+
wrapperEl?.querySelector<HTMLElement>('input')?.focus();
|
|
102
|
+
} else {
|
|
103
|
+
options[index - 1]?.focus();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const onInputKeydown = (event: KeyboardEvent) => {
|
|
109
|
+
if (event.key === 'Tab' && !event.shiftKey && combobox.open) {
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
const firstOption = wrapperEl?.querySelector('[data-melt-combobox-option]') as HTMLElement | null;
|
|
112
|
+
if (firstOption) {
|
|
113
|
+
firstOption.focus();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Delegate to Melt's keydown handler for all other keys
|
|
118
|
+
(combobox.input as { onkeydown?: (e: KeyboardEvent) => void }).onkeydown?.(event);
|
|
119
|
+
};
|
|
33
120
|
|
|
34
121
|
const filtered = $derived.by(() => {
|
|
35
122
|
if (!combobox.touched) return props.data;
|
|
36
123
|
return props.data.filter((o) =>
|
|
37
|
-
o.value.toLowerCase().includes(
|
|
124
|
+
o.value.toLowerCase().includes(inputValue.trim().toLowerCase())
|
|
125
|
+
|| o.label.toLowerCase().includes(inputValue.trim().toLowerCase())
|
|
38
126
|
);
|
|
39
127
|
});
|
|
128
|
+
|
|
129
|
+
const clearAll = () => value = [];
|
|
40
130
|
</script>
|
|
41
131
|
|
|
42
132
|
<!--
|
|
@@ -46,10 +136,29 @@ Melt-based Combobox
|
|
|
46
136
|
When migrating from Skeleton to Melt, change the value is no longer an array T[] but it's now T | undefined
|
|
47
137
|
-->
|
|
48
138
|
|
|
49
|
-
<div
|
|
50
|
-
|
|
139
|
+
<div
|
|
140
|
+
class={`pinya-combobox-wrapper ${props.class}`}
|
|
141
|
+
{...props}
|
|
142
|
+
onfocusin={onfocusin}
|
|
143
|
+
onfocusout={onblur}
|
|
144
|
+
bind:this={wrapperEl}
|
|
145
|
+
>
|
|
146
|
+
<div class="label-section">
|
|
147
|
+
<label {...combobox.label} for={_name}>{props.label}</label>
|
|
148
|
+
{#if value.length > 0 && multiple}
|
|
149
|
+
<button class="clear-btn" onclick={clearAll}>Clear all</button>
|
|
150
|
+
{:else }
|
|
151
|
+
<button class="clear-btn invisible" onclick={clearAll}>Clear all</button>
|
|
152
|
+
{/if}
|
|
153
|
+
</div>
|
|
51
154
|
<div class="pinya-combobox-control">
|
|
52
|
-
<input
|
|
155
|
+
<input
|
|
156
|
+
{...combobox.input}
|
|
157
|
+
disabled={props.disabled}
|
|
158
|
+
name={_name}
|
|
159
|
+
bind:value={inputValue}
|
|
160
|
+
onkeydown={onInputKeydown}
|
|
161
|
+
/>
|
|
53
162
|
<button class="size-[3rem]" {...combobox.trigger} disabled={props.disabled}>
|
|
54
163
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="opacity: 0.5" class="size-[2rem] m-auto">
|
|
55
164
|
<path d="m6 9 6 6 6-6"></path>
|
|
@@ -59,13 +168,13 @@ When migrating from Skeleton to Melt, change the value is no longer an array T[]
|
|
|
59
168
|
|
|
60
169
|
|
|
61
170
|
<div {...combobox.content}>
|
|
62
|
-
{#each filtered as option (option)}
|
|
63
|
-
<
|
|
171
|
+
{#each filtered as option, i (option)}
|
|
172
|
+
<button {...combobox.getOption(option.value)} tabindex="0" onkeydown={(e) => onOptionKeydown(e, i)}>
|
|
64
173
|
{option.label}
|
|
65
174
|
{#if combobox.isSelected(option.value)}
|
|
66
175
|
✓
|
|
67
176
|
{/if}
|
|
68
|
-
</
|
|
177
|
+
</button>
|
|
69
178
|
{:else}
|
|
70
179
|
<span>No results found</span>
|
|
71
180
|
{/each}
|
|
@@ -95,11 +204,20 @@ When migrating from Skeleton to Melt, change the value is no longer an array T[]
|
|
|
95
204
|
}
|
|
96
205
|
[data-melt-combobox-content][data-open] {
|
|
97
206
|
display: flex;
|
|
207
|
+
overflow-y: auto;
|
|
208
|
+
max-height: 8lh;
|
|
98
209
|
}
|
|
99
210
|
[data-melt-combobox-content] [data-melt-combobox-option] {
|
|
100
211
|
padding-block: calc(var(--spacing) * 1);
|
|
101
212
|
padding-inline: calc(var(--spacing) * 4);
|
|
102
213
|
border-radius: var(--radius-lg);
|
|
214
|
+
background: none;
|
|
215
|
+
border: none;
|
|
216
|
+
width: 100%;
|
|
217
|
+
text-align: left;
|
|
218
|
+
font: inherit;
|
|
219
|
+
color: inherit;
|
|
220
|
+
cursor: pointer;
|
|
103
221
|
}
|
|
104
222
|
[data-melt-combobox-content] [data-melt-combobox-option][aria-selected=true] {
|
|
105
223
|
background-color: light-dark(var(--color-secondary-400), var(--color-secondary-600));
|
|
@@ -108,6 +226,7 @@ When migrating from Skeleton to Melt, change the value is no longer an array T[]
|
|
|
108
226
|
.pinya-combobox-wrapper {
|
|
109
227
|
display: flex;
|
|
110
228
|
flex-direction: column;
|
|
229
|
+
gap: 0.2lh;
|
|
111
230
|
}
|
|
112
231
|
|
|
113
232
|
.pinya-combobox-control {
|
|
@@ -128,4 +247,28 @@ When migrating from Skeleton to Melt, change the value is no longer an array T[]
|
|
|
128
247
|
text-overflow: ellipsis;
|
|
129
248
|
padding: var(--spacing-2) var(--spacing-4);
|
|
130
249
|
padding-right: 0;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.clear-btn {
|
|
253
|
+
font-size: 0.75rem;
|
|
254
|
+
padding: 0.25rem 0.5rem;
|
|
255
|
+
border-radius: var(--radius-md);
|
|
256
|
+
border: 1px solid var(--color-primary-500);
|
|
257
|
+
background: transparent;
|
|
258
|
+
color: var(--color-primary-500);
|
|
259
|
+
cursor: pointer;
|
|
260
|
+
transition: background-color 0.2s;
|
|
261
|
+
}
|
|
262
|
+
.clear-btn.invisible {
|
|
263
|
+
pointer-events: none;
|
|
264
|
+
opacity: 0;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.clear-btn:hover {
|
|
268
|
+
background: var(--color-primary-100);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.label-section {
|
|
272
|
+
display: flex;
|
|
273
|
+
gap: 0.5em;
|
|
131
274
|
}</style>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PinyaCombobox.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/ui/elements/pinya-combobox/PinyaCombobox.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oDAAoD,CAAC;
|
|
1
|
+
{"version":3,"file":"PinyaCombobox.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/ui/elements/pinya-combobox/PinyaCombobox.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oDAAoD,CAAC;AAG7F,iBAAS,QAAQ,CAAC,CAAC,SAAS,MAAM;WAwKL,kBAAkB,CAAC,CAAC,CAAC;;;;;EAAiF;AACnI,cAAM,iBAAiB,CAAC,CAAC,SAAS,MAAM;IACpC,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAChD,MAAM,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAClD,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAChD,QAAQ;IACR,OAAO;CACV;AAED,UAAU,qBAAqB;IAC3B,KAAK,CAAC,SAAS,MAAM,EAAE,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAA;KAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC3Y,CAAC,CAAC,SAAS,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC1I,YAAY,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;CACjE;AACD;;;;GAIG;AACH,QAAA,MAAM,aAAa,EAAE,qBAAmC,CAAC;AACvC,KAAK,aAAa,CAAC,CAAC,SAAS,MAAM,IAAI,YAAY,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/E,eAAe,aAAa,CAAC"}
|
|
@@ -9,13 +9,12 @@ export interface ValueChangeDetails<T extends string> {
|
|
|
9
9
|
}
|
|
10
10
|
export type PinyaComboboxProps<T extends string> = {
|
|
11
11
|
data: GenericComboboxItem<T>[];
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
placeholder
|
|
16
|
-
onValueChange?: (selectedList:
|
|
17
|
-
onValueChangeBase?: (e: T | undefined) => void;
|
|
18
|
-
contentZIndex?: string;
|
|
12
|
+
value?: T[];
|
|
13
|
+
label?: string;
|
|
14
|
+
name?: string;
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
onValueChange?: (selectedList: T[]) => void;
|
|
19
17
|
disabled?: boolean;
|
|
18
|
+
multiple?: boolean;
|
|
20
19
|
} & HTMLAttributes<HTMLDivElement>;
|
|
21
20
|
//# sourceMappingURL=PinyaComboboxProps.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PinyaComboboxProps.d.ts","sourceRoot":"","sources":["../../../../src/lib/ui/elements/pinya-combobox/PinyaComboboxProps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,MAAM,WAAW,mBAAmB,CAAC,CAAC,SAAS,MAAM;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,CAAC,CAAC;CACT;AAED,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,MAAM;IACnD,KAAK,EAAE,CAAC,GAAG,SAAS,CAAC;IACrB,KAAK,EAAE,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAA;CAC/B;AAED,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,MAAM,IAAI;IAElD,IAAI,EAAE,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"PinyaComboboxProps.d.ts","sourceRoot":"","sources":["../../../../src/lib/ui/elements/pinya-combobox/PinyaComboboxProps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,MAAM,WAAW,mBAAmB,CAAC,CAAC,SAAS,MAAM;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,CAAC,CAAC;CACT;AAED,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,MAAM;IACnD,KAAK,EAAE,CAAC,GAAG,SAAS,CAAC;IACrB,KAAK,EAAE,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAA;CAC/B;AAED,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,MAAM,IAAI;IAElD,IAAI,EAAE,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAA;IAE9B,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,EAAE,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB,GAAG,cAAc,CAAC,cAAc,CAAC,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<!-- TODO: Documentation: consider documentation showcase -->
|
|
2
|
+
|
|
3
|
+
<script module lang="ts">
|
|
4
|
+
import { default as FourPartCard } from "../../components/FourPartCard.svelte";
|
|
5
|
+
import { TextChip } from "../../elements/TextChip";
|
|
6
|
+
import type { ProjectComponentProps } from "../../templates/SeaweedLayout/ProjectComponentProps";
|
|
7
|
+
|
|
8
|
+
const key = "Software Engineer at Old Company";
|
|
9
|
+
const dateStarted = "2022-01-01";
|
|
10
|
+
const dateFinished = "2024-12-31";
|
|
11
|
+
const tags = ["typescript", "svelte", "web"];
|
|
12
|
+
|
|
13
|
+
// note that we can cheat the regex by doing this!
|
|
14
|
+
// qt-web
|
|
15
|
+
export { component, key, dateStarted, dateFinished, tags };
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<script lang="ts">
|
|
19
|
+
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
{#snippet component(props: ProjectComponentProps)}
|
|
23
|
+
<FourPartCard>
|
|
24
|
+
{#snippet headerCover()}
|
|
25
|
+
<div class="company-logo-placeholder">
|
|
26
|
+
<h1>CX</h1>
|
|
27
|
+
</div>
|
|
28
|
+
{/snippet}
|
|
29
|
+
|
|
30
|
+
{#snippet header()}
|
|
31
|
+
<h3>Software Engineer</h3>
|
|
32
|
+
<h4>Old Company</h4>
|
|
33
|
+
<p class="date-range">Jan 2022 - Dec 2024</p>
|
|
34
|
+
{/snippet}
|
|
35
|
+
|
|
36
|
+
<p>
|
|
37
|
+
<span class="qt-typescript">TypeScript</span> and
|
|
38
|
+
<span class="qt-svelte">Svelte</span>. Why would anyone hire that though?
|
|
39
|
+
</p>
|
|
40
|
+
|
|
41
|
+
<div class="text-chip-container">
|
|
42
|
+
{#each tags as t (t)}
|
|
43
|
+
<TextChip queryClass="qt-{t}">{t}</TextChip>
|
|
44
|
+
{/each}
|
|
45
|
+
</div>
|
|
46
|
+
</FourPartCard>
|
|
47
|
+
{/snippet}
|
|
48
|
+
|
|
49
|
+
<style>
|
|
50
|
+
:global {
|
|
51
|
+
.company-logo-placeholder {
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
justify-content: center;
|
|
55
|
+
width: 100%;
|
|
56
|
+
height: 100%;
|
|
57
|
+
min-height: 150px;
|
|
58
|
+
background: linear-gradient(135deg, var(--color-primary-500), var(--color-secondary-500));
|
|
59
|
+
color: white;
|
|
60
|
+
font-size: 2rem;
|
|
61
|
+
font-weight: bold;
|
|
62
|
+
border-top-right-radius: var(--radius-xl);
|
|
63
|
+
border-top-left-radius: var(--radius-xl);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.date-range {
|
|
69
|
+
font-size: 0.875rem;
|
|
70
|
+
color: var(--color-text-secondary);
|
|
71
|
+
margin-bottom: 0.5rem;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
h4 {
|
|
75
|
+
margin-top: 0.25rem;
|
|
76
|
+
margin-bottom: 0.5rem;
|
|
77
|
+
color: var(--color-text-secondary);
|
|
78
|
+
}
|
|
79
|
+
</style>
|