@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.
Files changed (52) hide show
  1. package/README.md +11 -11
  2. package/dist/ui/components/FourPartCard.svelte +4 -0
  3. package/dist/ui/components/SortDropdown.svelte +50 -0
  4. package/dist/ui/components/SortDropdown.svelte.d.ts +9 -0
  5. package/dist/ui/components/SortDropdown.svelte.d.ts.map +1 -0
  6. package/dist/ui/components/TagFilter.svelte +32 -0
  7. package/dist/ui/components/TagFilter.svelte.d.ts +9 -0
  8. package/dist/ui/components/TagFilter.svelte.d.ts.map +1 -0
  9. package/dist/ui/components/accordion/PinyaAccordion.svelte +5 -3
  10. package/dist/ui/components/accordion/PinyaAccordion.svelte.d.ts.map +1 -1
  11. package/dist/ui/elements/CodeBlock/CodeBlock.svelte +12 -6
  12. package/dist/ui/elements/PineappleSwitch.svelte +2 -2
  13. package/dist/ui/elements/PineappleSwitch.svelte.d.ts +1 -1
  14. package/dist/ui/elements/PineappleSwitch.svelte.d.ts.map +1 -1
  15. package/dist/ui/elements/pinya-combobox/PinyaCombobox.svelte +169 -26
  16. package/dist/ui/elements/pinya-combobox/PinyaCombobox.svelte.d.ts.map +1 -1
  17. package/dist/ui/elements/pinya-combobox/PinyaComboboxProps.d.ts +6 -7
  18. package/dist/ui/elements/pinya-combobox/PinyaComboboxProps.d.ts.map +1 -1
  19. package/dist/ui/modules/experience/ExampleJob1.svelte +79 -0
  20. package/dist/ui/modules/experience/ExampleJob1.svelte.d.ts +26 -0
  21. package/dist/ui/modules/experience/ExampleJob1.svelte.d.ts.map +1 -0
  22. package/dist/ui/modules/experience/ExampleJob2.svelte +74 -0
  23. package/dist/ui/modules/experience/ExampleJob2.svelte.d.ts +25 -0
  24. package/dist/ui/modules/experience/ExampleJob2.svelte.d.ts.map +1 -0
  25. package/dist/ui/modules/experience/index.d.ts +3 -0
  26. package/dist/ui/modules/experience/index.d.ts.map +1 -0
  27. package/dist/ui/modules/experience/index.js +2 -0
  28. package/dist/ui/modules/modals/general-settings/LanguagePicker.svelte +7 -9
  29. package/dist/ui/modules/modals/general-settings/LanguagePicker.svelte.d.ts.map +1 -1
  30. package/dist/ui/modules/projects/Hepcat.svelte +5 -1
  31. package/dist/ui/modules/projects/Hepcat.svelte.d.ts +4 -1
  32. package/dist/ui/modules/projects/Hepcat.svelte.d.ts.map +1 -1
  33. package/dist/ui/modules/projects/Pengi.svelte +4 -1
  34. package/dist/ui/modules/projects/Pengi.svelte.d.ts +4 -1
  35. package/dist/ui/modules/projects/Pengi.svelte.d.ts.map +1 -1
  36. package/dist/ui/modules/projects/Soulwork.svelte +4 -1
  37. package/dist/ui/modules/projects/Soulwork.svelte.d.ts +4 -1
  38. package/dist/ui/modules/projects/Soulwork.svelte.d.ts.map +1 -1
  39. package/dist/ui/modules/projects/ThisWebpage.svelte +4 -2
  40. package/dist/ui/modules/projects/ThisWebpage.svelte.d.ts +3 -1
  41. package/dist/ui/modules/projects/ThisWebpage.svelte.d.ts.map +1 -1
  42. package/dist/ui/modules/universal-overlay/UniversalOverlay.svelte +0 -1
  43. package/dist/ui/modules/universal-overlay/UniversalOverlay.svelte.d.ts.map +1 -1
  44. package/dist/ui/templates/SeaweedLayout/EntryGroup.svelte +177 -29
  45. package/dist/ui/templates/SeaweedLayout/EntryGroup.svelte.d.ts.map +1 -1
  46. package/dist/ui/templates/SeaweedLayout/ProjectGroupConfig.svelte +1 -3
  47. package/dist/ui/templates/SeaweedLayout/SeaweedLayout.svelte +188 -44
  48. package/dist/ui/templates/SeaweedLayout/SeaweedLayout.svelte.d.ts.map +1 -1
  49. package/dist/ui/templates/SeaweedLayout/props.d.ts +18 -2
  50. package/dist/ui/templates/SeaweedLayout/props.d.ts.map +1 -1
  51. package/dist/ui/templates/SeaweedLayout/props.js +8 -1
  52. 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 `yarn`, start a development server:
9
+ Once you've created a project and installed dependencies with `pnpm`, start a development server:
10
10
 
11
11
  ```bash
12
- yarn dev
12
+ pnpm dev
13
13
 
14
14
  # or start the server and open the app in a new browser tab
15
- yarn dev -- --open
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
- yarn build
23
+ pnpm build
24
24
  ```
25
25
 
26
- You can preview the production build with `yarn preview`.
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
- yarn add @turnipxenon2/pineapple
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 `yarn link`
123
- 2. In seaweed2, run `yarn unlink @turnipxenon/pineapple`
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 `yarn unlink @turnipxenon/pineapple`
128
- 2. In pineapple, run `yarn unlink`
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: `yarn add @turnipxenon/pineapple --no-package-lock --no-save`
131
+ To reinstall a single package: `pnpm add @turnipxenon/pineapple --no-package-lock --no-save`
@@ -57,5 +57,9 @@
57
57
  border-top-left-radius: var(--radius-xl);
58
58
  }
59
59
  }
60
+
61
+ html.dark .pinya-four-part-card {
62
+ border: 0;
63
+ }
60
64
  }
61
65
  </style>
@@ -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 { accordionContextKey, type AccordionContext } from "./accordionContext";
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
- }, multiple
37
+ },
38
+ multiple,
37
39
  }));
38
40
  setContext<AccordionContext>(accordionContextKey, (key) => accordion.getItem(key));
39
- setContext<string[]>('accordionOpenItems', openItems);
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;AAmDjE,QAAA,MAAM,cAAc,kEAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,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: flex;
123
- flex-direction: column;
124
- gap: 0.2lh;
126
+ display: block;
125
127
 
126
128
  & > span {
127
- display: flex;
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: string;
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,7 +1,7 @@
1
1
  import type { Snippet } from "svelte";
2
2
  interface Props {
3
3
  checked?: boolean;
4
- name: string;
4
+ name?: string;
5
5
  onChange?: ((val: boolean) => void);
6
6
  children?: Snippet;
7
7
  }
@@ -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;IACb,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
+ {"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
- contentZIndex = "auto",
10
- value = $bindable(),
8
+ value = $bindable([]),
11
9
  onValueChange = () => {
12
10
  },
13
- onValueChangeBase = undefined,
11
+ multiple = false,
12
+ name = "",
14
13
  ...props
15
14
  }: PinyaComboboxProps<T> = $props();
16
15
 
17
- const onValueChangeBaseImpl = (e: T | undefined) => {
18
- value = e;
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<T, false>({
27
- value: props.defaultValue,
28
- inputValue: function() {
29
- return props.data.find((o) => o.value === value)?.label ?? "---";
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
- onValueChange: onValueChangeBase ?? onValueChangeBaseImpl,
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(combobox.inputValue.trim().toLowerCase())
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 class={`pinya-combobox-wrapper ${props.class}`} {...props}>
50
- <label {...combobox.label}>{props.label}</label>
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 {...combobox.input} disabled={props.disabled} />
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
- <div {...combobox.getOption(option.value)}>
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
- </div>
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;AAE7F,iBAAS,QAAQ,CAAC,CAAC,SAAS,MAAM;WAuEL,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"}
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
- defaultValue: T | undefined;
13
- value: T | undefined;
14
- label: string;
15
- placeholder: string;
16
- onValueChange?: (selectedList: ValueChangeDetails<T>) => void;
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;IAC9B,YAAY,EAAE,CAAC,GAAG,SAAS,CAAC;IAE5B,KAAK,EAAE,CAAC,GAAG,SAAS,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IAG9D,iBAAiB,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,KAAK,IAAI,CAAC;IAC/C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB,GAAG,cAAc,CAAC,cAAc,CAAC,CAAC"}
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>